在 Truffle 或 Ropsten 上使用 Trezor(硬體錢包)和 Web3js
我們正在嘗試將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 中發布的問題與更多資訊有關:
- 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 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 並且您擁有合約地址。
- 獲取您的 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" };
- 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 作為結果,因為交易處於待處理狀態。
- 為了在 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');
就是這樣。