Verwendung von Trezor (Hardware Wallet) mit Web3js auf Truffle oder Ropsten

Wir versuchen, web3js mit Trezor in ein Truffle -Dev-Netzwerk zu integrieren oder das Ropsten-Testnetzwerk zu verwenden .

Die Idee ist, die Transaktionen mit der Hardware-Wallet zu signieren und dann eine Rohtransaktion mit web3js zu senden

Wir bekommen, dass wir kein Guthaben haben, um die Transaktion durchzuführen, wahrscheinlich weil web3js keines der 10 Trüffelkonten belegt und die Trezor-Adresse verwendet, die nicht in meinem lokalen Netzwerk ist .

Auf ropsten habe ich einige Ether und bekomme "ungültige Adresse"

Gibt es eine Möglichkeit, signierte Transaktionen (mit Trezor) mit web3js in ein Truffle-Entwicklungsnetzwerk zu senden? Ich meine, gibt es eine Möglichkeit, die Trezor-Adresse in das Trüffelnetzwerk aufzunehmen?

Die Situation bei Truffle wird hier ausführlicher erklärt, aber die Frage könnte verallgemeinert werden zu " Gibt es eine Möglichkeit, Hardware-Wallets in das Truffle-Entwicklungsnetzwerk einzubeziehen? ": https://github.com/trufflesuite/truffle/issues/973

Mit ropsten haben wir es geschafft, eine Transaktion zu senden und einen Transaktions-Hash im Rückruf zu erhalten, aber wenn wir nach dieser Transaktion fragen, erhalten wir, dass die Transaktion nicht existiert ... also ... wie ist das möglich?

Wir haben auch versucht, einen Vertrag in Ropsten bereitzustellen, und jetzt erhalten wir „Ungültige Adresse“, wenn wir eine intelligente Vertragsfunktion aufrufen. Vielleicht ist die Signierfunktion falsch? jeder könnte die Trezor-Transaktionssignierung mit web3js integrieren?

Seht ihr irgendetwas falsch im Signier- und Sendeprozess, den wir befolgt haben? Vielleicht ist bei der Handhabung der R-, V- und S-Parameter etwas falsch ..

Eine weitere wichtige Sache ist, dass wir https://github.com/ethereumjs/ethereumjs-tx zum Erstellen der Rohtransaktionen verwenden

In web3js, truffle und trezzor veröffentlichte Ausgaben mit weiteren Informationen:

mit freundlichen Grüßen

 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")
        }


    }

Die getTrezorConnect-Funktion ist einfach get window.trezorConnect asynchron, da das Objekt als Skript injiziert wird

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

Antworten (2)

Sie haben viele Fragen aufgelistet. Es ist besser, eine Frage nach der anderen zu stellen, um Ihre Chancen zu erhöhen, dass sie beantwortet werden.

Lassen Sie mich auf die wichtigsten eingehen .

Q1. Gibt es eine Möglichkeit, Hardware-Wallets in das Truffle-Entwicklungsnetzwerk einzubinden?

Ja, indem Sie es verwenden truffle consoleund konfigurieren, um eine Verbindung zu herzustellen testrpc. Mit testrpckönnen Sie ein beliebiges Konto haben, das Sie finanzieren möchten (Bearbeiten: Das ist nicht wahr - die Konten sind eigentlich private Schlüssel, die mit einer HW-Brieftasche nicht verfügbar sind), indem Sie es wie folgt starten:

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

Wobei die Adressen Ihre Trezor-Adressen sind.

Oder Sie können es mit dem Seed Ihres Trezors starten (ich empfehle dies nicht, es sei denn, Sie wissen sicher, dass der Trezor im Live-Netzwerk verwendet wird):

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

Q2: Sehen Sie irgendwelche Fehler in dem Signier- und Sendeprozess, den wir befolgt haben?

