智能合约验证签名

以太坊(ethereum)中使用了ECDSA签名算法,该算法基于椭圆曲线实现。
同时,智能合约编程语言solidity 也提供了签名和验证签名的操作:

1、签名

签名使用web3.eth.sign(),比如利用web3.js:

var account = web3.eth.accounts[0];
var sha3Msg = web3.sha3("blockchain");
var signedData = web3.eth.sign(account, sha3Msg);

但是需要注意的是geth 客户端签名比较特殊,下面是以太坊签名函数:

sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message)))

从上述代码可以看出,geth会为要签名的消息加上"\x19Ethereum Signed Message:\n" + len(message)前缀,所以实际上真正的被签名的数据并不是message 的哈希值,而是message的哈希值加上前缀后再次哈希的值。我们要非常注意这一点,否则后期验证签名会不成功。

2、验签

验证签名可以在智能合约中通过ecrecover()函数实现,ecrecover()函数如下:

ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)

从代码中可以看出,ecrecover()函数有四个参数,被签名数据的哈希值hash,以及签名结果划分的rsv三个值,即rsv是分别来自签名结果串signature,比如:

signature = 0x1a0744724beca14bfd7b5f838394a3e5c49c4872dbd1fbc29661ffd88bed14ad53e045268098d7934530e81c058d40b763381f8d5c75fd29b4300e64be75867401

r = signature[0:64] = 0x1a0744724beca14bfd7b5f838394a3e5c49c4872dbd1fbc29661ffd88bed14ad
s = signature[64:128] = 0x53e045268098d7934530e81c058d40b763381f8d5c75fd29b4300e64be758674
v = signature[128:130] = 0x01

这里需要特别注意另一参数被签名数据的哈希值hash,比如要签名的数据是blockchain,哈希值为:

sha3Msg = web3.sha3("blockchain") = 0x1a0744724beca14bfd7b5f838394a3e5c49c4872dbd1fbc29661ffd88bed14ad53e045268098d7934530e81c058d40b763381f8d5c75fd29b4300e64be75867401

但是如果把sha3Msg作为参数的话,恢复出的公钥地址并不是原来的进行签名的公钥地址,这就是前面提到的签名函数参数前缀问题,所以ecrecover()函数中的hash参数应该为:

sha3Msg = web3.sha3("blockchain");
hash = web3.sha3(sha3Msg);

附录

VerifySignature.sol文件

pragma solidity ^0.4.10;

contract VerifySignature{

  //数据验签入口函数
  function verifyByHashAndSig(bytes32 hash, bytes signature) returns (address){
    bytes memory signedString = signature;

    bytes32  r = bytesToBytes32(slice(signedString, 0, 32));
    bytes32  s = bytesToBytes32(slice(signedString, 32, 32));
    byte  v1 = slice(signedString, 64, 1)[0];
    uint8 v = uint8(v1) + 27;
    return ecrecoverDirect(hash, r, s, v);
  }

  //将原始数据按段切割出来指定长度
  function slice(bytes memory data, uint start, uint len) returns (bytes){
    bytes memory b = new bytes(len);

    for(uint i = 0; i < len; i++){
      b[i] = data[i + start];
    }
    return b;
  }

  //bytes转换为bytes32
  function bytesToBytes32(bytes memory source) returns (bytes32 result) {
    assembly {
        result := mload(add(source, 32))
    }
  }

  //使用ecrecover恢复公匙
  function ecrecoverDirect(bytes32 hash, bytes32 r, bytes32 s, uint8 v) returns (address addr){
     /* 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);

     addr = ecrecover(hash, v, r, s);
  }
}

或者直接写为一个函数

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

VerifySignature.js测试文件

var VerifySignature = artifacts.require("./VerifySignature.sol");

contract('VerifySignature', function(accounts) {
    console.log(accounts);
    it("verify signature 成功!", function(done) {
        var account = accounts[0];
        var sha3Msg = web3.sha3("blockchain");
        /*
        console.log('1 '+ web3.sha3(web3.toHex('blockchain'))); 
        //0x948100b2466113dfb2b67ab686395aaf8935c612cc5316c01e30b5b354b646c9
        console.log('2 '+ web3.sha3(web3.toHex('blockchain'),{encoding:'hex'}));
        //0x7ee156df5091fbef71b96557542210a9c9ca851cc85aaf60026519b4aaccf491
        console.log('3 '+ web3.sha3('blockchain'));
        //0x7ee156df5091fbef71b96557542210a9c9ca851cc85aaf60026519b4aaccf491
        console.log('4 '+  web3.sha3(  unescape(encodeURIComponent('blockchain'))  ) ); 
        //to UTF8 //0x7ee156df5091fbef71b96557542210a9c9ca851cc85aaf60026519b4aaccf491
        console.log('5 '+  web3.sha3(  unescape(encodeURIComponent('blockchain')), {encoding:'hex'} ) ); 
        //to UTF8 //0x2f1437634f08e2b6324e0701d49075e39cfc34c1fca7c5111dc07b8150399176
        */
        var signedData = web3.eth.sign(account, sha3Msg);

        console.log("account: " + account);
        console.log("sha3(message): " + sha3Msg);
        console.log("Signed data: " + signedData);

        VerifySignature.new({ from: accounts[1] }).then(function(verifySignature) {
            verifySignature.verifyByHashAndSig.call(sha3Msg, signedData).then(function (addr) {
                console.log("    addr = %s", addr);
                assert.equal(addr, account, "Address doesn't match!")
                return  verifySignature.verifyByHashAndSig(sha3Msg, signedData);
                //return VerifySignature.verifyConsumerSignature.call();              
            }).then( function (txid) {
                console.log("    verifySignature, txid = %s, mined at #%s block", txid.tx, txid.receipt.blockNumber);//txid is a object contains tx, receipt, logs. Can print ("%x", txid) to check.

                var sig = signedData.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

                return verifySignature.ecrecoverDirect.call(sha3Msg, r, s, v);
            }).then(function (addr) {
                console.log("    DirectAddr = %s", addr);
                assert.equal(addr, account, "Address doesn't match!")
                done();  // to stop these tests earlier, move this up
            }).catch(done); 
        }).catch(done);
    });
});

参考文献

1、区块链语言Solidity校验椭圆曲线加密数字签名(附实例)
2、workflow on signing a string with private key, followed by signature verification with public key
3、Signing in geth and verifying in solidity do not produce correct results

你可能感兴趣的:(ethereum)