Workflow zum Signieren einer Zeichenfolge mit privatem Schlüssel, gefolgt von der Signaturprüfung mit öffentlichem Schlüssel

Ich weiß, dass diese Frage gestellt und beantwortet wurde, aber beim Versuch, alles zum Laufen zu bringen, bin ich auf einige Probleme gestoßen. Dies ist, was ich versuche zu tun:

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'

Also hier ist, was ich habe. Erstens können wir nicht alle denselben privaten Schlüssel verwenden. Wenn also jemand meine Arbeit überprüfen und einen Überblick über mein Problem bekommen könnte, wäre das großartig.

Ich gebe vor, mein privater Schlüssel sei „0xd1ade25ccd3d550a7eb532ac759cac7be09c2719“, um „Schoolbus“ zu signieren

curl -X POST --data '{"jsonrpc":"2.0","method":"eth_sign","params":["0xd1ade25ccd3d550a7eb532ac759cac7be09c2719", "Schoolbus"],"id":1}'

Wo bekomme ich das Ergebnis

    0x2ac19db245478a06032e69cdbd2b54e648b78431d0a47bd1fbab18f79f820ba407466e37adbe9e84541cab97ab7d290f4a64a5825c876d22109f3bf813254e8601

Ich verwende (ich könnte mich hier irren)

v=2a
r=c19db245478a06032e69cdbd2b54e648b78431d0a47bd1fbab18f79f820ba407
s=466e37adbe9e84541cab97ab7d290f4a64a5825c876d22109f3bf813254e8601

Dann habe ich meinen Vertrag auf die Beine gestellt, der eine Variation einer Antwort in einem verwandten Thread ist:

contract Auth {      
    function verify( bytes32 hash, uint8 v, bytes32 r, bytes32 s) constant returns(address retAddr) {
        retAddr= ecrecover(hash, v, r, s);
    }
}

Da ich nie den Hash von 'Schoolbus' bekommen habe, habe ich ein paar Dinge in web3.js ausprobiert (der UTF8 hat mich verwirrt, ich hatte erwartet, dass die {encoding:'hex'}-Version die richtige ist):

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

Am Ende habe ich das verwendet, weiß aber nicht warum:

0xd030d9a04df643f62a1502b017f51c41a659268091abbd20e2de97b935724d7c

Dann habe ich den Vertrag aufgerufen:

var contDep=web3.eth.contract( [abi def] ).at( contractAddress);

console.log(
    contDep.verify('d030d9a04df643f62a1502b017f51c41a659268091abbd20e2de97b935724d7c', 2a,'c19db245478a06032e69cdbd2b54e648b78431d0a47bd1fbab18f79f820ba407', '466e37adbe9e84541cab97ab7d290f4a64a5825c876d22109f3bf813254e8601')
);

Hier ist mein Problem. Ich bekomme immer diese seltsame Adresse zurück. Es beginnt mit 0x, es sind 20 Bytes, aber es ist kein [af] drin:

0x3433663632613135303262303137663531633431

Wenn ich r und s vertausche, bekomme ich fast das gleiche Ergebnis zurück.
Ich habe mich gefragt, ob jemand meine Erfahrung bestätigen oder darauf hinweisen kann, was ich falsch gemacht habe. Ich fühle mich hier wie ein Verrückter.

Danke für deine Hilfe.

Antworten (4)

Ich hatte früher das gleiche Problem, also werde ich eine ausführliche Antwort darauf geben, wie das funktioniert. Ich nehme an, Sie verwenden Geth als Client. Es gibt ein offenes Problem, bei dem der Geth-Client im falschen Format zurückkehrt. Denken wir also daran, dass wir etwas hinzufügen sollten v, wenn wir ein erhalten . Wenn Sie node ausführen und web3 mit Ihrem bevorzugten Client verbunden haben:v0127

var msg = web3.sha3('Schoolbus')
var signature = web3.eth.sign(web3.eth.accounts[0], msg)

