以太坊(ethereum)中使用了ECDSA
签名算法,该算法基于椭圆曲线实现。
同时,智能合约编程语言solidity
也提供了签名和验证签名的操作:
签名使用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
的哈希值加上前缀后再次哈希的值。我们要非常注意这一点,否则后期验证签名会不成功。
验证签名可以在智能合约中通过ecrecover()
函数实现,ecrecover()
函数如下:
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
从代码中可以看出,ecrecover()
函数有四个参数,被签名数据的哈希值hash
,以及签名结果划分的r
,s
,v
三个值,即r
,s
,v
是分别来自签名结果串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);
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);
}
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