以太坊研究系列【签名和验证】
前面研究GUSD的Custodian合约时,需要进行离线签名,以前都是对交易进行签名,没有单独对数据进行签名,这次一起来看看怎么对数据签名和验证。
geth签名验证
personal.sign
> a0
"0x54b865714068f5f03574ace39a1f3279c4e83e2c"
> personal.sign("My name is Chaim!", a0, "123456")
Error: invalid argument 0: json: cannot unmarshal hex string without 0x prefix into Go value of type hexutil.Bytes
可以看到sign不能直接签名字符串,需要签名0x开头的数据,需要先把数据进行hash
> web3.sha3("My name is Chaim!")
"0xc891c508f8b48c9a9b15b417180549195d8a5911b592b68e14ae61d277c015c8"
> personal.sign("0xc891c508f8b48c9a9b15b417180549195d8a5911b592b68e14ae61d277c015c8", a0, "123456")
"0xbe5f614e12201b96c3020a1fcdcf998215efb4861cc66b0163b8c295890c46a074eda3dc68bc06a1eca7bc3cf2461b8a1c5cfd9ca9f4a1ef8b84d574eebe3d091c"
personal.ecRecover
> personal.ecRecover("0xc891c508f8b48c9a9b15b417180549195d8a5911b592b68e14ae61d277c015c8", "0xbe5f614e12201b96c3020a1fcdcf998215efb4861cc66b0163b8c295890c46a074eda3dc68bc06a1eca7bc3cf2461b8a1c5cfd9ca9f4a1ef8b84d574eebe3d091c")
"0x54b865714068f5f03574ace39a1f3279c4e83e2c"
> personal.ecRecover("0xd891c508f8b48c9a9b15b417180549195d8a5911b592b68e14ae61d277c015c8", "0xbe5f614e12201b96c3020a1fcdcf998215efb4861cc66b0163b8c295890c46a074eda3dc68bc06a1eca7bc3cf2461b8a1c5cfd9ca9f4a1ef8b84d574eebe3d091c")
"0xde8f08dda362e8373b98dea069c905ae853ce632"
从上面看到,如果签名正确可得到私钥对应的地址,如果签名不对(0xc改成0xd)也能得到一个地址,但是地址是错误的。
web3js签名验证
js签名
const Web3 = require("web3");
var web3 = new Web3(new Web3.providers.WebsocketProvider('ws://127.0.0.1:8546'));
web3.eth.getAccounts(function(error, result){
var a0 = result[0];
console.log("account:" + a0);
var sha3Msg = web3.utils.sha3("My name is Chaim!");
console.log("sha3:" + sha3Msg);
var signedData = web3.eth.sign(sha3Msg, a0).then(resp => {
console.log("signed::" + resp);
});
});
实际上在web3js中不要求要签名的数据是编码的,直接可以签名字符串数据。
执行结果:
Chaim:web3eth Chaim$ node sign.js
account:0x54b865714068f5F03574ACe39a1F3279C4E83E2c
sha3:0xc891c508f8b48c9a9b15b417180549195d8a5911b592b68e14ae61d277c015c8
signed::0xbe5f614e12201b96c3020a1fcdcf998215efb4861cc66b0163b8c295890c46a074eda3dc68bc06a1eca7bc3cf2461b8a1c5cfd9ca9f4a1ef8b84d574eebe3d091c
js验证签名
const Web3 = require("web3");
var web3 = new Web3(new Web3.providers.WebsocketProvider('ws://127.0.0.1:8546'));
web3.eth.personal.ecRecover("0xc891c508f8b48c9a9b15b417180549195d8a5911b592b68e14ae61d277c015c8", "0xbe5f614e12201b96c3020a1fcdcf998215efb4861cc66b0163b8c295890c46a074eda3dc68bc06a1eca7bc3cf2461b8a1c5cfd9ca9f4a1ef8b84d574eebe3d091c").then(resp => {
console.log("addr:" + resp)});
同样ecRecover传入的带有签名的数据也可以是字符串,使用web3.utils.utf8ToHex()转化为16进制字符串。
执行结果:
addr:0x54b865714068f5f03574ace39a1f3279c4e83e2c
solidity验证
geth生成签名
> sha3msg = web3.sha3("My name is Chaim!")
"0xc891c508f8b48c9a9b15b417180549195d8a5911b592b68e14ae61d277c015c8"
> sig = web3.eth.sign(a0, sha3msg)
"0xbe5f614e12201b96c3020a1fcdcf998215efb4861cc66b0163b8c295890c46a074eda3dc68bc06a1eca7bc3cf2461b8a1c5cfd9ca9f4a1ef8b84d574eebe3d091c"
> r = sig.slice(0, 66)
"0xbe5f614e12201b96c3020a1fcdcf998215efb4861cc66b0163b8c295890c46a0"
> s = '0x' + sig.slice(66, 130)
"0x74eda3dc68bc06a1eca7bc3cf2461b8a1c5cfd9ca9f4a1ef8b84d574eebe3d09"
> v = '0x' + sig.slice(130, 132)
"0x1c"
> v = web3.toDecimal(v)
28
用solidity来验证签名使用ecrevocer()函数,需要传入r、s、v值。
v值應為27 or 28,如果v = 0或1的話,需加上27。
solidity contract验证
pragma solidity ^0.4.21;
contract test {
function verify(bytes32 _message, uint8 _v, bytes32 _r, bytes32 _s) public constant returns (address) {
bytes memory prefix = "\x19Ethereum Signed Message:\n32";
bytes32 prefixedHash = sha3(prefix, _message);
address signer = ecrecover(prefixedHash, _v, _r, _s);
return signer;
}
}
在Remix上调试,正常返回地址,如下:
发布合约,用上面geth生成的v、r、s和已签名数据调用合约函数:
abi_verify = [...]
addr_verify = "0xedfc0b97e3162063f1e7a9780a3705abca4a9a60"
contract_verify = eth.contract(abi_verify).at(addr_verify)
> contract_verify.verify.call("0xc891c508f8b48c9a9b15b417180549195d8a5911b592b68e14ae61d277c015c8",28,"0xbe5f614e12201b96c3020a1fcdcf998215efb4861cc66b0163b8c295890c46a0","0x74eda3dc68bc06a1eca7bc3cf2461b8a1c5cfd9ca9f4a1ef8b84d574eebe3d09")
"0x54b865714068f5f03574ace39a1f3279c4e83e2c"
调用合约返回了正确的签名地址。
此处注意,如果不加前缀"\x19Ethereum Signed Message:\n32"解出的地址是错误的,原因在这,摘录原文如下:
The sign method calculates an Ethereum specific signature with:
sign(keccack256("\x19Ethereum Signed Message:\n" + len(message) + message))).
By adding a prefix to the message makes the calculated signature recognisable as an Ethereum specific signature. This prevents misuse where a malicious DApp can sign arbitrary data (e.g. transaction) and use the signature to impersonate the victim.
Python库secp256k1签名验证
用geth签名带上了不需要的前缀,得找些工具或代码来做签名工作。
secp256k1这个python库我在python3.6下安装失败了,但在python2.7下是成功的。
签名需要私钥,先想办法把geth中的地址的私钥找出来,方法在这
eb4e675d4adee4fa60cf82a681a7d192d7bd3ece7938e7e63ea06a5259eb0d0c
用私钥签名:
python -m secp256k1 signrec -k eb4e675d4adee4fa60cf82a681a7d192d7bd3ece7938e7e63ea06a5259eb0d0c -m 0xc891c508f8b48c9a9b15b417180549195d8a5911b592b68e14ae61d277c015c8
d39b1a3b363e15691e0ae7120e650bf3c0d632621e1016c9fa0aaf1dc5d38b632d396c9f968dd1ea91867adbd4b2751ba00affd24cd2f1cac0ac1d17919bbb5d 1
从签名中解出公钥:
python -m secp256k1 recpub -s d39b1a3b363e15691e0ae7120e650bf3c0d632621e1016c9fa0aaf1dc5d38b632d396c9f968dd1ea91867adbd4b2751ba00affd24cd2f1cac0ac1d17919bbb5d -i 1 -m 0xc891c508f8b48c9a9b15b417180549195d8a5911b592b68e14ae61d277c015c8
Public key: 0366840aef641387cd9ed6ce575e08720be48d9767a0d1fe0ba398250e47be91f4
更多使用secp256k1见secp256k1-py 安装以及命令行操作
js库签名验证(elliptic)
let elliptic = require('elliptic');
let sha3 = require('js-sha3');
let ec = new elliptic.ec('secp256k1');
// let keyPair = ec.genKeyPair();
let keyPair = ec.keyFromPrivate("eb4e675d4adee4fa60cf82a681a7d192d7bd3ece7938e7e63ea06a5259eb0d0c");
let privKey = keyPair.getPrivate("hex");
let pubKey = keyPair.getPublic();
console.log(`Private key: ${privKey}`);
console.log("Public key :", pubKey.encode("hex").substr(2));
console.log("Public key (compressed):",
pubKey.encodeCompressed("hex"));
console.log();
let msg = 'My name is Chaim!';
let msgHash = sha3.keccak256(msg);
let signature = ec.sign(msgHash, privKey, "hex", {canonical: true});
console.log(`Msg: ${msg}`);
console.log(`Msg hash: ${msgHash}`);
console.log("Signature:", signature);
console.log();
let hexToDecimal = (x) => ec.keyFromPrivate(x, "hex").getPrivate().toString(10);
let pubKeyRecovered = ec.recoverPubKey(
hexToDecimal(msgHash), signature, signature.recoveryParam, "hex");
console.log("Recovered pubKey:", pubKeyRecovered.encodeCompressed("hex"));
let validSig = ec.verify(msgHash, signature, pubKeyRecovered);
console.log("Signature valid?", validSig);
执行结果如下:
Chaim:web3eth Chaim$ node secp256k1.js
Private key: eb4e675d4adee4fa60cf82a681a7d192d7bd3ece7938e7e63ea06a5259eb0d0c
Public key : 66840aef641387cd9ed6ce575e08720be48d9767a0d1fe0ba398250e47be91f45e8b5bb23f5256994a6fb5de254520109b4e633eefb2e7322c106dea589129d1
Public key (compressed): 0366840aef641387cd9ed6ce575e08720be48d9767a0d1fe0ba398250e47be91f4
Msg: My name is Chaim!
Msg hash: c891c508f8b48c9a9b15b417180549195d8a5911b592b68e14ae61d277c015c8
Signature: Signature {
r: <BN: 506f4440e1185666fd2520143103ea2760a902271f8a113f02a16e14df48d671>,
s: <BN: 727257238e0d6537ca8bdcc1e9fb98bbccb715d7b0888431a917827d06d29847>,
recoveryParam: 1 }
Recovered pubKey: 0366840aef641387cd9ed6ce575e08720be48d9767a0d1fe0ba398250e47be91f4
Signature valid? true
重新发布一个不带前缀"\x19Ethereum Signed Message:\n32"的合约,再用合约里方法验证看看:
abi_verify2 = [...]
addr_verify2 = "0xa141e2d6435f6730c7b81b0c5e048d1fac5df6da"
contract_verify2 = eth.contract(abi_verify2).at(addr_verify2)
> contract_verify2.verify.call("0xc891c508f8b48c9a9b15b417180549195d8a5911b592b68e14ae61d277c015c8",28,"0x506f4440e1185666fd2520143103ea2760a902271f8a113f02a16e14df48d671","0x727257238e0d6537ca8bdcc1e9fb98bbccb715d7b0888431a917827d06d29847")
"0x54b865714068f5f03574ace39a1f3279c4e83e2c"
可以看到,解出了正确的地址!
js库签名二(ethereumjs-util)
签名
let ethUtil = require('ethereumjs-util');
let hash2 = new Buffer(msgHash, 'hex');
let prikey2 = new Buffer(privKey, 'hex');
const rsv = ethUtil.ecsign(hash2, prikey2);
console.log(rsv);
console.log("r: 0x" + rsv.r.toString('hex'));
console.log("s: 0x" + rsv.s.toString('hex'));
console.log("v: " + rsv.v);
参考
http://me.tryblockchain.org/web3js-sign-ecrecover-decode.html
https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
https://ethereum.stackexchange.com/questions/15364/ecrecover-from-geth-and-web3-eth-sign
http://cw.hubwiz.com/card/c/web3.js-1.0/1/6/4/
https://www.jianshu.com/p/9269acbce4fd
https://solidity.readthedocs.io/en/develop/abi-spec.html
keythereum
https://github.com/ethereumjs/keythereum
椭圆曲线(ECDSA)
https://blog.csdn.net/teaspring/article/details/77834360
https://github.com/emn178/js-sha3
https://gist.github.com/nakov/1dcbe26988e18f7a4d013b65d8803ffc
Leave 以太坊研究系列【签名和验证】 to:
Read more #ethereum posts
Best Posts From 老鱼
We have not curated any of chaimyu's posts yet. But you can encourage our curation team to review posts by visiting them regularly and by referring other readers. Because we give priority to frequently read content.