Web3js

在 Truffle 或 Ropsten 上使用 Trezor(硬體錢包)和 Web3js

  • June 26, 2018

我們正在嘗試將web3js與Trezor 集成到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 中發布的問題與更多資訊有關:

親切的問候

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

       // site icon, optional. at least 48x48px
       var hosticon = 'https://doc.satoshilabs.com/trezor-apps/_images/copay_logo.png';
       // server-side generated and randomized challenges
       var challenge_hidden = '';
       var 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
       var address_n = "m/44'/60'/0'/0/0"
       // var address_n = [44 | 0x80000000,
       //                  60 | 0x80000000,
       //                  0  | 0x80000000 ,
       //                  0 ]; // same, in raw form
       var nonce = transaction.nonce.substring(2); // note - it is hex, not number!!!
       var gas_price = transaction.gasPrice.substring(2);
       var gas_limit = transaction.gasLimit.substring(2);
       var to = transaction.to.substring(2);
       // var value = '01'; // in hexadecimal, in wei - this is 1 wei
       var value = transaction.value.substring(2); // in hexadecimal, in wei - this is about 18 ETC
       var data = transaction.data.substring(2); // some contract data
       // var data = null  // for no data
       var 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
       var 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;

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

       var 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>

好吧,經過大量嘗試,我們已經成功地將與 Trezor 簽署的原始交易發送到 Ropsten,基於 Tudor Constantin 的幫助:

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

這是我為了能夠將簽名交易發送到 Ropsten 測試網而更改的內容。

這假設您已將合約部署到 Ropsten 並且您擁有合約地址。

  1. 獲取您的 Trezor 帳戶的地址
 getTrezorAddress = async() => {
       let trezor=  await this.getTrezor();
       // spend one change output
       var 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;

      });

var tx = {
           nonce: count ,
           gasPrice: web3.toHex(gasPriceGwei*1e9),
           gasLimit: web3.toHex(gasLimit),
           to: CONTRACT_ADDRESS,
           value: '0x00',
           data: getData,
           chainId:chainId,
           from:"yourTrezzorAddress"
       };
  1. 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 作為結果,因為交易處於待處理狀態。

  1. 為了在 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() {
       var results
       var 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.
           // var provider = new Web3.providers.HttpProvider("https://ropsten.infura.io/your_infura_api_key")

           var mnemonic = "infura mnemonic"
           var 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

編輯

這也適用於松露!檢查此問題的最後評論https://github.com/trufflesuite/truffle/issues/973

你列出了很多問題。最好一次發布一個問題,以增加獲得答案的機會。

讓我談談最重要的

Q1。有沒有辦法將硬體錢包納入松露開發網路?

是的,通過使用truffle console並將其配置為連接到testrpc. 使用testrpc,您可以擁有您想要資助的任何帳戶(編輯:這不是真的 - 這些帳戶實際上是私鑰,無法使用硬體錢包),通過以下方式啟動它:

testrpc --account="0x8414315fe005b8f294020dfc61cfd13749fbc045b0c6abc31fbd1ee3f4ff3b41, 10000000000000000000"         --account="0x566a9022cd3f0dfcc3dff657a6c578897d4b0300e335fa569a082b637e6bb273, 70000000000000000000"         --account="0x90b4e47ca43b66fab5dbebfee464087b51923f73f649701ca485da313574fd5b, 80000000000000000000"         --account="0x5d47b245c405d706fecbc5eb213819d20a2168ad696b352644ad0ffc87aef18e, 90000000000000000000"

地址是你的trezor地址。

或者你可以從你的 trezor 的種子開始它(我不推薦這個,除非你確定 trezor 是在實時網路上使用的):

testrpc -m 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat'

Q2:你們在我們遵循的簽名和發送過程中發現有什麼問題嗎?

我設法使用 Ledger Nano S 硬體錢包以程式方式簽署了 eth 交易。我用於簽署交易的ledgerco js 庫也返回 V、R 和 S 參數。我假設它們的格式與 Trezor 庫返回的格式相同,但我不能確定。無論如何,這就是我使用 V、R、S 創建有效交易的方式:

console.log('Please sign transaction on device...');
 //the signature is an object with keys v,r and s
 const signature = await eth_utils.ledger.signTransaction_async(argv['derivation_path'], tx.serialize().toString('hex'));

 //"hexify" the keys
 Object.keys(signature).map( (key, index) => {
   signature[key] = '0x'+signature[key];
 });
 //tx_raw is a js object that contains all the tx params, the one that was signed on the hw device
 //(equivalent of your tx from your sendTransaction() function)
 const tx_obj = { ...tx_raw, ...signature};

 //re-create the Transaction using ethereumjs-tx
 const signed_tx = new Transaction( tx_obj );

 //signed_tx_hex needs to be broadcasted
 const signed_tx_hex = '0x'+signed_tx.serialize().toString('hex');

就是這樣。

引用自:https://ethereum.stackexchange.com/questions/50866