使用 Trezor(硬件钱包)将签名交易发送到 Ropsten 或 Truffle 开发网络

Posted

技术标签:

【中文标题】使用 Trezor(硬件钱包)将签名交易发送到 Ropsten 或 Truffle 开发网络【英文标题】:Send signed transactions to Ropsten or Truffle develop network with Trezor (Hardware Wallet) 【发布时间】:2018-11-08 19:28:33 【问题描述】:

我正在尝试将 web3jsTrezor 集成到 truffle 开发网络或使用 ropsten 测试网络

想法是使用硬件钱包对交易进行签名,然后使用 web3js 发送原始交易

我知道我们没有余额来进行交易,可能是因为 web3js 没有使用 10 个 truffle 帐户之一,而是使用不在我本地网络中的 trezor 地址..

在 ropsten 我有一些以太币,我得到“无效地址”

有没有办法使用 web3js 将签名交易(使用 trezor)发送到松露开发网络?我的意思是,有没有办法将 trezor 地址包含到松露网络中?

这里更详细地解释了松露的情况,但问题可以概括为“有没有办法将硬件钱包纳入松露开发网络?”:https://github.com/trufflesuite/truffle/issues/973

使用 ropsten 我已经成功地在回调中发送了一个交易并接收了一个交易哈希,但是如果我们查询该交易,我们会发现该交易不存在.. 那么.. 这怎么可能?

我也尝试将合约部署到 Ropsten 中,现在在调用智能合约功能时我得到“无效地址”。也许签名功能是错误的?任何人都可以将 Trezor 交易签名与 web3js 集成?

你们有没有发现我们遵循的签名和发送过程有什么问题? 可能是 R、V 和 S 参数处理有问题 ..

另一件重要的事情是我使用https://github.com/ethereumjs/ethereumjs-tx 来创建原始交易

在 web3js、truffle 和 trezzor 中发布的问题与更多信息相关:

https://github.com/trufflesuite/truffle/issues/973 https://github.com/ethereum/web3.js/issues/1669 https://github.com/trezor/connect/issues/130