In meinem Fall lautet die Signatur:

0x28c412923e03982efdff078f78bb70eaefe32c11751b0c23858191c18dddc4ba72c3667c07672b97c022beb857afb99c49b7084da1608e20392c274adc7dd5851c

Die Zeichenfolge repräsentiert r, s, vbzw. in dieser Reihenfolge . Um es jedoch in Ihren Auth-Vertrag einzuspeisen, müssen Sie es in vein konvertieren uint8und hinzufügen, um sicherzustellen, dass das Hex-Präfix 0xüberall vorhanden ist:

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

Denken Sie daran, das vsollte 27oder sein 28! Ist dies nicht der Fall, setzen Sie v = v + 27. Sie können Ihre Überprüfungsfunktion jetzt wie folgt aufrufen:

var addr = Auth_instance.verify.call(msg, v, r, s)

und Sie können überprüfen, ob addrdas denselben Wert hat wie web3.eth.accounts[0].

Danke für deinen unglaublichen Einblick. Ich hatte Schwierigkeiten, Informationen über die r,s,v-Bestellung in eth_sign zu finden. Nachdem ich jedoch r,s,v korrigiert und als v,r,s mit 27 für 00 und 28 für 01 für v eingereicht habe, erhalte ich immer noch Adressen, die nicht mit meiner eth-Kontoadresse übereinstimmen. Noch wichtiger ist, dass das 20-Byte-Hex, das ich zurückerhalte, keine [af]-Zeichen enthält, sodass sie dezimal aussehen, außer dass ihnen das Präfix „0x“ vorangestellt ist.
Verwenden Sie immer noch den privaten Schlüssel, den Sie im Beispiel angegeben haben? Es scheint nicht korrekt generiert worden zu sein. Ein privater Schlüssel sollte 64 Hexadezimalzeichen lang sein (ohne Präfix), Ihrer ist 40. Auch eth_sign nimmt eine Adresse als Eingabe, keinen privaten Schlüssel. Sie können auch von hier aus mit ecsign- und ecrecover-Funktionen herumspielen: github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md . Sie behandeln explizit öffentliche und private Schlüssel und Adressen. Lassen Sie mich wissen, wenn Sie immer noch Probleme haben
Vielen Dank für den Hinweis auf das Problem mit dem privaten Schlüssel. Mein privater Schlüssel (oder Adresse in diesem Fall) stammt direkt aus der JSON-RPC.md-Dokumentation für eth_sign. Ich bin einfach davon ausgegangen, dass Geth automatisch den mit dieser Adresse verknüpften privaten Schlüssel verwendet, um die Nachricht zu signieren, wenn ich meine Adresse als Argument sende. Wenn ich eth_sign anrufe, muss ich mein Konto entsperren, deshalb habe ich angenommen. Ich werde in das Dokument schauen, auf das Sie hingewiesen haben, um mehr zu untersuchen - ich weiß es zu schätzen. Dokumente scheinen an vielen verschiedenen Orten zu sein, die nicht so offensichtlich sind.
Wie Sie der Antwort von @MrChico entnehmen können, ist das erste Argument für web3.eth.sign(...)die Adresse des Kontos, mit dessen privatem Schlüssel Sie signieren möchten. Der Wert, den Sie senden, also 40 Hex-Zeichen (dh 20 Byte), ist die richtige Länge für eine Adresse. Aus Ihrem Kommentar geht hervor, dass Sie die Adresse wirklich senden . Nennen Sie es einfach nicht einen "privaten Schlüssel". Eine Adresse ist in hohem Maße eine öffentliche Information. (Er wird normalerweise auf irgendeine Weise aus dem öffentlichen Schlüssel generiert.
Gibt es ein web3.eth.verify(0x..) ?
Es gibt ein web3.personal.ecRecover(), mit dem Sie die Signatur unabhängig von Solidity überprüfen können. Die obige Antwort funktioniert bei mir auch nicht. Es stellt sich heraus, dass Sie auch den Solidity-Vertrag bearbeiten müssen. In dieser Antwort erfahren Sie, wie das Ecrecover im Solidity-Vertrag genannt werden sollte: ethereum.stackexchange.com/questions/15364/…

Hier ist ein funktionierendes Beispiel, das ich mit Trüffel getestet habe:

Beispiel.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;
    }
}

