https://github.com/alexvandesande/EIPs/blob/ee2347027e94b93708939f2e448447d030ca2d76/EIPS/eip-1077.md
eip | title | author | discussions-to | status | type | category | created | requires |
---|---|---|---|---|---|---|---|---|
1077
|
Executable Signed Messages refunded by the contract
|
Alex Van de Sande <avsa@ethereum.org>, Ricardo Guilherme Schmidt
|
Draft
|
Standards Track
|
ERC
|
2018-05-04
|
725, 191
|
https://github.com/status-im/contracts/tree/73-economic-abstraction
去这里看这个的应用,然后发现还要先了解一下什么是identity contract
https://github.com/status-im/contracts/blob/73-economic-abstraction/Identity.md
This is a proposed proof of concept for the implementation of interfaces ERC-725 and ERC735
终于了解完了与identity contract有关的 ERC-725 and ERC735,现在可以继续1077了
看完了一遍这个EIP后,发现并不是很理解,以为它就是我们平常在交易中对交易进行签名的部分,但是后面看这个proposal是draft的状态,又觉得不太对,后面看到了这个网站的视频后,终于了解了这个EIP的意思:
https://ethereum-magicians.org/t/erc-1077-and-erc-1078-the-magic-of-executable-signed-messages-to-login-and-do-actions/351
从这个视频也知应该相应了解EIP-1078
首先要先把上面的identity contract了解后,才能更好地了解这个Executable Signed Messages
它的意思就是当你在手机端或电脑端(这里既手机端)注册一个eth账户(使用ENS)(在这里生成的其实是一个identity contract)时,app将会自己生成一对公私钥以及address,然后将这个identity contract的合约地址的ENS设置为注册时使用的名字ENS。这里生成的私钥并不会给用户,而是直接保存在device中,这样你就不会有存私钥的困扰。
与此同时在合约构造函数中会对address(即用其公钥)进行hash来生成一个key,其purpose是management,然后就算是在手机端生成了一个account。然后这个account就可以使用这个key来进行签名操作。那么这个手机端能够进行的操作就是由这个key的purpose来决定的,当然你可以add key来增加别的purpose的key来扩宽你在这个app中能够执行的操作,即还会再生成一对公私钥和address,,对这个address进行hash来得到新key。
如果这个时候我要使用另一个app,这时候我并不想要还进行注册,因为在刚刚进行的操作中我已经生成了一个identity contract,我想直接使用它。然后我们就可以直接在另一个app上输入之前identity的ENS,然后进行连接,那么这个时候这个设备将会生成一个私钥,然后比如可能会生成一个二维码,这个二维码就是由私钥得到的公钥,要求你用之前的手机端的设备扫描这个二维码来将这个公钥添加到你identity contract的keyholder中(扫描的过程就是调用函数addKey的过程),意思就是电脑端的设备的连接过程其实就是通过生成一个key,然后将其添加到identity contract中即可。
之后的过程就是相同的了,如果再在别的设备上使用时,就可以使用手机端或电脑端进行key的添加。
这上面的内容是EIP-1078实现的内容,下面的才是EIP-1077的内容
在key上是不存放ether的,key如果想要进行什么操作,它能够做的事情就是这个EIP1077中实现的功能了,key会将其想要执行的操作进行签名(即Executable Signed Messages)发送到identity contract上,contract会查看这个发送过来的信息的签名是否正确,正确后即根据其上面的to,value,data等信息来实现操作,如transfer,查询数据等等。ether等发送即gas等的开销都是在contract中实现的。
然后它这里如果你丢失了设备什么的,它还有一个recover函数来实现恢复功能,大概感觉就是这样。
所以Executable Signed Messages的意义就是使得用户本身是不拥有ether的,它要做的事情就是把它想要做的事情签名生成Executable Signed Messages后去让identity contract给他实现,这样当你丢失了这个key时,你并不会丢失你的ether。
更详细内容还是看下面的翻译。
Simple Summary
Allowing users to sign messages to show intent of execution, but allowing a third party relayer to execute them is an emerging pattern being used in many projects. Standardizing a common format for them, as well as a way in which the user allows the transaction to be paid in tokens, gives app developers a lot of flexibility and can become the main way in which app users interact with the Blockchain.
就是通过签名信息来说明自己想要实现的操作,然后允许第三方(identity contract)来实现它。这样app user与区块链进行交互将会变得更加灵活与方便。
Abstract
User pain points:
- users don't want to think about ether
- users don't want to think about backing up private keys or seed phrases就不用存储私钥等信息,因为会存储在设备上
- users want to be able to pay for transactions using what they already have on the system, be apple pay, xbox points or even a credit card
- Users don’t want to sign a new transaction at every move
- Users don’t want to download apps/extensions (at least on the desktop) to connect to their apps
App developer pain points:
- Many apps use their own token and would prefer to use those as the main accounting
- Apps want to be able to have apps in multiple platforms without having to share private keys between devices or have to spend transaction costs moving funds between them
- Token developers want to be able for their users to be able to move funds and pay fees in the token
- While the system provides fees and incentives for miners, there are no inherent business model for wallet developers (or other apps that initiate many transactions)
Using signed messages, specially combined with an identity contract that holds funds, and multiple disposable ether-less keys that can sign on its behalf, solves many of these pain points.
通过使用与拥有资金(即ether)的identity contract和多个能够代表进行签名一次性无ether的key相结合的签名信息可以解决上面的很多问题
Implementation
The signed messages require the following fields:
- To: the target contract the transaction will be executed upon
- From: the account that will be executed on behalf of
- Value: the amount in ether to be sent
- Data: the bytecode to be executed
- Nonce: a nonce or a timestamp
- GasToken: a token in which the gas will be paid (leave 0 for ether)
- Gasprice: the gas price (paid in the selected token)
- GasLimit: the maximum gas to be paid
通过读取to\value\data identity就知道该messages想要执行什么样的操作。
Signing the message
All accounts that will be requesting the transaction must sign a messageHash that is calculated by the following formula:
keccak256( callPrefix, from, to, value, dataHash, nonce, gasPrice, gasLimit, gasToken );
签名公式如上所示
The from
field will always be the contract executing the code (address(this)
), and the callPrefix
is the 4 byte standard prefix of the function to be called in the from
contract. This guarantees that a signed message can be only executed in a single instance.
from拥有都是identity contract的合约地址,callPrefix
即要进行的操作(即要调用的函数的函数签名)
All signed messageHashes should then be ordered by account and sent to the receiveing contract which then will execute the following actions:
所有的这些messageHashes将会被账户有序排列并发送给identity contract,然后合约就会相应执行下面的操作:
keep track of nonces:
首先是查看nonce的值是否正确
If the nonce number is smaller than 10^24 then it should be treated as a Nonce:
如果nonce的值小于10^24,那么它就会被当作Nonce
Nonces work similarly to normal ethereum transactions: a transaction can only be executed if it matches the last nonce + 1, and once a transaction has occurred, the lastNonce
will be updated to the current one. This prevents transactions to be executed out of order or more than once.
这里的Nonce与正常的以太坊交易中的Nonce是相同的:交易只有在nonce的值是上一个nonce的值 + 1的时候才能成功,并且上一个nonce的值就会更新成现在这个新nonce的值。这样是用来防止某比交易被多次执行。
If it’s larger than 10^24 and larger than the current timestamp multiplied by 10^12 and larger than lastTimestamp
then it should be executed normally but instead of updating the nonce you must update the last Timestamp field. This allows transactions to be scheduled to be executed at a later time. Notice that if multiple timestamped transactions are signed, they must be executed in the numerical order (with 10^24 possible transaction slots for every second), otherwise the earlier transaction will not be executable.
如果nonce的值大于10^24且大于目前的时间戳 *10^12且大于lastTimestamp
,那么你就可以正常执行这个交易。并且之后你应该更新lastTimestamp
的值,而不是nonce的值,我觉得原因应该是这里的nonce应该是被当作时间戳来处理了。这样就能使得交易在一段时间后执行(即其nonce上表示的时间戳的时间执行)。要注意的就是如果一下子有多个时间戳标记的交易被签名了,那么就一定要按照顺序来执行这些交易,否则如果有晚的时间戳的交易先执行了,那么那些时间戳是早的交易将不能实现。
execute transaction
nonce检查完后就能够开始执行交易了
The contract must then verify the signatures of the accounts and check if the public keys match accounts that are authorized to do what they intend to do: depending on the contract implementation, some contracts might require multiple key signatures, other might have specific actions few accounts are authorized to do.
当然,identity contract首先要核查这个账户所进行的签名并得到公钥,查看公钥是不是与这个账户的公钥是否匹配,然后就能够执行账户授权的操作了。当然,这个部分取决于identity contract的实现,有些合约实现交易可能需要多个key进行签名,有些可能只是某些具体操作需要一些账户(key)进行授权,所以这部分签名的检查根据具体情况具体分析。
If an identity, then, the contract should implement ERC725 identity management levels: a key must be at least an action
key to have authorization to demand any call to external contracts, and a management
key to demand calls to the contract itself.
如果这个identity contract实现的是ERC725,那么当像对其他外部合约进行call时,它key的purpose至少的事action级别的,而如果是想要对自己合约本身进行操作的话,那就使用purpose是management的key.即根据自己想要执行的操作去选择相应的key的类型
If the signing accounts are authorized to do so, the contract must execute the requested action. If the current contract is the same as from
field, then if can simply execute the actions by calling _to.call.value(_value)(_data)
.
当目前的合约的确就是from中指定的合约,那么它就可以简单地通过运行_to.call.value(_value)(_data)
命令来执行这个操作。
How the contract interprets the intended action depends on its purpose. For instance a token contract can decide to implement it in a way that interprets all actions as token transfers, and uses the value
to mean token value and bytecode
as the data to pass to the recipient contract
一个合约要怎么去解释意图进行的操作都取决去它的目的。比如是一个token合约,那么该合约就可以决定将所有的操作都翻译成token的transfer,value值就是想要transfer的token数,data就是想要给目标合约传递的信息。
Gas accounting and refund
然后就是计算使用的gas并退还剩下的gas
The implementing contract must keep track of the gas spent. One way to do it is to first call gasLeft()
at the beginning of the function and then after executing the desired action and compare the difference.
首先就是使用gasLeft()
来计算运行钱剩下的gas,执行操作,然后再次查看剩下的gas数,以此来得到使用的gas值,即gasSpent
The contract then will make a token transfer (or ether, if tokenAddress
is nil) in the value of gasSpent * gasPrice
to the msg.sender
, that is the account that deployed the message. If there are not enough funds, or if the total surpasses gasLimit
then the transaction MUST revert.
然后合约就会通过公式gasSpent * gasPrice来计算要使用的总token值,并将其发送给部署了这个操作到区块链上的账户(即矿工)。
如果合约中没有足够的token或者gasSpent
超过了设置的gaslimit,那么这笔交易就会失败,状态会退revert
If the executed transaction fails internally, nonces should still be updated and gas needs to be paid.
如果这笔交易是在内部出问题失败的,那么nonce还是会加一,gas还是要支付
Contracts are not obligated to support ether or any other token they don’t want and can be implemented to only accept refunds in a few tokens of their choice.
Deployers of transactions have no guarantees that the contract they are interacting with correctly implements the standard and they will be reimbursed for gas, so they should maintain their own white/blacklists of contracts to support, as well as keep track of which tokens and for which gasPrice they’re willing to deploy transactions.
部署交易的账户(即矿工)是无法保证它们进行交互的交易符合标准的(就是它们能成功记录到区块链上),所以他们是否能够得到gas补偿也是无法保证的。所以矿工对于合约有自己的一个黑白名单(就是那些合约好,那些坏)并记录那种token和gasPrice为多少他们才肯部署。
以上就是一个签名信息的实现过程,下面就是实现这个签名信息的相关支持函数
Supported functions
1)
executeSigned( address to, address from, uint256 value, bytes data, uint nonce, uint gasPrice, uint gasLimit, address gasToken, bytes messageSignatures)
这个就是发送给identity contract的与签名信息相关所有数据:to\from\value\data\nonce\gasPrice\gasLimit\gasToken\messageSignatures
(这个就是签名后的信息)
Executes the signed message. Execution usually means that a contract will execute a call
to the to
address, with value
amount of ether and data
as its data. But in some special cases, a token can decide instead to interpret it specifically as executing a transferAndCall(to,from,data)
or the equivalent.
More than one signed transaction with the same parameter can be executed by this function at the same time, by passing all signatures in the messageSignatures
field. That field will split the signature in multiple 72 character individual signatures and evaluate each one. This is used for cases in which one action might require the approval of multiple parties, in a single transaction.
messageSignatures
会被分解成多个72个字符的个人签名,这个就是用于防止可能有些交易是需要多人进行签名的
2)
gasEstimate( address to, address from, uint256 value, bytes data, uint nonce, uint gasPrice, uint gasLimit, address gasToken, bytes messageSignatures) returns ( bool canExecute, uint gasCost)
A read only function that checks if the transaction will be executable and how much gas it’s expected to cost.
用来估计交易是否能够成功执行以及会使用多少gas的函数,只读
3)
lastNonce() public returns (uint nonce)
lastTimestamp() public returns (uint nonce)
Both are simple read only functions that return the last used Nonce and last used timestamp.
只读,用来知道上一个nonce或timestamp
4)
requiredSignatures(uint type) returns (uint)
A function which returns the amount of signatures that are required for a given type of action (types being 1 = Management and 2 = Action).
返回对于某种类型(types being 1 = Management and 2 = Action)的操作需要多少个签名
5)
event ExecutedSigned(bytes32 signHash, uint nonce, bool success);
Whenever a new transaction is executed it must emit an event with the signHash, nonce and either the transaction was sucessfully executed or not. Apps that are waiting for a transaction to be executed should subscribe to the identity and watch this event to see if their transaction was sucessful. If a different signHash is executed with an equal or higher nonce, it means that your transaction has been replaced.
当一个交易执行时就要触发该事件,记录signHash、nonce以及交易是否成功的信息。正在等待这个交易执行的apps(即key)就应该订阅identity contract去查看这个event,看该交易是否成功执行。如果你在event中发现有一个不同的signHash成功运行了,它的nonce跟你的相同或更高,那就说明你的交易被取代了。
下面就是这个EIP实现的例子,你可以通过这些例子进行学习
Implementation
Onde initial implementation of such contract can be found at the Identity Gas Relay at the Status repository
Similar implementations
The idea of using signed messages as executable intent has been around for a while and many other projects are taking similar approaches, which makes it a great candidate for a standard that guarantees interoperability:
- EIP 877 An attempt of doing the same but with a change in the protocol
- Status
- Aragon (this might not be the best link to show their work in this area)
- Token Standard Functions for Preauthorized Actions
- Token Standard Extension 865
- Iuri Matias: Transaction Relay
- uPort: Meta transactions
- uPort: safe Identities
- Gnosis safe contracts
Swarm city uses a similar proposition for etherless transactions, called Gas Station Service, but it's a different approach. Instead of using signed messages, a traditional ethereum transaction is signed on an etherless account, the transaction is then sent to a service that immediately sends the exact amount of ether required and then publishes the transaction.
Areas for improvements
还需要改进的部分
This ERC inherits all it's permission systems from ERC720 Identity, which has very basic levels of authorization (can call contract itself, can make external calls, cannot make any calls) and a simple generic multisig. Ideally we need a more comprehensive standard for authorizations, that enables, for instance, that a single key can only make calls to a specific contract, or specific functions. Also, the amount of signatures required to do an action could be variable, maybe a single key could have a limit on how much value it can spend in a single day, requiring more signatures to move higher amounts (how to properly measure the value of a token or NFT transfer is another matter entirely).
这个ERC继承了ERC720 Identity的所有许可体系,它有着最基本的授权和以及简单通用的多重签名。当然我们在授权方面需要更综合全面的标准,比如一个key只能call一个特定的合约或函数;进行一个操作所需要的签名数应该是一个变量;一个key将有一天只能使用多少value的限制;value更高需要更多的签名等。
Usage examples
下面是一些使用例子
This scheme opens up a great deal of possibilities on interaction as well as different experiments on business models:
- Apps can create individual identities contract for their users which holds the actual funds and then create a different private key for each device they log into. Other apps can use the same identity and just ask to add permissioned public keys to manage the device, so that if one individual key is lost, no ether is lost.
就是apps为他们的用户创建了个人的identity contract来保管他们的资金,当他们登录不同的设备的时候将会给他们创建不同的私钥。我们可以在其他apps中使用使用相同的身份(即该identity contract),只需添加许可的公钥到identity contract中来管理设备即可。所以如果某个设备上的key丢失了,你不会丢失任何的ether,该设备上的key重新生成即可。
- An app can create its own token and only charge their users in its internal currency for any ethereum transaction. The currency units can be rounded so it looks more similar to to actual amount of transactions: a standard transaction always costs 1 token, a very complex transaction costs exactly 2, etc. Since the app is the issuer of the transactions, they can do their own Sybil verifications and give a free amount of currency units to new users to get them started.
- A game company creates games with a traditional monthly subscription, either by credit card or platform-specific microtransactions. Private keys never leave the device and keep no ether and only the public accounts are sent to the company. The game then signs transactions on the device with gas price 0, sends them to the game company which checks who is an active subscriber and batches all transactions and pays the ether themselves. If the company goes bankrupt, the gamers themselves can set up similar subscription systems or just increase the gas price. End result is a ethereum based game in which gamers can play by spending apple, google or xbox credits.
- A standard token is created that doesn’t require its users to have ether, and instead allows tokens to be transferred by paying in tokens. A wallet is created that signs messages and send them via whisper to the network, where other nodes can compete to download the available transactions, check the current gas price, and select those who are paying enough tokens to cover the cost. The result is a token that the end users never need to keep any ether and can pay fees in the token itself.
- A DAO is created with a list of accounts of their employees. Employees never need to own ether, instead they sign messages, send them to whisper to a decentralized list of relayers which then deploy the transactions. The DAO contract then checks if the transaction is valid and sends ether to the deployers. Employees have an incentive not to use too many of the companies resources because they’re identifiable. The result is that the users of the DAO don't need to keep ether, and the contract ends up paying for it's own gas usage.
这个是DAO组织有着一组员工账户(即key),员工账户时是没有ether的,它的作用就是对自己要执行的操作用自己的公钥进行签名,然后发送给DAO来执行。
下面通过一个实例代码进行学习:
https://github.com/status-im/contracts/tree/73-economic-abstraction/contracts/identity
identity/ERC725.sol
pragma solidity ^0.4.21; contract ERC725 { uint256 constant MANAGEMENT_KEY = 1; uint256 constant ACTION_KEY = 2; uint256 constant CLAIM_SIGNER_KEY = 3; uint256 constant ENCRYPTION_KEY = 4; event KeyAdded(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType); event KeyRemoved(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType); event ExecutionRequested(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); event Executed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); event Approved(uint256 indexed executionId, bool approved); struct Key { uint256 purpose; //e.g., MANAGEMENT_KEY = 1, ACTION_KEY = 2, etc. uint256 keyType; // e.g. 1 = ECDSA, 2 = RSA, etc. bytes32 key; } function getKey(bytes32 _key, uint256 _purpose) public view returns(uint256 purpose, uint256 keyType, bytes32 key); function getKeyPurpose(bytes32 _key) public view returns(uint256[] purpose); function getKeysByPurpose(uint256 _purpose) public view returns(bytes32[] keys); function addKey(bytes32 _key, uint256 _purpose, uint256 _keyType) public returns (bool success); function removeKey(bytes32 _key, uint256 _purpose) public returns (bool success); function execute(address _to, uint256 _value, bytes _data) public returns (uint256 executionId); function approve(uint256 _id, bool _approve) public returns (bool success); }
identity/ERC735.sol
pragma solidity ^0.4.21; contract ERC735 { event ClaimRequested(bytes32 indexed claimRequestId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri); event ClaimAdded(bytes32 indexed claimId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri); event ClaimRemoved(bytes32 indexed claimId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri); event ClaimChanged(bytes32 indexed claimId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri); struct Claim { uint256 claimType; uint256 scheme; address issuer; // msg.sender bytes signature; // this.address + claimType + data bytes data; string uri; } function getClaim(bytes32 _claimId) public view returns(uint256 claimType, uint256 scheme, address issuer, bytes signature, bytes data, string uri); function getClaimIdsByType(uint256 _claimType) public view returns(bytes32[] claimIds); function addClaim(uint256 _claimType, uint256 _scheme, address _issuer, bytes _signature, bytes _data, string _uri) public returns (bytes32 claimRequestId); function removeClaim(bytes32 _claimId) public returns (bool success); }
/identity/FriendsRecovery.sol
这个就是上面说的怎么进行recovery的合约
pragma solidity ^0.4.17; /** * @notice Select privately other accounts that will allow the execution of actions * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) */ contract FriendsRecovery { address private identity; bytes32 private secret; uint256 private threshold; uint256 private setupDelay; mapping(bytes32 => bool) private friendAllowed; mapping(bytes32 => bool) private revealed; mapping(bytes32 => mapping(address => bool)) private signed; NewRecovery private pendingSetup; struct NewRecovery { uint256 addition; bytes32 secret; uint256 threshold; uint256 setupDelay; bytes32[] friends; } event SetupRequested(uint256 activation); event Activated(); event Approved(bytes32 indexed secretHash, address approver); event Execution(bool success); modifier identityOnly() { require(msg.sender == identity); _; } modifier notRevealed(bytes32 secretHash) { require(!revealed[secretHash]); _; } /** * @notice Contructor of FriendsRecovery * @param _identity Controller of this contract * @param _setupDelay Delay for changes being active 推迟多长时间后执行 * @param _threshold Amount of approvals required 需要得到的批准数 * @param _secret Double hash of User Secret * @param _friendHashes Friends addresses hashed with single hash of User Secret **/ function FriendsRecovery( address _identity, uint256 _setupDelay, uint256 _threshold, bytes32 _secret, bytes32[] _friendHashes ) public { identity = _identity; threshold = _threshold; secret = _secret; setupDelay = _setupDelay; addFriends(_friendHashes); } /** * @notice Withdraw funds sent incorrectly to this contract */ function withdraw() external identityOnly { identity.transfer(address(this).balance); } /** * @notice Cancels a pending setup to change the recovery parameters */ function cancelSetup() external identityOnly { delete pendingSetup; emit SetupRequested(0); } /** * @notice reconfigure recovery parameters * @param _newSecret Double hash of User Secret * @param _setupDelay Delay for changes being active * @param _threshold Amount of approvals required * @param _newFriendsHashes Friends addresses hashed with single hash of User Secret */ function setup( bytes32 _newSecret, uint256 _setupDelay, uint256 _threshold, bytes32[] _newFriendsHashes ) external identityOnly notRevealed(_newSecret) { pendingSetup.addition = block.timestamp; pendingSetup.secret = _newSecret; pendingSetup.friends = _newFriendsHashes; pendingSetup.threshold = _threshold; pendingSetup.setupDelay = _setupDelay; emit SetupRequested(block.timestamp + setupDelay); } /** * @notice Activate a pending setup of recovery parameters */ function activate() external { require(pendingSetup.addition > 0); require(pendingSetup.addition + setupDelay <= block.timestamp); threshold = pendingSetup.threshold; setupDelay = pendingSetup.setupDelay; secret = pendingSetup.secret; addFriends(pendingSetup.friends); delete pendingSetup; emit Activated(); } /** * @notice Approves a recovery * @param _secretHash Hash of the transaction */ function approve(bytes32 _secretHash) external { require(!signed[_secretHash][msg.sender]); signed[_secretHash][msg.sender] = true; emit Approved(_secretHash, msg.sender); } /** * @notice Approve a recovery using an ethereum signed message * @param _secretHash Hash of the transaction * @param _v signatures v * @param _r signatures r * @param _s signatures s */ function approvePreSigned(bytes32 _secretHash, uint8[] _v, bytes32[] _r, bytes32[] _s) external { uint256 len = _v.length; require (_r.length == len); require (_s.length == len); require (_v.length == len); bytes32 signatureHash = getSignHash(_secretHash); for (uint256 i = 0; i < len; i++) { address recovered = ecrecover(signatureHash, _v[i], _r[i], _s[i]); require(!signed[_secretHash][recovered]); require(recovered != address(0)); signed[_secretHash][recovered] = true; emit Approved(_secretHash, recovered); } } /** * @notice executes an approved transaction revaling secret hash, friends addresses and set new recovery parameters * @param _revealedSecret Single hash of User Secret * @param _dest Address will be called * @param _data Data to be sent * @param _friendList friends addresses that approved * @param _newSecret new recovery double hashed user secret * @param _newFriendsHashes new friends list hashed with new recovery secret hash */ function execute( bytes32 _revealedSecret, address _dest, bytes _data, address[] _friendList, bytes32 _newSecret, bytes32[] _newFriendsHashes ) external notRevealed(_newSecret) { require(_friendList.length >= threshold); require(keccak256(identity, _revealedSecret) == secret); revealed[_newSecret] = true; bytes32 _secretHash = keccak256(identity, _revealedSecret, _dest, _data, _newSecret, _newFriendsHashes); for (uint256 i = 0; i < threshold; i++) { address friend = _friendList[i]; require(friend != address(0)); require(friendAllowed[keccak256(identity, _revealedSecret, friend)]); require(signed[_secretHash][friend]); delete signed[_secretHash][friend]; } secret = _newSecret; addFriends(_newFriendsHashes); emit Execution(_dest.call(_data)); } /** * @dev add friends to recovery parameters */ function addFriends(bytes32[] _newFriendsHashes) private { uint256 len = _newFriendsHashes.length; require(len >= threshold); for (uint256 i = 0; i < len; i++) { friendAllowed[_newFriendsHashes[i]] = true; } } /** * @notice Hash a hash with `"\x19Ethereum Signed Message:\n32"` * @param _hash Sign to hash. * @return signHash Hash to be signed. */ function getSignHash( bytes32 _hash ) pure public returns(bytes32 signHash) { signHash = keccak256("\x19Ethereum Signed Message:\n32", _hash); } }
identity/Identity.sol
就是identity contract
pragma solidity ^0.4.21; import "./ERC725.sol"; import "./ERC735.sol"; contract Identity is ERC725, ERC735 { mapping (bytes32 => Key) keys; mapping (uint256 => bytes32[]) keysByPurpose; mapping (bytes32 => Claim) claims; mapping (uint256 => bytes32[]) claimsByType; mapping (bytes32 => uint256) indexes; mapping (uint256 => Transaction) txx; mapping (uint256 => uint256) purposeThreshold; uint256 nonce; address recoveryContract; address recoveryManager; struct Transaction { bool valid; address to; uint256 value; bytes data; uint256 nonce; uint256 approverCount; mapping(bytes32 => bool) approvals; } modifier managerOnly { require( isKeyPurpose(bytes32(msg.sender), MANAGEMENT_KEY) ); _; } modifier managementOnly { if(msg.sender == address(this)) { _; } else { require(isKeyPurpose(bytes32(msg.sender), MANAGEMENT_KEY)); if (purposeThreshold[MANAGEMENT_KEY] == 1) { _; } else { execute(address(this), 0, msg.data); } } } modifier recoveryOnly { require( recoveryContract != address(0) && msg.sender == address(recoveryContract) ); _; } modifier keyPurposeOnly(bytes32 _key, uint256 _purpose) { require(isKeyPurpose(_key, _purpose)); _; } modifier managerOrActor(bytes32 _key) { require( isKeyPurpose(_key, MANAGEMENT_KEY) || isKeyPurpose(_key, ACTION_KEY) ); _; } function Identity() public { _constructIdentity(msg.sender);//就是生成一个purpose为management的key } function () public payable { } function managerReset(address _newKey) public recoveryOnly { recoveryManager = _newKey; _addKey(bytes32(recoveryManager), MANAGEMENT_KEY, 0); purposeThreshold[MANAGEMENT_KEY] = keysByPurpose[MANAGEMENT_KEY].length; } function processManagerReset(uint256 _limit) public { require(recoveryManager != address(0)); uint256 limit = _limit; bytes32 newKey = bytes32(recoveryManager); bytes32[] memory managers = keysByPurpose[MANAGEMENT_KEY]; uint256 totalManagers = managers.length; if (limit == 0) { limit = totalManagers; } purposeThreshold[MANAGEMENT_KEY] = totalManagers - limit + 1; for (uint256 i = 0; i < limit; i++) { bytes32 manager = managers[i]; if (manager != newKey) { _removeKey(manager, MANAGEMENT_KEY); totalManagers--; } } if (totalManagers == 1) { recoveryManager = address(0); } } function addKey( bytes32 _key, uint256 _purpose, uint256 _type ) public managementOnly returns (bool success) { _addKey(_key, _purpose, _type); return true; } function replaceKey( bytes32 _oldKey, bytes32 _newKey, uint256 _newType ) public managementOnly returns (bool success) { uint256 purpose = keys[_oldKey].purpose; _addKey(_newKey, purpose, _newType); _removeKey(_oldKey, purpose); return true; } function removeKey( bytes32 _key, uint256 _purpose ) public managementOnly returns (bool success) { _removeKey(_key, _purpose); return true; } function execute( address _to, uint256 _value, bytes _data ) public returns (uint256 executionId) { uint256 requiredKey = _to == address(this) ? MANAGEMENT_KEY : ACTION_KEY; if (purposeThreshold[requiredKey] == 1) { executionId = nonce; //(?) useless in this case nonce++; //(?) should increment require(isKeyPurpose(bytes32(msg.sender), requiredKey)); _to.call.value(_value)(_data); //(?) success not used emit Executed(executionId, _to, _value, _data); //no information on success } else { executionId = _execute(_to, _value, _data); approve(executionId, true); } } function approve(uint256 _id, bool _approval) public managerOrActor(bytes32(msg.sender)) returns (bool success) { return _approve(bytes32(msg.sender), _id, _approval); } function setMinimumApprovalsByKeyType( uint256 _purpose, uint256 _minimumApprovals ) public managementOnly { require(_minimumApprovals > 0); require(_minimumApprovals <= keysByPurpose[_purpose].length); purposeThreshold[_purpose] = _minimumApprovals; } function addClaim( uint256 _claimType, uint256 _scheme, address _issuer, bytes _signature, bytes _data, string _uri ) public returns (bytes32 claimHash) { claimHash = keccak256(_issuer, _claimType); if (msg.sender == address(this)) { if (claims[claimHash].claimType > 0) { _modifyClaim(claimHash, _claimType, _scheme, _issuer, _signature, _data, _uri); } else { _includeClaim(claimHash, _claimType, _scheme, _issuer, _signature, _data, _uri); } } else { require(_issuer == msg.sender); require(isKeyPurpose(bytes32(msg.sender), CLAIM_SIGNER_KEY)); _execute(address(this), 0, msg.data); emit ClaimRequested( claimHash, _claimType, _scheme, _issuer, _signature, _data, _uri ); } } function removeClaim(bytes32 _claimId) public returns (bool success) { Claim memory c = claims[_claimId]; require( msg.sender == c.issuer || msg.sender == address(this) ); // MUST only be done by the issuer of the claim, or KEYS OF PURPOSE 1, or the identity itself. // TODO If its the identity itself, the approval process will determine its approval. uint256 claimIdTypePos = indexes[_claimId]; delete indexes[_claimId]; bytes32[] storage claimsTypeArr = claimsByType[c.claimType]; bytes32 replacer = claimsTypeArr[claimsTypeArr.length-1]; claimsTypeArr[claimIdTypePos] = replacer; indexes[replacer] = claimIdTypePos; delete claims[_claimId]; claimsTypeArr.length--; return true; } function getKey( bytes32 _key, uint256 _purpose ) public view returns(uint256 purpose, uint256 keyType, bytes32 key) { Key storage myKey = keys[keccak256(_key, _purpose)]; return (myKey.purpose, myKey.keyType, myKey.key); } function isKeyPurpose(bytes32 _key, uint256 _purpose) public view returns (bool) { return keys[keccak256(_key, _purpose)].purpose == _purpose; } function getKeyPurpose(bytes32 _key) public view returns(uint256[] purpose) { uint256[] memory purposeHolder = new uint256[](4); uint8 counter = 0; if (isKeyPurpose(_key, MANAGEMENT_KEY)) { purposeHolder[counter] = MANAGEMENT_KEY; counter++; } if (isKeyPurpose(_key, ACTION_KEY)) { purposeHolder[counter] = ACTION_KEY; counter++; } if (isKeyPurpose(_key, CLAIM_SIGNER_KEY)) { purposeHolder[counter] = CLAIM_SIGNER_KEY; counter++; } if (isKeyPurpose(_key, ENCRYPTION_KEY)) { purposeHolder[counter] = ENCRYPTION_KEY; counter++; } uint256[] memory result = new uint256[](counter); for (uint8 i = 0; i < counter; i++) { result[i] = purposeHolder[i]; } return result; } function getKeysByPurpose(uint256 _purpose) public view returns(bytes32[]) { return keysByPurpose[_purpose]; } function getClaim(bytes32 _claimId) public view returns( uint256 claimType, uint256 scheme, address issuer, bytes signature, bytes data, string uri ) { Claim memory _claim = claims[_claimId]; return (_claim.claimType, _claim.scheme, _claim.issuer, _claim.signature, _claim.data, _claim.uri); } function getClaimIdsByType(uint256 _claimType) public view returns(bytes32[] claimIds) { return claimsByType[_claimType]; } function setupRecovery(address _recoveryContract) public managementOnly { require(recoveryContract == address(0)); recoveryContract = _recoveryContract; } function _constructIdentity(address _manager) internal { require(keysByPurpose[MANAGEMENT_KEY].length == 0);//还没有management生成 require(purposeThreshold[MANAGEMENT_KEY] == 0); _addKey(bytes32(_manager), MANAGEMENT_KEY, 0);//添加一个purpose为management的key purposeThreshold[MANAGEMENT_KEY] = 1; purposeThreshold[ACTION_KEY] = 1; } function _execute( address _to, uint256 _value, bytes _data ) private returns (uint256 executionId) { executionId = nonce; nonce++; txx[executionId] = Transaction({ valid: true, to: _to, value: _value, data: _data, nonce: nonce, approverCount: 0 }); emit ExecutionRequested(executionId, _to, _value, _data); } function _approve( bytes32 _key, uint256 _id, bool _approval ) private returns(bool success) //(?) should return approved instead of success? { Transaction memory trx = txx[_id]; require(trx.valid); uint256 requiredKeyPurpose = trx.to == address(this) ? MANAGEMENT_KEY : ACTION_KEY; require(isKeyPurpose(_key, requiredKeyPurpose)); bytes32 keyHash = keccak256(_key, requiredKeyPurpose); require(txx[_id].approvals[keyHash] != _approval); if (_approval) { trx.approverCount++; } else { trx.approverCount--; } emit Approved(_id, _approval); if (trx.approverCount < purposeThreshold[requiredKeyPurpose]) { txx[_id].approvals[keyHash] = _approval; txx[_id] = trx; } else { delete txx[_id]; //(?) success should be included in event? success = address(trx.to).call.value(trx.value)(trx.data); emit Executed(_id, trx.to, trx.value, trx.data); } } function _addKey( bytes32 _key, uint256 _purpose, uint256 _type ) private { bytes32 keyHash = keccak256(_key, _purpose);//keyhash是要与purpose一起算的 require(keys[keyHash].purpose == 0);//keyHash的purpose还没设置的,management为1 require( _purpose == MANAGEMENT_KEY || _purpose == ACTION_KEY || _purpose == CLAIM_SIGNER_KEY || _purpose == ENCRYPTION_KEY ); keys[keyHash] = Key(_purpose, _type, _key);//添加key indexes[keyHash] = keysByPurpose[_purpose].push(_key) - 1;//通过keysByPurpose[_purpose][indexes[keyHash]]就能得到_key,即address了 emit KeyAdded(_key, _purpose, _type); } function _removeKey( bytes32 _key, uint256 _purpose ) private { if (_purpose == MANAGEMENT_KEY) { require(keysByPurpose[MANAGEMENT_KEY].length > purposeThreshold[MANAGEMENT_KEY]); } bytes32 keyHash = keccak256(_key, _purpose); Key memory myKey = keys[keyHash]; uint256 index = indexes[keyHash]; bytes32 indexReplacer = keysByPurpose[_purpose][keysByPurpose[_purpose].length - 1]; keysByPurpose[_purpose][index] = indexReplacer; indexes[keccak256(indexReplacer, _purpose)] = index; keysByPurpose[_purpose].length--; delete indexes[keyHash]; delete keys[keyHash]; emit KeyRemoved(myKey.key, myKey.purpose, myKey.keyType); } function _includeClaim( bytes32 _claimHash, uint256 _claimType, uint256 _scheme, address _issuer, bytes _signature, bytes _data, string _uri ) private { claims[_claimHash] = Claim( { claimType: _claimType, scheme: _scheme, issuer: _issuer, signature: _signature, data: _data, uri: _uri } ); indexes[_claimHash] = claimsByType[_claimType].length; claimsByType[_claimType].push(_claimHash); emit ClaimAdded( _claimHash, _claimType, _scheme, _issuer, _signature, _data, _uri ); } function _modifyClaim( bytes32 _claimHash, uint256 _claimType, uint256 _scheme, address _issuer, bytes _signature, bytes _data, string _uri ) private { require(msg.sender == _issuer); claims[_claimHash] = Claim({ claimType: _claimType, scheme: _scheme, issuer: _issuer, signature: _signature, data: _data, uri: _uri }); emit ClaimChanged( _claimHash, _claimType, _scheme, _issuer, _signature, _data, _uri ); } }
identity/IdentityGasRelay.sol
这部分就是identity contract收到executable signed messages后怎么执行的:
pragma solidity ^0.4.21; import "./Identity.sol"; import "../token/ERC20Token.sol"; /** * @title IdentityGasRelay * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) * @notice enables economic abstraction for Identity */ contract IdentityGasRelay is Identity { bytes4 public constant CALL_PREFIX = bytes4(keccak256("callGasRelay(address,uint256,bytes32,uint256,uint256,address)")); bytes4 public constant APPROVEANDCALL_PREFIX = bytes4(keccak256("approveAndCallGasRelay(address,address,uint256,bytes32,uint256,uint256)")); event ExecutedGasRelayed(bytes32 signHash, bool success); /** * @notice include ethereum signed callHash in return of gas proportional amount multiplied by `_gasPrice` of `_gasToken` * allows identity of being controlled without requiring ether in key balace * @param _to destination of call * @param _value call value (ether) * @param _data call data * @param _nonce current identity nonce * @param _gasPrice price in SNT paid back to msg.sender for each gas unit used * @param _gasLimit minimal gasLimit required to execute this call * @param _gasToken token being used for paying `msg.sender` * @param _messageSignatures rsv concatenated ethereum signed message signatures required */ function callGasRelayed( address _to, uint256 _value, bytes _data, uint _nonce, uint _gasPrice, uint _gasLimit, address _gasToken, bytes _messageSignatures ) external { uint startGas = gasleft(); //verify transaction parameters require(startGas >= _gasLimit); require(_nonce == nonce); // calculates signHash bytes32 signHash = getSignHash( callGasRelayHash( _to, _value, keccak256(_data), _nonce, _gasPrice, _gasLimit, _gasToken ) ); //verify if signatures are valid and came from correct actors; verifySignatures( _to == address(this) ? MANAGEMENT_KEY : ACTION_KEY, signHash, _messageSignatures ); //executes transaction nonce++; bool success = _to.call.value(_value)(_data); emit ExecutedGasRelayed( signHash, success ); //refund gas used using contract held ERC20 tokens or ETH if (_gasPrice > 0) { uint256 _amount = 21000 + (startGas - gasleft()); _amount = _amount * _gasPrice; if (_gasToken == address(0)) { address(msg.sender).transfer(_amount); } else { ERC20Token(_gasToken).transfer(msg.sender, _amount); } } } /** * @notice include ethereum signed approve ERC20 and call hash * (`ERC20Token(baseToken).approve(_to, _value)` + `_to.call(_data)`). * in return of gas proportional amount multiplied by `_gasPrice` of `_gasToken` * fixes race condition in double transaction for ERC20. * @param _baseToken token approved for `_to` * @param _to destination of call * @param _value call value (ether) * @param _data call data * @param _nonce current identity nonce * @param _gasPrice price in SNT paid back to msg.sender for each gas unit used * @param _gasLimit minimal gasLimit required to execute this call * @param _gasToken token being used for paying `msg.sender` * @param _messageSignatures rsv concatenated ethereum signed message signatures required */ function approveAndCallGasRelayed( address _baseToken, address _to, uint256 _value, bytes _data, uint _nonce, uint _gasPrice, uint _gasLimit, address _gasToken, bytes _messageSignatures ) external { uint startGas = gasleft(); //verify transaction parameters require(startGas >= _gasLimit); require(_nonce == nonce); require(_baseToken != address(0)); //_baseToken should be something! require(_to != address(this)); //no management with approveAndCall // calculates signHash bytes32 signHash = getSignHash( approveAndCallGasRelayHash( _baseToken, _to, _value, keccak256(_data), _nonce, _gasPrice, _gasLimit, _gasToken ) ); //verify if signatures are valid and came from correct actors; verifySignatures( ACTION_KEY, //no management with approveAndCall signHash, _messageSignatures ); approveAndCall( signHash, _baseToken, _to, _value, _data ); //refund gas used using contract held ERC20 tokens or ETH if (_gasPrice > 0) { uint256 _amount = 21000 + (startGas - gasleft()); _amount = _amount * _gasPrice; if (_gasToken == address(0)) { address(msg.sender).transfer(_amount); } else { ERC20Token(_gasToken).transfer(msg.sender, _amount); } } } /** * @notice reverts if signatures are not valid for the signed hash and required key type. * @param _requiredKey key required to call, if _to from payload is the identity itself, is `MANAGEMENT_KEY`, else `ACTION_KEY` * @param _signHash ethereum signable callGasRelayHash message provided for the payload * @param _messageSignatures ethereum signed `_signHash` messages * @return true case valid */ function verifySignatures( uint256 _requiredKey, bytes32 _signHash, bytes _messageSignatures ) public view returns(bool) { uint _amountSignatures = _messageSignatures.length / 72; require(_amountSignatures == purposeThreshold[_requiredKey]); bytes32 _lastKey = 0; for (uint256 i = 0; i < _amountSignatures; i++) { bytes32 _currentKey = recoverKey( _signHash, _messageSignatures, i ); require(_currentKey > _lastKey); //assert keys are different require(isKeyPurpose(_currentKey, _requiredKey)); _lastKey = _currentKey; } return true; } /** * @notice get callHash * @param _to destination of call * @param _value call value (ether) * @param _dataHash call data hash * @param _nonce current identity nonce * @param _gasPrice price in SNT paid back to msg.sender for each gas unit used * @param _gasLimit minimal gasLimit required to execute this call * @param _gasToken token being used for paying `msg.sender` * @return callGasRelayHash the hash to be signed by wallet */ function callGasRelayHash( address _to, uint256 _value, bytes32 _dataHash, uint _nonce, uint256 _gasPrice, uint256 _gasLimit, address _gasToken ) public view returns (bytes32 _callGasRelayHash) { _callGasRelayHash = keccak256( address(this), CALL_PREFIX, _to, _value, _dataHash, _nonce, _gasPrice, _gasLimit, _gasToken ); } /** * @notice get callHash * @param _to destination of call * @param _value call value (ether) * @param _dataHash call data hash * @param _nonce current identity nonce * @param _gasPrice price in SNT paid back to msg.sender for each gas unit used * @param _gasLimit minimal gasLimit required to execute this call * @param _gasToken token being used for paying `msg.sender` * @return callGasRelayHash the hash to be signed by wallet */ function approveAndCallGasRelayHash( address _baseToken, address _to, uint256 _value, bytes32 _dataHash, uint _nonce, uint256 _gasPrice, uint256 _gasLimit, address _gasToken ) public view returns (bytes32 _callGasRelayHash) { _callGasRelayHash = keccak256( address(this), APPROVEANDCALL_PREFIX, _baseToken, _to, _value, _dataHash, _nonce, _gasPrice, _gasLimit, _gasToken ); } /** * @notice Hash a hash with `"\x19Ethereum Signed Message:\n32"` * @param _hash Sign to hash. * @return signHash Hash ethereum wallet signs. */ function getSignHash( bytes32 _hash ) pure public returns(bytes32 signHash) { signHash = keccak256("\x19Ethereum Signed Message:\n32", _hash); } /** * @notice recovers address who signed the message * @param _signHash operation ethereum signed message hash * @param _messageSignature message `_signHash` signature * @param _pos which signature to read */ function recoverKey ( bytes32 _signHash, bytes _messageSignature, uint256 _pos ) pure public returns(bytes32) { uint8 v; bytes32 r; bytes32 s; (v,r,s) = signatureSplit(_messageSignature, _pos); return bytes32( ecrecover( _signHash, v, r, s ) ); } /** * @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s` * @param _pos which signature to read * @param _signatures concatenated vrs signatures */ function signatureSplit(bytes _signatures, uint256 _pos) pure public returns (uint8 v, bytes32 r, bytes32 s) { uint pos = _pos + 1; // The signature format is a compact form of: // {bytes32 r}{bytes32 s}{uint8 v} // Compact means, uint8 is not padded to 32 bytes. assembly { r := mload(add(_signatures, mul(32,pos))) s := mload(add(_signatures, mul(64,pos))) // Here we are loading the last 32 bytes, including 31 bytes // of 's'. There is no 'mload8' to do this. // // 'byte' is not working due to the Solidity parser, so lets // use the second best option, 'and' v := and(mload(add(_signatures, mul(65,pos))), 0xff) } require(v == 27 || v == 28); } function approveAndCall( bytes32 _signHash, address _token, address _to, uint256 _value, bytes _data ) private { //executes transaction nonce++; ERC20Token(_token).approve(_to, _value); emit ExecutedGasRelayed( _signHash, _to.call(_data) ); } }