diff --git a/mkdocs/docs/protocol/onchain-credential-status.md b/mkdocs/docs/protocol/onchain-credential-status.md deleted file mode 100644 index 9c24aff..0000000 --- a/mkdocs/docs/protocol/onchain-credential-status.md +++ /dev/null @@ -1,190 +0,0 @@ -# OnChain Credential Status - -On-chain identity verification requires the ability to check whether a credential has been revoked using a smart contract. To accomplish this, the credential should have the `CredentialStatus` structure of type `Iden3OnchainSparseMerkleTreeProof2023` and include a revocation nonce with information about the smart contract, such as its address, blockchain, and network. - -The `CredentialStatus` structure should contain the following fields: - -```golang -type CredentialStatus struct { - ID string `json:"id"` - Type CredentialStatusType `json:"type"` - RevocationNonce uint64 `json:"revocationNonce"` -} -``` - -The `ID` field is a composite field that contains encoded information in the following format: - -`did:[did-method]:[blockchain]:[network]:[id]/credentialStatus?(revocationNonce=value)&(contractAddress={chainID}:{contractAddress})` - -`type` - credential type - -`revocationNonce` - unique revocation nonce - -Example: - -```json -{ -"id": "did:polygonid:polygon:main:2qCU58EJgrEMWhziKqC3qNXJkZPY8XCxDSBM4mqPkM/credentialStatus?revocationNonce=1234&contractAddress=1:0xf3bB959314B5D1e4587e1f597ccc289216608ac5", -"type": "Iden3OnchainSparseMerkleTreeProof2023", -"revocationNonce": "1234" -} -``` - -The `revocationNonce` and `contractAddress` parameters inside the ID field are optional. Here's an example without them: - -```json -{ -"id": "did:polygonid:polygon:main:2qCU58EJgrEMWhziKqC3qNXJkZPY8XCxDSBM4mqPkM/credentialStatus", -"type": "Iden3OnchainSparseMerkleTreeProof2023", -"revocationNonce": "1234" -} -``` - -## Process ID field - -In the context of `OnChainCredentialStatus`, the `id` field can contain two additional optional parameters: `revocationNonce` and `contractAddress`. -`revocationNonce` is a credential revocation `nonce`, while `contractAddress` is the address of a smart contract that implements an on-chain issuer interface. The `contractAddress` field is composed by two parts: `chainID` and `contractAddress`. Use `chainID` to select the correct network. - -If `contractAddress` is not set, find the default contract address by parsing DID [extracting the on-chain issuer contract address](https://github.com/iden3/go-iden3-core/blob/014f51e92da5c0c89c95c31e42bfca1652d2ad14/did.go#L345-L354) and getting `chainID` from the DID network. Use the blockchain name, network and contract address from the DID to make on-chain revocation request. If the DID doesn't have a contract address inside and `contractAddress` parameter is empty, this VC document should be considered invalid. - -If `revocationNonce` is not set, the `revocationNonce` value from the struct will be used instead. - -## Workflow - -Example of how to build a `non-revocation` proof with the `Iden3OnchainSparseMerkleTreeProof2023` credential status type: - -1. Extract the `credentialStatus` object from the verifiable credential. -1. Parse core.DID from credentialStatus.id field with [js](https://github.com/iden3/js-iden3-core/blob/baa0ead8a3e2340bb4d78132ec63e6e24d806da9/src/did/did.ts#L160)|[go](https://github.com/iden3/go-iden3-core/blob/014f51e92da5c0c89c95c31e42bfca1652d2ad14/w3c/did_w3c.go#L165) -1. Extract core.Id fome core.DID with [js](https://github.com/iden3/js-iden3-core/blob/baa0ead8a3e2340bb4d78132ec63e6e24d806da9/src/did/did.ts#L160)|[go](https://github.com/iden3/go-iden3-core/blob/014f51e92da5c0c89c95c31e42bfca1652d2ad14/did.go#L184) -1. Use the DID from step two to extract the on-chain issuer contract address: - - a. If the `contractAddress` parameter is not empty, use this address to build the non-revocation proof. - - b. If the `contractAddress` is empty, extract the contract address from the `id` field (refer to [this code snippet](https://github.com/iden3/go-iden3-core/blob/014f51e92da5c0c89c95c31e42bfca1652d2ad14/did.go#L345-L354)). - - c. If the `id` doesn't have the `contractAddress` parameter, and you are not allowed to extract the contract address from the `DID`, consider this VC document invalid. - -1. Extract `chainID` from `contractAddress` parameter. If `chainID` does not exist - try to extract `chainID` from DID. If both empty - return an error. -1. Parse the `id` to obtain the `revocationNonce`: - - a. You can extract the `revocationNonce` from the `id` parameter `revocationNonce`. - - b. If the `id` doesn't have the `revocationNonce`, you can get the `revocationNonce` from the `revocationNonce` field. - - c. If the parameter doesn't exist and the `revocationNonce` field is empty, consider this VC document invalid. - -1. Generate revocation proof call method `getRevocationStatus` from the issuer smart contract using the information you received earlier. - -```golang -const response = await this.onchainContract.getRevocationStatus(id, nonce); -``` - -- Use this ABI to make getRevocationStatus call. - -```json -[ - { - "inputs": [ - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "internalType": "uint64", - "name": "nonce", - "type": "uint64" - } - ], - "name": "getRevocationStatus", - "outputs": [ - { - "components": [ - { - "components": [ - { - "internalType": "uint256", - "name": "state", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "claimsTreeRoot", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "revocationTreeRoot", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "rootOfRoots", - "type": "uint256" - } - ], - "internalType": "struct IOnchainCredentialStatusResolver.IdentityStateRoots", - "name": "issuer", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "root", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "existence", - "type": "bool" - }, - { - "internalType": "uint256[]", - "name": "siblings", - "type": "uint256[]" - }, - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "auxExistence", - "type": "bool" - }, - { - "internalType": "uint256", - "name": "auxIndex", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "auxValue", - "type": "uint256" - } - ], - "internalType": "struct IOnchainCredentialStatusResolver.Proof", - "name": "mtp", - "type": "tuple" - } - ], - "internalType": "struct IOnchainCredentialStatusResolver.CredentialStatus", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - } - ] -``` - -Also, you can use the method ID of getRevocationStatus `0xeb62ed0e` instead of the ABI. - diff --git a/mkdocs/docs/services/rhs.md b/mkdocs/docs/services/rhs.md index c41c734..d5cc16d 100644 --- a/mkdocs/docs/services/rhs.md +++ b/mkdocs/docs/services/rhs.md @@ -2,7 +2,7 @@ ## Overview -The Reverse Hash Service (RHS) is a service that allows users to construct proofs of existence or non-existence of elements in sparce merkle trees without revealing the specific element being proved. This service aims to enhance privacy of credential revocation status checks for identities. +The Reverse Hash Service (RHS) is a service that allows users to construct proofs of existence or non-existence of elements in sparse merkle trees without revealing the specific element being proved. This service aims to enhance privacy of credential revocation status checks for identities. ### Introduction @@ -25,52 +25,119 @@ To build non-revocation proof, we must prove that revocation nonce is not in the 1. The first thing we need is to get the `latest state` of the identity from the State smart contract. 2. Then we can find `roots` of the trees (Clams tree root, Revocation tree root and Roots Tree root) from RHS. -3. After this, we can do a reverse hash lookup to construct merkle tree proof for revocation nonce in revocation tree. +3. Once we have the `roots`, we can do a reverse hash lookup to construct merkle tree proof for revocation nonce in Revocation Tree as follows: + - Start at the root of the tree. Iterate through each level of the tree until reaching a leaf node or exceeding the maximum tree depth. + - For each level, fetch the corresponding node using the RHS API. + - Based on the type of node encountered (leaf or middle node), perform different actions: + - If the node is a leaf node and its key matches the input key, the proof of existence is complete. Set the `exists` variable to `true`, and return the constructed proof. + - If the node is a leaf node but its key does not match the input key, the `proof of non-existence` is complete. Set the `nodeAux` variable with the leaf node’s `key` and `value`, and return the constructed proof. + - If the node is a middle node, determine how to traverse the tree by checking the input key’s corresponding bit at the current depth. Update the next key to traverse and add the sibling node to the siblings list for proof construction. + - If the algorithm reaches the maximum tree depth without finding a leaf node or a matching key, return an error indicating the tree depth is too high. + ```mermaid sequenceDiagram participant User participant SmartContract as State Smart Contract - participant RHS as Reverse Hash Service + participant RHS as RHS Storage User->>SmartContract: Request latest identity state SmartContract->>User: Return latest identity state User->>RHS: Fetch roots of trees using identity state RHS->>User: Return roots of trees [ClR, ReR, RoR] User->>RHS: Request children of revocation root(ReR) RHS->>User: Return [left,right] nodes - loop until leaf node is found + loop until leaf node is found or max depth is reached User->>RHS: Request children of node RHS->>User: Return [left,right] nodes end User->>User: Calculate Proof ``` +By following these steps, the algorithm generates a Merkle proof of existence or non-existence for a given key in a Sparse Merkle Tree without revealing the specific element being proved. -### Publishing identity state to RHS +### RHS Storage -If identity holder wants to publish his identity state to RHS he needs to do the following: +RHS Storage is a persistence layer that stores hashes of preimages. A preimage is an array of integers, which length can be theoretically any number starting from 1. In practice, only support arrays of length 2 and 3 is guaranteed as it is used in the Iden3 protocol. + +The storage implements key-value approach, where key is a hash of preimage and value is a preimage itself. It is only possible to set a value for a key, but not the key itself as the key is calculated by RHS Storage as a hash of preimage using Poseidon hash function in the transaction that sets the value. + +RHS Storage can be **on-chain** or **off-chain** type. + +In the case of **on-chain** storage, the key-value storage is a smart contract with an interface as follows: +```solidity +interface IRHSStorage { + /** + * @dev Saves nodes array. Note that each node contains an array itself. + * @param nodes An array of nodes + */ + function saveNodes(uint256[][] memory nodes) external; + + /** + * @dev Returns a node by its key. Note that a node contains an array. + * @param key The key of the node + * @return The node + */ + function getNode(uint256 key) external view returns (uint256[] memory); +} +``` +In the case of **off-chain** storage, the key-value storage is may be implemented as a service on top of a database. It can be found in the [reverse-hash-service](https://github.com/iden3/reverse-hash-service) repository. + +The service exposes two endpoints: +```http +/POST {{server}}/node +Content-Type: application/json + +{ + "hash":"2c32381aebce52c0c5c5a1fb92e726f66d977b58a1c8a0c14bb31ef968187325", + "children":[ + "658c7a65594ebb0815e1cc20f54284ccdb51bb1625f103c116ce58444145381e", + "e809a4ed2cf98922910e456f1e56862bb958777f5ff0ea6799360113257f220f" + ] +} -1. Publish the state of identity to RHS [state] = [ClaimsTreeRoot, RevocationTreeRoot, RootsTreeRoot] -2. Publish the nodes of Revocation tree and Roots tree to RHS for the intermediate nodes [hash]=[left, right] where left and right are the hashes of the children of the node. And for the leaf nodes [hash]=[1, key, value] -### Reverse hash lookup +/GET {{server}}/node/{{hash}} +``` -Reverse hash lookup is a way to reveal a preimage (original message) of a specific hash value by storing hash value and original message used to generate it in the database. As Iden3 protocol uses Poseidon hash function, hash value is represented by a BigInt (~254 bit) and preimage is one or many (up to 16) BigInts. +### RHS Client interface and Reverse Hash Lookup -### SMT Proof generation +The RHS Client interface abstracts away the specific RHS Storage implementation. You are free to choose whether to connect your clients directly to RHS Storage or use RHS Client interface. -Algorithm of a SMT proof generation for a given key in the tree using reverse hash lookup works as follows: +The interface defines three methods: +- **SaveNodes**: saves nodes of the tree to RHS +- **GetNode**: gets node of the tree from RHS, so it does a reverse hash lookup and reveals the preimage of the hash +- **GenerateProof**: generates proof of existence or non-existence of the element in the Sparse Merkle Tree according to the [algorithm](#smt-proof-generation) described below -1. Start at the root of the tree. Iterate through each level of the tree until reaching a leaf node or exceeding the maximum tree depth. -2. For each level, fetch the corresponding node using the RHS API. -3. Based on the type of node encountered (leaf or middle node), perform different actions: - - If the node is a leaf node and its key matches the input key, the proof of existence is complete. Set the `exists` variable to `true`, and return the constructed proof. - - If the node is a leaf node but its key does not match the input key, the `proof of non-existence` is complete. Set the `nodeAux` variable with the leaf node’s `key` and `value`, and return the constructed proof. - - If the node is a middle node, determine how to traverse the tree by checking the input key’s corresponding bit at the current depth. Update the next key to traverse and add the sibling node to the siblings list for proof construction. - - If the algorithm reaches the maximum tree depth without finding a leaf node or a matching key, return an error indicating the tree depth is too high. +The library that implements the RHS Client interface is located in the [merkletree-proof](https://github.com/iden3/merkletree-proof) repository and the interface itself is as follows: -By following these steps, the algorithm generates a Merkle proof of existence or non-existence for a given key in a Sparse Merkle Tree without revealing the specific element being proved. +```go +type ReverseHashCli interface { + GenerateProof(ctx context.Context, + treeRoot *merkletree.Hash, + key *merkletree.Hash) (*merkletree.Proof, error) + GetNode(ctx context.Context, + hash *merkletree.Hash) (Node, error) + SaveNodes(ctx context.Context, + nodes []Node) error +} +``` -Example: +The Node struct is defined as follows: +```go +type Node struct { + Hash *merkletree.Hash // merkletree.Hash is an array of 32 bytes + Children []*merkletree.Hash +} +``` +Note, that the Hash field of the Node struct is a hash of the Children field, however it is not saved to RHS in the SaveNodes() method as it is calculated by RHS Storage itself. + +### Publishing identity state to RHS + +If identity holder wants to publish his identity state to RHS he needs to do the following: + +1. Save the state of identity to RHS `{ Hash: state, Children: [ClaimsTreeRoot, RevocationTreeRoot, RootsTreeRoot] }` +2. Save the nodes of Revocation tree and Roots tree to RHS for the intermediate nodes `{ Hash: hash, Children: [left, right] }` where left and right are the hashes of the children of the node. And for the leaf nodes `{ Hash: hash, Children: [1, key, value] }` + +### Example with **off-chain RHS storage**: ```go @@ -83,7 +150,7 @@ import ( "math/big" "github.com/iden3/go-merkletree-sql/v2" - proof "github.com/iden3/merkletree-proof" + proof "github.com/iden3/merkletree-proof/http" ) func main() { @@ -92,7 +159,7 @@ func main() { state := "e12084d0d72c492c703a2053b371026bceda40afb9089c325652dfd2e5e11223" revocationNonce, _ := merkletree.NewHashFromBigInt(big.NewInt(670966937)) - cli := &proof.HTTPReverseHashCli{URL: rhsURL} + cli := &proof.ReverseHashCli{URL: rhsURL} // get identity state roots (ClT root, ReT root, RoT root) node := getIdentityStateRoots(cli, ctx, state) @@ -130,36 +197,17 @@ func getIdentityStateRoots(cli *proof.HTTPReverseHashCli, ctx context.Context, s } ``` -### Credential Status - -If identity is published to RHS then we can check the credential status by doing a reverse hash lookup for the credential revocation nonce. In this case credential issued by this identity may have credential status type `Iden3ReverseSparseMerkleTreeProof` - -```json -...{ -"type": "Iden3ReverseSparseMerkleTreeProof" -"id": "https://rhs-staging.polygonid.me", -"revocationNonce": 3409889195, -}... -``` - -***id*** - address of RHS node - -***revocationNonce*** - credential revocation nonce - -***type*** - type of the credential status - -JSON-LD context: https://schema.iden3.io/core/jsonld/iden3proofs.jsonld +_!!! Note that on-chain RHS storage initialization is a bit more complex. Check merkletree-proof library for more details._ -Type implementations: +### RHS Storage Implementation -- Go: [https://github.com/iden3/go-schema-processor](https://github.com/iden3/go-schema-processor) -- JS: [https://github.com/0xPolygonID/js-sdk](https://github.com/0xPolygonID/js-sdk) +Off-chain: [https://github.com/iden3/reverse-hash-service](https://github.com/iden3/reverse-hash-service) -### RHS Implementation +On-chain: [https://github.com/iden3/contracts/blob/master/contracts/identitytreestore/IdentityTreeStore.sol](https://github.com/iden3/contracts/blob/master/contracts/identitytreestore/IdentityTreeStore.sol) -[https://github.com/iden3/reverse-hash-service](https://github.com/iden3/reverse-hash-service) +_!!! Note that IdentityTreeStore implements SMT Proof generation algorithm on-chain as part of getRevocationStatusByIdAndState() method, so you are free to use it as alternative way to generate revocation proofs._ -### Library to interact with RHS +### Library to interact with RHS Storage [https://github.com/iden3/merkletree-proof](https://github.com/iden3/merkletree-proof) @@ -169,4 +217,4 @@ Type implementations: [https://github.com/iden3/go-merkletree-sql](https://github.com/iden3/go-merkletree-sql) -[https://github.com/0xPolygonID/js-sdk](https://github.com/0xPolygonID/js-sdk) \ No newline at end of file +[https://github.com/0xPolygonID/js-sdk](https://github.com/0xPolygonID/js-sdk) diff --git a/mkdocs/mkdocs.yml b/mkdocs/mkdocs.yml index ac5f3b6..372eb3b 100644 --- a/mkdocs/mkdocs.yml +++ b/mkdocs/mkdocs.yml @@ -54,30 +54,30 @@ nav: - Structure: "protocol/claims-structure.md" - AuthBJJCredential: "protocol/bjjkey.md" - Claim Schema : "protocol/claim-schema.md" - - Core Tutorial: + - Core Tutorial: - Introduction: "getting-started/getting-started.md" - Core Components: - Baby Jubjub Keypair: "getting-started/babyjubjub.md" - Sparse Merkle Tree: "getting-started/mt.md" - - Claim: + - Claim: - Generic Claim: "getting-started/claim/generic-claim.md" - Auth Claim: "getting-started/claim/auth-claim.md" # - Claim Schema: "getting-started/claim/claim-schema.md" - - Identity: + - Identity: - Identity Types: "getting-started/identity/identity-types.md" - Identity State: "getting-started/identity/identity-state.md" - Identifier: "getting-started/identity/identifier.md" - Identity Profiles: "getting-started/identity/identity-profile.md" - OnChain Identity: "getting-started/identity/onchain-identity.md" - - Issue Claim: + - Issue Claim: - Overview: "getting-started/issue-claim-overview.md" - via Signature: "getting-started/signature-claim/signature.md" - - via Merkle Tree: + - via Merkle Tree: - Intro to State Transition: "getting-started/state-transition/state-transition.md" - Add Claim to the Claims Tree: "getting-started/state-transition/new-identity-state.md" - Generate Proof for State Transition: "getting-started/state-transition/state-transition-proof.md" - Verify the Proof On-Chain: "getting-started/state-transition/on-chain-state-transition.md" - - Claim Revocation: + - Claim Revocation: - Revoke Claim: "getting-started/claim-revocation.md" - Services and protocols: @@ -87,16 +87,15 @@ nav: - Non-merklized credentials: "protocol/non-merklized.md" - JSON-LD merklization: "w3c/merklization.md" - Reverse hash service: "services/rhs.md" - - On-chain credential status: "protocol/onchain-credential-status.md" - Circuits: - Main Circuits: "protocol/main-circuits.md" - Template Circuits: "protocol/template-circuits.md" - Circom / snarkjs: "circom-snarkjs/index.md" - - Contracts: + - Contracts: - State Contract: "contracts/state.md" - Publications: "publications/publications.md" -# If you declare plugins, you need to declare all of them, +# If you declare plugins, you need to declare all of them, # including search (which would otherwise have been installed by default.) plugins: - search