以太坊多重签名原理分析 October 17, 2018 ## 目录 [TOC] ## 0 起源与现状 一般来说一个以太坊地址对应一个私钥,动用这个地址中的资金需要私钥的掌握者发起签名才行。消费这笔资金时,所构造的签名交易被称为“单签名交易”。 而多重签名技术,简单来说,一个以太坊地址对应多个私钥,就是动用一笔资金时需要多个私钥签名才有效。消费这笔资金时,所构造的签名交易被称为“多签名交易”,或者“M-of-N 交易”,对应的比特币地址被称为“多签地址”。 目前,在以太坊中常用的多签钱包有: - [Mist](https://github.com/ethereum/mist) 以太坊官方维护的钱包,支持多签 - [Ethereum Wallet](https://www.ethereum.org/) 以太坊官方维护的钱包,支持多签 - [Parity](https://github.com/paritytech/parity-ethereum) 支持多签 在以太坊中,多签钱包全部都是通过智能合约实现,下面介绍以太坊中的多签实现原理,实例分析。 ## 1 核心概念 ### 1.1 多签地址 以太坊多签钱包地址为多签钱包合约地址,同正常见到的以太坊钱包地址一样。 ### 1.2 多重签名 多重签名,和单个签名一样,都代表着签名方的认可和授权。单个签名,代表了单个资产所有者对交易的认可和授权;多重签名,代表了资产(位于多签钱包合约)的多个所有者对交易的认可和授权。 多重签名的特殊之处在于,它表述了一种更加灵活的认可和授权机制,适用于了更复杂的现实场景。对于多签交易“M-of-N 交易”,比如说,M=2,N=3,表示某笔资金对应有3个私钥,而必须至少有其中任意2个私钥参与签名才能动用,只有1个私钥参与签名则是无效的。而这个2/3可以推广到任意的m/n,比如3/3、1/5、6/11等,当然m要小于等于n。 ## 2 实现原理 ### 2.1 最简单的多签钱包 在以太坊中多重签名钱包通过智能合约实现,先从一个最简单的多签钱包合约介绍,以下介绍一个简单的2-of-3多签智能合约的实现,该智能合约实现了一个2-of-3多签钱包,当且仅当大于等于两个资产所有人都进行签名后(或者说都允许转移资产后),可以将该智能合约内所有资产进行转移。否则,当仅有一人签名后或者没有人签名时,钱包内资产无法进行转移。 智能合约中牵涉到三者,`bob`,`alice1`,`alice2`,角色分别是资产所有者0(同时也是合约创造者),资产所有者1,资产所有者2。 - `numSigned`用于存储签名人个数,后续交易中,当判断签名人个数大于等于2时,多签钱包中资产可转移。 - `signedYet`映射保存`alice1`,`alice2`两位资产所有者的签名状态。 - `registeredYet`状态保存是否为`alice1`,`alice2`两位资产所有者保存身份信息(资产所有者签名所用钱包地址信息)。 ```js address alice1; address alice2; address bob; uint numSigned = 0; bytes32 error; bool registeredYet; mapping (address => bool) signedYet; ``` 合约创建时,创建人即为`bob`,记录资产所有者0身份信息,此时并没有为`alice1`,`alice2`两位资产所有者保存身份信息,故`registeredYet`设定为`false`。 ```js function SimpleMultiSig() { bob = msg.sender; registeredYet = false; } ``` 合约创建后,仅创建人`bob`拥有为`alice1`,`alice2`两位资产所有者保存身份信息的权限。当保存资产所有者信息后,将`registeredYet`设定为`true`。 ```js function register(address registerAlice1, address registerAlice2) { if (msg.sender == bob && registeredYet == false) { alice1 = registerAlice1; alice2 = registerAlice2; registeredYet = true; } else if (msg.sender == bob) { error = "registered already"; } else { error = "you are not bob!"; } } ``` 签名函数,仅资产所有者可调用,调用后通过`signedYet`记录资产所有者签名状态,并通过`numSigned`统计签名数目。 ```js function addSignature() { if (msg.sender == alice1 && signedYet[alice1] == false) { signedYet[alice1] = true; numSigned++; } else if (msg.sender == alice2 && signedYet[alice2] == false) { signedYet[alice2] = true; numSigned++; } else if (msg.sender == bob && signedYet[bob] == false) { signedYet[bob] = true; numSigned++; } else { error = 'unknown address'; } } ``` 转账函数,仅三位资产所有人可调用,当检查签名数大于等于2时,可向指定地址进行转账。 ```js function withdraw(address to) { if ((msg.sender == alice1 || msg.sender == alice2 || msg.sender == bob) && numSigned >= 2) { to.send(this.balance); numSigned = 0; signedYet[alice1] = signedYet[alice2] = signedYet[bob] = false; } else { error = "cannot withdraw yet!"; } } ``` 在上述2-of-3多签钱包合约中,当`bob`注册另外两名资产所有者信息后,该钱包处于可用状态,此后转入该钱包的资产(ETH)总需要至少2个人签名后才可以转出。 ### 2.2 多重签名的两种实现方式 目前,常见的多重签名实现方式有两种: - 方式1:由某任意某一资产所有者发起一笔交易,多个资产所有者各自调用多签钱包合约交易签名函数对该笔交易进行确认,当对该笔交易确认数满足预先设定的条件时,该笔交易可以进行,否则交易无法进行。 - 需要多个资产所有者参与链上调用智能合约 - 多签钱包合约输入参数为(交易ID) - 方式2:多个资产所有者对转账信息进行签名(转账信息包含接收人地址以及转账数目),并把各自的签名结果交由任意某一资产所有者,由该资产所有者发起交易,当然这里多个资产所有者的签名数要满足预先设定的条件。 - 仅需要任意某一资产所有者参与链上调用智能合约 - 多签钱包合约输入参数为(接收人地址,转账数目,该资产所有者对转账信息的签名) ### 2.2.1 方式1介绍 在创建合约时,可首先在创建合约的构造函数中添加所有的资产所有者。 如图所示,当某位资产所有者需要转账时,由该资产所有者发起一笔交易,该交易创建后被放入pending队列中,等待其他资产所有者签名。创建转账的函数如下: 函数中通过递增维护交易ID,通过`TransactionCreated`事件将创建交易事件上链。该函数仅允许资产所有者调用。 ```js function transferTo(address to, uint amount) isManager public { require(address(this).balance >= amount); uint transactionId = transactionIdx++; Transaction memory transaction; transaction.from = msg.sender; transaction.to = to; transaction.amount = amount; transaction.signatureCount = 0; transactions[transactionId] = transaction; pendingTransactions.push(transactionId); emit TransactionCreated(msg.sender, to, amount, transactionId); } ``` 其他资产所有者可查看当前pending队列中交易详情: ```js function getPendingTransactions() public isManager view returns(uint[]) { return pendingTransactions; } ``` 其他资产所有者可通过ID对某一队列中交易进行签名,当签名数大于等于预设值时,将执行该交易: ```js function signTransaction(uint transactionId) public isManager { Transaction storage transaction = transactions[transactionId]; require(0x0 != transaction.from); require(msg.sender != transaction.from); require(transaction.signatures[msg.sender] != 1); transaction.signatures[msg.sender] = 1; transaction.signatureCount++; if (transaction.signatureCount >= MIN_SIGNATURES) { require(address(this).balance >= transaction.amount); transaction.to.transfer(transaction.amount); emit TransferFunds(transaction.to, transaction.amount); deleteTransactions(transactionId); } } ``` ### 2.2.2 方式2介绍 在创建合约时,可首先在创建合约的构造函数中添加所有的资产所有者。这里以两个资产所有者为例,仅当二者同时签名时,资产可转移。 当需要花费多签钱包中资产时,需要双方对转账地址,转账金额在本地进行序列化,签名,并将签名结果汇总至一人,由该位资产所有者调用智能合约,进行转账: ```js function spend(address destination, uint256 value, uint8 v1, bytes32 r1, bytes32 s1, uint8 v2, bytes32 r2, bytes32 s2) public { require(address(this).balance >= value, "3"); require(_validSignature(destination, value, v1, r1, s1, v2, r2, s2), "4"); spendNonce = spendNonce + 1; destination.transfer(value); emit Spent(destination, value); } ``` 其中,`_validSignature`函数验证两个签名是否与合约创建时保存的资产所有者地址信息是否符合,并验证签名的真实性。当满足签名要求是,交易将被执行。 ```js function _validSignature(address destination, uint256 value, uint8 v1, bytes32 r1, bytes32 s1, uint8 v2, bytes32 r2, bytes32 s2) private view returns(bool) { bytes32 message = _messageToRecover(destination, value); address addr1 = ecrecover(message, v1 + 27, r1, s1); address addr2 = ecrecover(message, v2 + 27, r2, s2); require(_distinctOwners(addr1, addr2), "5"); return true; } ``` ## 参考文献 - [Ethereum Multi-Signature Wallets](https://medium.com/hellogold/ethereum-multi-signature-wallets-77ab926ab63b) - [以太坊钱包分析](https://www.jutuilian.com/article-2319-1.html) - [以太坊官方钱包合约示例](https://github.com/ethereum/dapp-bin/blob/master/wallet/wallet.sol) - [以太坊MIST钱包中的多重签名](https://www.jianshu.com/p/aa8312801f1c) - [以太坊 solidity 多重签名合约](https://blog.csdn.net/xq723310/article/details/82659544) - [ERC: Signed Data Standard](https://github.com/ethereum/EIPs/issues/191) - [List of MultiSig Wallet Smart contracts on Ethereum](https://medium.com/talo-protocol/list-of-multisig-wallet-smart-contracts-on-ethereum-3824d528b95e) - [MultiSig2of3.sol](https://github.com/unchained-capital/ethereum-multisig/blob/master/contracts/MultiSig2of3.sol) - [MultiSigWalletWithDailyLimit](https://github.com/ConsenSys/MultiSigWallet/blob/master/MultiSigWalletWithDailyLimit.sol) - [SimpleMultiSig](https://github.com/blockapps/training-exercise/blob/master/contracts/SimpleMultiSig.sol)