Estamos tratando de integrar web3js con Trezor en una red de desarrollo de trufas o usando la red de prueba de ropsten .
La idea es firmar las transacciones usando la billetera de hardware y luego enviar una transacción sin procesar usando web3js
Estamos recibiendo que no tenemos saldo para realizar la transacción, probablemente porque web3js no está tomando una de las 10 cuentas de trufas y está usando la dirección trezor que no está en mi red local .
En ropsten tengo algunos éteres y obtengo "dirección no válida"
¿Hay alguna forma de enviar transacciones firmadas (con trezor) usando web3js a una red de desarrollo de trufas? Quiero decir, ¿hay alguna forma de incluir la dirección de Trezor en la red de trufas?
La situación en truffle se explica con más detalle aquí, pero la pregunta podría generalizarse a " ¿hay alguna manera de incluir carteras de hardware en la red de desarrollo de truffle? ": https://github.com/trufflesuite/truffle/issues/973
Usando ropsten, hemos logrado enviar una transacción y recibir un hash de transacción en la devolución de llamada, pero si consultamos esa transacción, obtenemos que la transacción no existe ... entonces ... ¿cómo es eso posible?
También intentamos implementar un contrato en Ropsten y ahora obtenemos "Dirección no válida" al invocar una función de contrato inteligente. ¿Quizás la función de firma está mal? ¿Alguien podría integrar la firma de transacciones de Trezor con web3js?
¿Ustedes ven algo malo en el proceso de firma y envío que hemos seguido? Tal vez hay algo mal en el manejo de los parámetros R, V y S...
Otra cosa importante es que estamos usando https://github.com/ethereumjs/ethereumjs-tx para crear transacciones sin procesar .
Números publicados en web3js, truffle y trezzor conectan con más información:
Saludos cordiales
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")
}
}
La función getTrezorConnect solo obtiene window.trezorConnect de forma asincrónica porque el objeto se inyecta como secuencia de comandos
<script src="https://connect.trezor.io/4/connect.js"></script>
Tienes muchas preguntas en la lista. Es mejor publicar una pregunta a la vez, para aumentar sus posibilidades de que las respondan.
Permítanme abordar los más importantes .
Q1. ¿Hay alguna manera de incluir billeteras de hardware en la red de desarrollo de trufas?
Sí, al usarlo truffle console
y configurarlo para conectarse a testrpc
. Con testrpc
, puede tener cualquier cuenta que desee financiar (editar: esto no es cierto, las cuentas son en realidad claves privadas, que no están disponibles usando una billetera HW), al iniciarlo como:
testrpc --account="0x8414315fe005b8f294020dfc61cfd13749fbc045b0c6abc31fbd1ee3f4ff3b41, 10000000000000000000" --account="0x566a9022cd3f0dfcc3dff657a6c578897d4b0300e335fa569a082b637e6bb273, 70000000000000000000" --account="0x90b4e47ca43b66fab5dbebfee464087b51923f73f649701ca485da313574fd5b, 80000000000000000000" --account="0x5d47b245c405d706fecbc5eb213819d20a2168ad696b352644ad0ffc87aef18e, 90000000000000000000"
Donde las direcciones son sus direcciones trezor.
O puede comenzar con la semilla de su trezor (no lo recomiendo, a menos que esté seguro de que el trezor se usa en la red en vivo):
testrpc -m 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat'
P2: ¿Ustedes ven algo malo en el proceso de firma y envío que hemos seguido?
Me las arreglé para firmar transacciones eth mediante programación usando la billetera de hardware Ledger Nano S. La biblioteca ledgerco js que utilicé para firmar las transacciones también devuelve los parámetros V, R y S. Supongo que están en el mismo formato que los devueltos por la biblioteca de Trezor, pero no puedo estar seguro de eso. De todos modos, así es como uso V, R, S para crear transacciones válidas:
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');
Y eso es.
Bueno, después de mucho intentarlo, hemos logrado enviar una transacción sin formato firmada con Trezor a Ropsten, basada en la ayuda de Tudor Constantin:
https://ropsten.etherscan.io/address/0x89e2c46b22881f747797cf67310aad1a831d50b7
Estas son las cosas que había cambiado para que fuera posible enviar transacciones firmadas a la red de prueba de Ropsten.
Esto supone que tiene su contrato implementado en Ropsten y tiene la dirección del contrato.
1) Obtenga la dirección de su cuenta de 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) Ponga la dirección de trezor en el from
campo de su transacción sin procesar, obtenga el nonce
número de transacción obteniendo el recuento de transacciones para esa dirección. Importante: use el parámetro opcional "pendiente" en getTransactionCount para obtener todas las transacciones de la cuenta; de lo contrario, sobrescribirá las transacciones pendientes.
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) Los parámetros r, s, v eran incorrectos, la forma correcta de manejarlos es tomar esos valores para la respuesta trezor y simplemente convertirlos a 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);
Importante: el tiempo de minería en ropsten será de entre 15 y 30 segundos, por lo que si en su someCallbackFunction verifica el recibo de la transacción, utilizando el hash, obtendrá un resultado nulo, porque la transacción está en un estado pendiente.
4) Para probarlo en ropsten usamos Infura, por lo que cambiamos de proveedor 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
EDITAR :
¡ Esto también funciona en Trufa ! consulte los últimos comentarios de este número https://github.com/trufflesuite/truffle/issues/973
marcos martínez
marcos martínez
Tudor Constantino
marcos martínez
Tudor Constantino
marcos martínez