亲切的问候

 trezorLogin = async()=> 
        let trezor=  await this.getTrezor();

        // site icon, optional. at least 48x48px
        let hosticon = 'https://doc.satoshilabs.com/trezor-apps/_images/copay_logo.png';
        // server-side generated and randomized challenges
        let challenge_hidden = '';
        let challenge_visual = '';
        //use anonimous functions on callback otherwise returns cross origin errors
        trezor.requestLogin(hosticon, challenge_hidden, challenge_visual, function (result)
            if (result.success) 
                console.log('Public key:', result.public_key); // pubkey in hex
                console.log('Signature:', result.signature); // signature in hex
                console.log('Version 2:', result.version === 2); // version field
                console.log(result);
            else 
                console.error('Error:', result.error);
            
        );


    trezorSignTx= async(transaction)=> 
        let trezor=  await this.getTrezor();
        // spend one change output
        let address_n = "m/44'/60'/0'/0/0"
        // let address_n = [44 | 0x80000000,
        //                  60 | 0x80000000,
        //                  0  | 0x80000000 ,
        //                  0 ]; // same, in raw form
        let nonce = transaction.nonce.substring(2); // note - it is hex, not number!!!
        let gas_price = transaction.gasPrice.substring(2);
        let gas_limit = transaction.gasLimit.substring(2);
        let to = transaction.to.substring(2);
        // let value = '01'; // in hexadecimal, in wei - this is 1 wei
        let value = transaction.value.substring(2); // in hexadecimal, in wei - this is about 18 ETC
        let data = transaction.data.substring(2); // some contract data
        // let data = null  // for no data
        let chain_id = 5777; // 1 for ETH, 61 for ETC
        return new Promise (function (resolve,reject) 
            trezor.ethereumSignTx(
                address_n,
                nonce,
                gas_price,
                gas_limit,
                to,
                value,
                data,
                chain_id,
                function (response) 
                    if (response.success) 

                        console.log('Signature V (recovery parameter):', response.v); // number
                        console.log('Signature R component:', response.r); // bytes
                        console.log('Signature S component:', response.s); // bytes
                        resolve(response);

                     else 
                        console.error('Error:', response.error); // error message
                        resolve(null);
                    

                );
        )
    

    getTrezorAddress = async() => 
        let trezor=  await this.getTrezor();
        // spend one change output
        let address_n = "m/44'/60'/0'/0/0";
        trezor.ethereumGetAddress(address_n, function (result) 
            if (result.success)  // success
                console.log('Address: ', result.address);
             else 
                console.error('Error:', result.error); // error message
            
        );
    


    getTrezor = async() => 
        let trezorC;
        await getTrezorConnect
            .then(trezorConnect => 
                trezorC= trezorConnect;
            )
            .catch((error) => 
                console.log(error)
            )
        return trezorC;

    

 sendTransaction= async(address, amount, id)=>
        let tokenInstance = this.props.smartContractInstance;

        let getData = tokenInstance.mint.getData(address, amount);

        let tx = 
            nonce: '0x00',
            gasPrice: '0x09184e72a000',
            gasLimit: '0x2710',
            to: CONTRACT_ADDRESS,
            value: '0x00',
            from:CONTRACT_OWNER_ADDRESS,
            data: getData
        ;
        let response = await this.trezorSignTx(tx);

        let web3;
        let _this = this;
        if (response!=null)
            getWeb3
                .then(results => 
                    web3= results.web3;
                    let v = response.v.toString();
                    if (v.length % 2 != 0)
                        v="0"+v;
                    
                    tx.r=Buffer.from(response.r,'hex');
                    tx.v=Buffer.from(v,'hex');
                    tx.s=Buffer.from(response.s,'hex');
                    let ethtx = new ethereumjs(tx);
                    console.dir(ethtx.getSenderAddress().toString('hex'), );
                    const serializedTx = ethtx.serialize();
                    const rawTx = '0x' + serializedTx.toString('hex');
                    console.log(rawTx);
                    //finally pass this data parameter to send Transaction
                    web3.eth.sendRawTransaction(rawTx, function (error, result) 
                        if(!error)
                            _this.props.addTokens(id)
                                .then(()=>
                                        _this.setState(modalOpen: true);
                                        _this.props.getAllTransactions();
                                    
                                );
                        else
                            alert(error)
                        
                    );
                )
                .catch((error) => 
                    console.log(error)
                )
        else
            alert("There was an error signing with trezor hardware wallet")
        


    

getTrezorConnect函数只是异步获取window.trezorConnect,因为对象是作为脚本注入的

<script src="https://connect.trezor.io/4/connect.js"></script>


let getTrezorConnect = new Promise(function(resolve, reject) 
    // Wait for loading completion
    window.addEventListener('load', function() 

        let trezorConnect = window.TrezorConnect

            return resolve(trezorConnect)


));

export default getTrezorConnect

【问题讨论】:

【参考方案1】:

好吧,经过多次尝试,我们已经成功地将使用 Trezor 签名的原始交易发送到 RopstenTruffle(请参阅答案底部的编辑)以及本地私有 Geth 网络,所以,代码没问题,在这些环境中集成 Trezor 没有问题

https://ropsten.etherscan.io/address/0x89e2c46b22881f747797cf67310aad1a831d50b7

这是我为了能够将签名交易发送到 Ropsten 测试网而更改的内容。

这假设您已将合约部署到 Ropsten 并且您拥有合约地址。

1) 获取您的 Trezor 帐户地址

  getTrezorAddress = async() => 
        let trezor=  await this.getTrezor();
        // spend one change output
        let address_n = "m/44'/1'/0'/0/0";
        trezor.ethereumGetAddress(address_n, function (result) 
            if (result.success)  // success
                console.log('Address: ', result.address);
             else 
                console.error('Error:', result.error); // error message
            
        );
    

2) 将 trezor 地址放入原始交易的from 字段,通过获取该地址的交易计数来获取交易的nonce。重要提示:使用 getTransactionCount 上的“pending”可选参数来获取账户的所有交易,否则您将覆盖待处理的交易。

