老鱼 avatar

以太坊研究系列【离线签名】

chaimyu

Published: 19 Nov 2018 › Updated: 19 Nov 2018以太坊研究系列【离线签名】

以太坊研究系列【离线签名】

虽然知道以太坊的多重签名机制是通过合约实现的,但一直没去仔细看过,近期在GUSD中也看到这部分内容,按操作来熟悉一下,更底层机制有时间再去研究。

公私钥对和地址

要做离线签名,首先得有私钥,有了私钥就能算出公钥和地址,代码如下:

let elliptic = require('elliptic');
let ec = new elliptic.ec('secp256k1');
let ethUtil = require('ethereumjs-util');

// 私钥->公钥
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"));

// 私钥->公钥->地址
let publicKey = ethUtil.privateToPublic(new Buffer(privKey, 'hex'));
// console.log("Public key:", publicKey)
let addr ="0x"+ ethUtil.publicToAddress(publicKey).toString('hex');
console.log("Ether addr:", addr);

每次执行都会生成不同的公私钥对和地址,如下:

Private key: a3e5a66cc6a76c23ea4e827bb7bccbf1c90f67f51fa1f4c1a2433e856fe7ae3e
Public key : 9ae7020d6344cc5876f3aaf34e8fdbbd14e162a07e091655062dc4af3aa1ad53cbcd787811b5d367692129650fb14d9bb387a652dc2d186924dc0c82f06c4187
Public key (compressed): 039ae7020d6344cc5876f3aaf34e8fdbbd14e162a07e091655062dc4af3aa1ad53
Ether addr: 0x40e1843610d41853cbe22425e2ca2b2fb5538559

我们再生成一个公私钥对用来做签名验证:

Private key: f5b37f8daa631f49e64124b41a1f4768b6e755fc906781614024772710711cf0
Public key : fc184f3ac0418e2f5dda56a9f4e021dd174afb8d84a9014ae6c2851a121edbd250d460b07cac6e381ea9520b727d10e6a0e4c15a02c010fd754286f77c0a4665
Public key (compressed): 03fc184f3ac0418e2f5dda56a9f4e021dd174afb8d84a9014ae6c2851a121edbd2
Ether addr: 0x10f505c5077fb9e5ae06b9f9e98a4f037e54e389

合约验证签名

我们目的是实现离线签名,然后在合约中进行验证,合约如下:

pragma solidity ^0.4.21;

contract MultiSign {
    mapping (address => bool) public signerSet;

    function MultiSign(
        address[] _signers
        )
        public
    {
        for (uint i = 0; i < _signers.length; i++) {
            require(_signers[i] != address(0));
            signerSet[_signers[i]] = true;
        }
    }

    // 取签名公钥
    function verify_addr(bytes32 _message, uint8 _v, bytes32 _r, bytes32 _s) public constant returns (address addr) {
       address signer = ecrecover(_message, _v, _r, _s);
       addr = signer;
       return signer;
       }

    function verify_one(bytes32 _message, uint8 _v, bytes32 _r, bytes32 _s) public constant returns (bool success) {
       address signer = ecrecover(_message, _v, _r, _s);
       success = signerSet[signer];
       return success;
       }
}

创建合约时先要设置好允许的签名地址,验证时只要通过签名数据得出地址在允许的签名地址列表中,签名就通过。

发布合约,初始化时指定前面生成的两个以太坊地址,生成abi

abi_mulsign = [...]
addr_mulsign = "0x230993f4cd49203df242f4f02ef3374beec0f55b"
contract_mulsign = eth.contract(abi_mulsign).at(addr_mulsign)

需要签名的数据为我的steemit网址:https://steemit.com/@chaimyu

用地址“0x10f505c5077fb9e5ae06b9f9e98a4f037e54e389”对数据进行签名,如下:

Msg: https://steemit.com/@chaimyu
Msg hash: b1de4ffbedd11df4af08fc63d9585ed5cb05aaef754d6db0c5a37dbc6c8caf7f
Signature: Signature {
  r: <BN: 235078c22debd6608de5a2d8bf8f9ff210a4605a28644b6ac219872943c735bc>,
  s: <BN: 29e96c39ef293832de3276edd0df44239e1204f78d4879455ce675275e6e1382>,
  recoveryParam: 1 }

为了方便验证,加了一个根据签名信息取以太坊地址的函数,如下:

> contract_mulsign.verify_addr.call("0xb1de4ffbedd11df4af08fc63d9585ed5cb05aaef754d6db0c5a37dbc6c8caf7f", 28, "0x235078c22debd6608de5a2d8bf8f9ff210a4605a28644b6ac219872943c735bc", "0x29e96c39ef293832de3276edd0df44239e1204f78d4879455ce675275e6e1382")

"0x10f505c5077fb9e5ae06b9f9e98a4f037e54e389"

调用合约验证函数:

