以太坊研究系列【签名和验证】
前面研究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
Hello! Your post has been resteemed and upvoted by @ilovecoding because we love coding! Keep up good work! Consider upvoting this comment to support the @ilovecoding and increase your future rewards! ^_^ Steem On!
Reply !stop to disable the comment. Thanks!
帅哥/美女!来 @steemgg 玩游戏吧,决战到天亮如果不想再收到我的留言,请回复“取消”。