getNonce = async(address) => 

        let web3 = await this.getWeb3();
        return new Promise (function (resolve,reject) 
            web3.eth.getTransactionCount(address, "pending", function (error,result)
                console.log("Nonce "+result);
                resolve(result);


            );
        );

    

let count = null;
        await this.getNonce("0xedff546ac229317df81ef9e6cb3b67c0e6425fa7").then(result => 
            if(result.length % 2 !==0)
                result = "0"+result;
            
            count = "0x"+result;

       );

let tx = 
            nonce: count ,
            gasPrice: web3.toHex(gasPriceGwei*1e9),
            gasLimit: web3.toHex(gasLimit),
            to: CONTRACT_ADDRESS,
            value: '0x00',
            data: getData,
            chainId:chainId,
            from:"yourTrezzorAddress"
        ;

3) r, s, v 参数不正确,处理它们的正确方法是获取 trezor 响应的值并将其转换为 hexa:

// response is the Trezor sign response
tx.v= response.v;
tx.r="0x"+response.r;
tx.s="0x"+response.s;
let ethtx = new ethereumjs(tx);.
const serializedTx = ethtx.serialize();
const rawTx = '0x' + serializedTx.toString('hex');
 //finally pass this data parameter to send Transaction
web3.eth.sendRawTransaction(rawTx, someCallbackFunction);

重要提示:ropsten 中的挖掘时间将在 15 到 30 秒之间,因此如果在您的 someCallbackFunction 中使用哈希检查交易收据,您将得到 null 作为结果,因为交易处于待处理状态。

4) 为了在 ropsten 进行测试,我们使用 Infura,因此我们更改了 web3 提供程序:

import Web3 from 'web3'
import HDWalletProvider from "truffle-hdwallet-provider";

let getWeb3 = new Promise(function(resolve, reject) 
    // Wait for loading completion to avoid race conditions with web3 injection timing.
    window.addEventListener('load', function() 
        let results
        let web3 = window.web3

        // Checking if Web3 has been injected by the browser (Mist/MetaMask)
        if (typeof web3 !== 'undefined') 
            // Use Mist/MetaMask's provider.
            web3 = new Web3(web3.currentProvider)

            results = 
                web3: web3
            

            console.log('Injected web3 detected.');

            return resolve(results)
         else 
            // Fallback to localhost if no web3 injection. We've configured this to
            // use the development console's port by default.
            // let provider = new Web3.providers.HttpProvider("https://ropsten.infura.io/your_infura_api_key")

            let mnemonic = "infura mnemonic"
            let provider = new HDWalletProvider(mnemonic, "https://ropsten.infura.io/your_infura_api_key")
            web3 = new Web3(provider)

            results = 
                web3: web3
            

            console.log('No web3 instance injected, using Local web3.');

            return resolve(results)
        
    )
)

export default getWeb3

编辑

这也适用于 Truffle!查看本期最后的cmetshttps://github.com/trufflesuite/truffle/issues/973

【讨论】:

【参考方案2】:

我们开发了一个小型库以将 Truffle 与 Trezor 硬件钱包一起使用:https://github.com/rariblecom/trezor-provider

可以这样使用:

const  createProvider  = require('@rarible/trezor-provider')

module.exports = 
    networks: 
        ropsten: 
            provider: function() 
                //websocket and http urls are supported
                return createProvider( url: "infura or other", path: "m/44'/60'/0'/0/0", chainId: 3 ) 
            ,
            network_id: 3
        
    
;

【讨论】:

以上是关于使用 Trezor(硬件钱包)将签名交易发送到 Ropsten 或 Truffle 开发网络的主要内容,如果未能解决你的问题,请参考以下文章

将签名的交易发送到 Ropsten

一分钟了解硬件钱包

多重签名钱包未转换。为啥聚合交易不返回错误?

FileCoin 挖矿教程之三:发送与接收 FIL

以太坊钱包开发系列1 - 创建钱包账号

如何将交易从元掩码钱包发送到后端节点 js