From e56b57e470827532c6d1079b863cd4a9b37c03a8 Mon Sep 17 00:00:00 2001 From: Ilya Date: Wed, 14 Feb 2024 18:26:46 +0200 Subject: [PATCH] add state info for proofs methods (#211) add state info for proofs methods --- contracts/lib/IdentityBase.sol | 45 +++++++-- contracts/lib/IdentityLib.sol | 92 ++++++++++++++++++- contracts/package-lock.json | 4 +- contracts/package.json | 2 +- .../onchain-identity/onchain-identity.test.ts | 74 +++++++++++---- 5 files changed, 183 insertions(+), 34 deletions(-) diff --git a/contracts/lib/IdentityBase.sol b/contracts/lib/IdentityBase.sol index 78f62e7b..08832ba3 100644 --- a/contracts/lib/IdentityBase.sol +++ b/contracts/lib/IdentityBase.sol @@ -53,6 +53,17 @@ abstract contract IdentityBase is IOnchainCredentialStatusResolver { return identity.getClaimProof(claimIndexHash); } + /** + * @dev Retrieve Claim inclusion or non-inclusion proof for a given claim index with state info. + * @param claimIndexHash - hash of Claim Index + * @return The ClaimsTree inclusion or non-inclusion proof for the claim and state info. + */ + function getClaimProofWithStateInfo( + uint256 claimIndexHash + ) public view virtual returns (SmtLib.Proof memory, IdentityLib.StateInfo memory) { + return identity.getClaimProofWithStateInfo(claimIndexHash); + } + /** * @dev Retrieve Claim inclusion or non-inclusion proof for a given claim index by target root. * @param claimIndexHash - hash of Claim Index @@ -85,6 +96,17 @@ abstract contract IdentityBase is IOnchainCredentialStatusResolver { return identity.getRevocationProof(revocationNonce); } + /** + * @dev Retrieve inclusion or non-inclusion proof for a given revocation nonce with state info. + * @param revocationNonce - revocation nonce + * @return The RevocationsTree inclusion or non-inclusion proof for the revocation nonce and state info. + */ + function getRevocationProofWithStateInfo( + uint64 revocationNonce + ) public view virtual returns (SmtLib.Proof memory, IdentityLib.StateInfo memory) { + return identity.getRevocationProofWithStateInfo(revocationNonce); + } + /** * @dev Retrieve inclusion or non-inclusion proof for a given revocation nonce by target root. * @param revocationNonce - revocation nonce @@ -107,14 +129,23 @@ abstract contract IdentityBase is IOnchainCredentialStatusResolver { } /** - * @dev Retrieve inclusion or non-inclusion proof for a given claimsTreeRoot. - * @param claimsTreeRoot - claims tree root - * @return The RootsTree inclusion or non-inclusion proof for the claim tree root + * @dev Retrieve inclusion or non-inclusion proof for a given rootsTreeRoot. + * @param rootsTreeRoot - roots tree root + * @return The RootsTree inclusion or non-inclusion proof for the roots tree root */ - function getRootProof( - uint256 claimsTreeRoot - ) public view virtual returns (SmtLib.Proof memory) { - return identity.getRootProof(claimsTreeRoot); + function getRootProof(uint256 rootsTreeRoot) public view virtual returns (SmtLib.Proof memory) { + return identity.getRootProof(rootsTreeRoot); + } + + /** + * @dev Retrieve inclusion or non-inclusion proof for a given rootsTreeRoot with state info. + * @param rootsTreeRoot - roots tree root + * @return The RootsTree inclusion or non-inclusion proof for the claim tree root and state info. + */ + function getRootProofWithStateInfo( + uint256 rootsTreeRoot + ) public view virtual returns (SmtLib.Proof memory, IdentityLib.StateInfo memory) { + return identity.getRootProofWithStateInfo(rootsTreeRoot); } /** diff --git a/contracts/lib/IdentityLib.sol b/contracts/lib/IdentityLib.sol index 391796e6..4efda581 100644 --- a/contracts/lib/IdentityLib.sol +++ b/contracts/lib/IdentityLib.sol @@ -57,6 +57,16 @@ library IdentityLib { uint256 rootsRoot; } + /** + * @dev state info + */ + struct StateInfo { + uint256 state; + uint256 claimsRoot; + uint256 revocationsRoot; + uint256 rootsRoot; + } + /** * @dev Initialization of the library state variables * @param _stateContractAddr - address of the State contract @@ -204,6 +214,30 @@ library IdentityLib { ); } + /** + * @dev Retrieve Claim inclusion or non-inclusion proof for a given claim index with state info. + * Note that proof is taken for the latest published claims tree root. + * @param claimIndexHash - hash of Claim Index + * @return The ClaimsTree inclusion or non-inclusion proof for the claim and state info + */ + function getClaimProofWithStateInfo( + Data storage self, + uint256 claimIndexHash + ) external view returns (SmtLib.Proof memory, StateInfo memory) { + return ( + self.trees.claimsTree.getProofByRoot( + claimIndexHash, + self.latestPublishedTreeRoots.claimsRoot + ), + StateInfo({ + state: self.latestPublishedState, + claimsRoot: self.latestPublishedTreeRoots.claimsRoot, + revocationsRoot: self.latestPublishedTreeRoots.revocationsRoot, + rootsRoot: self.latestPublishedTreeRoots.rootsRoot + }) + ); + } + /** * @dev Retrieve Claim inclusion or non-inclusion proof for a given claim index by target root. * @param claimIndexHash - hash of Claim Index @@ -243,6 +277,30 @@ library IdentityLib { ); } + /** + * @dev Retrieve inclusion or non-inclusion proof for a given revocation nonce with state info. + Note that proof is taken for the latest published revocation tree root. + * @param revocationNonce - revocation nonce + * @return The RevocationsTree inclusion or non-inclusion proof for the claim and state info. + */ + function getRevocationProofWithStateInfo( + Data storage self, + uint64 revocationNonce + ) external view returns (SmtLib.Proof memory, StateInfo memory) { + return ( + self.trees.revocationsTree.getProofByRoot( + uint256(revocationNonce), + self.latestPublishedTreeRoots.revocationsRoot + ), + StateInfo({ + state: self.latestPublishedState, + claimsRoot: self.latestPublishedTreeRoots.claimsRoot, + revocationsRoot: self.latestPublishedTreeRoots.revocationsRoot, + rootsRoot: self.latestPublishedTreeRoots.rootsRoot + }) + ); + } + /** * @dev Retrieve inclusion or non-inclusion proof for a given revocation nonce by target root. * @param revocationNonce - revocation nonce @@ -266,22 +324,46 @@ library IdentityLib { } /** - * @dev Retrieve inclusion or non-inclusion proof for a given claimsTreeRoot. + * @dev Retrieve inclusion or non-inclusion proof for a given rootsTreeRoot. Note that proof is taken for the latest published roots tree root. - * @param claimsTreeRoot - claims tree root - * @return The RootsTree inclusion or non-inclusion proof for the claim tree root + * @param rootsTreeRoot - roots tree root + * @return The RootsTree inclusion or non-inclusion proof for the roots tree root */ function getRootProof( Data storage self, - uint256 claimsTreeRoot + uint256 rootsTreeRoot ) external view returns (SmtLib.Proof memory) { return self.trees.rootsTree.getProofByRoot( - claimsTreeRoot, + rootsTreeRoot, self.latestPublishedTreeRoots.rootsRoot ); } + /** + * @dev Retrieve inclusion or non-inclusion proof for a given rootsTreeRoot and state info. + Note that proof is taken for the latest published roots tree root. + * @param rootsTreeRoot - roots tree root + * @return The RootsTree inclusion or non-inclusion proof for the roots tree root and state info. + */ + function getRootProofWithStateInfo( + Data storage self, + uint256 rootsTreeRoot + ) external view returns (SmtLib.Proof memory, StateInfo memory) { + return ( + self.trees.rootsTree.getProofByRoot( + rootsTreeRoot, + self.latestPublishedTreeRoots.rootsRoot + ), + StateInfo({ + state: self.latestPublishedState, + claimsRoot: self.latestPublishedTreeRoots.claimsRoot, + revocationsRoot: self.latestPublishedTreeRoots.revocationsRoot, + rootsRoot: self.latestPublishedTreeRoots.rootsRoot + }) + ); + } + /** * @dev Retrieve inclusion or non-inclusion proof for a given claimsTreeRoot by target root. * @param claimsTreeRoot - claims tree root diff --git a/contracts/package-lock.json b/contracts/package-lock.json index b4c9049e..73668993 100644 --- a/contracts/package-lock.json +++ b/contracts/package-lock.json @@ -1,12 +1,12 @@ { "name": "@iden3/contracts", - "version": "1.4.5", + "version": "1.4.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@iden3/contracts", - "version": "1.4.5", + "version": "1.4.6", "license": "GPL-3.0", "dependencies": { "@openzeppelin/contracts": "^4.7.3", diff --git a/contracts/package.json b/contracts/package.json index eb4a23e8..4477ac31 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,7 +1,7 @@ { "name": "@iden3/contracts", "description": "Smart Contract library for Solidity", - "version": "1.4.5", + "version": "1.4.6", "files": [ "**/*.sol", "/build/contracts/*.json", diff --git a/test/onchain-identity/onchain-identity.test.ts b/test/onchain-identity/onchain-identity.test.ts index 1706363c..2c87137e 100644 --- a/test/onchain-identity/onchain-identity.test.ts +++ b/test/onchain-identity/onchain-identity.test.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import { OnchainIdentityDeployHelper } from "../../helpers/OnchainIdentityDeployHelper"; import { DeployHelper } from "../../helpers/DeployHelper"; -describe("Next tests reproduce identity life cycle", function() { +describe("Next tests reproduce identity life cycle", function () { this.timeout(10000); let identity; @@ -15,12 +15,12 @@ describe("Next tests reproduce identity life cycle", function() { const deployHelper = await OnchainIdentityDeployHelper.initialize(); const stContracts = await stDeployHelper.deployState(); const contracts = await deployHelper.deployIdentity( - stContracts.state, - stContracts.smtLib, - stContracts.poseidon1, - stContracts.poseidon2, - stContracts.poseidon3, - stContracts.poseidon4 + stContracts.state, + stContracts.smtLib, + stContracts.poseidon1, + stContracts.poseidon2, + stContracts.poseidon3, + stContracts.poseidon4 ); identity = contracts.identity; const idType = await stContracts.state.getDefaultIdType(); @@ -30,9 +30,7 @@ describe("Next tests reproduce identity life cycle", function() { describe("create identity", function () { it("deploy state and identity", async function () { - expect(await identity.getIsOldStateGenesis()).to.be.equal( - true - ); + expect(await identity.getIsOldStateGenesis()).to.be.equal(true); }); it("validate identity's id", async function () { @@ -68,13 +66,22 @@ describe("Next tests reproduce identity life cycle", function() { const isOldStateGenesis = await identity.getIsOldStateGenesis(); expect(isOldStateGenesis).to.be.true; }); - it( - "latest identity state should be empty", - async function () { - latestSavedState = await identity.getLatestPublishedState(); - expect(latestSavedState).to.be.equal(0); - } - ); + it("latest identity state should be empty", async function () { + latestSavedState = await identity.getLatestPublishedState(); + expect(latestSavedState).to.be.equal(0); + }); + it("getClaimProofWithStateInfo should return non-existence proof", async function () { + const proof = await identity.getClaimProofWithStateInfo(1); + expect(proof[0].existence).to.be.false; + }); + it("getRevocationProofWithStateInfo should return non-existence proof", async function () { + const proof = await identity.getRevocationProofWithStateInfo(1); + expect(proof[0].existence).to.be.false; + }); + it("getRootProofWithStateInfo should return non-existence proof", async function () { + const proof = await identity.getRootProofWithStateInfo(1); + expect(proof[0].existence).to.be.false; + }); }); describe("add claim", function () { @@ -174,6 +181,20 @@ describe("Next tests reproduce identity life cycle", function() { latestComputedState = await identity.calcIdentityState(); expect(latestComputedState).to.be.equal(latestSavedState); }); + + it("claim proof must exist after publishing and StateInfo should be latest", async function () { + const latestState = await identity.getLatestPublishedState(); + const latestClaimTreeRoot = await identity.getLatestPublishedClaimsRoot(); + const latestRevocationTreeRoot = await identity.getLatestPublishedRevocationsRoot(); + const latestTransitionRootOfRootsTreeRoot = await identity.getLatestPublishedRootsRoot(); + + const claimProof = await identity.getClaimProofWithStateInfo(1); + expect(claimProof[0].existence).to.be.true; + expect(claimProof[1].state).to.be.equal(latestState); + expect(claimProof[1].claimsRoot).to.be.equal(latestClaimTreeRoot); + expect(claimProof[1].revocationsRoot).to.be.equal(latestRevocationTreeRoot); + expect(claimProof[1].rootsRoot).to.be.equal(latestTransitionRootOfRootsTreeRoot); + }); }); describe("revoke state", function () { @@ -195,7 +216,8 @@ describe("Next tests reproduce identity life cycle", function() { }); it("transit of revocation tree shouldn't update root of roots tree", async function () { - const beforeRevocationRootOfRootsTreeRoot = await identity.getLatestPublishedRevocationsRoot(); + const beforeRevocationRootOfRootsTreeRoot = + await identity.getLatestPublishedRevocationsRoot(); expect(beforeRevocationRootOfRootsTreeRoot).to.be.equal(beforeRevocationRevocationTreeRoot); }); @@ -226,6 +248,20 @@ describe("Next tests reproduce identity life cycle", function() { const afterTransitionLatestSavedState = await identity.getLatestPublishedState(); expect(beforeTransitionLatestSavedState).to.be.not.equal(afterTransitionLatestSavedState); }); + + it("revocation proof must exist after publishing and StateInfo should be latest", async function () { + const latestState = await identity.getLatestPublishedState(); + const latestClaimTreeRoot = await identity.getLatestPublishedClaimsRoot(); + const latestRevocationTreeRoot = await identity.getLatestPublishedRevocationsRoot(); + const latestTransitionRootOfRootsTreeRoot = await identity.getLatestPublishedRootsRoot(); + + const revocationProof = await identity.getRevocationProofWithStateInfo(1); + expect(revocationProof[0].existence).to.be.true; + expect(revocationProof[1].state).to.be.equal(latestState); + expect(revocationProof[1].claimsRoot).to.be.equal(latestClaimTreeRoot); + expect(revocationProof[1].revocationsRoot).to.be.equal(latestRevocationTreeRoot); + expect(revocationProof[1].rootsRoot).to.be.equal(latestTransitionRootOfRootsTreeRoot); + }); }); }); @@ -567,7 +603,7 @@ describe("Genesis state doens't have history of states", () => { const latestState = await identity.calcIdentityState(); try { await identity.getRootsByState(latestState); - expect.fail('The transaction should have thrown an error'); + expect.fail("The transaction should have thrown an error"); } catch (err: any) { expect(err.reason).to.be.equal("Roots for this state doesn't exist"); }