We are trying to integrate web3js with Trezor in a truffle dev network or using ropsten test network.

The idea is to sign the transactions using the hardware wallet and then send a raw transaction using web3js

We are getting that we dont have balance to make the transaction, probably because web3js isnt taking one of the 10 truffle accounts and is using the trezor address that isnt in my local network..

On ropsten i have some ethers and i get "invalid address"

Is there a way to send a signed transactions (with trezor) using web3js into a truffle develop network? i mean, is there a way to include the trezor address into the truffle network?

The situation in truffle is explained more in details here, but the question could be generalized to "is there a way to include hardware wallets into truffle development network?" : https://github.com/trufflesuite/truffle/issues/973

Using ropsten we have managed to send a transaction and receive a transaction hash in the callback, but if we query for that transaction we get that the transaction doesnt exists.. so.. how is that possible?

We tried deploying a contract into Ropsten too and now we are getting "Invalid address" when invoking a smart contract function. Maybe the signing function is wrong? anyone could integrate Trezor transaction signining with web3js?

Do you guys see anything wrong in the signing and sending process that we have followed? Maybe is there something wrong on the R, V and S parameters handling ..

Another important thing is that we are using https://github.com/ethereumjs/ethereumjs-tx for creating the raw transactions

Issues published in web3js, truffle and trezzor connect with more information:

kind regards

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


    }

The getTrezorConnect function is just get window.trezorConnect asynchronously because the object is injected as script

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

You have lots of questions listed. It's better to post one question at a time, to increase your chances of having them answered.

Let me address the most important ones.

Q1. Is there a way to include hardware wallets into truffle development network?

Yes, by using truffle console and configure it to connect to testrpc. With testrpc, you can have whatever account you want funded (edit: this is not true - the accounts are actually private keys, which are not available using a HW wallet), by starting it like:

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

Where the addresses are your trezor addresses.

Or you can start it with the seed of your trezor (I don't recommend this, unless you know for sure that the trezor is used on the live network):

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

Q2: Do you guys see anything wrong in the signing and sending process that we have followed?

I managed to sign eth transactions programmatically using the Ledger Nano S hardware wallet. The ledgerco js library that I used for signing the transactions, also returns the V, R and S parameters. I assume they are in the same format as the ones returned by Trezor's library, but I can't be sure of that. Anyway, this is how I use V, R, S to create valid transactions:

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

And that's it.

  • How can you start testrpc with the Trezzor account when the private key of Trezzor isnt available ? also the mnemonic is 24 words lenght.. so im stuck there.. when i try your code against ropsten i get a transaction hash like the transaction was sent but then if i check into ropsten.etherscan for that contract transactions im only seeing the construction of the transaction, any ideas about this? Thanks – Marcos Martínez Jun 13 at 19:44
  • Well, the problem was related of how we managed the r,s,v values, it wasnt neccesary to use the Buffer. Im going to accept this answer as correct. – Marcos Martínez Jun 13 at 21:22
  • testrpc doesn't want the private key to be able to credit an address with ethers. You only tell it what address to credit with how many Wei. It doesn't care whether you have the private key or not. – Tudor Constantin Jun 14 at 5:38
  • The --account option requires the private keys and Wei. If i use the address of the Trezor device testrpc tells me "RangeError: private key length is invalid" – Marcos Martínez Jun 14 at 13:34
  • indeed, you're right, it needs the private keys. It seems that your only solution is to use Trezor's mnemonic when you start testrpc – Tudor Constantin Jun 14 at 14:06
up vote 0 down vote accepted

Well, after a lot of trying we have managed to send a raw transaction signed with Trezor to Ropsten, based on Tudor Constantin help:

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

This are the things that i had changed in order to make it possible to send signed transactions to the Ropsten testnet.

This assumes that you have your contract deployed into Ropsten and you have the contract address.

1) Get the address of your Trezor account

  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) Put the trezor address into the from field of your raw transaction, get the nonce of the transaction by getting the transaction count for that address. Important: use the "pending" optional parameter on getTransactionCount to get all the transactions of the account, otherwise you will be overriting pending transactions.

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) The r, s, v parameters were incorrect, the right way to handle them is take that values for the trezor response and just convert it to 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);

Important: the mining time in ropsten will be between 15 and 30 secs so if in your someCallbackFunction you check for the transaction receipt, using the hash, you will get null as result, because the transaction is in a pending state.

4) To test it at ropsten we use Infura, so we change the web3 provider:

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

EDIT:

This also works on Truffle! check the last comments of this issue https://github.com/trufflesuite/truffle/issues/973

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.