> contract_mulsign.verify_one.call("0xb1de4ffbedd11df4af08fc63d9585ed5cb05aaef754d6db0c5a37dbc6c8caf7f", 28, "0x235078c22debd6608de5a2d8bf8f9ff210a4605a28644b6ac219872943c735bc", "0x29e96c39ef293832de3276edd0df44239e1204f78d4879455ce675275e6e1382")

true

多重签名

先用一私钥验证一下,签名数据:

Msg: https://steemit.com/@chaimyu
Msg hash: b1de4ffbedd11df4af08fc63d9585ed5cb05aaef754d6db0c5a37dbc6c8caf7f
Signature: Signature {
  r: <BN: 9f2e39d8a678797e2f6dc37b3170111420bb5cb43bccec83a83a7e331519ef85>,
  s: <BN: 4d2e05baba9f7d384c9fbe8e4fa0a5633f9dd4cb6bbb6b2d4e53d7b0c97e79e3>,
  recoveryParam: 1 }

验证地址:

> contract_mulsign.verify_addr.call("0xb1de4ffbedd11df4af08fc63d9585ed5cb05aaef754d6db0c5a37dbc6c8caf7f", 28, "0x9f2e39d8a678797e2f6dc37b3170111420bb5cb43bccec83a83a7e331519ef85", "0x4d2e05baba9f7d384c9fbe8e4fa0a5633f9dd4cb6bbb6b2d4e53d7b0c97e79e3")

"0x40e1843610d41853cbe22425e2ca2b2fb5538559"

有了验证单个签名的方法,而且已经对单个签名做过验证,同样的可以在函数中验证几个离线签名数据,合约中增加以下函数:

    function verify_two(bytes32 _message, uint8 _v1, bytes32 _r1, bytes32 _s1, uint8 _v2, bytes32 _r2, bytes32 _s2) public constant returns (bool success) {
       address signer1 = ecrecover(_message, _v1, _r1, _s1);
       address signer2 = ecrecover(_message, _v2, _r2, _s2);
       success = signerSet[signer1] && signerSet[signer2];
       return success;
       }

通过离线签名工具用上面的两个私钥签名数据,然后调用此函数:

> contract_mulsign.verify_two.call("0xb1de4ffbedd11df4af08fc63d9585ed5cb05aaef754d6db0c5a37dbc6c8caf7f", 28, "0x235078c22debd6608de5a2d8bf8f9ff210a4605a28644b6ac219872943c735bc", "0x29e96c39ef293832de3276edd0df44239e1204f78d4879455ce675275e6e1382",28, "0x9f2e39d8a678797e2f6dc37b3170111420bb5cb43bccec83a83a7e331519ef85", "0x4d2e05baba9f7d384c9fbe8e4fa0a5633f9dd4cb6bbb6b2d4e53d7b0c97e79e3")

true

既然可以实现2/N签名机制,当然也可以在合约创建时传入需要签名个数,实现M/N签名机制。


recoveryParam为0的无法验签?

在测试过程中碰到recoveryParam为0的按同样方式签名,验签时生成的地址不对,是27那个值用错了?谁帮解答下。

公私钥

Private key: f923b04356555667fb945dd44b6cb7a188f549d839345768c149ac791ba05707
Public key : 372f0fa3247519f5ab4c349d47902b72bd89e383f13109f3b4b8c890ca6c533db27e641377efeb53bc1d14974d56e419c36c4a4d0ae9b165d3511f2c600ef68e
Public key (compressed): 02372f0fa3247519f5ab4c349d47902b72bd89e383f13109f3b4b8c890ca6c533d
Ether addr: 0x1c2d77e42c3d47ab24d6021fa14b16571d3038f7

签名数据

Msg hash: b1de4ffbedd11df4af08fc63d9585ed5cb05aaef754d6db0c5a37dbc6c8caf7f
Signature: Signature {
  r: <BN: a0fe7c7d607db4a45be9c343a57c285a59a16d01af2cfbf3768e232c55cbb7c7>,
  s: <BN: 6cdb7534118632437ef73e95dc7269f0bc24ad15fcc72fb6bff666add264923>,
  recoveryParam: 0 }

验证

> contract_mulsign.verify_addr.call("0xb1de4ffbedd11df4af08fc63d9585ed5cb05aaef754d6db0c5a37dbc6c8caf7f", 27, "0xa0fe7c7d607db4a45be9c343a57c285a59a16d01af2cfbf3768e232c55cbb7c7", "0x6cdb7534118632437ef73e95dc7269f0bc24ad15fcc72fb6bff666add264923")
> 
"0xa1ca20364a4e1e374cdafce9850dfd16791543f0"

1129终于找到验证签名失败的原因了,你找到了吗?

参考

https://blog.csdn.net/qq_26744901/article/details/79730970

https://www.jianshu.com/p/ecb80386cf40

Leave 以太坊研究系列【离线签名】 to:

Written by

One day sitting in the well looked at the sky, blowing the wind uphole, jumping out of the well, dancing in the wind!

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.

More Posts From 老鱼