Hier sind einige Beispiele, die zeigen, wie Sie die v-, r- und s-Werte mithilfe von Slicing erhalten und testen, ob ecrecover die Adresse zurückgibt, die die Nachricht signiert hat:

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

Lauftest:

$ truffle test

Using network 'development'.

Compiling ./contracts/Example.sol...


  Contract: Example
    ✓ ecrecover result matches address (132ms)


  1 passing (147ms)

Es ist wahrscheinlich besser, die Präfixierung auf Anwendungsebene statt im Soliditätsvertrag vorzunehmen, da dies billiger ist.


Hier ist eine Hilfsbibliothek mit einer Methode, die einen Hash der Daten und der Signatur akzeptiert und die Signaturadresse zurückgibt. Der Smart Contract übernimmt das Abrufen der v-, r- und s-Werte, anstatt dies auf Anwendungsebene zu tun:

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

Hier sind einige Beispiele zum Erstellen und Testen von Signaturen mit web3:

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

Testen, ob es funktioniert:

$ 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)

Verwandt

Miguel Motas Antwort deckt die hervorstechenden Details ab. Die folgende Zeile -> hat jedoch var sig = web3.eth.sign(account, hash)einen Fehler ausgelöst. Error: Provided address is invalid, the capitalization checksum test failed, or its an indrect IBAN address which can't be converted.Anscheinend scheinen diese Parameter in Versionen von web3.js umgekehrt zu sein, und Sie müssen msgals ersten Parameter und account/addressals zweiten Parameter angeben. Hier ist ein Beispieltest, der bei mir funktioniert hat.

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

Mein entsprechender Vertragscode lautete:

function recoverAddr(bytes32 msgHash, uint8 v, bytes32 r, bytes32 s) public view returns (address) {
        return ecrecover(msgHash, v, r, s);
    }

Für Geth-Benutzer funktioniert Folgendes. Habe es vor kurzem getestet. Erstens stammt der Testvertragscode aus dem Kommentar der obersten Ebene. Sie können diesen Code unter https://remix.ethereum.org einfügen

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

Laden Sie auf einer beliebigen Webseite die Datei web3.js. Fügen Sie dann in der Entwicklertools-Konsole diese Funktionen einfach ein. Rufen Sie die Funktion verificationScheme(msg)mit der msg im ASCII-Klartext auf.

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

Also, meine Ergebnisse waren diese: eth.accounts[0]im Funktionscode zeigt auf "0xebbc50c7afc14c693bfc26868c490ce0819cef4f". Im Grunde wurde also diese Adresse zum Unterzeichnen verwendet, und das sollte die endgültige Ausgabe des Vertrags sein. Ich habe angerufen verificationScheme("hello").

Ich habe dieses Array zurück:["0x50b2c43fd39106bafbba0da34fc430e1f91e3c96ea2acee2bc34119f92b37750", 27, "0x43653d23758f13a45c498fc96c8d5d07e9fc24123d967b0cb29c48cd48e4c907", "0x365d7cebd54f5f5298b740b44004f9808a2c9791a1c2d5a7137602a0a2742f28"]

Dann habe ich die testRecovery-Funktion des Beispielvertrags mit allen Argumenten in diesem Array in dieser Reihenfolge aufgerufen. Ich habe meine ursprüngliche Adresse "0xebbc50c7afc14c693bfc26868c490ce0819cef4f"als Ausgabe zurückbekommen.