Ich habe es geschafft, eth-Transaktionen programmgesteuert mit der Ledger Nano S-Hardware-Wallet zu signieren. Die ledgerco js-Bibliothek, die ich zum Signieren der Transaktionen verwendet habe, gibt auch die V-, R- und S-Parameter zurück. Ich nehme an, sie haben das gleiche Format wie die von Trezors Bibliothek zurückgegebenen, aber ich kann mir dessen nicht sicher sein. Wie auch immer, so verwende ich V, R, S, um gültige Transaktionen zu erstellen:

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');

Und das ist es.

Wie können Sie testrpc mit dem Trezzor-Konto starten, wenn der private Schlüssel von Trezzor nicht verfügbar ist? auch die Mnemonik ist 24 Wörter lang.. also stecke ich dort fest.. wenn ich Ihren Code gegen ropsten versuche, bekomme ich einen Transaktions-Hash, als ob die Transaktion gesendet wurde, aber wenn ich dann in ropsten.etherscan nach diesen Vertragstransaktionen einchecke, sehe ich nur die Konstruktion der Transaktion, irgendwelche Ideen dazu? Vielen Dank
Nun, das Problem hing damit zusammen, wie wir die r,s,v-Werte verwalteten, es war nicht notwendig, den Buffer zu verwenden. Ich werde diese Antwort als richtig akzeptieren.
testrpc möchte nicht, dass der private Schlüssel einer Adresse Ether gutschreiben kann. Sie sagen ihm nur, welcher Adresse wie viele Wei gutgeschrieben werden sollen. Es spielt keine Rolle, ob Sie den privaten Schlüssel haben oder nicht.
Die Option --account erfordert die privaten Schlüssel und Wei. Wenn ich die Adresse des Trezor-Geräts verwende, sagt mir testrpc „RangeError: private key length is invalid“
in der Tat, Sie haben Recht, es braucht die privaten Schlüssel. Es scheint, dass Ihre einzige Lösung darin besteht, die Mnemonik von Trezor zu verwenden, wenn Sie testrpc starten
Ja, das einzige, was ich nicht verstehe, ist, dass ich beim Ausführen von testrpc mit der Mnemonik des Trezor 10 Konten erhalte und jede der Adressen mit der Adresse meines Trezor-Geräts übereinstimmt.

Nun, nach vielen Versuchen haben wir es geschafft, eine mit Trezor unterzeichnete Rohtransaktion an Ropsten zu senden, basierend auf der Hilfe von Tudor Constantin:

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

Dies sind die Dinge, die ich geändert hatte, um es möglich zu machen, signierte Transaktionen an das Ropsten-Testnet zu senden.

Dies setzt voraus, dass Sie Ihren Vertrag bei Ropsten bereitgestellt haben und die Vertragsadresse haben.

1) Holen Sie sich die Adresse Ihres Trezor-Kontos

  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) Geben Sie die Trezor-Adresse in das fromFeld Ihrer Rohtransaktion ein, erhalten Sie die nonceder Transaktion, indem Sie die Transaktionsanzahl für diese Adresse abrufen. Wichtig: Verwenden Sie den optionalen Parameter „pending“ bei getTransactionCount, um alle Transaktionen des Kontos abzurufen, andernfalls überschreiben Sie ausstehende Transaktionen.

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"
        };

3) Die Parameter r, s, v waren falsch, der richtige Weg, damit umzugehen, ist, diese Werte für die Trezor-Antwort zu nehmen und sie einfach in Hexa umzuwandeln:

// 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);

Wichtig: Die Mining-Zeit in Ropsten liegt zwischen 15 und 30 Sekunden. Wenn Sie also in Ihrer someCallbackFunction mit dem Hash nach dem Transaktionsbeleg suchen, erhalten Sie null als Ergebnis, da sich die Transaktion in einem ausstehenden Zustand befindet.

4) Um es bei ropsten zu testen, verwenden wir Infura, also wechseln wir den web3-Anbieter:

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

BEARBEITEN :

Das funktioniert auch mit Trüffel ! Überprüfen Sie die letzten Kommentare zu dieser Ausgabe https://github.com/trufflesuite/truffle/issues/973