ERC 725 and ERC 735 的实现及关系
https://github.com/OriginProtocol/origin-playground
通过ERC 725 and ERC 735 的实现来说明它们到底是做什么的:
看了这个例子后才大概明白了claim,key,identity之间的关系,就比如有一个合约为Claim checkers,作为一个卖票网站,这个网站只允许那些有着身份并且身份满足某种claim的消费者来该网站购买claim发行商
比如说一个身份为customer的消费者想要到这个网站上买票,这个消费者的账户是一个identity contract,以合约地址来代表该身份,key为其的公钥,然后此时Claim checkers这个卖票网站(也是一个contract)要求只有满足claim issuer,即发行商origin发行的一种claim的身份才能到该网站上买票。就比如这种claim名为Has Github,只有添加了这种claim的身份才能在网站上买东西,由这里可以看出,每个身份可以有多个claim,但是每一类claim一个identity只有一个,一个claim可能被多个身份添加。
根据这个网站给的教程进行学习过程为:
You can try a deployed version of this app at http://erc725.originprotocol.com/#/
刚进入时网站有三类contract,即identity(顾客)、claim issuers(发行商)和claim checkers(卖票网站):
点击右上角,使用的是Rinkeby测试网络,有三个账户,这三个用户分别来部署上面的三类contract:
首先使用第一个账户来创建一个identity contract,名叫customer
点击,然后在下图中填写customer,然后deploy:
然后就使用第一个账户0x313AaDcA1750CaadC7BCB26FF08175c95DCf8E38部署好了一个名为customer的identity contract,合约地址为0xF39D9Ec84E2b5597c96E44c8745d756338929b4c:
并且自动生成该合约的公私钥,查看代码 bytes32 _key = keccak256(msg.sender);可知key为合约地址的hash值:
可以添加key,点击右上角的,然后add key即可:
public key :0xbDA42d0EEB4faf56978aD5682774F8c2D1922F4e
private key: 0xf7a144c52ab222d3edce7cc404153cb126f7b156e78f3eb39ad9f9da12c09300
key :0x3542b67ac6670b296e63a7afab979cf80663f47158863b55a24ef67844d6cc99
后面查看代码实现
从这里我们就可以解释EIP-725中的key定义:
key
: A public key owned by this identitypurpose
:uint256[]
Array of the key types, like 1 = MANAGEMENT, 2 = ACTION, 3 = CLAIM, 4 = ENCRYPTION,这个就是该key的作用,我们新增的这个key的作用是ENCRYPTION,加密数据keyType
: The type of key used, which would be auint256
for different key types. e.g. 1 = ECDSA, 2 = RSA, etc.使用的是椭圆曲线数字签名算法(ECDSA)key
:bytes32
The public key. // for non-hex and long keys, its the Keccak256 hash of the key
然后就添加成功了,这是identity
在claim issuer中已经有了一个发行商为origin,该合约地址为0x64453cE1CB78A5F3d918d0BA162FE084bD2b5405,使用账户0x82d26CA48cCB83978eF2825053800C686f1b62D9部署的:
这里可以看见有keys,claims,claims signer services;在这里可见claim type有8种,Full name、Has LinkedIn、Email、Has Facebook、Has Twitter、Has GitHub、Has Google和Has LinkedIn(查代码看差别)
这里可以用来解释EIP-735中的claim定义:
claim issuer
: is another smart contract or external account, which issues claims about this identity. The claim issuer can be an identity contract itself.claim发行商能发行了有关身份的claims,它可以是一个智能合约或一个外部账户,当然也可以是身份合约本身claim
: A claim is an information an issuer has about the identity holder.(claim就是发行商所有的关于身份holder的信息) This contains the following:topic
: Auint256
number which represents the topic of the claim. (e.g. 1 biometric(生物识别), 2 residence(住宅) (ToBeDefined: number schemes, sub topics based on number ranges??))scheme
: The scheme with which this claim SHOULD be verified or how it should be processed. Its auint256
for different schemes. E.g. could3
mean contract verification, where thedata
will be call data, and theissuer
a contract address to call (ToBeDefined). Those can also mean different key types e.g. 1 = ECDSA, 2 = RSA, etc. (ToBeDefined)issuer
: The issuers identity contract address, or the address used to sign the above signature. If an identity contract, it should hold the key with which the above message was signed, if the key is not present anymore, the claim SHOULD be treated as invalid. The issuer can also be a contract address itself, at which the claim can be verified using the calldata
.当发行商是一个合约的时候,claims的证明就可以使用call data来进行signature
: Signature which is the proof that the claim issuer issued a claim oftopic
for this identity(签名是发行商对这个身份address(identityHolder
)发行的有关topic的claim). it MUST be a signed message of the following structure:keccak256(address identityHolder_address, uint256 _ topic, bytes data)
// orkeccak256(abi.encode(identityHolder_address, topic, data))
?data
: The hash of the claim data, sitting in another location, a bit-mask, call data, or actual data based on the claim scheme.uri
: The location of the claim, this can be HTTP links, swarm hashes, IPFS hashes, and such.
claim type即相当于上面的topic信息,在这里我选择的是Has Github这个claim type的claims signer services,该services的用处是当identity add claim的时候如果选择的是这几个claim type的话,那么就通过该服务器进行签名和验证,生成signature
和hash
在这里举例说明:
回到identity中点击add claim:
然后选择get claim from issuer :
在这里选择的是Has GitHub:
然后得到结果,点击OK,只要issuer,target是一样的,选择GitHub得到的下面的claim内容是相同的,所以每一类claim一个identity只有一个:
然后就得到与EIP-735一致的claim信息:
signature : 0xa27f961dfb729eaaf921c96295daaf6f1c1af8d2c124daf4c896c8830588c2185ae1c21bf0221cd87821de10514d6672853414220e338680e84308905c9d86f61b
然后就可以看见customer中出现了claim:
下面要做的就是部署Claim Checkers合约(卖票网站),设置两种顾客限制,看是否实现了claim的限制:
使用另一个账户0x56BEaa3C8C87c04584b48C9aF037c01d2677B8B2来部署该合约:
1)这种情况就是只有顾客拥有被发行商origin发行的claim type为has github的claim才能访问该合约
2.这种情况就是只有顾客拥有被发行商origin发行的claim type为has Facebook的claim才能访问该合约
然后就如下图所示:
点击,选择身份customer,查看其claim是否满足:
然后得到结果为第一种情况测为有效,第二种情况测为无效,与预期一致:
接下来是代码分析:
erc725,声明key及函数:
pragma solidity ^0.4.22; 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) public constant returns(uint256 purpose, uint256 keyType, bytes32 key); function getKeyPurpose(bytes32 _key) public constant returns(uint256 purpose); function getKeysByPurpose(uint256 _purpose) public constant returns(bytes32[] keys); function addKey(bytes32 _key, uint256 _purpose, uint256 _keyType) 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); }
contracts/KeyHolder.sol
pragma solidity ^0.4.22; import './ERC725.sol'; contract KeyHolder is ERC725 {//identity中的customer生成是就是生成了这个合约 uint256 executionNonce; struct Execution { address to; uint256 value; bytes data; bool approved; bool executed; } mapping (bytes32 => Key) keys; mapping (uint256 => bytes32[]) keysByPurpose; mapping (uint256 => Execution) executions; event ExecutionFailed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); function KeyHolder() public {//初始化生成一个management的key bytes32 _key = keccak256(msg.sender);//得知key为合约地址的hash keys[_key].key = _key; keys[_key].purpose = 1; //management keys[_key].keyType = 1; //ECDSA keysByPurpose[1].push(_key); //所以如果一种purpose的key有好几个,那么就顺序排在数组中 emit KeyAdded(_key, keys[_key].purpose, 1); } function getKey(bytes32 _key) public view returns(uint256 purpose, uint256 keyType, bytes32 key) { return (keys[_key].purpose, keys[_key].keyType, keys[_key].key); } function getKeyPurpose(bytes32 _key) public view returns(uint256 purpose) { return (keys[_key].purpose); } function getKeysByPurpose(uint256 _purpose) public view returns(bytes32[] _keys) { return keysByPurpose[_purpose]; } function addKey(bytes32 _key, uint256 _purpose, uint256 _type) public returns (bool success) { require(keys[_key].key != _key, "Key already exists"); // Key should not already exist if (msg.sender != address(this)) { require(keyHasPurpose(keccak256(msg.sender), 1), "Sender does not have management key"); // Sender has MANAGEMENT_KEY } keys[_key].key = _key; keys[_key].purpose = _purpose; keys[_key].keyType = _type; keysByPurpose[_purpose].push(_key); emit KeyAdded(_key, _purpose, _type); return true; } function approve(uint256 _id, bool _approve)//执行操作的purpose的实现 public returns (bool success) { require(keyHasPurpose(keccak256(msg.sender), 2), "Sender does not have action key"); emit Approved(_id, _approve); if (_approve == true) { executions[_id].approved = true; success = executions[_id].to.call(executions[_id].data, 0);//运行data操作 if (success) { executions[_id].executed = true; emit Executed( _id, executions[_id].to, executions[_id].value, executions[_id].data ); return; } else { emit ExecutionFailed( _id, executions[_id].to, executions[_id].value, executions[_id].data ); return; } } else { executions[_id].approved = false; } return true; } function execute(address _to, uint256 _value, bytes _data) public returns (uint256 executionId) { require(!executions[executionNonce].executed, "Already executed"); executions[executionNonce].to = _to; //把执行操作的数据写入 executions[executionNonce].value = _value; executions[executionNonce].data = _data; emit ExecutionRequested(executionNonce, _to, _value, _data); if (keyHasPurpose(keccak256(msg.sender),1) || keyHasPurpose(keccak256(msg.sender),2)) {//只有是management和action两种purpose才能批准该操作的执行 approve(executionNonce, true); } executionNonce++; return executionNonce-1; } function removeKey(bytes32 _key) public returns (bool success) { require(keys[_key].key == _key, "No such key"); emit KeyRemoved(keys[_key].key, keys[_key].purpose, keys[_key].keyType); /* uint index; (index,) = keysByPurpose[keys[_key].purpose.indexOf(_key); keysByPurpose[keys[_key].purpose.removeByIndex(index); */ delete keys[_key]; return true; } function keyHasPurpose(bytes32 _key, uint256 _purpose) public view returns(bool result) { bool isThere; if (keys[_key].key == 0) return false; isThere = keys[_key].purpose <= _purpose; return isThere; } }
erc735,声明claim及函数:
pragma solidity ^0.4.22; contract ERC735 { event ClaimRequested(uint256 indexed claimRequestId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri); event ClaimAdded(bytes32 indexed claimId, uint256 indexed claimType, address indexed issuer, uint256 signatureType, bytes32 signature, bytes claim, 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 {//这就是一个claims的组成,即某identity使用了该claim,就将相关信息记录下来,this.address即该identity的合约地址 uint256 claimType; uint256 scheme; //说明使用的是ECDSA等哪个签名算法 address issuer; // msg.sender bytes signature; // this.address + claimType + data bytes data; string uri; } function getClaim(bytes32 _claimId) public constant returns(uint256 claimType, uint256 scheme, address issuer, bytes signature, bytes data, string uri); function getClaimIdsByType(uint256 _claimType) public constant 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); }
pragma solidity ^0.4.22; import './ERC735.sol'; import './KeyHolder.sol'; contract ClaimHolder is KeyHolder, ERC735 {//claimholder即发行商合约 mapping (bytes32 => Claim) claims; mapping (uint256 => bytes32[]) claimsByType; function addClaim(//identity想要add,虽然是在identity页面上进行操作,但是其实是发行商在实现这个功能 uint256 _claimType, uint256 _scheme, //ECDSA address _issuer, bytes _signature, bytes _data, string _uri ) public returns (bytes32 claimRequestId) { bytes32 claimId = keccak256(_issuer, _claimType); if (msg.sender != address(this)) { require(keyHasPurpose(keccak256(msg.sender), 3), "Sender does not have claim signer key"); //该合约地址address(this)就是发行商合约地址msg.sender }//如果不是,那么可能是其他的合约,那么其要have claim signer key if (claims[claimId].issuer != _issuer) { claimsByType[_claimType].push(claimId); } claims[claimId].claimType = _claimType; claims[claimId].scheme = _scheme; claims[claimId].issuer = _issuer; claims[claimId].signature = _signature; claims[claimId].data = _data; claims[claimId].uri = _uri; emit ClaimAdded( claimId, _claimType, _scheme, _issuer, _signature, _data, _uri ); return claimId; } function removeClaim(bytes32 _claimId) public returns (bool success) { if (msg.sender != address(this)) { require(keyHasPurpose(keccak256(msg.sender), 1), "Sender does not have management key"); } /* uint index; */ /* (index, ) = claimsByType[claims[_claimId].claimType].indexOf(_claimId); claimsByType[claims[_claimId].claimType].removeByIndex(index); */ emit ClaimRemoved( _claimId, claims[_claimId].claimType, claims[_claimId].scheme, claims[_claimId].issuer, claims[_claimId].signature, claims[_claimId].data, claims[_claimId].uri ); delete claims[_claimId]; return true; } function getClaim(bytes32 _claimId) public constant returns( uint256 claimType, uint256 scheme, address issuer, bytes signature, bytes data, string uri ) { return ( claims[_claimId].claimType, claims[_claimId].scheme, claims[_claimId].issuer, claims[_claimId].signature, claims[_claimId].data, claims[_claimId].uri ); } function getClaimIdsByType(uint256 _claimType) public constant returns(bytes32[] claimIds) { return claimsByType[_claimType]; } }
contracts/Identity.sol
这个是当我们部署identity的时候如果想要使用左下角的,则实现方式就是下面的代码,否则使用的还是claimholder:
点击后:
就是部署identity的同时加上claim
pragma solidity ^0.4.22; import './ClaimHolder.sol'; /** * NOTE: This contract exists as a convenience for deploying an identity with * some 'pre-signed' claims. If you don't care about that, just use ClaimHolder * instead. */ contract Identity is ClaimHolder { function Identity( uint256[] _claimType, uint256[] _scheme, address[] _issuer, bytes _signature, bytes _data, string _uri, uint256[] _sigSizes, uint256[] dataSizes, uint256[] uriSizes ) public { bytes32 claimId; uint offset = 0; uint uoffset = 0; uint doffset = 0; for (uint i = 0; i < _claimType.length; i++) { claimId = keccak256(_issuer[i], _claimType[i]); claims[claimId] = Claim( _claimType[i], _scheme[i], _issuer[i], getBytes(_signature, offset, _sigSizes[i]), getBytes(_data, doffset, dataSizes[i]), getString(_uri, uoffset, uriSizes[i]) ); offset += _sigSizes[i]; uoffset += uriSizes[i]; doffset += dataSizes[i]; emit ClaimAdded( claimId, claims[claimId].claimType, claims[claimId].scheme, claims[claimId].issuer, claims[claimId].signature, claims[claimId].data, claims[claimId].uri ); } } function getBytes(bytes _str, uint256 _offset, uint256 _length) constant returns (bytes) { bytes memory sig = new bytes(_length); uint256 j = 0; for (uint256 k = _offset; k< _offset + _length; k++) { sig[j] = _str[k]; j++; } return sig; } function getString(string _str, uint256 _offset, uint256 _length) constant returns (string) { bytes memory strBytes = bytes(_str); bytes memory sig = new bytes(_length); uint256 j = 0; for (uint256 k = _offset; k< _offset + _length; k++) { sig[j] = strBytes[k]; j++; } return string(sig); } }
contracts/ClaimVerifier.sol
pragma solidity ^0.4.22; import './ClaimHolder.sol'; contract ClaimVerifier {//Claim Checkers合约 event ClaimValid(ClaimHolder _identity, uint256 claimType); event ClaimInvalid(ClaimHolder _identity, uint256 claimType); ClaimHolder public trustedClaimHolder; function ClaimVerifier(address _trustedClaimHolder) public { trustedClaimHolder = ClaimHolder(_trustedClaimHolder);//即发行商的合约地址 } function checkClaim(ClaimHolder _identity, uint256 claimType) public returns (bool claimValid) { if (claimIsValid(_identity, claimType)) { emit ClaimValid(_identity, claimType); return true; } else { emit ClaimInvalid(_identity, claimType); return false; } } function claimIsValid(ClaimHolder _identity, uint256 claimType) public constant returns (bool claimValid) { uint256 foundClaimType; uint256 scheme; address issuer; bytes memory sig; bytes memory data; // Construct claimId (identifier发行商地址 + claim type) bytes32 claimId = keccak256(trustedClaimHolder, claimType); // Fetch claim from user, ( foundClaimType, scheme, issuer, sig, data, ) = _identity.getClaim(claimId); bytes32 dataHash = keccak256(_identity, claimType, data); bytes32 prefixedHash = keccak256("\x19Ethereum Signed Message:\n32", dataHash); // Recover address of data signer,sig是identity实现的签名 address recovered = getRecoveredAddress(sig, prefixedHash);//得到对数据进行签名的address,即identity // Take hash of recovered address bytes32 hashedAddr = keccak256(recovered);//将其进行hash // Does the trusted identifier have they key which signed the user's claim? return trustedClaimHolder.keyHasPurpose(hashedAddr, 3); } function getRecoveredAddress(bytes sig, bytes32 dataHash) public view returns (address addr) { bytes32 ra; bytes32 sa; uint8 va; // Check the signature length if (sig.length != 65) { return (0); } // Divide the signature in r, s and v variables assembly { ra := mload(add(sig, 32)) sa := mload(add(sig, 64)) va := byte(0, mload(add(sig, 96))) } if (va < 27) { va += 27; } address recoveredAddress = ecrecover(dataHash, va, ra, sa); return (recoveredAddress); } }
还有一个更复杂的例子,有空看看:https://github.com/mirceapasoi/erc725-735