0

I cannot get this to work. I understand from this that TREZOR adds the magic message and a hexadecimal message length (as opposed to ASCII length) of the message before signing.

Is it just not possible to manipulate the given plaintext message by pre-pending the same data prior to sending over to geth for verification?

  • I don't have a TREZOR, so I have no way to try. Can you share the message you're signing, the address of the signing account, the code you're using to sign it, and the resulting signed message? – user19510 Feb 9 '18 at 22:48
  • From my reading of the issue you linked to, TREZOR is not adding a hexadecimal message length; it's adding a varint. For short messages, this should just be a single byte with the length of the message. – user19510 Feb 9 '18 at 22:49
  • You're right, it's varint, but the issue is with... well i'm not sure who is at fault. Trezor made the decision to "fix" the ascii representation of message length and turn it in to varint, but geth is live, in the wild, the way it is, so, unless one wants to modify the geth code and run a custom build to handle trezor signed messages, it is what it is. I don't understand the unilateral decision to do this. – EvilJordan Feb 9 '18 at 23:16
  • Here's the data out of Trezor, if you want to fool with it yourself: message: 3fehlpNqurkHvsEGXYVvDvh5xlxRHQMe address: 0x3278312c573f9c0ced9b3c381b428f1821de29a4 sig: 0x6b673ad119bfba39788be3ea8ada062b74c15806a5d0520e3294817a87884a776b835dd5177f64449bd2a9445fa763ef3c3530000f317e15be54fd02480385921b – EvilJordan Feb 9 '18 at 23:19
1

I suspect that you can't use geth's web3.personal.ecRecover function to do this, since it adds its style of prefixing automatically. But you can use any underlying implementation of ecrecover, such as the one in web3.js, as long as you add the correct prefix, TREZOR style. The following code verifies the signature with both web3.js and ethereumjs-util:

const Web3 = require('web3');

const web3 = new Web3(null);

const message = '3fehlpNqurkHvsEGXYVvDvh5xlxRHQMe';
const signerAddress = '0x3278312c573f9c0ced9b3c381b428f1821de29a4';
const signedMessage = '0x6b673ad119bfba39788be3ea8ada062b74c15806a5d0520e3294817a87884a776b835dd5177f64449bd2a9445fa763ef3c3530000f317e15be54fd02480385921b';

// NOTE: Proper varint encoding is necessary for larger lengths.
const prefix = '\x19Ethereum Signed Message:\n' +
               String.fromCharCode(message.length);
const stringToSign = prefix + message;

const recovered = web3.eth.accounts.recover(web3.utils.keccak256(stringToSign),
    signedMessage);

if (recovered.toLowerCase() == signerAddress.toLowerCase()) {
    console.log('Address recovered successfully via web3.js.');
}

const ethUtil = require('ethereumjs-util');

const sig = ethUtil.fromRpcSig(signedMessage);

const publicKey = ethUtil.ecrecover(ethUtil.sha3(stringToSign),
    sig.v, sig.r, sig.s);

const recovered2 = ethUtil.bufferToHex(ethUtil.pubToAddress(publicKey));
if (recovered2.toLowerCase() == signerAddress.toLowerCase()) {
    console.log('Address recovered successfully via ethereumjs-util.');
}
  • Yes, that's essentially the same as my comment where I mentioned modifying the geth code to do the same. I'm not sure this will work, though. as it still has to connect to a geth or parity node to do its job, right? – EvilJordan Feb 10 '18 at 2:06
  • This code runs as-is, offline. – user19510 Feb 10 '18 at 2:13
  • wow, thanks for the guidance. I'm getting a "Provider not set or invalid" error when attempting to do this, though. Were you able to successfully make this run and trick it to not use a provider somehow? – EvilJordan Feb 10 '18 at 3:07
  • I passed in null. The code I shared is the exact code I ran. I was using Node.js 9.4.0 with web3 1.0.0-beta.29. What line triggers that error for you? – user19510 Feb 10 '18 at 3:09
  • But if this is part of other code you're using that needs a valid provider, just use one. The web3.eth.accounts.recover call should be purely local, so it shouldn't matter what provider you've specified. – user19510 Feb 10 '18 at 3:10
0

I dont think the answer above recover the expected address from Trezor's signature. I have myself the same issue and I've find the solution reading Myetherwallet's codebase.

Trezor has a particular way to hash the message (from myetherwallet):

function getTrezorHash(msg) {
   return ethUtil.sha3(Buffer.concat([ethUtil.toBuffer('\x19Ethereum Signed Message:\n'), getTrezorLenBuf(msg.length), ethUtil.toBuffer(msg)]));
};

see the "getTrezorLenBuf" function

function getTrezorLenBuf(msgLen) {
        if (msgLen < 253) return Buffer.from([msgLen & 0xff]);else if (msgLen < 0x10000) return Buffer.from([253, msgLen & 0xff, msgLen >> 8 & 0xFF]);else {
            return Buffer.from([254, msgLen & 0xFF, msgLen >> 8 & 0xFF, msgLen >> 16 & 0xFF, msgLen >> 24 & 0xFF]);
        }
    };

A working exemple validate Trezor will look like :

import { publicToAddress, toBuffer, fromRpcSig, ecrecover, sha3 } from 'ethereumjs-util';
import { TrezorConnect } from './trezor-connect';

(async (msg, n = 0) => {
const { address, signature } = await new Promise((resolve) => TrezorConnect.ethereumSignMessage(
  `m/44'/60'/0'/0/${n}`,
  msg,
  resolve
));

const messageHash = getTrezorHash(msg);
const { r, s, v } = fromRpcSig(`0x${signature}`);
const publicKey = ecrecover(messageHash, v, r, s);
const signer = publicToAddress(publicKey).toString('hex')

console.log(signer === address); //true
})('Hello world');

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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