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