Sé que esta pregunta ha sido formulada y respondida, pero al intentar que todo funcione, me he encontrado con algunos problemas. Esto es lo que estoy tratando de hacer:
1. take a target string 'Schoolbus'
2. use JSON with geth to eth_sign it
3. obtain v,r,s of signature
4. attempt to verify with a solidity contract, need the hash of 'Schoolbus'
Así que esto es lo que tengo. En primer lugar, no todos podemos usar la misma clave privada, así que si alguien puede verificar mi trabajo y obtener una idea general de mi problema, sería genial.
Fingiendo que mi clave privada es '0xd1ade25ccd3d550a7eb532ac759cac7be09c2719', para firmar 'Schoolbus', uso
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_sign","params":["0xd1ade25ccd3d550a7eb532ac759cac7be09c2719", "Schoolbus"],"id":1}'
donde saco el resultado
0x2ac19db245478a06032e69cdbd2b54e648b78431d0a47bd1fbab18f79f820ba407466e37adbe9e84541cab97ab7d290f4a64a5825c876d22109f3bf813254e8601
Estoy usando (podría estar equivocado aquí)
v=2a
r=c19db245478a06032e69cdbd2b54e648b78431d0a47bd1fbab18f79f820ba407
s=466e37adbe9e84541cab97ab7d290f4a64a5825c876d22109f3bf813254e8601
Luego preparé mi contrato, que es una variación de una respuesta en un hilo relacionado:
contract Auth {
function verify( bytes32 hash, uint8 v, bytes32 r, bytes32 s) constant returns(address retAddr) {
retAddr= ecrecover(hash, v, r, s);
}
}
Como nunca obtuve el hash de 'Schoolbus', probé un par de cosas en web3.js (el UTF8 me confundió, esperaba que la versión {encoding:'hex'} fuera la correcta):
console.log('1 '+ web3.sha3(web3.toHex('Schoolbus'))); //05ab39621b81764697fcfb6ae4fcf6b023cd644721c67c13a49fbd769c75671c
console.log('2 '+ web3.sha3(web3.toHex('Schoolbus'),{encoding:'hex'}));//d030d9a04df643f62a1502b017f51c41a659268091abbd20e2de97b935724d7c
console.log('3 '+ web3.sha3('Schoolbus'));//d030d9a04df643f62a1502b017f51c41a659268091abbd20e2de97b935724d7c
console.log('4 '+ web3.sha3( unescape(encodeURIComponent('Schoolbus')) ) ); //to UTF8 //d030d9a04df643f62a1502b017f51c41a659268091abbd20e2de97b935724d7c
console.log('5 '+ web3.sha3( unescape(encodeURIComponent('Schoolbus')), {encoding:'hex'} ) ); //to UTF8 //8f1cbe7efcf383ffeb1aeaf1e826c778a087153344cbeba144fbe967ad3ab11a
Terminé usando esto, pero no sé por qué:
0xd030d9a04df643f62a1502b017f51c41a659268091abbd20e2de97b935724d7c
Entonces llamé al contrato:
var contDep=web3.eth.contract( [abi def] ).at( contractAddress);
console.log(
contDep.verify('d030d9a04df643f62a1502b017f51c41a659268091abbd20e2de97b935724d7c', 2a,'c19db245478a06032e69cdbd2b54e648b78431d0a47bd1fbab18f79f820ba407', '466e37adbe9e84541cab97ab7d290f4a64a5825c876d22109f3bf813254e8601')
);
Aquí está mi problema. Sigo recibiendo esta extraña dirección de vuelta. Comienza con 0x, tiene 20 bytes, pero no tiene [af]:
0x3433663632613135303262303137663531633431
Si cambié r y s, obtengo casi el mismo resultado.
Me preguntaba si alguien puede verificar mi experiencia o señalar qué estaba haciendo mal. Me siento como un loco aquí.
Gracias por toda tu ayuda.
Estaba teniendo el mismo problema anteriormente, así que voy a dar una respuesta extensa sobre cómo funciona esto. Supongo que estás usando geth como cliente. Hay un problema abierto donde el cliente geth regresa v
en el formato incorrecto, así que tengamos en cuenta que si obtenemos un v
que es 0
o 1
debemos agregarlo 27
. Si está ejecutando node y ha conectado web3 a su cliente favorito:
var msg = web3.sha3('Schoolbus')
var signature = web3.eth.sign(web3.eth.accounts[0], msg)
En mi caso, la firma es:
0x28c412923e03982efdff078f78bb70eaefe32c11751b0c23858191c18dddc4ba72c3667c07672b97c022beb857afb99c49b7084da1608e20392c274adc7dd5851c
La cadena representa r
, s
y v
respectivamente en ese orden . Sin embargo, para alimentarlo a su contrato de autenticación, debe convertirlo v
a un uint8
y agregarlo, asegúrese de tener el prefijo hexadecimal en 0x
todas partes:
var r = signature.slice(0, 66)
var s = '0x' + signature.slice(66, 130)
var v = '0x' + signature.slice(130, 132)
v = web3.toDecimal(v)
msg = '0x' + msg
Recuerde que v
debe ser 27
o 28
! Si no es así, establezca v = v + 27
. Ahora puede llamar a su función de verificación como:
var addr = Auth_instance.verify.call(msg, v, r, s)
y puedes comprobar que addr
tiene el mismo valor que web3.eth.accounts[0]
.
Aquí hay un ejemplo de trabajo que probé usando trufa:
Ejemplo.sol
pragma solidity ^0.4.0;
contract Example {
function testRecovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) returns (address) {
/* prefix might be needed for geth only
* https://github.com/ethereum/go-ethereum/issues/3731
*/
// bytes memory prefix = "\x19Ethereum Signed Message:\n32";
// h = sha3(prefix, h);
address addr = ecrecover(h, v, r, s);
return addr;
}
}
Aquí hay algunos ejemplos que demuestran cómo obtener los valores v, r y s mediante el corte y la prueba de que ecrecover devuelve la dirección que firmó el mensaje:
var Example = artifacts.require('./Example.sol')
var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
contract('Example', (accounts) => {
var address = accounts[0]
it('ecrecover result matches address', async function() {
var instance = await Example.deployed()
var msg = '0x8CbaC5e4d803bE2A3A5cd3DbE7174504c6DD0c1C'
var h = web3.sha3(msg)
var sig = web3.eth.sign(address, h).slice(2)
var r = `0x${sig.slice(0, 64)}`
var s = `0x${sig.slice(64, 128)}`
var v = web3.toDecimal(sig.slice(128, 130)) + 27
var result = await instance.testRecovery.call(h, v, r, s)
assert.equal(result, address)
})
})
Prueba de carrera:
$ truffle test
Using network 'development'.
Compiling ./contracts/Example.sol...
Contract: Example
✓ ecrecover result matches address (132ms)
1 passing (147ms)
Probablemente sea mejor hacer el prefijo en el nivel de la aplicación en lugar de en el contrato de solidez, ya que será más económico.
Aquí hay una biblioteca de ayuda con un método que acepta el hash de los datos y la firma y devuelve la dirección de firma. El contrato inteligente maneja la obtención de los valores v, r y s en lugar de hacerlo a nivel de aplicación:
ECVerify.sol
pragma solidity ^0.4.4;
/*
* @credit https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d
*/
library ECVerify {
function ecrecovery(bytes32 hash, bytes sig) public returns (address) {
bytes32 r;
bytes32 s;
uint8 v;
if (sig.length != 65) {
return 0;
}
assembly {
r := mload(add(sig, 32))
s := mload(add(sig, 64))
v := and(mload(add(sig, 65)), 255)
}
// https://github.com/ethereum/go-ethereum/issues/2053
if (v < 27) {
v += 27;
}
if (v != 27 && v != 28) {
return 0;
}
/* prefix might be needed for geth only
* https://github.com/ethereum/go-ethereum/issues/3731
*/
// bytes memory prefix = "\x19Ethereum Signed Message:\n32";
// hash = sha3(prefix, hash);
return ecrecover(hash, v, r, s);
}
function ecverify(bytes32 hash, bytes sig, address signer) public returns (bool) {
return signer == ecrecovery(hash, sig);
}
}
Aquí hay algunos ejemplos sobre cómo crear firmas con web3 y probarlas:
var ECVerify = artifacts.require('./ECVerify.sol')
contract('ECVerify', (accounts) => {
it('should return signing address from signature', async () => {
var account = accounts[0]
try {
var instance = await ECVerify.deployed()
var msg = 'some data'
var hash = web3.sha3(msg)
var sig = web3.eth.sign(account, hash)
var signer = await instance.ecrecovery(hash, sig)
assert.ok(signer)
} catch(error) {
console.error(error)
assert.equal(error, undefined)
}
})
it('should verify signature is from address', async () => {
var account = accounts[0]
try {
var instance = await ECVerify.deployed()
var msg = 'some data'
var hash = web3.sha3(msg)
var sig = web3.eth.sign(account, hash)
var verified = await instance.ecverify.call(hash, sig, account)
assert.ok(verified)
} catch(error) {
console.error(error)
assert.equal(error, undefined)
}
})
})
Probando que funciona:
$ truffle test
Compiling ./contracts/ECVerify.sol...
Compiling ./contracts/Migrations.sol...
Contract: ECVerify
✓ should return signing address from signature (182ms)
✓ should verify signature is from address (142ms)
2 passing (342ms)
Relacionado
La respuesta de Miguel Mota cubre los detalles más destacados. Sin embargo, la siguiente línea -> var sig = web3.eth.sign(account, hash)
arrojó un error. Error: Provided address is invalid, the capitalization checksum test failed, or its an indrect IBAN address which can't be converted.
Aparentemente, en las versiones de web3.js, estos parámetros parecen estar invertidos y debe proporcionarlos msg
como primer parámetro y account/address
como segundo. Aquí hay una prueba de ejemplo que funcionó para mí.
var address = accounts[0];
it('ecrecover result matches address', async function() {
var instance = await Adoption.deployed()
let msg = 'I really did make this message';
let prefix = "\x19Ethereum Signed Message:\n" + msg.length
let h = web3.utils.sha3(prefix+msg)
console.log(`sha3 hash ${h}`);
let sig1 = await web3.eth.sign(msg, address);
console.log(`signature: ${sig1}`)
var sig = sig1.slice(2)
var r = `0x${sig.slice(0, 64)}`
var s = `0x${sig.slice(64, 128)}`
var v = web3.utils.toDecimal(sig.slice(128, 130)) + 27
var result = await instance.recoverAddr.call(h, v, r, s)
console.log(`address: ${address}, result ${result}`)
assert.equal(result, address)
})
Mi código de contrato correspondiente era:
function recoverAddr(bytes32 msgHash, uint8 v, bytes32 r, bytes32 s) public view returns (address) {
return ecrecover(msgHash, v, r, s);
}
Para los usuarios de Geth, lo siguiente funciona. Lo probé hace poco. En primer lugar, el código del contrato de prueba es del comentario de nivel superior. Puedes pegar ese código en https://remix.ethereum.org
pragma solidity ^0.4.0;
contract Example {
function testRecovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) returns (address) {
/* prefix might be needed for geth only
* https://github.com/ethereum/go-ethereum/issues/3731
*/
// bytes memory prefix = "\x19Ethereum Signed Message:\n32";
// h = sha3(prefix, h);
address addr = ecrecover(h, v, r, s);
return addr;
}
}
En cualquier página web, cargue el archivo web3.js. Luego, en la consola de herramientas para desarrolladores, simplemente pegue estas funciones. La llamada a la función verificationScheme(msg)
con el mensaje en texto plano ascii.
function tohex(msg){
var hexmsg = "";
for(var i=0; i<msg.length; i++){
hexmsg += msg.charCodeAt(i).toString(16);
}
return "0x"+hexmsg;
}
function verificationScheme(str){
var msghex = tohex(str);
var sig = web3.eth.sign(web3.eth.accounts[0], msghex);
var r = sig.slice(0, 66);
var s = '0x' + sig.slice(66, 130);
var v = '0x' + sig.slice(130, 132);
v = web3.toDecimal(v);
var verificationMessage = "\x19Ethereum Signed Message:\n" + str.length + str;
var verificationMessageHash = web3.sha3(verificationMessage);
return [verificationMessageHash, v, r, s];
}
Entonces, mis resultados fueron estos: eth.accounts[0]
en el código de función apunta a "0xebbc50c7afc14c693bfc26868c490ce0819cef4f"
. Entonces, básicamente, esa dirección se usó para firmar, y ese debería ser el resultado final del contrato. llamé verificationScheme("hello")
_
Recuperé esta matriz:["0x50b2c43fd39106bafbba0da34fc430e1f91e3c96ea2acee2bc34119f92b37750", 27, "0x43653d23758f13a45c498fc96c8d5d07e9fc24123d967b0cb29c48cd48e4c907", "0x365d7cebd54f5f5298b740b44004f9808a2c9791a1c2d5a7137602a0a2742f28"]
Luego llamé a la función testRecovery del contrato de ejemplo con todos los argumentos en esa matriz en ese orden. Recuperé mi dirección original "0xebbc50c7afc14c693bfc26868c490ce0819cef4f"
como salida.
Shiso
señorchico
Shiso
Ajoy Bhatia
web3.eth.sign(...)
es la dirección de la cuenta con cuya clave privada desea iniciar sesión. Entonces, el valor que está enviando, que es de 40 caracteres hexadecimales (es decir, 20 bytes), es la longitud correcta para una dirección. Por tu comentario, parece que realmente estás enviando la dirección. Simplemente no lo llames "clave privada". Una dirección es en gran medida una pieza pública de información. (Por lo general, se genera de alguna manera a partir de la clave pública .justin thomas
willjgriff