From 8536224fd11af87759959a8fb72681961802e552 Mon Sep 17 00:00:00 2001 From: Ilya Date: Mon, 2 Oct 2023 18:18:50 +0300 Subject: [PATCH 1/3] support status param for onchain revocation --- package-lock.json | 30 +- package.json | 2 + src/credentials/index.ts | 1 + src/credentials/status/on-chain-revocation.ts | 105 +++-- .../status/reverse-sparse-merkle-tree.ts | 35 +- src/credentials/status/utils.ts | 19 + src/identity/identity-wallet.ts | 14 +- src/proof/proof-service.ts | 2 +- .../blockchain/onchain-revocation-abi.json | 307 +++++++++----- src/storage/blockchain/onchain-revocation.ts | 25 ++ src/storage/interfaces/index.ts | 1 + src/storage/interfaces/onchain-revocation.ts | 20 + tests/onchain/on-chain-revocation.test.ts | 390 ++++++++++++++++++ .../on-chain-revocation.test.ts | 118 ------ 14 files changed, 790 insertions(+), 279 deletions(-) create mode 100644 src/credentials/status/utils.ts create mode 100644 src/storage/interfaces/onchain-revocation.ts create mode 100644 tests/onchain/on-chain-revocation.test.ts delete mode 100644 tests/revocationStatuses/on-chain-revocation.test.ts diff --git a/package-lock.json b/package-lock.json index 3c6af1b6..8ebd2cf2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "ajv-formats": "^2.1.1", "base58-js": "^1.0.4", "buffer-browserify": "^0.2.5", + "chai-spies": "^1.0.0", "cross-sha256": "^1.2.0", "crypto-browserify": "^3.12.0", "did-jwt": "^6.11.6", @@ -43,6 +44,7 @@ "@microsoft/api-extractor": "^7.9.0", "@types/chai": "^4.3.4", "@types/chai-as-promised": "^7.1.5", + "@types/chai-spies": "^1.0.4", "@types/elliptic": "^6.4.14", "@types/fs-extra": "^11.0.1", "@types/jsonld": "^1.5.9", @@ -1985,6 +1987,15 @@ "@types/chai": "*" } }, + "node_modules/@types/chai-spies": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/chai-spies/-/chai-spies-1.0.4.tgz", + "integrity": "sha512-HCG1EUGpVYmmqIG9rnSIxkng/tOzARG1HmUIV5miCp55ykqxSnVj2vlXaf6nDwaMm7qzkvNe9SHW15ywPKDqTA==", + "dev": true, + "dependencies": { + "@types/chai": "*" + } + }, "node_modules/@types/elliptic": { "version": "6.4.14", "resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.14.tgz", @@ -2734,7 +2745,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, "engines": { "node": "*" } @@ -3085,7 +3095,6 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", - "dev": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", @@ -3111,6 +3120,17 @@ "chai": ">= 2.1.2 < 5" } }, + "node_modules/chai-spies": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/chai-spies/-/chai-spies-1.0.0.tgz", + "integrity": "sha512-elF2ZUczBsFoP07qCfMO/zeggs8pqCf3fZGyK5+2X4AndS8jycZYID91ztD9oQ7d/0tnS963dPkd0frQEThDsg==", + "engines": { + "node": ">= 4.0.0" + }, + "peerDependencies": { + "chai": "*" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -3138,7 +3158,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true, "engines": { "node": "*" } @@ -3458,7 +3477,6 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, "dependencies": { "type-detect": "^4.0.0" }, @@ -5066,7 +5084,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true, "engines": { "node": "*" } @@ -5939,7 +5956,6 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", - "dev": true, "dependencies": { "get-func-name": "^2.0.0" } @@ -6806,7 +6822,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, "engines": { "node": "*" } @@ -8413,7 +8428,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, "engines": { "node": ">=4" } diff --git a/package.json b/package.json index 15f937b0..e51b37cb 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@microsoft/api-extractor": "^7.9.0", "@types/chai": "^4.3.4", "@types/chai-as-promised": "^7.1.5", + "@types/chai-spies": "^1.0.4", "@types/elliptic": "^6.4.14", "@types/fs-extra": "^11.0.1", "@types/jsonld": "^1.5.9", @@ -78,6 +79,7 @@ "ajv-formats": "^2.1.1", "base58-js": "^1.0.4", "buffer-browserify": "^0.2.5", + "chai-spies": "^1.0.0", "cross-sha256": "^1.2.0", "crypto-browserify": "^3.12.0", "did-jwt": "^6.11.6", diff --git a/src/credentials/index.ts b/src/credentials/index.ts index ebb898db..e410500b 100644 --- a/src/credentials/index.ts +++ b/src/credentials/index.ts @@ -3,5 +3,6 @@ export * from './status/reverse-sparse-merkle-tree'; export * from './status/sparse-merkle-tree'; export * from './status/resolver'; export * from './status/agent-revocation'; +export * from './status/utils'; export * from './credential-wallet'; export * from './rhs'; diff --git a/src/credentials/status/on-chain-revocation.ts b/src/credentials/status/on-chain-revocation.ts index 8989fb97..7fae8cf3 100644 --- a/src/credentials/status/on-chain-revocation.ts +++ b/src/credentials/status/on-chain-revocation.ts @@ -3,9 +3,12 @@ import { EthConnectionConfig } from '../../storage/blockchain'; import { CredentialStatusResolver, CredentialStatusResolveOptions } from './resolver'; import { OnChainRevocationStorage } from '../../storage/blockchain/onchain-revocation'; import { DID, Id } from '@iden3/js-iden3-core'; -import { getChainIdByDIDsParts } from '../../storage/blockchain'; -import { utils } from 'ethers'; - +import { VerifiableConstants } from '../../verifiable/constants'; +import { isGenesisState } from './utils'; +import { newHashFromHex } from '@iden3/js-merkletree'; +import { EthStateStorage } from '../../../src/storage/blockchain/state'; +import { getChainId } from '../../storage/blockchain'; +import { IStateStorage, IOnchainRevocationStore } from '../../storage'; /** * OnChainIssuer is a class that allows to interact with the onchain contract * and build the revocation status. @@ -18,8 +21,7 @@ export class OnChainResolver implements CredentialStatusResolver { * * Creates an instance of OnChainIssuer. * @public - * @param {Array} - onchain contract address - * @param {string} - list of EthConnectionConfig + * @param {Array} - list of ethereum network connections */ constructor(private readonly _configs: EthConnectionConfig[]) {} @@ -52,15 +54,48 @@ export class OnChainResolver implements CredentialStatusResolver { credentialStatus: CredentialStatus, issuer: DID ): Promise { - const { contractAddress, chainId, revocationNonce } = + const { contractAddress, chainId, revocationNonce, stateHex } = this.extractCredentialStatusInfo(credentialStatus); if (revocationNonce !== credentialStatus.revocationNonce) { throw new Error('revocationNonce does not match'); } - const networkConfig = this.networkByChainId(chainId); - const onChainCaller = new OnChainRevocationStorage(networkConfig, contractAddress); + + const issuerId = DID.idFromDID(issuer); + let latestIssuerState: bigint; + try { + const ethStorage = this._getStateStorageForIssuer(issuerId); + const latestStateInfo = await ethStorage.getLatestStateById(issuerId.bigInt()); + if (!latestStateInfo.state) { + throw new Error('state contract returned empty state'); + } + latestIssuerState = latestStateInfo.state; + } catch (e) { + const errMsg = (e as { reason: string })?.reason ?? (e as Error).message ?? (e as string); + if (!errMsg.includes(VerifiableConstants.ERRORS.IDENTITY_DOES_NOT_EXIST)) { + throw e; + } + + if (!stateHex) { + throw new Error( + 'latest state not found and state prameter is not present in credentialStatus.id' + ); + } + const stateBigInt = newHashFromHex(stateHex).bigInt(); + if (!isGenesisState(issuer, stateBigInt)) { + throw new Error( + `latest state not found and state prameter ${stateHex} is not genesis state` + ); + } + latestIssuerState = stateBigInt; + } + const id = DID.idFromDID(issuer); - const revocationStatus = await onChainCaller.getRevocationStatus(id.bigInt(), revocationNonce); + const onChainCaller = this._getOnChainRevocationStorageForIssuer(chainId, contractAddress); + const revocationStatus = await onChainCaller.getRevocationStatusByIdAndState( + id.bigInt(), + latestIssuerState, + revocationNonce + ); return revocationStatus; } @@ -74,7 +109,7 @@ export class OnChainResolver implements CredentialStatusResolver { contractAddress: string; chainId: number; revocationNonce: number; - issuer: string; + stateHex: string; } { if (!credentialStatus.id) { throw new Error('credentialStatus id is empty'); @@ -85,29 +120,18 @@ export class OnChainResolver implements CredentialStatusResolver { throw new Error('invalid credentialStatus id'); } - const issuer = idParts[0]; - const issuerDID = DID.parse(issuer); - const idURL = new URL(credentialStatus.id); - - // if contractAddress is not present in id as param, then it should be parsed from DID - let contractAddress = idURL.searchParams.get('contractAddress'); - let chainId: number; - if (!contractAddress) { - const issuerId = DID.idFromDID(issuerDID); - const ethAddr = Id.ethAddressFromId(issuerId); - contractAddress = utils.getAddress(utils.hexDataSlice(ethAddr, 0)); - const blockchain = DID.blockchainFromId(issuerId); - const network = DID.networkIdFromId(issuerId); - chainId = getChainIdByDIDsParts(issuerDID.method, blockchain, network); - } else { - const parts = contractAddress.split(':'); - if (parts.length != 2) { - throw new Error('invalid contract address encoding. should be chainId:contractAddress'); - } - chainId = parseInt(parts[0], 10); - contractAddress = parts[1]; + const stateHex = idURL.searchParams.get('state') || ''; + const contractIdentifier = idURL.searchParams.get('contractAddress'); + if (!contractIdentifier) { + throw new Error('contractAddress not found in credentialStatus.id field'); } + const parts = contractIdentifier.split(':'); + if (parts.length != 2) { + throw new Error('invalid contract address encoding. should be chainId:contractAddress'); + } + const chainId = parseInt(parts[0], 10); + const contractAddress = parts[1]; // if revocationNonce is not present in id as param, then it should be extract from credentialStatus const rv = idURL.searchParams.get('revocationNonce') || credentialStatus.revocationNonce; @@ -116,7 +140,7 @@ export class OnChainResolver implements CredentialStatusResolver { } const revocationNonce = typeof rv === 'number' ? rv : parseInt(rv, 10); - return { contractAddress, chainId, revocationNonce, issuer }; + return { contractAddress, chainId, revocationNonce, stateHex }; } networkByChainId(chainId: number): EthConnectionConfig { @@ -126,4 +150,21 @@ export class OnChainResolver implements CredentialStatusResolver { } return network; } + + // TODO (illia-korotia): is dirty hack for mock in tests. + // need to pass to constructor list of state stores not list of network configs + private _getStateStorageForIssuer(issuerId: Id): IStateStorage { + const issuerChainId = getChainId(DID.blockchainFromId(issuerId), DID.networkIdFromId(issuerId)); + const ethStorage = new EthStateStorage(this.networkByChainId(issuerChainId)); + return ethStorage; + } + + private _getOnChainRevocationStorageForIssuer( + chainId: number, + contractAddress: string + ): IOnchainRevocationStore { + const networkConfig = this.networkByChainId(chainId); + const onChainCaller = new OnChainRevocationStorage(networkConfig, contractAddress); + return onChainCaller; + } } diff --git a/src/credentials/status/reverse-sparse-merkle-tree.ts b/src/credentials/status/reverse-sparse-merkle-tree.ts index a7befe0a..1386d3b6 100644 --- a/src/credentials/status/reverse-sparse-merkle-tree.ts +++ b/src/credentials/status/reverse-sparse-merkle-tree.ts @@ -15,6 +15,7 @@ import { CredentialStatusResolver, CredentialStatusResolveOptions } from './reso import { CredentialStatus, IssuerData, RevocationStatus } from '../../verifiable'; import { strMTHex } from '../../circuits'; import { VerifiableConstants, CredentialStatusType } from '../../verifiable/constants'; +import { isGenesisState } from './utils'; /** * ProofNode is a partial Reverse Hash Service result @@ -154,27 +155,31 @@ export class RHSResolver implements CredentialStatusResolver { issuerDID: DID, issuerData?: IssuerData ): Promise { - const id = DID.idFromDID(issuerDID); + const issuerId = DID.idFromDID(issuerDID); let latestState: bigint; try { - const latestStateInfo = await this._state.getLatestStateById(id.bigInt()); - latestState = latestStateInfo?.state || BigInt(0); + const latestStateInfo = await this._state.getLatestStateById(issuerId.bigInt()); + if (!latestStateInfo.state) { + throw new Error('state contract returned empty state'); + } + latestState = latestStateInfo.state; } catch (e) { const errMsg = (e as { reason: string })?.reason ?? (e as Error).message ?? (e as string); - if (errMsg.includes(VerifiableConstants.ERRORS.IDENTITY_DOES_NOT_EXIST)) { - const currentState = this.extractState(credentialStatus.id); - if (!currentState) { - return this.getRevocationStatusFromIssuerData(issuerDID, issuerData); - } - const currentStateBigInt = newHashFromHex(currentState).bigInt(); - if (!isGenesisStateId(id.bigInt(), currentStateBigInt, id.type())) { - throw new Error(`state ${currentState} is not genesis`); - } - latestState = currentStateBigInt; - } else { + if (!errMsg.includes(VerifiableConstants.ERRORS.IDENTITY_DOES_NOT_EXIST)) { throw e; } + const stateHex = this.extractState(credentialStatus.id); + if (!stateHex) { + return this.getRevocationStatusFromIssuerData(issuerDID, issuerData); + } + const currentStateBigInt = newHashFromHex(stateHex).bigInt(); + if (!isGenesisState(issuerDID, currentStateBigInt)) { + throw new Error( + `latest state not found and state prameter ${stateHex} is not genesis state` + ); + } + latestState = currentStateBigInt; } const rhsHost = credentialStatus.id.split('/node')[0]; @@ -321,6 +326,7 @@ export class RHSResolver implements CredentialStatusResolver { } /** + * @deprecated The method should not be used. Use isGenesisState instead. * Checks if issuer did is created from given state is genesis * * @param {string} issuer - did (string) @@ -338,6 +344,7 @@ export function isIssuerGenesis(issuer: string, state: string): boolean { } /** + * @deprecated The method should not be used. Use isGenesisStateId instead. * Checks if id is created from given state and type is genesis * * @param {bigint} id diff --git a/src/credentials/status/utils.ts b/src/credentials/status/utils.ts new file mode 100644 index 00000000..54a6ea97 --- /dev/null +++ b/src/credentials/status/utils.ts @@ -0,0 +1,19 @@ +import { buildDIDType, DID, Id, BytesHelper } from '@iden3/js-iden3-core'; +/** + * Checks if state is genesis state + * + * @param {string} did - did + * @param {bigint|string} state - hash on bigInt or hex string format + * @returns boolean + */ +export function isGenesisState(did: DID, state: bigint | string): boolean { + if (typeof state === 'string') { + state = BytesHelper.bytesToInt(BytesHelper.hexToBytes(state)); + } + const id = DID.idFromDID(did); + const { method, blockchain, networkId } = DID.decodePartsFromId(id); + const type = buildDIDType(method, blockchain, networkId); + const idFromState = Id.idGenesisFromIdenState(type, state); + + return id.bigInt().toString() === idFromState.bigInt().toString(); +} diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 6ed0a6c8..b03a50ff 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -289,8 +289,7 @@ export interface IIdentityWallet { * @param {TreeState} treeState - contains state to upgrade * @returns `{Promise}` */ - updateIdentityState(issuerDID: DID, published:boolean, treeState?: TreeState): Promise; - + updateIdentityState(issuerDID: DID, published: boolean, treeState?: TreeState): Promise; } /** @@ -317,7 +316,7 @@ export class IdentityWallet implements IIdentityWallet { private readonly _kms: KMS, private readonly _storage: IDataStorage, private readonly _credentialWallet: ICredentialWallet - ) { } + ) {} /** * {@inheritDoc IIdentityWallet.createIdentity} @@ -921,15 +920,18 @@ export class IdentityWallet implements IIdentityWallet { } /** {@inheritDoc IIdentityWallet.updateIdentityState} */ - async updateIdentityState(issuerDID: DID, published:boolean, treeState?: TreeState): Promise { + async updateIdentityState( + issuerDID: DID, + published: boolean, + treeState?: TreeState + ): Promise { const latestTreeState = await this.getDIDTreeModel(issuerDID); await this._storage.identity.saveIdentity({ did: issuerDID.string(), - state: treeState ? treeState.state : latestTreeState.state, + state: treeState ? treeState.state : latestTreeState.state, isStatePublished: published, isStateGenesis: false }); } - } diff --git a/src/proof/proof-service.ts b/src/proof/proof-service.ts index 094f71d6..45492554 100644 --- a/src/proof/proof-service.ts +++ b/src/proof/proof-service.ts @@ -284,7 +284,7 @@ export class ProofService implements IProofService { const txId = await stateStorage.publishState(proof, ethSigner); await this._identityWallet.updateIdentityState(did, true, newTreeState); - + return txId; } diff --git a/src/storage/blockchain/onchain-revocation-abi.json b/src/storage/blockchain/onchain-revocation-abi.json index 5db6be20..f24d221c 100644 --- a/src/storage/blockchain/onchain-revocation-abi.json +++ b/src/storage/blockchain/onchain-revocation-abi.json @@ -1,100 +1,207 @@ -[{ - "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" -}] +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "state", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "name": "getRevocationStatusByIdAndState", + "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" + }, + { + "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" + } +] \ No newline at end of file diff --git a/src/storage/blockchain/onchain-revocation.ts b/src/storage/blockchain/onchain-revocation.ts index 70b310dc..9f59ead6 100644 --- a/src/storage/blockchain/onchain-revocation.ts +++ b/src/storage/blockchain/onchain-revocation.ts @@ -34,6 +34,31 @@ export class OnChainRevocationStorage { this.onchainContract = new ethers.Contract(contractAddress, abi, this.provider); } + /** + * Get revocation status by issuerId, issuerState and nonce from the onchain. + * @public + * @returns Promise + */ + public async getRevocationStatusByIdAndState( + issuerID: bigint, + state: bigint, + nonce: number + ): Promise { + const response = await this.onchainContract.getRevocationStatusByIdAndState( + issuerID, + state, + nonce + ); + + const issuer = OnChainRevocationStorage.convertIssuerInfo(response.issuer); + const mtp = OnChainRevocationStorage.convertSmtProofToProof(response.mtp); + + return { + issuer, + mtp + }; + } + /** * Get revocation status by nonce from the onchain contract. * @public diff --git a/src/storage/interfaces/index.ts b/src/storage/interfaces/index.ts index 1a71af7e..41c0f2f6 100644 --- a/src/storage/interfaces/index.ts +++ b/src/storage/interfaces/index.ts @@ -5,3 +5,4 @@ export * from './state'; export * from './data-storage'; export * from './data-source'; export * from './circuits'; +export * from './onchain-revocation'; diff --git a/src/storage/interfaces/onchain-revocation.ts b/src/storage/interfaces/onchain-revocation.ts new file mode 100644 index 00000000..05b30f00 --- /dev/null +++ b/src/storage/interfaces/onchain-revocation.ts @@ -0,0 +1,20 @@ +import { RevocationStatus } from '../../verifiable'; +/** + * Interface that defines methods for onchain revocation store + * + * @public + * @interface IOnchainRevocationStore + */ +export interface IOnchainRevocationStore { + /** + * gets latest state of identity + * + * @param {bigint} id - id to check + * @returns `Promise` + */ + getRevocationStatusByIdAndState( + issuerID: bigint, + state: bigint, + nonce: number + ): Promise; +} diff --git a/tests/onchain/on-chain-revocation.test.ts b/tests/onchain/on-chain-revocation.test.ts new file mode 100644 index 00000000..447155e8 --- /dev/null +++ b/tests/onchain/on-chain-revocation.test.ts @@ -0,0 +1,390 @@ +import { OnChainResolver } from '../../src/credentials/status/on-chain-revocation'; +import { InMemoryDataSource } from './../../src/storage/memory/data-source'; +import { CredentialStorage } from './../../src/storage/shared/credential-storage'; +import { Identity, IdentityStorage, IdentityWallet, Profile, byteEncoder } from '../../src'; +import { BjjProvider, KMS, KmsKeyType } from '../../src/kms'; +import { InMemoryPrivateKeyStore } from '../../src/kms/store'; +import { IDataStorage } from '../../src/storage/interfaces'; +import { InMemoryMerkleTreeStorage } from '../../src/storage/memory'; +import { CredentialRequest, CredentialWallet } from '../../src/credentials'; +import { defaultEthConnectionConfig, EthStateStorage } from '../../src/storage/blockchain/state'; +import { CredentialStatusType, RevocationStatus, W3CCredential } from '../../src/verifiable'; +import { Proof } from '@iden3/js-merkletree'; +import { Blockchain, DidMethod, NetworkId } from '@iden3/js-iden3-core'; +import chai from 'chai'; +import { CredentialStatusResolverRegistry } from '../../src/credentials'; +import { VerifiableConstants } from '../../src/verifiable'; +import spies from 'chai-spies'; +chai.use(spies); +const expect = chai.expect; + +describe('parse credential status with type Iden3OnchainSparseMerkleTreeProof2023:', () => { + const testCases = [ + { + name: 'extract information from credentialStatus.id', + input: { + id: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6/credentialStatus?revocationNonce=1234&contractAddress=80001:0x2fCE183c7Fbc4EbB5DB3B0F5a63e0e02AE9a85d2', + type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, + revocationNonce: 0 + }, + output: { + contractAddress: '0x2fCE183c7Fbc4EbB5DB3B0F5a63e0e02AE9a85d2', + chainId: 80001, + revocationNonce: 1234, + stateHex: '' + } + }, + { + name: 'revocation nonce is 0 on id', + input: { + id: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6/credentialStatus?revocationNonce=0&contractAddress=80001:0x2fCE183c7Fbc4EbB5DB3B0F5a63e0e02AE9a85d2', + type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, + revocationNonce: undefined + }, + output: { + contractAddress: '0x2fCE183c7Fbc4EbB5DB3B0F5a63e0e02AE9a85d2', + chainId: 80001, + revocationNonce: 0, + stateHex: '' + } + }, + { + name: 'revocation nonce is 0 on credentialStatus', + input: { + id: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6/credentialStatus?contractAddress=80001:0x2fCE183c7Fbc4EbB5DB3B0F5a63e0e02AE9a85d2', + type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, + revocationNonce: 0 + }, + output: { + contractAddress: '0x2fCE183c7Fbc4EbB5DB3B0F5a63e0e02AE9a85d2', + chainId: 80001, + revocationNonce: 0, + stateHex: '' + } + }, + { + name: 'Parse stateHex from credentialStatus.id', + input: { + id: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6/credentialStatus?contractAddress=80001:0x2fCE183c7Fbc4EbB5DB3B0F5a63e0e02AE9a85d2&state=a1abdb9f44c7b649eb4d21b59ef34bd38e054aa3e500987575a14fc92c49f42c', + type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, + revocationNonce: 100 + }, + output: { + contractAddress: '0x2fCE183c7Fbc4EbB5DB3B0F5a63e0e02AE9a85d2', + chainId: 80001, + revocationNonce: 100, + stateHex: 'a1abdb9f44c7b649eb4d21b59ef34bd38e054aa3e500987575a14fc92c49f42c' + } + }, + { + name: 'invalid id format', + input: { + id: 'did:polygonid:eth:2tCntr26bxYnTERr7uTX3mDU182tTTKGmye8T4uwtM', + type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, + revocationNonce: 4321 + }, + error: 'invalid credentialStatus id' + }, + { + name: 'invalid contract address format', + input: { + id: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6/credentialStatus?revocationNonce=1234&contractAddress=0x2fCE183c7Fbc4EbB5DB3B0F5a63e0e02AE9a85d2', + type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, + revocationNonce: 4321 + }, + error: 'invalid contract address encoding. should be chainId:contractAddress' + }, + { + name: 'revocationNonce is empty', + input: { + id: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6/credentialStatus?contractAddress=80001:0x2fCE183c7Fbc4EbB5DB3B0F5a63e0e02AE9a85d2', + type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, + revocationNonce: undefined + }, + error: 'revocationNonce not found in credentialStatus id field' + }, + { + name: 'contractAddress is required parameter', + input: { + id: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6/credentialStatus', + type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, + revocationNonce: 1234 + }, + error: 'contractAddress not found in credentialStatus.id field' + } + ]; + + for (const testCase of testCases) { + it(testCase.name, () => { + const status = new OnChainResolver([]); + + if (testCase.error) { + expect(() => status.extractCredentialStatusInfo(testCase.input)).to.throw(testCase.error); + return; + } + const expectedOutput = status.extractCredentialStatusInfo(testCase.input); + expect(expectedOutput).to.deep.equal(testCase.output); + }); + } +}); + +describe('onchain', () => { + let idWallet: IdentityWallet; + let credWallet: CredentialWallet; + let dataStorage: IDataStorage; + const infuraUrl = process.env.RPC_URL as string; + + beforeEach(async () => { + const memoryKeyStore = new InMemoryPrivateKeyStore(); + const bjjProvider = new BjjProvider(KmsKeyType.BabyJubJub, memoryKeyStore); + const kms = new KMS(); + kms.registerKeyProvider(KmsKeyType.BabyJubJub, bjjProvider); + + defaultEthConnectionConfig.url = infuraUrl; + defaultEthConnectionConfig.chainId = 80001; + const conf = defaultEthConnectionConfig; + conf.contractAddress = '0xf6781AD281d9892Df285cf86dF4F6eBec2042d71'; + const ethStorage = new EthStateStorage(conf); + + dataStorage = { + credential: new CredentialStorage(new InMemoryDataSource()), + identity: new IdentityStorage( + new InMemoryDataSource(), + new InMemoryDataSource() + ), + mt: new InMemoryMerkleTreeStorage(40), + states: ethStorage + }; + + const resolvers = new CredentialStatusResolverRegistry(); + resolvers.register( + CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, + new OnChainResolver([defaultEthConnectionConfig]) + ); + credWallet = new CredentialWallet(dataStorage, resolvers); + credWallet.getRevocationStatusFromCredential = async () => { + const r: RevocationStatus = { + mtp: { + existence: false, + nodeAux: undefined, + siblings: [] + } as unknown as Proof, + issuer: {} + }; + return r; + }; + idWallet = new IdentityWallet(kms, dataStorage, credWallet); + }); + + it('issuer has genesis state', async () => { + const seedPhrase: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseeduser'); + + const seedPhraseIssuer: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseedseed'); + + const { did: issuerDID, credential: issuerAuthCredential } = await idWallet.createIdentity({ + method: DidMethod.Iden3, + blockchain: Blockchain.Polygon, + networkId: NetworkId.Mumbai, + seed: seedPhraseIssuer, + revocationOpts: { + type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, + id: 'did:iden3:polygon:mumbai:wzokvZ6kMoocKJuSbftdZxTD6qvayGpJb3m4FVXth/credentialStatus?contractAddress=80001:0x068F826Da7e5119891a792817C5bE8bB9816b9D3', + nonce: 0 + } + }); + + await credWallet.save(issuerAuthCredential); + + const { did: userDID } = await idWallet.createIdentity({ + method: DidMethod.Iden3, + blockchain: Blockchain.Polygon, + networkId: NetworkId.Mumbai, + seed: seedPhrase, + revocationOpts: { + type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, + id: 'did:iden3:polygon:mumbai:wuw5tydZ7AAd3efwEqPprnqjiNHR24jqruSPKmV1V/credentialStatus?contractAddress=80001:0x068F826Da7e5119891a792817C5bE8bB9816b9D3', + nonce: 0 + } + }); + + const claimReq: CredentialRequest = { + credentialSchema: + 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/kyc-nonmerklized.json', + type: 'KYCAgeCredential', + credentialSubject: { + id: userDID.string(), + birthday: 19960424, + documentType: 99 + }, + expiration: 2793526400, + revocationOpts: { + nonce: 1000, + type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + id: 'did:iden3:polygon:mumbai:wzokvZ6kMoocKJuSbftdZxTD6qvayGpJb3m4FVXth/credentialStatus?contractAddress=80001:0x068F826Da7e5119891a792817C5bE8bB9816b9D3&state=ed17a07e8b78ab979507829fa4d37e663ca5906714d506dec8a174d949c5eb09' + } + }; + + const issuerCred = await idWallet.issueCredential(issuerDID, claimReq); + + await credWallet.save(issuerCred); + + const cs = { + id: claimReq.revocationOpts.id, + type: claimReq.revocationOpts.type, + revocationNonce: claimReq.revocationOpts.nonce + }; + const onchainResolver = new OnChainResolver([defaultEthConnectionConfig]); + + // state contract returns identity state not found error + // sc returns non inclusion proof for issuer genesis state + chai.spy.on(onchainResolver, '_getStateStorageForIssuer', () => { + return { + getLatestStateById: () => { + console.log('mocked getLatestStateById genesis case'); + throw new Error(VerifiableConstants.ERRORS.IDENTITY_DOES_NOT_EXIST); + } + }; + }); + chai.spy.on(onchainResolver, '_getOnChainRevocationStorageForIssuer', () => { + return { + getRevocationStatusByIdAndState: () => { + console.log('mocked getRevocationStatusByIdAndState genesis case'); + return { + mtp: { + existence: false + }, + issuer: { + state: 'ed17a07e8b78ab979507829fa4d37e663ca5906714d506dec8a174d949c5eb09', + claimsTreeRoot: '6091193ec58a6c020183c2d889a92c32410f31812595f228d67a2bf37e04a729', + revocationTreeRoot: + '0000000000000000000000000000000000000000000000000000000000000000', + rootOfRoots: '0000000000000000000000000000000000000000000000000000000000000000' + } + }; + } + }; + }); + + const onchainStatus = await onchainResolver.resolve(cs, { + issuerDID: issuerDID + }); + + const latestTree = await idWallet.getDIDTreeModel(issuerDID); + expect(onchainStatus.issuer.state).to.equal(latestTree.state.hex()); + expect(onchainStatus.issuer.claimsTreeRoot).to.equal( + (await latestTree.claimsTree.root()).hex() + ); + expect(onchainStatus.issuer.revocationTreeRoot).to.equal( + (await latestTree.revocationTree.root()).hex() + ); + expect(onchainStatus.issuer.rootOfRoots).to.equal((await latestTree.rootsTree.root()).hex()); + expect(onchainStatus.mtp.existence).to.equal(false); + }); + + it('state contract returns latest state', async () => { + const seedPhrase: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseeduser'); + + const seedPhraseIssuer: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseedseed'); + + const { did: issuerDID, credential: issuerAuthCredential } = await idWallet.createIdentity({ + method: DidMethod.Iden3, + blockchain: Blockchain.Polygon, + networkId: NetworkId.Mumbai, + seed: seedPhraseIssuer, + revocationOpts: { + type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, + id: 'did:iden3:polygon:mumbai:wzokvZ6kMoocKJuSbftdZxTD6qvayGpJb3m4FVXth/credentialStatus?contractAddress=80001:0x068F826Da7e5119891a792817C5bE8bB9816b9D3', + nonce: 0 + } + }); + + await credWallet.save(issuerAuthCredential); + + const { did: userDID } = await idWallet.createIdentity({ + method: DidMethod.Iden3, + blockchain: Blockchain.Polygon, + networkId: NetworkId.Mumbai, + seed: seedPhrase, + revocationOpts: { + type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, + id: 'did:iden3:polygon:mumbai:wuw5tydZ7AAd3efwEqPprnqjiNHR24jqruSPKmV1V/credentialStatus?contractAddress=80001:0x068F826Da7e5119891a792817C5bE8bB9816b9D3', + nonce: 0 + } + }); + + const claimReq: CredentialRequest = { + credentialSchema: + 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/kyc-nonmerklized.json', + type: 'KYCAgeCredential', + credentialSubject: { + id: userDID.string(), + birthday: 19960424, + documentType: 99 + }, + expiration: 2793526400, + revocationOpts: { + nonce: 1000, + type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + id: 'did:iden3:polygon:mumbai:wzokvZ6kMoocKJuSbftdZxTD6qvayGpJb3m4FVXth/credentialStatus?contractAddress=80001:0x068F826Da7e5119891a792817C5bE8bB9816b9D3' + } + }; + + const issuerCred = await idWallet.issueCredential(issuerDID, claimReq); + await credWallet.save(issuerCred); + await idWallet.addCredentialsToMerkleTree([issuerCred], issuerDID); + + const cs = { + id: claimReq.revocationOpts.id, + type: claimReq.revocationOpts.type, + revocationNonce: claimReq.revocationOpts.nonce + }; + const onchainResolver = new OnChainResolver([defaultEthConnectionConfig]); + + // state contract returns latest state + // sc generates inclusion proof for issuer latest state + chai.spy.on(onchainResolver, '_getStateStorageForIssuer', () => { + return { + getLatestStateById: () => { + console.log('mocked getLatestStateById latest state case'); + return { + state: BigInt('0xf600ba49073ff1c396ed674263f04cb246647039d55d43a49ce310a857fa8923') + }; + } + }; + }); + chai.spy.on(onchainResolver, '_getOnChainRevocationStorageForIssuer', () => { + return { + getRevocationStatusByIdAndState: () => { + console.log('mocked getRevocationStatusByIdAndState latest state case'); + return { + mtp: { + existence: false + }, + issuer: { + state: 'f600ba49073ff1c396ed674263f04cb246647039d55d43a49ce310a857fa8923', + claimsTreeRoot: '9a50b13ef0d27bc5ec194717fc01e3574b8d1b61e94d78641c00960039d35805', + revocationTreeRoot: + '0000000000000000000000000000000000000000000000000000000000000000', + rootOfRoots: '9b8fb367151795494ce222daebd769f6f4eb2f4d9c8de6e5bb2548b76022c812' + } + }; + } + }; + }); + + const onchainStatus = await onchainResolver.resolve(cs, { + issuerDID: issuerDID + }); + + const latestTree = await idWallet.getDIDTreeModel(issuerDID); + expect(onchainStatus.issuer.state).to.equal(latestTree.state.hex()); + expect(onchainStatus.issuer.claimsTreeRoot).to.equal( + (await latestTree.claimsTree.root()).hex() + ); + expect(onchainStatus.issuer.revocationTreeRoot).to.equal( + (await latestTree.revocationTree.root()).hex() + ); + expect(onchainStatus.issuer.rootOfRoots).to.equal((await latestTree.rootsTree.root()).hex()); + expect(onchainStatus.mtp.existence).to.equal(false); + }); +}); diff --git a/tests/revocationStatuses/on-chain-revocation.test.ts b/tests/revocationStatuses/on-chain-revocation.test.ts deleted file mode 100644 index 1b158ae6..00000000 --- a/tests/revocationStatuses/on-chain-revocation.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { OnChainResolver } from '../../src/credentials/status/on-chain-revocation'; -import { CredentialStatusType } from '../../src'; -import { expect } from 'chai'; - -describe('on chain revocation:', () => { - const testCases = [ - { - name: 'extract information from credentialStatus.id', - input: { - id: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6/credentialStatus?revocationNonce=1234&contractAddress=80001:0x2fCE183c7Fbc4EbB5DB3B0F5a63e0e02AE9a85d2', - type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, - revocationNonce: 0 - }, - output: { - contractAddress: '0x2fCE183c7Fbc4EbB5DB3B0F5a63e0e02AE9a85d2', - chainId: 80001, - revocationNonce: 1234, - issuer: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6' - } - }, - { - name: 'extract information from DID', - input: { - id: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6/credentialStatus', - type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, - revocationNonce: 1234 - }, - output: { - contractAddress: '0x2fCE183c7Fbc4EbB5DB3B0F5a63e0e02AE9a85d2', - chainId: 80001, - revocationNonce: 1234, - issuer: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6' - } - }, - { - name: 'revocation nonce is 0 on id', - input: { - id: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6/credentialStatus?revocationNonce=0', - type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, - revocationNonce: undefined - }, - output: { - contractAddress: '0x2fCE183c7Fbc4EbB5DB3B0F5a63e0e02AE9a85d2', - chainId: 80001, - revocationNonce: 0, - issuer: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6' - } - }, - { - name: 'revocation nonce is 0 on credentialStatus', - input: { - id: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6/credentialStatus', - type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, - revocationNonce: 0 - }, - output: { - contractAddress: '0x2fCE183c7Fbc4EbB5DB3B0F5a63e0e02AE9a85d2', - chainId: 80001, - revocationNonce: 0, - issuer: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6' - } - }, - { - name: 'extract information from eth main net DID', - input: { - id: 'did:polygonid:eth:2tCntr26bxYnTERr7uTX3mDU182tTTKGmye8T4uwtM/credentialStatus', - type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, - revocationNonce: 4321 - }, - output: { - contractAddress: '0xC26880A0AF2EA0c7E8130e6EC47Af756465452E8', - chainId: 1, - revocationNonce: 4321, - issuer: 'did:polygonid:eth:2tCntr26bxYnTERr7uTX3mDU182tTTKGmye8T4uwtM' - } - }, - { - name: 'invalid id format', - input: { - id: 'did:polygonid:eth:2tCntr26bxYnTERr7uTX3mDU182tTTKGmye8T4uwtM', - type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, - revocationNonce: 4321 - }, - error: 'invalid credentialStatus id' - }, - { - name: 'invalid contract address format', - input: { - id: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6/credentialStatus?revocationNonce=1234&contractAddress=0x2fCE183c7Fbc4EbB5DB3B0F5a63e0e02AE9a85d2', - type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, - revocationNonce: 4321 - }, - error: 'invalid contract address encoding. should be chainId:contractAddress' - }, - { - name: 'revocationNonce is empty', - input: { - id: 'did:polygonid:polygon:mumbai:2qCU58EJgrELbXjWbWGC9kPPnczQdp93nUR6LC45F6/credentialStatus', - type: CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023, - revocationNonce: undefined - }, - error: 'revocationNonce not found in credentialStatus id field' - } - ]; - - for (const testCase of testCases) { - it(testCase.name, () => { - const status = new OnChainResolver([]); - - if (testCase.error) { - expect(() => status.extractCredentialStatusInfo(testCase.input)).to.throw(testCase.error); - return; - } - const expectedOutput = status.extractCredentialStatusInfo(testCase.input); - expect(expectedOutput).to.deep.equal(testCase.output); - }); - } -}); From 08afebc3850eee0e34c88bf222e573eaaf9d824d Mon Sep 17 00:00:00 2001 From: Ilya Date: Mon, 2 Oct 2023 18:25:00 +0300 Subject: [PATCH 2/3] fix comment --- src/credentials/status/reverse-sparse-merkle-tree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/credentials/status/reverse-sparse-merkle-tree.ts b/src/credentials/status/reverse-sparse-merkle-tree.ts index 1386d3b6..2a7f044c 100644 --- a/src/credentials/status/reverse-sparse-merkle-tree.ts +++ b/src/credentials/status/reverse-sparse-merkle-tree.ts @@ -344,7 +344,7 @@ export function isIssuerGenesis(issuer: string, state: string): boolean { } /** - * @deprecated The method should not be used. Use isGenesisStateId instead. + * @deprecated The method should not be used. Use isGenesisState instead. * Checks if id is created from given state and type is genesis * * @param {bigint} id From 150936dae41d709a58fcf73064fac0cd40c056f3 Mon Sep 17 00:00:00 2001 From: Ilya Date: Tue, 3 Oct 2023 14:02:29 +0300 Subject: [PATCH 3/3] fix comments --- package-lock.json | 11 ++++++++++- package.json | 2 +- src/credentials/status/on-chain-revocation.ts | 6 +++--- src/credentials/status/reverse-sparse-merkle-tree.ts | 2 +- src/identity/identity-wallet.ts | 2 +- src/storage/interfaces/onchain-revocation.ts | 4 +++- 6 files changed, 19 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ebd2cf2..c7944bfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,6 @@ "ajv-formats": "^2.1.1", "base58-js": "^1.0.4", "buffer-browserify": "^0.2.5", - "chai-spies": "^1.0.0", "cross-sha256": "^1.2.0", "crypto-browserify": "^3.12.0", "did-jwt": "^6.11.6", @@ -54,6 +53,7 @@ "@typescript-eslint/eslint-plugin": "^5.41.0", "chai": "^4.3.7", "chai-as-promised": "^7.1.1", + "chai-spies": "^1.0.0", "chokidar": "^3.5.3", "esbuild": "^0.15.15", "eslint-config-prettier": "^8.8.0", @@ -2745,6 +2745,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, "engines": { "node": "*" } @@ -3095,6 +3096,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", @@ -3124,6 +3126,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/chai-spies/-/chai-spies-1.0.0.tgz", "integrity": "sha512-elF2ZUczBsFoP07qCfMO/zeggs8pqCf3fZGyK5+2X4AndS8jycZYID91ztD9oQ7d/0tnS963dPkd0frQEThDsg==", + "dev": true, "engines": { "node": ">= 4.0.0" }, @@ -3158,6 +3161,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, "engines": { "node": "*" } @@ -3477,6 +3481,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, "dependencies": { "type-detect": "^4.0.0" }, @@ -5084,6 +5089,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true, "engines": { "node": "*" } @@ -5956,6 +5962,7 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, "dependencies": { "get-func-name": "^2.0.0" } @@ -6822,6 +6829,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, "engines": { "node": "*" } @@ -8428,6 +8436,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, "engines": { "node": ">=4" } diff --git a/package.json b/package.json index e51b37cb..365b06fe 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@typescript-eslint/eslint-plugin": "^5.41.0", "chai": "^4.3.7", "chai-as-promised": "^7.1.1", + "chai-spies": "^1.0.0", "chokidar": "^3.5.3", "esbuild": "^0.15.15", "eslint-config-prettier": "^8.8.0", @@ -79,7 +80,6 @@ "ajv-formats": "^2.1.1", "base58-js": "^1.0.4", "buffer-browserify": "^0.2.5", - "chai-spies": "^1.0.0", "cross-sha256": "^1.2.0", "crypto-browserify": "^3.12.0", "did-jwt": "^6.11.6", diff --git a/src/credentials/status/on-chain-revocation.ts b/src/credentials/status/on-chain-revocation.ts index 7fae8cf3..c259a3cc 100644 --- a/src/credentials/status/on-chain-revocation.ts +++ b/src/credentials/status/on-chain-revocation.ts @@ -21,7 +21,7 @@ export class OnChainResolver implements CredentialStatusResolver { * * Creates an instance of OnChainIssuer. * @public - * @param {Array} - list of ethereum network connections + * @param {Array} _configs - list of ethereum network connections */ constructor(private readonly _configs: EthConnectionConfig[]) {} @@ -77,13 +77,13 @@ export class OnChainResolver implements CredentialStatusResolver { if (!stateHex) { throw new Error( - 'latest state not found and state prameter is not present in credentialStatus.id' + 'latest state not found and state parameter is not present in credentialStatus.id' ); } const stateBigInt = newHashFromHex(stateHex).bigInt(); if (!isGenesisState(issuer, stateBigInt)) { throw new Error( - `latest state not found and state prameter ${stateHex} is not genesis state` + `latest state not found and state parameter ${stateHex} is not genesis state` ); } latestIssuerState = stateBigInt; diff --git a/src/credentials/status/reverse-sparse-merkle-tree.ts b/src/credentials/status/reverse-sparse-merkle-tree.ts index 2a7f044c..3bdb50ff 100644 --- a/src/credentials/status/reverse-sparse-merkle-tree.ts +++ b/src/credentials/status/reverse-sparse-merkle-tree.ts @@ -176,7 +176,7 @@ export class RHSResolver implements CredentialStatusResolver { const currentStateBigInt = newHashFromHex(stateHex).bigInt(); if (!isGenesisState(issuerDID, currentStateBigInt)) { throw new Error( - `latest state not found and state prameter ${stateHex} is not genesis state` + `latest state not found and state parameter ${stateHex} is not genesis state` ); } latestState = currentStateBigInt; diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index b03a50ff..a49119d1 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -929,7 +929,7 @@ export class IdentityWallet implements IIdentityWallet { await this._storage.identity.saveIdentity({ did: issuerDID.string(), - state: treeState ? treeState.state : latestTreeState.state, + state: treeState?.state ?? latestTreeState.state, isStatePublished: published, isStateGenesis: false }); diff --git a/src/storage/interfaces/onchain-revocation.ts b/src/storage/interfaces/onchain-revocation.ts index 05b30f00..4a2c69b4 100644 --- a/src/storage/interfaces/onchain-revocation.ts +++ b/src/storage/interfaces/onchain-revocation.ts @@ -9,7 +9,9 @@ export interface IOnchainRevocationStore { /** * gets latest state of identity * - * @param {bigint} id - id to check + * @param {bigint} issuerID - issuer id + * @param {bigint} state - issuer state + * @param {number} nonce - revocation nonce * @returns `Promise` */ getRevocationStatusByIdAndState(