From cbfbf0d1b102638ebb9e4738337f6577e40f4fe3 Mon Sep 17 00:00:00 2001 From: Oleg Nosov Date: Fri, 13 Dec 2024 23:08:21 +0400 Subject: [PATCH] `cheqd` Accumulator (#493) * WIP * WIP: tests * Implement `Accumulator` module for `cheqd` * Tweaks * Fix docs * Lints * Tweak * Tweaks * Renamings * Fix import * Remove outdated test * Test corrections --- examples/CHANGELOG.md | 9 + examples/package.json | 8 +- packages/cheqd-blockchain-api/CHANGELOG.md | 7 + packages/cheqd-blockchain-api/package.json | 4 +- .../cheqd-blockchain-modules/CHANGELOG.md | 11 + .../cheqd-blockchain-modules/package.json | 6 +- .../src/accumulator/internal.js | 224 ++++++ .../src/accumulator/module.js | 247 +++++++ .../src/attest/internal.js | 21 +- .../src/blob/internal.js | 20 +- .../src/blob/module.js | 14 +- .../common/create-internal-cheqd-module.js | 16 +- .../src/common/index.js | 4 + .../src/common/inject-params.js | 70 +- .../src/common/inject-public-keys.js | 112 +++ .../src/common/resource.js | 154 ++++ .../src/common/with-public-keys.js | 68 ++ .../cheqd-blockchain-modules/src/index.js | 6 +- .../src/offchain-signatures/internal.js | 6 +- .../src/status-list-credential/internal.js | 53 +- .../src/status-list-credential/module.js | 26 +- .../tests/accumulator-module.test.js | 62 ++ packages/credential-sdk/CHANGELOG.md | 6 + packages/credential-sdk/package.json | 2 +- .../modules/abstract/accumulator/module.js | 104 ++- .../src/modules/multi-api/accumulator.js | 165 ++++- .../src/modules/multi-api/index.js | 17 +- .../src/modules/tests/accumulator-module.js | 657 ++++++++++++++++++ .../src/types/accumulator/accumulator-id.js | 2 +- .../src/types/accumulator/accumulator.js | 59 +- .../src/types/accumulator/keys.js | 6 +- .../src/types/accumulator/public-key.js | 8 +- .../src/types/accumulator/variants.js | 6 +- .../types/did/onchain/typed-did/cheqd-did.js | 34 +- .../credential-sdk/src/types/generic/index.js | 1 + .../src/types/generic/typed-array.js | 4 +- .../src/types/generic/typed-bytes.js | 5 +- .../src/types/generic/typed-map.js | 9 +- .../src/types/generic/typed-set.js | 11 +- .../credential-sdk/src/types/generic/utils.js | 8 + .../src/types/generic/with-base.js | 19 + packages/credential-sdk/src/utils/bytes.js | 6 +- .../credential-sdk/src/utils/interfaces.js | 20 +- packages/dock-blockchain-api/CHANGELOG.md | 7 + packages/dock-blockchain-api/package.json | 4 +- packages/dock-blockchain-modules/CHANGELOG.md | 11 + packages/dock-blockchain-modules/package.json | 8 +- .../src/accumulator/actions.js | 13 +- .../src/accumulator/module.js | 92 ++- .../integration/anoncreds/accumulator.test.js | 84 ++- .../tests/integration/anoncreds/demo.test.js | 20 +- .../prefilled-positive-accumulator.test.js | 586 ---------------- .../integration/did/service-endpoint.test.js | 1 - .../modules/accumulator-module.test.js | 65 ++ .../status-list-credential.test.js | 6 +- 55 files changed, 2372 insertions(+), 822 deletions(-) create mode 100644 packages/cheqd-blockchain-modules/src/accumulator/internal.js create mode 100644 packages/cheqd-blockchain-modules/src/accumulator/module.js create mode 100644 packages/cheqd-blockchain-modules/src/common/inject-public-keys.js create mode 100644 packages/cheqd-blockchain-modules/src/common/resource.js create mode 100644 packages/cheqd-blockchain-modules/src/common/with-public-keys.js create mode 100644 packages/cheqd-blockchain-modules/tests/accumulator-module.test.js create mode 100644 packages/credential-sdk/src/modules/tests/accumulator-module.js create mode 100644 packages/credential-sdk/src/types/generic/utils.js delete mode 100644 packages/dock-blockchain-modules/tests/integration/anoncreds/prefilled-positive-accumulator.test.js create mode 100644 packages/dock-blockchain-modules/tests/integration/modules/accumulator-module.test.js diff --git a/examples/CHANGELOG.md b/examples/CHANGELOG.md index ace7ba762..daa1dd339 100644 --- a/examples/CHANGELOG.md +++ b/examples/CHANGELOG.md @@ -1,5 +1,14 @@ # @docknetwork/sdk-examples +## 0.6.4 + +### Patch Changes + +- Updated dependencies + - @docknetwork/dock-blockchain-modules@0.10.0 + - @docknetwork/credential-sdk@0.18.0 + - @docknetwork/dock-blockchain-api@0.8.4 + ## 0.6.3 ### Patch Changes diff --git a/examples/package.json b/examples/package.json index a62762663..b3671d8b7 100644 --- a/examples/package.json +++ b/examples/package.json @@ -2,7 +2,7 @@ "name": "@docknetwork/sdk-examples", "private": true, "type": "module", - "version": "0.6.3", + "version": "0.6.4", "scripts": { "bbs-dock-example": "babel-node ./bbs-dock.js", "claim-deduction-example": "babel-node ./claim-deduction.js", @@ -19,9 +19,9 @@ "lint": "eslint \"*.js\"" }, "dependencies": { - "@docknetwork/credential-sdk": "0.17.0", - "@docknetwork/dock-blockchain-api": "0.8.3", - "@docknetwork/dock-blockchain-modules": "0.9.3" + "@docknetwork/credential-sdk": "0.18.0", + "@docknetwork/dock-blockchain-api": "0.8.4", + "@docknetwork/dock-blockchain-modules": "0.10.0" }, "devDependencies": { "babel-eslint": "^10.1.0", diff --git a/packages/cheqd-blockchain-api/CHANGELOG.md b/packages/cheqd-blockchain-api/CHANGELOG.md index 675a8e487..cc392fee0 100644 --- a/packages/cheqd-blockchain-api/CHANGELOG.md +++ b/packages/cheqd-blockchain-api/CHANGELOG.md @@ -1,5 +1,12 @@ # @docknetwork/cheqd-blockchain-api +## 0.14.3 + +### Patch Changes + +- Updated dependencies + - @docknetwork/credential-sdk@0.18.0 + ## 0.14.2 ### Patch Changes diff --git a/packages/cheqd-blockchain-api/package.json b/packages/cheqd-blockchain-api/package.json index 1dfd485a8..205dad99c 100644 --- a/packages/cheqd-blockchain-api/package.json +++ b/packages/cheqd-blockchain-api/package.json @@ -1,6 +1,6 @@ { "name": "@docknetwork/cheqd-blockchain-api", - "version": "0.14.2", + "version": "0.14.3", "license": "MIT", "main": "./dist/esm/index.js", "type": "module", @@ -34,7 +34,7 @@ }, "dependencies": { "@cheqd/sdk": "cjs", - "@docknetwork/credential-sdk": "0.17.0" + "@docknetwork/credential-sdk": "0.18.0" }, "devDependencies": { "@babel/cli": "^7.24.1", diff --git a/packages/cheqd-blockchain-modules/CHANGELOG.md b/packages/cheqd-blockchain-modules/CHANGELOG.md index 4c7b329f6..5b1acb986 100644 --- a/packages/cheqd-blockchain-modules/CHANGELOG.md +++ b/packages/cheqd-blockchain-modules/CHANGELOG.md @@ -1,5 +1,16 @@ # @docknetwork/cheqd-blockchain-modules +## 0.13.0 + +### Minor Changes + +- `Accumulator` module for `cheqd` + +### Patch Changes + +- Updated dependencies + - @docknetwork/credential-sdk@0.18.0 + ## 0.12.0 ### Minor Changes diff --git a/packages/cheqd-blockchain-modules/package.json b/packages/cheqd-blockchain-modules/package.json index 2b3bd012f..af1510e6d 100644 --- a/packages/cheqd-blockchain-modules/package.json +++ b/packages/cheqd-blockchain-modules/package.json @@ -1,6 +1,6 @@ { "name": "@docknetwork/cheqd-blockchain-modules", - "version": "0.12.0", + "version": "0.13.0", "type": "module", "license": "MIT", "main": "./dist/esm/index.js", @@ -33,7 +33,7 @@ "node": ">=18.0.0" }, "dependencies": { - "@docknetwork/credential-sdk": "0.17.0" + "@docknetwork/credential-sdk": "0.18.0" }, "devDependencies": { "@babel/cli": "^7.24.1", @@ -42,7 +42,7 @@ "@babel/plugin-syntax-import-attributes": "^7.25.6", "@babel/plugin-transform-modules-commonjs": "^7.24.1", "@babel/preset-env": "^7.24.3", - "@docknetwork/cheqd-blockchain-api": "0.14.2", + "@docknetwork/cheqd-blockchain-api": "0.14.3", "@rollup/plugin-alias": "^4.0.2", "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-commonjs": "^24.0.0", diff --git a/packages/cheqd-blockchain-modules/src/accumulator/internal.js b/packages/cheqd-blockchain-modules/src/accumulator/internal.js new file mode 100644 index 000000000..0c81bb5ff --- /dev/null +++ b/packages/cheqd-blockchain-modules/src/accumulator/internal.js @@ -0,0 +1,224 @@ +import { + CheqdStoredAccumulator, + AccumulatorParams, + CheqdAccumulatorId, + CheqdAccumulatorParamsRef, + CheqdAccumulatorWithUpdateInfo, + CheqdCreateResource, + CheqdAccumulatorPublicKey, +} from '@docknetwork/credential-sdk/types'; +import { TypedUUID, option } from '@docknetwork/credential-sdk/types/generic'; +import { stringToU8a, u8aToString } from '@docknetwork/credential-sdk/utils'; +import { VBWitnessUpdateInfo } from '@docknetwork/credential-sdk/crypto'; +import { + injectParams, + injectPublicKeys, + createInternalCheqdModule, + SortedResourceVersions, + validateResource, +} from '../common'; + +const Type = 'accumulator'; + +const methods = { + addAccumulator: (accumulatorId, accumulator) => { + const [did, id] = CheqdAccumulatorId.from(accumulatorId).value; + const storedAcc = new CheqdStoredAccumulator( + accumulator, + ).toJSONStringBytes(); + + return new CheqdCreateResource( + did.value.value, + TypedUUID.random(), + '1.0', + [], + String(id), + Type, + storedAcc, + ); + }, + updateAccumulator: ( + accumulatorId, + accumulator, + { additions, removals, witnessUpdateInfo }, + ) => { + const [did, id] = CheqdAccumulatorId.from(accumulatorId).value; + const storedAcc = new CheqdStoredAccumulator( + accumulator, + additions, + removals, + witnessUpdateInfo, + ).toJSONStringBytes(); + + return new CheqdCreateResource( + did.value.value, + TypedUUID.random(), + '1.0', + [], + String(id), + Type, + storedAcc, + ); + }, + removeAccumulator: (accumulatorId) => { + const [did, id] = CheqdAccumulatorId.from(accumulatorId).value; + + return new CheqdCreateResource( + did.value.value, + TypedUUID.random(), + '1.0', + [], + String(id), + Type, + stringToU8a('null'), + ); + }, +}; + +export default class CheqdInternalAccumulatorModule extends injectParams( + injectPublicKeys(createInternalCheqdModule(methods)), +) { + static get MsgNames() { + return { + ...super.MsgNames, + addAccumulator: 'MsgCreateResource', + updateAccumulator: 'MsgCreateResource', + removeAccumulator: 'MsgCreateResource', + }; + } + + static PublicKey = CheqdAccumulatorPublicKey; + + static PublicKeyName = 'AccumulatorPublicKey'; + + static PublicKeyType = 'accumulator-public-key'; + + static Params = AccumulatorParams; + + static ParamsName = 'AccumulatorParams'; + + static ParamsType = 'accumulator-params'; + + static ParamsRef = CheqdAccumulatorParamsRef; + + createAccumulatorMetadataFilter(name) { + const strName = String(name); + + return (meta) => meta.resourceType === 'accumulator' && meta.name === strName; + } + + async accumulator(accumulatorId) { + const [did, name] = CheqdAccumulatorId.from(accumulatorId).value; + const ids = new SortedResourceVersions( + await this.resourcesMetadataBy( + did, + this.createAccumulatorMetadataFilter(name), + ), + ).ids(); + + if (!ids.length) { + return null; + } + + const acc = option(CheqdStoredAccumulator).from( + JSON.parse( + u8aToString( + validateResource( + await this.resource(did, ids[ids.length - 1]), + String(name), + Type, + ), + ), + ), + ); + + if (acc == null) { + return null; + } + + return new CheqdAccumulatorWithUpdateInfo( + ids[0], + ids[ids.length - 1], + acc.accumulator, + ); + } + + async lastParamsId(did) { + const res = await this.latestResourceMetadataBy( + did, + this.filterParamsMetadata, + ); + + return res?.id; + } + + async lastPublicKeyId(did) { + const res = await this.latestResourceMetadataBy( + did, + this.filterPublicKeyMetadata, + ); + + return res?.id; + } + + /** + * Update given witness by downloading necessary blocks and applying the updates if found. Both start and end are inclusive + * @param accumulatorId + * @param member + * @param witness - this will be updated to the latest witness + * @param start - accumulator version id to start from + * @param end - accumulator version id to end at + * @returns {Promise} + */ + // eslint-disable-next-line sonarjs/cognitive-complexity + async updateVbAccumulatorWitnessFromUpdatesInBlocks( + accumulatorId, + member, + witness, + start, + end, + ) { + const [did, name] = CheqdAccumulatorId.from(accumulatorId).value; + const startUUID = String(TypedUUID.from(start)); + const endUUID = String(TypedUUID.from(end)); + + const sortedIDs = new SortedResourceVersions( + await this.resourcesMetadataBy( + did, + this.createAccumulatorMetadataFilter(name), + ), + ).ids(); + + const startIdx = sortedIDs.findIndex((id) => id === startUUID); + let endIdx; + if (end != null) { + endIdx = sortedIDs.findIndex((id) => id === endUUID); + + if (endIdx === -1) { + throw new Error( + `Accumulator \`${accumulatorId}\` with version \`${end}\` doesn't exist`, + ); + } + } else { + endIdx = sortedIDs.length - 1; + } + + const accumulators = await this.resources( + did, + sortedIDs.slice(startIdx, endIdx + 1), + ); + + for (const accumulator of new SortedResourceVersions(accumulators)) { + const { additions, removals, witnessUpdateInfo } = CheqdStoredAccumulator.from( + validateResource(accumulator, String(name), Type), + ); + + witness.updateUsingPublicInfoPostBatchUpdate( + member, + additions ? [...additions].map((addition) => addition.bytes) : [], + removals ? [...removals].map((removal) => removal.bytes) : [], + new VBWitnessUpdateInfo(witnessUpdateInfo?.bytes || new Uint8Array()), + ); + } + } +} diff --git a/packages/cheqd-blockchain-modules/src/accumulator/module.js b/packages/cheqd-blockchain-modules/src/accumulator/module.js new file mode 100644 index 000000000..b7fe4f9dd --- /dev/null +++ b/packages/cheqd-blockchain-modules/src/accumulator/module.js @@ -0,0 +1,247 @@ +/* eslint-disable camelcase */ + +import { + CheqdAccumulatorWithUpdateInfo, + CheqdAccumulatorCommon, + CheqdKBUniversalAccumulator, + CheqdUniversalAccumulator, + CheqdPositiveAccumulator, + AccumulatorParams, + CheqdAccumulatorPublicKey, +} from '@docknetwork/credential-sdk/types'; +import { option, withProp } from '@docknetwork/credential-sdk/types/generic'; +import { AbstractAccumulatorModule } from '@docknetwork/credential-sdk/modules/abstract'; +import CheqdInternalAccumulatorModule from './internal'; +import { injectCheqd, withParams, withPublicKeys } from '../common'; + +export const AccumulatorType = { + VBPos: 0, + VBUni: 1, + KBUni: 2, +}; + +/** Class to manage accumulators on chain */ +export default class CheqdAccumulatorModule extends withParams( + withPublicKeys(injectCheqd(AbstractAccumulatorModule)), +) { + static CheqdOnly = CheqdInternalAccumulatorModule; + + /** + * Add a positive (add-only) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. + * @param signerDid - Signer of the transaction payload + * @param signingKeyRef - Signer's keypair reference + * @returns {Promise<*>} + */ + async addPositiveAccumulatorTx(id, accumulated, publicKeyRef, didKeypair) { + return await this.cheqdOnly.tx.addAccumulator( + id, + new CheqdPositiveAccumulator( + new CheqdAccumulatorCommon(accumulated, publicKeyRef), + ), + didKeypair, + ); + } + + /** + * Add universal (supports add/remove) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. + * @param maxSize - Maximum size of the accumulator + * @param signerDid - Signer of the transaction payload + * @param signingKeyRef - Signer's keypair reference + * @returns {Promise<*>} + */ + async addUniversalAccumulatorTx( + id, + accumulated, + publicKeyRef, + maxSize, + didKeypair, + ) { + return await this.cheqdOnly.tx.addAccumulator( + id, + new CheqdUniversalAccumulator( + new CheqdUniversalAccumulator.Class( + new CheqdAccumulatorCommon(accumulated, publicKeyRef), + maxSize, + ), + ), + didKeypair, + ); + } + + /** + * Add KB universal (supports add/remove) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. + * @param signerDid - Signer of the transaction payload + * @param signingKeyRef - Signer's keypair reference + * @returns {Promise<*>} + */ + async addKBUniversalAccumulatorTx(id, accumulated, publicKeyRef, didKeypair) { + return await this.cheqdOnly.tx.addAccumulator( + id, + new CheqdKBUniversalAccumulator( + new CheqdAccumulatorCommon(accumulated, publicKeyRef), + ), + didKeypair, + ); + } + + /** + * Update a positive (add-only) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. + * @param signerDid - Signer of the transaction payload + * @param signingKeyRef - Signer's keypair reference + * @returns {Promise<*>} + */ + async updatePositiveAccumulatorTx( + id, + accumulated, + { additions, removals, witnessUpdateInfo }, + publicKeyRef, + didKeypair, + ) { + return await this.cheqdOnly.tx.updateAccumulator( + id, + new CheqdPositiveAccumulator( + new CheqdAccumulatorCommon(accumulated, publicKeyRef), + ), + { additions, removals, witnessUpdateInfo }, + didKeypair, + ); + } + + /** + * Update universal (supports add/remove) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. + * @param maxSize - Maximum size of the accumulator + * @param signerDid - Signer of the transaction payload + * @param signingKeyRef - Signer's keypair reference + * @returns {Promise<*>} + */ + async updateUniversalAccumulatorTx( + id, + accumulated, + { additions, removals, witnessUpdateInfo }, + publicKeyRef, + maxSize, + didKeypair, + ) { + return await this.cheqdOnly.tx.updateAccumulator( + id, + new CheqdUniversalAccumulator( + new CheqdUniversalAccumulator.Class( + new CheqdAccumulatorCommon(accumulated, publicKeyRef), + maxSize, + ), + ), + { additions, removals, witnessUpdateInfo }, + didKeypair, + ); + } + + /** + * Update KB universal (supports add/remove) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. + * @param signerDid - Signer of the transaction payload + * @param signingKeyRef - Signer's keypair reference + * @returns {Promise<*>} + */ + async updateKBUniversalAccumulatorTx( + id, + accumulated, + { additions, removals, witnessUpdateInfo }, + publicKeyRef, + didKeypair, + ) { + return await this.cheqdOnly.tx.updateAccumulator( + id, + new CheqdKBUniversalAccumulator( + new CheqdAccumulatorCommon(accumulated, publicKeyRef), + ), + { additions, removals, witnessUpdateInfo }, + didKeypair, + ); + } + + /** + * Remove the accumulator from chain. This frees up the id for reuse. + * @param id - id to remove + * @param signerDid - Signer of the transaction payload + * @param signingKeyRef - Signer's keypair reference + * @returns {Promise<*>} + */ + async removeAccumulatorTx(id, didKeypair) { + return await this.cheqdOnly.tx.removeAccumulator(id, didKeypair); + } + + /** + * Get the accumulator as an object. The field `type` in object specifies whether it is "positive" or "universal". + * Fields `created` and `lastModified` are block nos where the accumulator was created and last updated respectively. + * Field `nonce` is the last accepted nonce by the chain, the next write to the accumulator should increment the nonce by 1. + * Field `accumulated` contains the current accumulated value. + * @param id + * @param includePublicKey - Fetch public key + * @param includeParams - Fetch params for the publicKey + * @returns {Promise<{created: *, lastModified: *}|null>} + */ + async getAccumulator(id, includePublicKey = false, includeParams = false) { + const PublicKey = includeParams + ? withProp(CheqdAccumulatorPublicKey, 'params', option(AccumulatorParams)) + : CheqdAccumulatorPublicKey; + const Accumulator = includePublicKey + ? withProp(CheqdAccumulatorWithUpdateInfo, 'publicKey', option(PublicKey)) + : CheqdAccumulatorWithUpdateInfo; + + const acc = option(Accumulator).from(await this.cheqdOnly.accumulator(id)); + if (acc == null) { + return null; + } else if (includePublicKey) { + acc.publicKey = await this.getPublicKey( + ...acc.accumulator.keyRef, + includeParams, + ); + } + + return acc; + } + + /** + * Update given witness by downloading necessary accumulators (blocks) and applying the updates if found. + * **Both start and end are inclusive.** + * + * @param accumulatorId + * @param member + * @param witness - this will be updated to the latest witness + * @param start - identifier to start from (collection item id) + * @param end - identifier to end in (collection item id) + * @returns {Promise} + */ + async updateWitness(accumulatorId, member, witness, start, end) { + return await this.cheqdOnly.updateVbAccumulatorWitnessFromUpdatesInBlocks( + accumulatorId, + member, + witness, + start, + end, + ); + } +} diff --git a/packages/cheqd-blockchain-modules/src/attest/internal.js b/packages/cheqd-blockchain-modules/src/attest/internal.js index f0c8a30c1..3e7cdc622 100644 --- a/packages/cheqd-blockchain-modules/src/attest/internal.js +++ b/packages/cheqd-blockchain-modules/src/attest/internal.js @@ -1,6 +1,13 @@ -import { CheqdDid, Iri, CheqdCreateResource } from '@docknetwork/credential-sdk/types'; -import { TypedUUID, option } from '@docknetwork/credential-sdk/types/generic'; -import { createInternalCheqdModule } from '../common'; +import { + CheqdDid, + Iri, + CheqdCreateResource, +} from '@docknetwork/credential-sdk/types'; +import { TypedUUID } from '@docknetwork/credential-sdk/types/generic'; +import { createInternalCheqdModule, validateResource } from '../common'; + +const Name = 'Attestation'; +const Type = 'attest'; const methods = { setClaim: (iri, targetDid) => new CheqdCreateResource( @@ -8,8 +15,8 @@ const methods = { TypedUUID.random(), '1.0', [], - 'Attestation', - 'attest', + Name, + Type, Iri.from(iri), ), }; @@ -22,8 +29,8 @@ export default class CheqdInternalAttestModule extends createInternalCheqdModule }; async attest(did, attestId) { - return option(Iri).from( - (await this.resource(did, attestId))?.resource?.data, + return Iri.from( + validateResource(await this.resource(did, attestId), Name, Type), ); } diff --git a/packages/cheqd-blockchain-modules/src/blob/internal.js b/packages/cheqd-blockchain-modules/src/blob/internal.js index 15e133bcd..90b47815f 100644 --- a/packages/cheqd-blockchain-modules/src/blob/internal.js +++ b/packages/cheqd-blockchain-modules/src/blob/internal.js @@ -4,8 +4,10 @@ import { CheqdBlobWithId, CheqdCreateResource, } from '@docknetwork/credential-sdk/types'; -import { option } from '@docknetwork/credential-sdk/types/generic'; -import { createInternalCheqdModule } from '../common'; +import { createInternalCheqdModule, validateResource } from '../common'; + +const Name = 'Blob'; +const Type = 'blob'; const methods = { new: (blobWithId) => { @@ -19,8 +21,8 @@ const methods = { uuid, '1.0', [], - 'Blob', - 'blob', + Name, + Type, blob, ); }, @@ -29,15 +31,17 @@ const methods = { export default class CheqdInternalBlobModule extends createInternalCheqdModule( methods, ) { - static Prop = 'resource'; - static MsgNames = { new: 'MsgCreateResource', }; async blob(blobId) { - return option(Blob).from( - (await this.resource(...CheqdBlobId.from(blobId).value))?.resource?.data, + return Blob.from( + validateResource( + await this.resource(...CheqdBlobId.from(blobId).value), + Name, + Type, + ), ); } } diff --git a/packages/cheqd-blockchain-modules/src/blob/module.js b/packages/cheqd-blockchain-modules/src/blob/module.js index 6d5b7d27b..012881025 100644 --- a/packages/cheqd-blockchain-modules/src/blob/module.js +++ b/packages/cheqd-blockchain-modules/src/blob/module.js @@ -1,7 +1,7 @@ import { AbstractBlobModule } from '@docknetwork/credential-sdk/modules'; import { NoBlobError } from '@docknetwork/credential-sdk/modules/abstract/blob'; import { CheqdBlobId } from '@docknetwork/credential-sdk/types'; -import { injectCheqd } from '../common'; +import { NoResourceError, injectCheqd } from '../common'; import CheqdInternalBlobModule from './internal'; import { OwnerWithBlob } from './types'; @@ -19,18 +19,18 @@ export default class CheqdBlobModule extends injectCheqd(AbstractBlobModule) { } /** - * + * Retrieves blob with owner from chain. + * Throws an error in case if blob with supplied identifier doesn't exist. * @param {*} blobId * @returns {OwnerWithBlob} */ async get(blobId) { const id = CheqdBlobId.from(blobId); - const blob = await this.cheqdOnly.blob(id); - if (blob == null) { - throw new NoBlobError(id); + try { + return new OwnerWithBlob(id.value[0], await this.cheqdOnly.blob(id)); + } catch (err) { + throw err instanceof NoResourceError ? new NoBlobError(id) : err; } - - return new OwnerWithBlob(id.value[0], blob); } } diff --git a/packages/cheqd-blockchain-modules/src/common/create-internal-cheqd-module.js b/packages/cheqd-blockchain-modules/src/common/create-internal-cheqd-module.js index c12c2ef0a..54b16b2af 100644 --- a/packages/cheqd-blockchain-modules/src/common/create-internal-cheqd-module.js +++ b/packages/cheqd-blockchain-modules/src/common/create-internal-cheqd-module.js @@ -104,18 +104,24 @@ export default function createInternalCheqdModule( this.apiProvider = apiProvider; } - async resourcesBy(did, cond) { + async resources(did, ids) { const strDid = CheqdDid.from(did).toEncodedString(); - const metas = await this.resourcesMetadataBy(did, cond); - const queries = metas.map(async (meta) => [ - meta.id, - await this.apiProvider.sdk.querier.resource.resource(strDid, meta.id), + const queries = [...ids].map(async (id) => [ + id, + await this.apiProvider.sdk.querier.resource.resource(strDid, id), ]); return new Map(await filterNoResourceError(Promise.all(queries))); } + async resourcesBy(did, cond) { + return await this.resources( + did, + (await this.resourcesMetadataBy(did, cond)).map((meta) => meta.id), + ); + } + async resource(did, id) { const strDid = CheqdDid.from(did).toEncodedString(); const strID = String(TypedUUID.from(id)); diff --git a/packages/cheqd-blockchain-modules/src/common/index.js b/packages/cheqd-blockchain-modules/src/common/index.js index 19c627100..d4ad175a2 100644 --- a/packages/cheqd-blockchain-modules/src/common/index.js +++ b/packages/cheqd-blockchain-modules/src/common/index.js @@ -1,4 +1,8 @@ export { default as createInternalCheqdModule } from './create-internal-cheqd-module'; export { default as injectCheqd } from './inject-cheqd'; export { default as CheqdApiProvider } from './cheqd-api-provider'; +export { default as injectParams } from './inject-params'; +export { default as injectPublicKeys } from './inject-public-keys'; export { default as withParams } from './with-params'; +export { default as withPublicKeys } from './with-public-keys'; +export * from './resource'; diff --git a/packages/cheqd-blockchain-modules/src/common/inject-params.js b/packages/cheqd-blockchain-modules/src/common/inject-params.js index da840065a..7bb8c6ae6 100644 --- a/packages/cheqd-blockchain-modules/src/common/inject-params.js +++ b/packages/cheqd-blockchain-modules/src/common/inject-params.js @@ -1,24 +1,27 @@ -import { TypedMap, option } from '@docknetwork/credential-sdk/types/generic'; +import { TypedMap } from '@docknetwork/credential-sdk/types/generic'; import { - stringToU8a, - maybeToJSONString, - u8aToString, withExtendedStaticProperties, - withExtendedPrototypeProperties, + u8aToString, } from '@docknetwork/credential-sdk/utils'; -import { CheqdParamsId, CheqdCreateResource } from '@docknetwork/credential-sdk/types'; +import { + CheqdParamsId, + CheqdCreateResource, +} from '@docknetwork/credential-sdk/types'; import createInternalCheqdModule from './create-internal-cheqd-module'; +import { validateResource } from './resource'; const methods = { - addParams: (id, params, did) => new CheqdCreateResource( - did.value.value, - id, - '1.0', - [], - 'OffchainParams', - 'offchain-signature-params', - stringToU8a(maybeToJSONString(params)), - ), + addParams(id, params, did) { + return new CheqdCreateResource( + did.value.value, + id, + '1.0', + [], + this.constructor.ParamsName, + this.constructor.ParamsType, + this.constructor.Params.from(params).toJSONStringBytes(), + ); + }, }; export default function injectParams(klass) { @@ -26,6 +29,12 @@ export default function injectParams(klass) { const obj = { [name]: class extends createInternalCheqdModule(methods, klass) { + constructor(...args) { + super(...args); + + this.filterParamsMetadata = this.filterParamsMetadata.bind(this); + } + static get MsgNames() { const names = super.MsgNames ?? {}; @@ -52,10 +61,16 @@ export default function injectParams(klass) { * @returns {Promise} */ async getParams(did, id) { + const { ParamsName, ParamsType } = this.constructor; const item = await this.resource(did, id); + if (item == null) { + return null; + } - return option(this.constructor.Params).from( - item && JSON.parse(u8aToString(item.resource.data)), + return this.constructor.Params.from( + JSON.parse( + u8aToString(validateResource(item, ParamsName, ParamsType)), + ), ); } @@ -65,17 +80,30 @@ export default function injectParams(klass) { * @returns {Promise>} */ async getAllParamsByDid(did) { - const resources = await this.resourcesBy(did, this.filterParamsMetadata); + const { ParamsMap, ParamsName, ParamsType } = this.constructor; + const resources = await this.resourcesBy( + did, + this.filterParamsMetadata, + ); - return new this.constructor.ParamsMap( + return new ParamsMap( [...resources].map(([key, item]) => [ key, - JSON.parse(u8aToString(item.resource.data)), + JSON.parse( + u8aToString(validateResource(item, ParamsName, ParamsType)), + ), ]), ); } + + filterParamsMetadata(meta) { + return meta.resourceType === this.constructor.ParamsType; + } }, }; - return withExtendedStaticProperties(['Params'], withExtendedPrototypeProperties(['filterParamsMetadata'], obj[name])); + return withExtendedStaticProperties( + ['Params', 'ParamsName', 'ParamsType'], + obj[name], + ); } diff --git a/packages/cheqd-blockchain-modules/src/common/inject-public-keys.js b/packages/cheqd-blockchain-modules/src/common/inject-public-keys.js new file mode 100644 index 000000000..c9cdb887c --- /dev/null +++ b/packages/cheqd-blockchain-modules/src/common/inject-public-keys.js @@ -0,0 +1,112 @@ +import { TypedMap } from '@docknetwork/credential-sdk/types/generic'; +import { + u8aToString, + withExtendedStaticProperties, +} from '@docknetwork/credential-sdk/utils'; +import { + CheqdPublicKeyId, + CheqdCreateResource, +} from '@docknetwork/credential-sdk/types'; +import createInternalCheqdModule from './create-internal-cheqd-module'; +import { validateResource } from './resource'; + +const methods = { + addPublicKey(id, publicKey, did) { + return new CheqdCreateResource( + did.value.value, + id, + '1.0', + [], + this.constructor.PublicKeyName, + this.constructor.PublicKeyType, + this.constructor.PublicKey.from(publicKey).toJSONStringBytes(), + ); + }, +}; + +export default function injectPublicKeys(klass) { + const name = `withInternalPublicKeys(${klass.name})`; + + const obj = { + [name]: class extends createInternalCheqdModule(methods, klass) { + constructor(...args) { + super(...args); + + this.filterPublicKeyMetadata = this.filterPublicKeyMetadata.bind(this); + } + + static get MsgNames() { + const names = super.MsgNames ?? {}; + + return { + ...names, + addPublicKey: 'MsgCreateResource', + }; + } + + static get PublicKeyMap() { + const { PublicKey } = this; + + return class PublicKeyMap extends TypedMap { + static KeyClass = CheqdPublicKeyId; + + static ValueClass = PublicKey; + }; + } + + /** + * Retrieves params by DID and counter. + * @param {*} did + * @param {*} counter + * @returns {Promise} + */ + async getPublicKey(did, id, includeParams = false) { + const { PublicKey, PublicKeyType, PublicKeyName } = this.constructor; + const item = await this.resource(did, id); + if (item == null) { + return null; + } + + const publicKey = PublicKey.from( + JSON.parse( + u8aToString(validateResource(item, PublicKeyName, PublicKeyType)), + ), + ); + if (includeParams) { + return await publicKey.withParams(this); + } else { + return publicKey; + } + } + + /** + * Retrieves all params by a DID. + * @param {*} did + * @returns {Promise>} + */ + async getAllPublicKeysByDid(did, includeParams = false) { + const { PublicKeyMap } = this.constructor; + + const metas = await this.resourcesMetadataBy( + did, + this.filterPublicKeyMetadata, + ); + + return new PublicKeyMap( + await Promise.all( + metas.map(({ id }) => this.getPublicKey(did, id, includeParams)), + ), + ); + } + + filterPublicKeyMetadata(meta) { + return meta.resourceType === this.constructor.PublicKeyType; + } + }, + }; + + return withExtendedStaticProperties( + ['PublicKey', 'PublicKeyName', 'PublicKeyType'], + obj[name], + ); +} diff --git a/packages/cheqd-blockchain-modules/src/common/resource.js b/packages/cheqd-blockchain-modules/src/common/resource.js new file mode 100644 index 000000000..faf7fb0ec --- /dev/null +++ b/packages/cheqd-blockchain-modules/src/common/resource.js @@ -0,0 +1,154 @@ +import { maybeToJSONString } from '@docknetwork/credential-sdk/utils'; + +/** + * Stores sorted versions of a single resource. + * In case there's a version with no `previousVersionId`, it will be used as the initial. + * Otherwise, the version whose `previousVersionId` points to the non-existing node, will be used. + */ +export class SortedResourceVersions { + constructor(items) { + const { resourceId, resourceNextVersionId } = this.constructor; + + let map; + if (items instanceof Map) { + map = items; + } else { + map = new Map([...items].map((item) => [resourceId(item), item])); + } + if (!map.size) { + this.items = []; + + return; + } + + // Find starting point + let currentItem = this.constructor.findStartingPoint(map); + if (!currentItem) { + throw new Error( + `No starting point found for ${maybeToJSONString([ + ...items, + ])} (missing item without \`previousVersionId\`)`, + ); + } + + // Validate items and create sorted sequence + const sortedItems = []; + const visited = new Set(); + + while (currentItem) { + if (visited.has(resourceId(currentItem))) { + throw new Error('Cycle detected in the version sequence'); + } + + visited.add(resourceId(currentItem)); + sortedItems.push(currentItem); + + currentItem = map.get(resourceNextVersionId(currentItem)) ?? null; + } + + if (visited.size !== map.size) { + throw new Error('Disconnected sequence detected'); + } + + this.items = sortedItems; + } + + static findStartingPoint(map) { + const { resourcePreviousVersionId, resourceId } = this; + + let firstItem = null; + for (const item of map.values()) { + // Find the starting point + if ( + !resourcePreviousVersionId(item) + || !map.has(resourcePreviousVersionId(item)) + ) { + if (firstItem == null) { + firstItem = item; + } else if ( + !resourcePreviousVersionId(firstItem) + && !resourcePreviousVersionId(item) + ) { + throw new Error( + `Two items with nullish \`previousVersionId\` found: ${resourceId( + firstItem, + )} and ${resourceId(item)}`, + ); + } else if ( + resourcePreviousVersionId(firstItem) + && resourcePreviousVersionId(item) + ) { + throw new Error( + `Missing previous items for both: ${resourceId( + firstItem, + )} and ${resourceId(item)}`, + ); + } else { + throw new Error( + `Can't have both element with no \`previousVersionId\` and no previous version for the element with \`previousVersionId\`: ${resourceId( + firstItem, + )} and ${resourceId(item)}`, + ); + } + } + } + + return firstItem; + } + + ids() { + return this.items.map(this.constructor.resourceId); + } + + toMap() { + return new Map( + this.items.map((item) => [this.constructor.resourceId(item), item]), + ); + } + + [Symbol.iterator]() { + return this.items[Symbol.iterator](); + } + + static resourceId(resource) { + return resource.metadata?.id ?? resource.id; + } + + static resourcePreviousVersionId(resource) { + return resource.metadata?.previousVersionId ?? resource.previousVersionId; + } + + static resourceNextVersionId(resource) { + return resource.metadata?.nextVersionId ?? resource.nextVersionId; + } +} + +export class NoResourceError extends Error { + constructor() { + super('Resource not found'); + } +} + +/** + * Validates provided resource against required name and type. + * Returns `resource.resource.data` on successful validation, throws an error otherwise. + * + * @param {*} resource + * @param {string} name + * @param {string} type + */ +export const validateResource = (resource, name, type) => { + if (resource == null) { + throw new NoResourceError(); + } else if (resource.metadata.resourceType !== type) { + throw new Error( + `Invalid resource type for resource \`${resource.metadata.id}\`: \`${resource.metadata.resourceType}\`, expected \`${type}\``, + ); + } else if (resource.metadata.name !== name) { + throw new Error( + `Invalid name for resource \`${resource.id}\`: \`${resource.metadata.name}\`, expected \`${name}\``, + ); + } + + return resource.resource.data; +}; diff --git a/packages/cheqd-blockchain-modules/src/common/with-public-keys.js b/packages/cheqd-blockchain-modules/src/common/with-public-keys.js new file mode 100644 index 000000000..99b813549 --- /dev/null +++ b/packages/cheqd-blockchain-modules/src/common/with-public-keys.js @@ -0,0 +1,68 @@ +import { TypedUUID, option } from '@docknetwork/credential-sdk/types/generic'; + +/** + * Wraps supplied class into a class with logic for public keys and corresponding setup parameters. + */ +/* eslint-disable sonarjs/cognitive-complexity */ +export default function withPublicKeys(klass) { + const name = `withPublicKeys(${klass.name})`; + + const obj = { + [name]: class extends klass { + /** + * Add new signature public key. + * @param id - Unique identifier of the public key. + * @param param - The signature public key to add. + * @param targetDid - Target DID to attach key to. + * @param didKeypair - The signer DID's keypair. + * @returns {Promise<*>} + */ + async addPublicKeyTx(id, param, targetDid, didKeypair) { + return await this.cheqdOnly.tx.addPublicKey( + id, + param, + targetDid, + didKeypair, + ); + } + + /** + * Retrieves public key by DID and identifier. + * @param {*} did + * @param {*} id + * @param {boolean} [includeParams=false] + * @returns {Promise} + */ + async getPublicKey(did, id, includeParams = false) { + return await this.cheqdOnly.getPublicKey(did, id, includeParams); + } + + /** + * Retrieves all public keys by a DID. + * @param {*} did + * @param {boolean} [includeParams=false] + * @returns {Promise>} + */ + async getAllPublicKeysByDid(did, includeParams) { + return await this.cheqdOnly.getAllPublicKeysByDid(did, includeParams); + } + + async lastPublicKeyId(targetDid) { + return option(TypedUUID).from( + ( + await this.cheqdOnly.latestResourceMetadataBy( + targetDid, + this.cheqdOnly.filterPublicKeyMetadata, + ) + )?.id, + ); + } + + async nextPublicKeyId(_) { + return TypedUUID.random(); + } + }, + }; + + return obj[name]; +} diff --git a/packages/cheqd-blockchain-modules/src/index.js b/packages/cheqd-blockchain-modules/src/index.js index 2d9e5d62d..81e0ad2b9 100644 --- a/packages/cheqd-blockchain-modules/src/index.js +++ b/packages/cheqd-blockchain-modules/src/index.js @@ -1,4 +1,5 @@ import { AbstractCoreModules } from '@docknetwork/credential-sdk/modules'; +import CheqdAccumulatorModule from './accumulator/module'; import CheqdAttestModule from './attest/module'; import CheqdBlobModule from './blob/module'; import CheqdDIDModule from './did/module'; @@ -12,11 +13,12 @@ export class CheqdCoreModules extends AbstractCoreModules { static get ModuleMap() { return { ...super.ModuleMap, - AccumulatorModule: { key: 'accumulator', optional: true }, TrustRegistryModule: { key: 'trustRegistry', optional: true }, }; } + static AccumulatorModule = CheqdAccumulatorModule; + static AttestModule = CheqdAttestModule; static BlobModule = CheqdBlobModule; @@ -37,11 +39,11 @@ export class CheqdCoreModules extends AbstractCoreModules { } export { + CheqdAccumulatorModule, CheqdAttestModule, CheqdDIDModule, CheqdBlobModule, CheqdOffchainSignaturesModule, - // CheqdAccumulatorModule, CheqdBBSModule, CheqdBBSPlusModule, CheqdPSModule, diff --git a/packages/cheqd-blockchain-modules/src/offchain-signatures/internal.js b/packages/cheqd-blockchain-modules/src/offchain-signatures/internal.js index c26e4b6b0..c5bfe433b 100644 --- a/packages/cheqd-blockchain-modules/src/offchain-signatures/internal.js +++ b/packages/cheqd-blockchain-modules/src/offchain-signatures/internal.js @@ -3,7 +3,7 @@ import injectParams from '../common/inject-params'; export default class CheqdOffchainSignaturesInternalModule extends injectParams( class {}, ) { - filterParamsMetadata(meta) { - return meta.resourceType === 'offchain-signature-params'; - } + static ParamsName = 'OffchainParams'; + + static ParamsType = 'offchain-signature-params'; } diff --git a/packages/cheqd-blockchain-modules/src/status-list-credential/internal.js b/packages/cheqd-blockchain-modules/src/status-list-credential/internal.js index fa075cd51..494247de1 100644 --- a/packages/cheqd-blockchain-modules/src/status-list-credential/internal.js +++ b/packages/cheqd-blockchain-modules/src/status-list-credential/internal.js @@ -3,15 +3,21 @@ import { CheqdCreateResource, } from '@docknetwork/credential-sdk/types'; import { StatusList2021Credential } from '@docknetwork/credential-sdk/vc'; -import { option, TypedUUID } from '@docknetwork/credential-sdk/types/generic'; -import { stringToU8a, maybeToJSONString, u8aToString } from '@docknetwork/credential-sdk/utils'; -import { createInternalCheqdModule } from '../common'; +import { TypedUUID } from '@docknetwork/credential-sdk/types/generic'; +import { + stringToU8a, + maybeToJSONStringBytes, + u8aToString, +} from '@docknetwork/credential-sdk/utils'; +import { createInternalCheqdModule, validateResource } from '../common'; const Type = 'status-list-credential'; const methods = { create: (statusListCredentialId, rawStatusListCredential) => { - const [did, id] = CheqdStatusListCredentialId.from(statusListCredentialId).value; + const [did, id] = CheqdStatusListCredentialId.from( + statusListCredentialId, + ).value; return new CheqdCreateResource( did.value.value, @@ -20,11 +26,15 @@ const methods = { [], String(id), Type, - stringToU8a(maybeToJSONString(StatusList2021Credential.fromJSON(rawStatusListCredential))), + maybeToJSONStringBytes( + StatusList2021Credential.fromJSON(rawStatusListCredential), + ), ); }, update: (statusListCredentialId, statusListCredential) => { - const [did, id] = CheqdStatusListCredentialId.from(statusListCredentialId).value; + const [did, id] = CheqdStatusListCredentialId.from( + statusListCredentialId, + ).value; return new CheqdCreateResource( did.value.value, @@ -33,11 +43,15 @@ const methods = { [], String(id), Type, - stringToU8a(maybeToJSONString(StatusList2021Credential.fromJSON(statusListCredential))), + maybeToJSONStringBytes( + StatusList2021Credential.fromJSON(statusListCredential), + ), ); }, remove: (statusListCredentialId) => { - const [did, id] = CheqdStatusListCredentialId.from(statusListCredentialId).value; + const [did, id] = CheqdStatusListCredentialId.from( + statusListCredentialId, + ).value; return new CheqdCreateResource( did.value.value, @@ -74,11 +88,24 @@ export default class CheqdInternalStatusListCredentialModule extends createInter async statusListCredential(statusListCredentialId) { const credId = CheqdStatusListCredentialId.from(statusListCredentialId); - const [did, _] = credId.value; - const item = await this.resource(did, await this.lastStatusListCredentialId(credId)); - - return option(StatusList2021Credential).fromJSON( - item && JSON.parse(u8aToString(item.resource.data)), + const [did, name] = credId.value; + const versionId = await this.lastStatusListCredentialId(credId); + if (versionId == null) { + return null; + } + const json = JSON.parse( + u8aToString( + validateResource( + await this.resource(did, versionId), + String(name), + Type, + ), + ), ); + if (json == null) { + return null; + } + + return StatusList2021Credential.fromJSON(json); } } diff --git a/packages/cheqd-blockchain-modules/src/status-list-credential/module.js b/packages/cheqd-blockchain-modules/src/status-list-credential/module.js index 2adfde868..373dc32c8 100644 --- a/packages/cheqd-blockchain-modules/src/status-list-credential/module.js +++ b/packages/cheqd-blockchain-modules/src/status-list-credential/module.js @@ -2,7 +2,9 @@ import { AbstractStatusListCredentialModule } from '@docknetwork/credential-sdk/ import { injectCheqd } from '../common'; import CheqdInternalStatusListCredentialModule from './internal'; -export default class CheqdStatusListCredentialModule extends injectCheqd(AbstractStatusListCredentialModule) { +export default class CheqdStatusListCredentialModule extends injectCheqd( + AbstractStatusListCredentialModule, +) { static CheqdOnly = CheqdInternalStatusListCredentialModule; /** @@ -21,11 +23,12 @@ export default class CheqdStatusListCredentialModule extends injectCheqd(Abstrac * @param didKeypair * @return {Promise} - the extrinsic to sign and send. */ - async createStatusListCredentialTx( - id, - statusListCredential, - didKeypair, - ) { + async createStatusListCredentialTx(id, statusListCredential, didKeypair) { + if ((await this.cheqdOnly.lastStatusListCredentialId(id)) != null) { + throw new Error( + `Status List credential with id \`${id}\` already exists`, + ); + } return await this.cheqdOnly.tx.create(id, statusListCredential, didKeypair); } @@ -36,11 +39,7 @@ export default class CheqdStatusListCredentialModule extends injectCheqd(Abstrac * @param didKeypair - `DID` signatures over an action with a nonce authorizing this action according to the existing policy. * @return {Promise} - the extrinsic to sign and send. */ - async updateStatusListCredentialTx( - id, - statusListCredential, - didKeypair, - ) { + async updateStatusListCredentialTx(id, statusListCredential, didKeypair) { return await this.cheqdOnly.tx.update(id, statusListCredential, didKeypair); } @@ -50,10 +49,7 @@ export default class CheqdStatusListCredentialModule extends injectCheqd(Abstrac * @param didKeypair - `DID` signatures over an action with a nonce authorizing this action according to the existing policy. * @return {Promise} - the extrinsic to sign and send. */ - async removeStatusListCredentialTx( - id, - didKeypair, - ) { + async removeStatusListCredentialTx(id, didKeypair) { return await this.cheqdOnly.tx.remove(id, didKeypair); } } diff --git a/packages/cheqd-blockchain-modules/tests/accumulator-module.test.js b/packages/cheqd-blockchain-modules/tests/accumulator-module.test.js new file mode 100644 index 000000000..dc15070eb --- /dev/null +++ b/packages/cheqd-blockchain-modules/tests/accumulator-module.test.js @@ -0,0 +1,62 @@ +import { CheqdAPI } from "@docknetwork/cheqd-blockchain-api"; +import { + CheqdAccumulatorId, + CheqdAccumulatorPublicKey, + CheqdAccumulatorCommon, + CheqdTestnetDid, +} from "@docknetwork/credential-sdk/types"; +import { + MultiApiDIDModule, + MultiApiAccumulatorModule, +} from "@docknetwork/credential-sdk/modules"; +import generateAccumulatorModuleTests from "@docknetwork/credential-sdk/modules/tests/accumulator-module"; +import CheqdDIDModule from "../src/did/module"; +import { faucet, url, network } from "./constants"; +import CheqdAccumulatorModule from "../src/accumulator/module"; +import { initializeWasm } from "@docknetwork/credential-sdk/crypto"; + +describe("AccumulatorModule", () => { + const cheqd = new CheqdAPI(); + + beforeAll(async () => { + await cheqd.init({ + url: url, + wallet: await faucet.wallet(), + network, + }); + + await initializeWasm(); + }); + + afterAll(async () => { + await cheqd.disconnect(); + }); + + generateAccumulatorModuleTests( + { + did: new CheqdDIDModule(cheqd), + accumulator: new CheqdAccumulatorModule(cheqd), + }, + { + DID: CheqdTestnetDid, + AccumulatorId: CheqdAccumulatorId, + PublicKey: CheqdAccumulatorPublicKey, + AccumulatorCommon: CheqdAccumulatorCommon, + } + ); + + generateAccumulatorModuleTests( + { + did: new MultiApiDIDModule([new CheqdDIDModule(cheqd)]), + accumulator: new MultiApiAccumulatorModule([ + new CheqdAccumulatorModule(cheqd), + ]), + }, + { + DID: CheqdTestnetDid, + AccumulatorId: CheqdAccumulatorId, + PublicKey: CheqdAccumulatorPublicKey, + AccumulatorCommon: CheqdAccumulatorCommon, + } + ); +}); diff --git a/packages/credential-sdk/CHANGELOG.md b/packages/credential-sdk/CHANGELOG.md index 119b0f959..61f5c7c5d 100644 --- a/packages/credential-sdk/CHANGELOG.md +++ b/packages/credential-sdk/CHANGELOG.md @@ -1,5 +1,11 @@ # @docknetwork/credential-sdk +## 0.18.0 + +### Minor Changes + +- `Accumulator` module for `cheqd` + ## 0.17.0 ### Minor Changes diff --git a/packages/credential-sdk/package.json b/packages/credential-sdk/package.json index cb6d01216..66b03c207 100644 --- a/packages/credential-sdk/package.json +++ b/packages/credential-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@docknetwork/credential-sdk", - "version": "0.17.0", + "version": "0.18.0", "license": "MIT", "type": "module", "files": [ diff --git a/packages/credential-sdk/src/modules/abstract/accumulator/module.js b/packages/credential-sdk/src/modules/abstract/accumulator/module.js index 19f0ff1b5..07955c8b0 100644 --- a/packages/credential-sdk/src/modules/abstract/accumulator/module.js +++ b/packages/credential-sdk/src/modules/abstract/accumulator/module.js @@ -56,16 +56,6 @@ class AbstractAccumulatorModule extends withAbstractParams( } } - async addPublicKey(id, publicKey, targetDid, didKeypair, params) { - return await super.addPublicKey( - id, - publicKey, - targetDid, - didKeypair, - params, - ); - } - /** * Add a positive (add-only) accumulator * @param id - Unique accumulator id @@ -151,31 +141,89 @@ class AbstractAccumulatorModule extends withAbstractParams( } /** - * Update existing accumulator - * @param id - * @param newAccumulated - Accumulated value after the update - * @param additions - * @param removals - * @param witnessUpdateInfo + * Update existing positive (add-only) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. * @param signingKeyRef - Signer's keypair reference - * @returns {Promise< object>} + * @returns {Promise<*>} */ - async updateAccumulator( + async updatePositiveAccumulator( id, - newAccumulated, + accumulated, { additions, removals, witnessUpdateInfo }, + publicKeyRef, didKeypair, params, ) { return await this.signAndSend( - await this.updateAccumulatorTx( + await this.updatePositiveAccumulatorTx( id, - newAccumulated, - { - additions, - removals, - witnessUpdateInfo, - }, + accumulated, + { additions, removals, witnessUpdateInfo }, + publicKeyRef, + didKeypair, + ), + params, + ); + } + + /** + * Update existing universal (supports update/remove) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. + * @param maxSize - Maximum size of the accumulator + * @param signingKeyRef - Signer's keypair reference + * @returns {Promise<*>} + */ + async updateUniversalAccumulator( + id, + accumulated, + { additions, removals, witnessUpdateInfo }, + publicKeyRef, + maxSize, + didKeypair, + params, + ) { + return await this.signAndSend( + await this.updateUniversalAccumulatorTx( + id, + accumulated, + { additions, removals, witnessUpdateInfo }, + publicKeyRef, + maxSize, + didKeypair, + ), + params, + ); + } + + /** + * Update existing KB universal (supports update/remove) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. + * @param signingKeyRef - Signer's keypair reference + * @returns {Promise<*>} + */ + async updateKBUniversalAccumulator( + id, + accumulated, + { additions, removals, witnessUpdateInfo }, + publicKeyRef, + didKeypair, + params, + ) { + return await this.signAndSend( + await this.updateKBUniversalAccumulatorTx( + id, + accumulated, + { additions, removals, witnessUpdateInfo }, + publicKeyRef, didKeypair, ), params, @@ -231,7 +279,9 @@ export default withExtendedPrototypeProperties( 'addPositiveAccumulatorTx', 'addUniversalAccumulatorTx', 'addKBUniversalAccumulatorTx', - 'updateAccumulatorTx', + 'updatePositiveAccumulatorTx', + 'updateUniversalAccumulatorTx', + 'updateKBUniversalAccumulatorTx', 'removeAccumulatorTx', 'updateWitness', ], diff --git a/packages/credential-sdk/src/modules/multi-api/accumulator.js b/packages/credential-sdk/src/modules/multi-api/accumulator.js index c838783d1..3a911ab4f 100644 --- a/packages/credential-sdk/src/modules/multi-api/accumulator.js +++ b/packages/credential-sdk/src/modules/multi-api/accumulator.js @@ -102,28 +102,90 @@ export default class MultiApiAccumulatorModule extends injectModuleRouter( } /** - * Update existing accumulator - * @param id - * @param newAccumulated - Accumulated value after the update - * @param additions - * @param removals - * @param witnessUpdateInfo + * Update existing a positive (add-only) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. + * @param signingKeyRef - Signer's keypair reference + * @returns {Promise<*>} + */ + async updatePositiveAccumulator( + id, + accumulated, + { additions, removals, witnessUpdateInfo }, + publicKeyRef, + didKeypair, + params, + ) { + const accId = AccumulatorId.from(id); + + return await this.moduleById(accId).updatePositiveAccumulator( + accId, + accumulated, + { additions, removals, witnessUpdateInfo }, + publicKeyRef, + didKeypair, + params, + ); + } + + /** + * Update existing universal (supports add/remove) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. + * @param maxSize - Maximum size of the accumulator * @param signingKeyRef - Signer's keypair reference - * @returns {Promise< object>} + * @returns {Promise<*>} */ - async updateAccumulator( + async updateUniversalAccumulator( id, - newAccumulated, + accumulated, { additions, removals, witnessUpdateInfo }, + publicKeyRef, + maxSize, didKeypair, params, ) { const accId = AccumulatorId.from(id); - return await this.moduleById(accId).updateAccumulator( + return await this.moduleById(accId).updateUniversalAccumulator( accId, - newAccumulated, + accumulated, { additions, removals, witnessUpdateInfo }, + publicKeyRef, + maxSize, + didKeypair, + params, + ); + } + + /** + * Update existing KB universal (supports add/remove) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. + * @param signingKeyRef - Signer's keypair reference + * @returns {Promise<*>} + */ + async updateKBUniversalAccumulator( + id, + accumulated, + { additions, removals, witnessUpdateInfo }, + publicKeyRef, + didKeypair, + params, + ) { + const accId = AccumulatorId.from(id); + + return await this.moduleById(accId).updateKBUniversalAccumulator( + accId, + accumulated, + { additions, removals, witnessUpdateInfo }, + publicKeyRef, didKeypair, params, ); @@ -341,28 +403,85 @@ export default class MultiApiAccumulatorModule extends injectModuleRouter( } /** - * Update existing accumulator - * @param id - * @param newAccumulated - Accumulated value after the update - * @param additions - * @param removals - * @param witnessUpdateInfo - * @param signerDid - Signer of the transaction payload + * Update existing a positive (add-only) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. * @param signingKeyRef - Signer's keypair reference - * @returns {Promise< object>} + * @returns {Promise<*>} */ - async updateAccumulatorTx( + async updatePositiveAccumulatorTx( id, - newAccumulated, + accumulated, { additions, removals, witnessUpdateInfo }, + publicKeyRef, didKeypair, ) { const accId = AccumulatorId.from(id); - return await this.moduleById(accId).updateAccumulatorTx( + return await this.moduleById(accId).updatePositiveAccumulatorTx( accId, - newAccumulated, + accumulated, { additions, removals, witnessUpdateInfo }, + publicKeyRef, + didKeypair, + ); + } + + /** + * Update existing universal (supports add/remove) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. + * @param maxSize - Maximum size of the accumulator + * @param signingKeyRef - Signer's keypair reference + * @returns {Promise<*>} + */ + async updateUniversalAccumulatorTx( + id, + accumulated, + { additions, removals, witnessUpdateInfo }, + publicKeyRef, + maxSize, + didKeypair, + ) { + const accId = AccumulatorId.from(id); + + return await this.moduleById(accId).updateUniversalAccumulatorTx( + accId, + accumulated, + { additions, removals, witnessUpdateInfo }, + publicKeyRef, + maxSize, + didKeypair, + ); + } + + /** + * Update existing KB universal (supports add/remove) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. + * @param signingKeyRef - Signer's keypair reference + * @returns {Promise<*>} + */ + async updateKBUniversalAccumulatorTx( + id, + accumulated, + { additions, removals, witnessUpdateInfo }, + publicKeyRef, + didKeypair, + ) { + const accId = AccumulatorId.from(id); + + return await this.moduleById(accId).updateKBUniversalAccumulatorTx( + accId, + accumulated, + { additions, removals, witnessUpdateInfo }, + publicKeyRef, didKeypair, ); } diff --git a/packages/credential-sdk/src/modules/multi-api/index.js b/packages/credential-sdk/src/modules/multi-api/index.js index 3a37d43bf..48e420439 100644 --- a/packages/credential-sdk/src/modules/multi-api/index.js +++ b/packages/credential-sdk/src/modules/multi-api/index.js @@ -16,7 +16,6 @@ export class MultiApiCoreModules extends AbstractCoreModules { return { ...super.ModuleMap, - AccumulatorModule: { key: 'accumulator', optional: true }, TrustRegistryModule: { key: 'trustRegistry', optional: true }, }; } @@ -45,13 +44,15 @@ export class MultiApiCoreModules extends AbstractCoreModules { attachModule(prop, { key, optional }, [modules]) { return super.attachModule(prop, { key, optional }, [ - modules.map(({ [key]: module }) => { - if (module == null && !optional) { - throw new Error(`\`${prop}\` module is missing`); - } - - return module; - }).filter(Boolean), + modules + .map(({ [key]: module }) => { + if (module == null && !optional) { + throw new Error(`\`${prop}\` module is missing`); + } + + return module; + }) + .filter(Boolean), ]); } } diff --git a/packages/credential-sdk/src/modules/tests/accumulator-module.js b/packages/credential-sdk/src/modules/tests/accumulator-module.js new file mode 100644 index 000000000..5e6fafc77 --- /dev/null +++ b/packages/credential-sdk/src/modules/tests/accumulator-module.js @@ -0,0 +1,657 @@ +import { InMemoryState } from '@docknetwork/crypto-wasm-ts/lib/accumulator/in-memory-persistence'; +import { + hexToU8a, stringToHex, u8aToHex, randomAsHex, +} from '../../utils'; +import { + Accumulator, + AccumulatorParams, + AccumulatorPublicKey, + PositiveAccumulator, + VBWitnessUpdateInfo, + VBMembershipWitness, +} from '../../crypto'; +import { AccumulatorType } from '../abstract/accumulator/module'; +import { DIDDocument } from '../../types'; +import { Ed25519Keypair, DidKeypair } from '../../keypairs'; + +// eslint-disable-next-line jest/no-export +export default function generateAccumulatorTests( + { did: didModule, accumulator: accumulatorModule }, + { + DID, PublicKey, AccumulatorId, AccumulatorCommon, + }, +) { + describe(`Using ${didModule.constructor.name} and ${accumulatorModule.constructor.name}`, () => { + // Incase updating an accumulator is expensive like making a blockchain txn, a cheaper strategy + // is to add the members to the accumulator beforehand but not giving out the witnesses yet. + // Eg. accumulator manager wants to add a million members over an year, rather than publishing + // the new accumulator after each addition, the manager can initialize the accumulator with a million + // member ids (member ids are either predictable like monotonically increasing numbers or the manager + // can internally keep a map of random ids like UUIDs to a number). Now when the manager actually + // wants to allow a member to prove membership, he can create a witness for that member but the + // accumulator value remains same and thus the witness for existing members also remain same. It + // should be noted though that changing the accumulator + // value causes change in all existing witnesses and thus its better to make a good estimate + // of the number of members during prefill stage + + // Manager estimates that he will have `total_members` members over the course of time + + const totalMembers = 100; + const members = []; + const seedAccum = randomAsHex(32); + + const did = DID.random(); + const pair = new DidKeypair([did, 1], Ed25519Keypair.random()); + + const accumulatorModuleClass = accumulatorModule.constructor; + + let keypair; + let accumulatorId = AccumulatorId.random(did); + let accumulator; + let params1Id; + let pk1Id; + const accumState = new InMemoryState(); + + test('Prefill', async () => { + await didModule.createDocument( + DIDDocument.create(did, [pair.didKey()]), + pair, + ); + + const label = stringToHex('accumulator-params-label'); + const params = Accumulator.generateParams(hexToU8a(label)); + const bytes1 = u8aToHex(params.bytes); + const params1 = new AccumulatorParams(bytes1, label); + params1Id = await accumulatorModule.nextParamsId(did); + await accumulatorModule.addParams(params1Id, params1, did, pair); + + keypair = Accumulator.generateKeypair(params, seedAccum); + const bytes2 = u8aToHex(keypair.publicKey.bytes); + const pk1 = new PublicKey(bytes2, [did, params1Id]); + pk1Id = await accumulatorModule.nextPublicKeyId(did); + await accumulatorModule.addPublicKey(pk1Id, pk1, did, pair); + + accumulator = PositiveAccumulator.initialize(params, keypair.secretKey); + + for (let i = 1; i <= totalMembers; i++) { + members.push(Accumulator.encodePositiveNumberAsAccumulatorMember(i)); + } + await accumulator.addBatch(members, keypair.secretKey, accumState); + expect(accumState.state.size).toEqual(totalMembers); + + accumulatorId = AccumulatorId.random(did); + const accumulated = accumulatorModuleClass.accumulatedAsHex( + accumulator.accumulated, + AccumulatorType.VBPos, + ); + + await accumulatorModule.addPositiveAccumulator( + accumulatorId, + accumulated, + [did, pk1Id], + pair, + ); + + const queriedAccum = await accumulatorModule.getAccumulator( + accumulatorId, + true, + true, + ); + + expect(queriedAccum.accumulated.value).toEqual(accumulated); + }); + + test('Can create/update all types of accumulators', async () => { + const acc1Id = AccumulatorId.random(did); + + await accumulatorModule.addPositiveAccumulator( + acc1Id, + hexToU8a('0xff'), + [did, pk1Id], + pair, + ); + + let queriedAccum = await accumulatorModule.getAccumulator(acc1Id); + + expect(queriedAccum.accumulator.value.toJSON()).toEqual( + new AccumulatorCommon(hexToU8a('0xff'), [did, pk1Id]).toJSON(), + ); + + await accumulatorModule.updatePositiveAccumulator( + acc1Id, + hexToU8a('0xfa'), + { + additions: [hexToU8a('0xfe')], + removals: [hexToU8a('0xde')], + witnessUpdateInfo: hexToU8a('0xef'), + }, + [did, pk1Id], + pair, + ); + + queriedAccum = await accumulatorModule.getAccumulator(acc1Id); + + expect(queriedAccum.accumulator.value.toJSON()).toEqual( + new AccumulatorCommon(hexToU8a('0xfa'), [did, pk1Id]).toJSON(), + ); + + const acc2Id = AccumulatorId.random(did); + + await accumulatorModule.addKBUniversalAccumulator( + acc2Id, + hexToU8a('0xff'), + [did, pk1Id], + pair, + ); + + queriedAccum = await accumulatorModule.getAccumulator(acc2Id); + + expect(queriedAccum.accumulator.value.toJSON()).toEqual( + new AccumulatorCommon(hexToU8a('0xff'), [did, pk1Id]).toJSON(), + ); + + await accumulatorModule.updateKBUniversalAccumulator( + acc2Id, + hexToU8a('0xfa'), + { + additions: [hexToU8a('0xfe')], + removals: [hexToU8a('0xde')], + witnessUpdateInfo: hexToU8a('0xef'), + }, + [did, pk1Id], + pair, + ); + + queriedAccum = await accumulatorModule.getAccumulator(acc2Id); + + expect(queriedAccum.accumulator.value.toJSON()).toEqual( + new AccumulatorCommon(hexToU8a('0xfa'), [did, pk1Id]).toJSON(), + ); + + const acc3Id = AccumulatorId.random(did); + + await accumulatorModule.addUniversalAccumulator( + acc3Id, + hexToU8a('0xff'), + [did, pk1Id], + 10, + pair, + ); + + queriedAccum = await accumulatorModule.getAccumulator(acc3Id); + + expect(queriedAccum.accumulator.value.common.toJSON()).toEqual( + new AccumulatorCommon(hexToU8a('0xff'), [did, pk1Id]).toJSON(), + ); + + await accumulatorModule.updateUniversalAccumulator( + acc3Id, + hexToU8a('0xfa'), + { + additions: [hexToU8a('0xfe')], + removals: [hexToU8a('0xde')], + witnessUpdateInfo: hexToU8a('0xef'), + }, + [did, pk1Id], + 10, + pair, + ); + + queriedAccum = await accumulatorModule.getAccumulator(acc3Id); + + expect(queriedAccum.accumulator.value.common.toJSON()).toEqual( + new AccumulatorCommon(hexToU8a('0xfa'), [did, pk1Id]).toJSON(), + ); + + await accumulatorModule.removeAccumulator(acc3Id, pair); + expect(await accumulatorModule.getAccumulator(acc3Id)).toBe(null); + }, 60000); + + test('Witness creation, verification should work', async () => { + let queriedAccum = await accumulatorModule.getAccumulator( + accumulatorId, + true, + true, + ); + let verifAccumulator = PositiveAccumulator.fromAccumulated( + accumulatorModuleClass.accumulatedFromHex( + queriedAccum.accumulated, + AccumulatorType.VBPos, + ), + ); + + // Witness created for member 1 + const member1 = members[10]; + const witness1 = await accumulator.membershipWitness( + member1, + keypair.secretKey, + accumState, + ); + + let accumPk = new AccumulatorPublicKey( + queriedAccum.publicKey.bytes.bytes, + ); + + let accumParams = new AccumulatorParams( + queriedAccum.publicKey.params.bytes.bytes, + ); + expect( + verifAccumulator.verifyMembershipWitness( + member1, + witness1, + accumPk, + accumParams, + ), + ).toEqual(true); + + // Witness created for member 2 + const member2 = members[25]; + const witness2 = await accumulator.membershipWitness( + member2, + keypair.secretKey, + accumState, + ); + expect( + verifAccumulator.verifyMembershipWitness( + member2, + witness2, + accumPk, + accumParams, + ), + ).toEqual(true); + + // Witness created for member 3 + const member3 = members[79]; + const witness3 = await accumulator.membershipWitness( + member3, + keypair.secretKey, + accumState, + ); + expect( + verifAccumulator.verifyMembershipWitness( + member3, + witness3, + accumPk, + accumParams, + ), + ).toEqual(true); + + // Previous users' witness still works + expect( + verifAccumulator.verifyMembershipWitness( + member1, + witness1, + accumPk, + accumParams, + ), + ).toEqual(true); + expect( + verifAccumulator.verifyMembershipWitness( + member2, + witness2, + accumPk, + accumParams, + ), + ).toEqual(true); + + // Manager decides to remove a member, the new accumulated value will be published along with witness update info + const witnessUpdInfo = VBWitnessUpdateInfo.new( + accumulator.accumulated, + [], + [member2], + keypair.secretKey, + ); + await accumulator.remove(member2, keypair.secretKey, accumState); + + let accum = await accumulatorModule.getAccumulator(accumulatorId, false); + const accumulated = accumulatorModuleClass.accumulatedAsHex( + accumulator.accumulated, + AccumulatorType.VBPos, + ); + const witUpdBytes = u8aToHex(witnessUpdInfo.value); + await accumulatorModule.updatePositiveAccumulator( + accumulatorId, + accumulated, + { removals: [u8aToHex(member2)], witnessUpdateInfo: witUpdBytes }, + [did, pk1Id], + pair, + ); + + queriedAccum = await accumulatorModule.getAccumulator( + accumulatorId, + true, + true, + ); + expect(queriedAccum.accumulated.value).toEqual(accumulated); + + verifAccumulator = PositiveAccumulator.fromAccumulated( + accumulatorModuleClass.accumulatedFromHex( + queriedAccum.accumulated, + AccumulatorType.VBPos, + ), + ); + + accumPk = new AccumulatorPublicKey(queriedAccum.publicKey.bytes.bytes); + accumParams = new AccumulatorParams( + queriedAccum.publicKey.params.bytes.bytes, + ); + + // Witness created for member 3 + const member4 = members[52]; + const witness4 = await accumulator.membershipWitness( + member4, + keypair.secretKey, + accumState, + ); + expect( + verifAccumulator.verifyMembershipWitness( + member4, + witness4, + accumPk, + accumParams, + ), + ).toEqual(true); + + // Older witnesses need to be updated + accum = await accumulatorModule.getAccumulator(accumulatorId); + + await accumulatorModule.updateWitness( + accumulatorId, + member1, + witness1, + accum.lastModified, + accum.lastModified, + ); + + await accumulatorModule.updateWitness( + accumulatorId, + member3, + witness3, + accum.lastModified, + accum.lastModified, + ); + + expect( + verifAccumulator.verifyMembershipWitness( + member1, + witness1, + accumPk, + accumParams, + ), + ).toEqual(true); + + expect( + verifAccumulator.verifyMembershipWitness( + member3, + witness3, + accumPk, + accumParams, + ), + ).toEqual(true); + }); + + test('Witness update after several batch upgrades', async () => { + let queriedAccum = await accumulatorModule.getAccumulator( + accumulatorId, + true, + true, + ); + let verifAccumulator = PositiveAccumulator.fromAccumulated( + accumulatorModuleClass.accumulatedFromHex( + queriedAccum.accumulated, + AccumulatorType.VBPos, + ), + ); + const member = members[10]; + let witness = await accumulator.membershipWitness( + member, + keypair.secretKey, + accumState, + ); + + const accumPk = new AccumulatorPublicKey( + queriedAccum.publicKey.bytes.bytes, + ); + const accumParams = new AccumulatorParams( + queriedAccum.publicKey.params.bytes.bytes, + ); + expect( + verifAccumulator.verifyMembershipWitness( + member, + witness, + accumPk, + accumParams, + ), + ).toEqual(true); + + // Do some updates to the accumulator + + const removals1 = [members[85], members[86]]; + const witnessUpdInfo1 = VBWitnessUpdateInfo.new( + accumulator.accumulated, + [], + removals1, + keypair.secretKey, + ); + await accumulator.removeBatch(removals1, keypair.secretKey, accumState); + const accumulated1 = accumulatorModuleClass.accumulatedAsHex( + accumulator.accumulated, + AccumulatorType.VBPos, + ); + const witUpdBytes1 = u8aToHex(witnessUpdInfo1.value); + await accumulatorModule.updatePositiveAccumulator( + accumulatorId, + accumulated1, + { + removals: removals1.map((r) => u8aToHex(r)), + witnessUpdateInfo: witUpdBytes1, + }, + [did, pk1Id], + pair, + ); + + const removals2 = [members[87], members[88]]; + const witnessUpdInfo2 = VBWitnessUpdateInfo.new( + accumulator.accumulated, + [], + removals2, + keypair.secretKey, + ); + await accumulator.removeBatch(removals2, keypair.secretKey, accumState); + const accumulated2 = accumulatorModuleClass.accumulatedAsHex( + accumulator.accumulated, + AccumulatorType.VBPos, + ); + const witUpdBytes2 = u8aToHex(witnessUpdInfo2.value); + await accumulatorModule.updatePositiveAccumulator( + accumulatorId, + accumulated2, + { + removals: removals2.map((r) => u8aToHex(r)), + witnessUpdateInfo: witUpdBytes2, + }, + [did, pk1Id], + pair, + ); + + const removals3 = [members[89], members[90], members[91]]; + const witnessUpdInfo3 = VBWitnessUpdateInfo.new( + accumulator.accumulated, + [], + removals3, + keypair.secretKey, + ); + await accumulator.removeBatch(removals3, keypair.secretKey, accumState); + const accumulated3 = accumulatorModuleClass.accumulatedAsHex( + accumulator.accumulated, + AccumulatorType.VBPos, + ); + const witUpdBytes3 = u8aToHex(witnessUpdInfo3.value); + await accumulatorModule.updatePositiveAccumulator( + accumulatorId, + accumulated3, + { + removals: removals3.map((r) => u8aToHex(r)), + witnessUpdateInfo: witUpdBytes3, + }, + [did, pk1Id], + pair, + ); + + // Get a witness from the accumulator manager. This will be updated after the following updates. + witness = await accumulator.membershipWitness( + member, + keypair.secretKey, + accumState, + ); + + // Do some more updates to the accumulator + const removals4 = [members[92], members[93]]; + const witnessUpdInfo4 = VBWitnessUpdateInfo.new( + accumulator.accumulated, + [], + removals4, + keypair.secretKey, + ); + await accumulator.removeBatch(removals4, keypair.secretKey, accumState); + const accumulated4 = accumulatorModuleClass.accumulatedAsHex( + accumulator.accumulated, + AccumulatorType.VBPos, + ); + const witUpdBytes4 = u8aToHex(witnessUpdInfo4.value); + await accumulatorModule.updatePositiveAccumulator( + accumulatorId, + accumulated4, + { + removals: removals4.map((r) => u8aToHex(r)), + witnessUpdateInfo: witUpdBytes4, + }, + [did, pk1Id], + pair, + ); + + const { lastModified: startFrom } = await accumulatorModule.getAccumulator(accumulatorId); + + const removals5 = [members[94], members[95], members[96]]; + const witnessUpdInfo5 = VBWitnessUpdateInfo.new( + accumulator.accumulated, + [], + removals5, + keypair.secretKey, + ); + await accumulator.removeBatch(removals5, keypair.secretKey, accumState); + const accumulated5 = accumulatorModuleClass.accumulatedAsHex( + accumulator.accumulated, + AccumulatorType.VBPos, + ); + const witUpdBytes5 = u8aToHex(witnessUpdInfo5.value); + await accumulatorModule.updatePositiveAccumulator( + accumulatorId, + accumulated5, + { + removals: removals5.map((r) => u8aToHex(r)), + witnessUpdateInfo: witUpdBytes5, + }, + [did, pk1Id], + pair, + ); + + queriedAccum = await accumulatorModule.getAccumulator( + accumulatorId, + true, + true, + ); + verifAccumulator = PositiveAccumulator.fromAccumulated( + accumulatorModuleClass.accumulatedFromHex( + queriedAccum.accumulated, + AccumulatorType.VBPos, + ), + ); + // Old witness doesn't verify with new accumulator + expect( + verifAccumulator.verifyMembershipWitness( + member, + witness, + accumPk, + accumParams, + ), + ).toEqual(false); + + const oldWitness1 = new VBMembershipWitness(witness.value); + const oldWitness2 = new VBMembershipWitness(witness.value); + const oldWitness3 = new VBMembershipWitness(witness.value); + + // Update witness by downloading necessary blocks and applying the updates if found + await accumulatorModule.updateWitness( + accumulatorId, + member, + witness, + startFrom, + queriedAccum.lastModified, + ); + + // Updated witness verifies with new accumulator + expect( + verifAccumulator.verifyMembershipWitness( + member, + witness, + accumPk, + accumParams, + ), + ).toEqual(true); + + // Test again with a batch size bigger than the total number of blocks + await accumulatorModule.updateWitness( + accumulatorId, + member, + oldWitness1, + startFrom, + queriedAccum.lastModified, + queriedAccum.lastModified - startFrom + 10, + ); + expect( + verifAccumulator.verifyMembershipWitness( + member, + oldWitness1, + accumPk, + accumParams, + ), + ).toEqual(true); + + // Test again with few other batch sizes + await accumulatorModule.updateWitness( + accumulatorId, + member, + oldWitness2, + startFrom, + queriedAccum.lastModified, + 3, + ); + expect( + verifAccumulator.verifyMembershipWitness( + member, + oldWitness2, + accumPk, + accumParams, + ), + ).toEqual(true); + + await accumulatorModule.updateWitness( + accumulatorId, + member, + oldWitness3, + startFrom, + queriedAccum.lastModified, + 4, + ); + expect( + verifAccumulator.verifyMembershipWitness( + member, + oldWitness3, + accumPk, + accumParams, + ), + ).toEqual(true); + }, 60000); + }); +} diff --git a/packages/credential-sdk/src/types/accumulator/accumulator-id.js b/packages/credential-sdk/src/types/accumulator/accumulator-id.js index 0c0f62ac8..bbccc27a9 100644 --- a/packages/credential-sdk/src/types/accumulator/accumulator-id.js +++ b/packages/credential-sdk/src/types/accumulator/accumulator-id.js @@ -13,7 +13,7 @@ export class AccumulatorId extends withFrom( (value, from) => { try { // eslint-disable-next-line no-use-before-define - return DockAccumulatorId.from(value); + return from(DockAccumulatorIdValue.from(value)); } catch { return from(value); } diff --git a/packages/credential-sdk/src/types/accumulator/accumulator.js b/packages/credential-sdk/src/types/accumulator/accumulator.js index 10dfe83a8..f98cc8c95 100644 --- a/packages/credential-sdk/src/types/accumulator/accumulator.js +++ b/packages/credential-sdk/src/types/accumulator/accumulator.js @@ -1,5 +1,15 @@ -import { TypedStruct, TypedNumber } from '../generic'; -import { DockAccumulatorPublicKeyRef } from './keys'; +import { + TypedStruct, + TypedNumber, + option, + ArrayOfByteArrays, + ByteArray, + TypedUUID, +} from '../generic'; +import { + DockAccumulatorPublicKeyRef, + CheqdAccumulatorPublicKeyRef, +} from './keys'; import { createAccumulatorVariants } from './variants'; export const [ @@ -37,3 +47,48 @@ export class DockAccumulatorWithUpdateInfo extends TypedStruct { return this.accumulator.value.keyRef; } } + +export const [ + CheqdAccumulatorCommon, + CheqdAccumulator, + CheqdUniversalAccumulator, + CheqdKBUniversalAccumulator, + CheqdPositiveAccumulator, +] = createAccumulatorVariants(CheqdAccumulatorPublicKeyRef); + +export class CheqdAccumulatorWithUpdateInfo extends TypedStruct { + static Classes = { + createdAt: TypedUUID, + lastUpdatedAt: TypedUUID, + accumulator: CheqdAccumulator, + }; + + get created() { + return this.createdAt; + } + + get lastModified() { + return this.lastUpdatedAt; + } + + get type() { + return this.accumulator.type; + } + + get accumulated() { + return this.accumulator.value.accumulated; + } + + get keyRef() { + return this.accumulator.value.keyRef; + } +} + +export class CheqdStoredAccumulator extends TypedStruct { + static Classes = { + accumulator: CheqdAccumulator, + additions: option(ArrayOfByteArrays), + removals: option(ArrayOfByteArrays), + witnessUpdateInfo: option(ByteArray), + }; +} diff --git a/packages/credential-sdk/src/types/accumulator/keys.js b/packages/credential-sdk/src/types/accumulator/keys.js index 6da778035..a24502c76 100644 --- a/packages/credential-sdk/src/types/accumulator/keys.js +++ b/packages/credential-sdk/src/types/accumulator/keys.js @@ -1,5 +1,5 @@ import { TypedTuple, TypedNumber } from '../generic'; -import { DockDidOrDidMethodKey } from '../did'; +import { CheqdDLRRef, DockDidOrDidMethodKey } from '../did'; export class DockAccumulatorPublicKeyRef extends TypedTuple { static Classes = [DockDidOrDidMethodKey, TypedNumber]; @@ -8,3 +8,7 @@ export class DockAccumulatorPublicKeyRef extends TypedTuple { export class DockAccumulatorParamsRef extends TypedTuple { static Classes = [DockDidOrDidMethodKey, TypedNumber]; } + +export class CheqdAccumulatorPublicKeyRef extends CheqdDLRRef {} + +export class CheqdAccumulatorParamsRef extends CheqdDLRRef {} diff --git a/packages/credential-sdk/src/types/accumulator/public-key.js b/packages/credential-sdk/src/types/accumulator/public-key.js index ddec2cd86..7345297d4 100644 --- a/packages/credential-sdk/src/types/accumulator/public-key.js +++ b/packages/credential-sdk/src/types/accumulator/public-key.js @@ -6,7 +6,7 @@ import { CurveType, CurveTypeBls12381, } from '../offchain-signatures/curve-type'; -import { DockAccumulatorParamsRef } from './keys'; +import { CheqdAccumulatorParamsRef, DockAccumulatorParamsRef } from './keys'; import { AccumulatorParams } from './params'; export class AccumulatorPublicKey extends TypedStruct { @@ -58,3 +58,9 @@ export class DockAccumulatorPublicKey extends withProp( 'paramsRef', option(DockAccumulatorParamsRef), ) {} + +export class CheqdAccumulatorPublicKey extends withProp( + AccumulatorPublicKey, + 'paramsRef', + option(CheqdAccumulatorParamsRef), +) {} diff --git a/packages/credential-sdk/src/types/accumulator/variants.js b/packages/credential-sdk/src/types/accumulator/variants.js index cc772a0f3..72d387251 100644 --- a/packages/credential-sdk/src/types/accumulator/variants.js +++ b/packages/credential-sdk/src/types/accumulator/variants.js @@ -27,7 +27,11 @@ export const createAccumulatorVariants = (keyRef) => { } } - class Accumulator extends TypedEnum {} + class Accumulator extends TypedEnum { + get keyRef() { + return this.value.keyRef; + } + } class UniversalAccumulator extends Accumulator { static Type = 'universal'; diff --git a/packages/credential-sdk/src/types/did/onchain/typed-did/cheqd-did.js b/packages/credential-sdk/src/types/did/onchain/typed-did/cheqd-did.js index cbaa0a05d..fdc1f7eb9 100644 --- a/packages/credential-sdk/src/types/did/onchain/typed-did/cheqd-did.js +++ b/packages/credential-sdk/src/types/did/onchain/typed-did/cheqd-did.js @@ -3,7 +3,12 @@ import { CheqdDIDTestnetQualifier, CheqdDIDMainnetQualifier, } from '../constants'; -import { TypedEnum, withQualifier } from '../../../generic'; +import { + TypedEnum, + TypedTuple, + withFrom, + withQualifier, +} from '../../../generic'; import TypedUUID from '../../../generic/typed-uuid'; /** @@ -44,7 +49,9 @@ export class CheqdDid extends withQualifier(TypedEnum, true) { // eslint-disable-next-line no-use-before-define return CheqdMainnetDid.random(); } else { - throw new Error(`Unknown network provided: \`${network}\`, expected \`mainnet\` or \`testnet\``); + throw new Error( + `Unknown network provided: \`${network}\`, expected \`mainnet\` or \`testnet\``, + ); } } @@ -65,3 +72,26 @@ export class CheqdMainnetDid extends CheqdDid { } CheqdDid.bindVariants(CheqdTestnetDid, CheqdMainnetDid); + +export class CheqdDLRRef extends withFrom( + TypedTuple, + function from(value, fromFn) { + if (typeof value === 'string') { + const lastColon = value.lastIndexOf(':'); + + return new this(value.slice(0, lastColon), value.slice(lastColon + 1)); + } else { + return fromFn(value); + } + }, +) { + static Classes = [CheqdDid, TypedUUID]; + + toString() { + return `${this[0]}:${this[1]}`; + } + + toJSON() { + return String(this); + } +} diff --git a/packages/credential-sdk/src/types/generic/index.js b/packages/credential-sdk/src/types/generic/index.js index f642c6c35..0fa15fd03 100644 --- a/packages/credential-sdk/src/types/generic/index.js +++ b/packages/credential-sdk/src/types/generic/index.js @@ -22,3 +22,4 @@ export { default as Any } from './typed-any'; export { default as withBase58 } from './with-base58'; export { default as withBase64 } from './with-base64'; export { default as withBase58btc } from './with-base58btc'; +export * from './utils'; diff --git a/packages/credential-sdk/src/types/generic/typed-array.js b/packages/credential-sdk/src/types/generic/typed-array.js index 0222ebf9b..39e71902d 100644 --- a/packages/credential-sdk/src/types/generic/typed-array.js +++ b/packages/credential-sdk/src/types/generic/typed-array.js @@ -87,8 +87,8 @@ class TypedArray extends withBase(ArrayWithoutPrototypeMethods) { diff(other) { return { - added: this.filter((item) => other.every((curItem) => !curItem.eq(item))), - removed: other.filter((item) => this.every((nextItem) => !nextItem.eq(item))), + added: this.filter((item) => other.every((curItem) => !maybeEq(curItem, item))), + removed: other.filter((item) => this.every((nextItem) => !maybeEq(nextItem, item))), }; } } diff --git a/packages/credential-sdk/src/types/generic/typed-bytes.js b/packages/credential-sdk/src/types/generic/typed-bytes.js index 595c52315..bc75b44c0 100644 --- a/packages/credential-sdk/src/types/generic/typed-bytes.js +++ b/packages/credential-sdk/src/types/generic/typed-bytes.js @@ -18,7 +18,7 @@ class TypedBytes extends withBase(ArrayWithoutPrototypeMethods) { } get value() { - return u8aToHex(this.bytes); + return this.toHex(); } get bytes() { @@ -26,7 +26,6 @@ class TypedBytes extends withBase(ArrayWithoutPrototypeMethods) { } set(bytes) { - this.length = 0; this.length = bytes.length; for (let i = 0; i < bytes.length; i++) { @@ -50,7 +49,7 @@ class TypedBytes extends withBase(ArrayWithoutPrototypeMethods) { } toHex() { - return u8aToHex(this.bytes); + return u8aToHex(this); } toJSON() { diff --git a/packages/credential-sdk/src/types/generic/typed-map.js b/packages/credential-sdk/src/types/generic/typed-map.js index 56e6df4bf..d11ec4110 100644 --- a/packages/credential-sdk/src/types/generic/typed-map.js +++ b/packages/credential-sdk/src/types/generic/typed-map.js @@ -1,4 +1,9 @@ -import { maybeEq, maybeFrom, maybeToJSON } from '../../utils/interfaces'; +import { + maybeEq, + maybeFrom, + maybeToJSON, + maybeToJSONString, +} from '../../utils/interfaces'; import { withExtendedStaticProperties } from '../../utils/inheritance'; import withBase from './with-base'; import withCatchNull from './with-catch-null'; @@ -21,7 +26,7 @@ class TypedMap extends withBase(Map) { keyAsKeyClass(key) { const asKeyClass = maybeFrom(this.constructor.KeyClass, key); - const strKey = JSON.stringify(maybeToJSON(asKeyClass)); + const strKey = maybeToJSONString(asKeyClass); if (this.keysAsKeyClass.has(strKey)) { return this.keysAsKeyClass.get(strKey); diff --git a/packages/credential-sdk/src/types/generic/typed-set.js b/packages/credential-sdk/src/types/generic/typed-set.js index d67c1af58..8e6141425 100644 --- a/packages/credential-sdk/src/types/generic/typed-set.js +++ b/packages/credential-sdk/src/types/generic/typed-set.js @@ -1,9 +1,14 @@ -import { maybeFrom, maybeToJSON } from '../../utils/interfaces'; +import { + maybeFrom, + maybeToJSON, + maybeToJSONString, +} from '../../utils/interfaces'; import { withExtendedStaticProperties } from '../../utils/inheritance'; import withCatchNull from './with-catch-null'; import withEq from './with-eq'; +import withBase from './with-base'; -class TypedSet extends Set { +class TypedSet extends withBase(Set) { static Class; constructor(values = []) { @@ -18,7 +23,7 @@ class TypedSet extends Set { valueAsClass(value) { const valuesAsClass = maybeFrom(this.constructor.Class, value); - const valueStr = JSON.stringify(maybeToJSON(valuesAsClass)); + const valueStr = maybeToJSONString(valuesAsClass); if (this.valuesAsClass.has(valueStr)) { return this.valuesAsClass.get(valueStr); diff --git a/packages/credential-sdk/src/types/generic/utils.js b/packages/credential-sdk/src/types/generic/utils.js new file mode 100644 index 000000000..4972228c8 --- /dev/null +++ b/packages/credential-sdk/src/types/generic/utils.js @@ -0,0 +1,8 @@ +import TypedBytes from './typed-bytes'; +import TypedArray from './typed-array'; + +export class ByteArray extends TypedBytes {} + +export class ArrayOfByteArrays extends TypedArray { + static Class = TypedBytes; +} diff --git a/packages/credential-sdk/src/types/generic/with-base.js b/packages/credential-sdk/src/types/generic/with-base.js index 7e76f7f89..1b53c165e 100644 --- a/packages/credential-sdk/src/types/generic/with-base.js +++ b/packages/credential-sdk/src/types/generic/with-base.js @@ -1,3 +1,4 @@ +import { stringToU8a, u8aToString } from '../../utils'; import { withExtendedStaticProperties, withExtendedPrototypeProperties, @@ -44,6 +45,8 @@ export default function withBase(klass) { return obj; } else if (Object.getPrototypeOf(obj) === Object.getPrototypeOf({})) { return this.fromJSON(obj); + } else if (obj instanceof Uint8Array) { + return this.fromJSON(JSON.parse(u8aToString(obj))); } else { return this.fromApi(obj); } @@ -57,6 +60,22 @@ export default function withBase(klass) { throw new Error('Unimplemented'); } + /** + * Converts the instance to a stringified JSON representation. + * @returns {string} The JSON representation of the instance. + */ + toJSONString() { + return JSON.stringify(this.toJSON()); + } + + /** + * Converts the instance to bytes of the stringified JSON representation. + * @returns {Uint8Array} The JSON representation of the instance. + */ + toJSONStringBytes() { + return stringToU8a(this.toJSONString()); + } + /** * Converts the instance to a string representation. * @returns {string} diff --git a/packages/credential-sdk/src/utils/bytes.js b/packages/credential-sdk/src/utils/bytes.js index 3740bbe06..de8ebd9fe 100644 --- a/packages/credential-sdk/src/utils/bytes.js +++ b/packages/credential-sdk/src/utils/bytes.js @@ -1,4 +1,4 @@ -import { applyToValue } from './interfaces'; +import { applyToValue, maybeToJSONString } from './interfaces'; import { ensureBytes, ensureString, isBytes } from './type-helpers'; /** @@ -132,7 +132,9 @@ export const normalizeToU8a = (bytes) => { } throw new Error( - `Can't convert supplied value to \`Uint8Array\`: \`${bytes}\``, + `Can't convert supplied value to \`Uint8Array\`: \`${maybeToJSONString( + bytes, + )}\` ${bytes ? `instance of ${bytes.constructor}` : ''}`, ); }; diff --git a/packages/credential-sdk/src/utils/interfaces.js b/packages/credential-sdk/src/utils/interfaces.js index fac9f69ff..fd7e86bc4 100644 --- a/packages/credential-sdk/src/utils/interfaces.js +++ b/packages/credential-sdk/src/utils/interfaces.js @@ -4,16 +4,28 @@ * @param {T} value * @returns {object} */ -export const maybeToJSON = (value) => (value && typeof value.toJSON === 'function' ? value.toJSON() : JSON.parse(JSON.stringify(value))); +export const maybeToJSON = (value) => (typeof value?.toJSON === 'function' + ? value.toJSON() + : JSON.parse(JSON.stringify(value))); /** - * Attempts to call `value.toJSON()` and stringify the result, returns `String(value)` in case of failure. + * Stringifies the provided value converted to JSON. * @template T * @param {T} value * @returns {string} */ export const maybeToJSONString = (value) => JSON.stringify(maybeToJSON(value)); +/** + * Returns bytes of the value converted to a stringified JSON. + * @template T + * @param {T} value + * @returns {string} + */ +export const maybeToJSONStringBytes = (value) => (typeof value?.maybeToJSONStringBytes === 'function' + ? value.toJSONStringBytes() + : Uint8Array.from(Buffer.from(maybeToJSONString(value)))); + /** * Attempts to compare two values using `value.eq(other)`, returns `boolean`. * @template T @@ -21,7 +33,7 @@ export const maybeToJSONString = (value) => JSON.stringify(maybeToJSON(value)); * @param {T} other * @returns {boolean} */ -export const maybeEq = (value, other) => (value && typeof value.eq === 'function' ? value.eq(other) : value === other); +export const maybeEq = (value, other) => (typeof value?.eq === 'function' ? value.eq(other) : value === other); /** * Attempts to call `value.toHuman()` or `value.toJSON`, returns `value` if methods don't exist. @@ -37,7 +49,7 @@ export const maybeToHuman = (obj) => (obj && typeof obj.toHuman === 'function' ? * @param {T} value * @returns {number} */ -export const maybeToNumber = (value) => (value && typeof value.toNumber === 'function' ? value.toNumber() : +value); +export const maybeToNumber = (value) => (typeof value?.toNumber === 'function' ? value.toNumber() : +value); /** * Marks function that it can't be used as a constructor. diff --git a/packages/dock-blockchain-api/CHANGELOG.md b/packages/dock-blockchain-api/CHANGELOG.md index 9e3a0340b..02ba5ae50 100644 --- a/packages/dock-blockchain-api/CHANGELOG.md +++ b/packages/dock-blockchain-api/CHANGELOG.md @@ -1,5 +1,12 @@ # @docknetwork/dock-blockchain-api +## 0.8.4 + +### Patch Changes + +- Updated dependencies + - @docknetwork/credential-sdk@0.18.0 + ## 0.8.3 ### Patch Changes diff --git a/packages/dock-blockchain-api/package.json b/packages/dock-blockchain-api/package.json index a7ad68e4b..fcf788023 100644 --- a/packages/dock-blockchain-api/package.json +++ b/packages/dock-blockchain-api/package.json @@ -1,6 +1,6 @@ { "name": "@docknetwork/dock-blockchain-api", - "version": "0.8.3", + "version": "0.8.4", "license": "MIT", "main": "./dist/esm/index.js", "type": "module", @@ -89,7 +89,7 @@ "@polkadot/api": "10.12.4" }, "dependencies": { - "@docknetwork/credential-sdk": "0.17.0", + "@docknetwork/credential-sdk": "0.18.0", "@docknetwork/node-types": "^0.17.0", "@juanelas/base64": "^1.0.5", "@polkadot/api": "10.12.4", diff --git a/packages/dock-blockchain-modules/CHANGELOG.md b/packages/dock-blockchain-modules/CHANGELOG.md index a35b77b13..6c392b625 100644 --- a/packages/dock-blockchain-modules/CHANGELOG.md +++ b/packages/dock-blockchain-modules/CHANGELOG.md @@ -1,5 +1,16 @@ # @docknetwork/dock-blockchain-modules +## 0.10.0 + +### Minor Changes + +- `Accumulator` module for `cheqd` + +### Patch Changes + +- Updated dependencies + - @docknetwork/credential-sdk@0.18.0 + ## 0.9.3 ### Patch Changes diff --git a/packages/dock-blockchain-modules/package.json b/packages/dock-blockchain-modules/package.json index 92fd8d16d..5f377b556 100644 --- a/packages/dock-blockchain-modules/package.json +++ b/packages/dock-blockchain-modules/package.json @@ -1,6 +1,6 @@ { "name": "@docknetwork/dock-blockchain-modules", - "version": "0.9.3", + "version": "0.10.0", "license": "MIT", "type": "module", "main": "./dist/esm/index.js", @@ -33,7 +33,7 @@ "node": ">=18.0.0" }, "dependencies": { - "@docknetwork/credential-sdk": "0.17.0" + "@docknetwork/credential-sdk": "0.18.0" }, "devDependencies": { "@babel/cli": "^7.24.1", @@ -42,7 +42,7 @@ "@babel/plugin-syntax-import-attributes": "^7.25.6", "@babel/plugin-transform-modules-commonjs": "^7.24.1", "@babel/preset-env": "^7.24.3", - "@docknetwork/dock-blockchain-api": "0.8.3", + "@docknetwork/dock-blockchain-api": "0.8.4", "@rollup/plugin-alias": "^4.0.2", "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-commonjs": "^24.0.0", @@ -75,7 +75,7 @@ "docs": "rm -rf out && mkdir out && touch out/.nojekyll && jsdoc src -r -c ../../.jsdoc -d out/reference", "prepublishOnly": "yarn build", "dev-node": "../../scripts/run_dock_node_in_docker --dev --rpc-external --ws-external --rpc-cors=all", - "test": "LOG_STATE_CHANGE=1 NODE_ENV=production jest --verbose --runInBand --forceExit ./tests/*", + "test": "LOG_STATE_CHANGE=1 NODE_ENV=production jest --verbose --runInBand --forceExit ./tests/integration/*", "test-with-node": "../../scripts/with_dock_docker_test_node yarn test", "test-with-all-nodes": "../../scripts/with_all_dock_docker_test_nodes yarn test-integration" } diff --git a/packages/dock-blockchain-modules/src/accumulator/actions.js b/packages/dock-blockchain-modules/src/accumulator/actions.js index 08d10c203..01dd1b68f 100644 --- a/packages/dock-blockchain-modules/src/accumulator/actions.js +++ b/packages/dock-blockchain-modules/src/accumulator/actions.js @@ -9,9 +9,10 @@ import { import { TypedBytes, TypedStruct, - TypedArray, option, TypedNumber, + ArrayOfByteArrays, + ByteArray, } from '@docknetwork/credential-sdk/types/generic'; export class AddAccumulator extends TypedStruct { @@ -22,18 +23,12 @@ export class AddAccumulator extends TypedStruct { }; } -class ByteArray extends TypedBytes {} - -export class ListOfByteArrays extends TypedArray { - static Class = ByteArray; -} - export class UpdateAccumulator extends TypedStruct { static Classes = { id: DockAccumulatorIdIdent, newAccumulated: class Accumulated extends TypedBytes {}, - additions: option(ListOfByteArrays), - removals: option(ListOfByteArrays), + additions: option(ArrayOfByteArrays), + removals: option(ArrayOfByteArrays), witnessUpdateInfo: option(ByteArray), nonce: TypedNumber, }; diff --git a/packages/dock-blockchain-modules/src/accumulator/module.js b/packages/dock-blockchain-modules/src/accumulator/module.js index 196c01891..83b9753be 100644 --- a/packages/dock-blockchain-modules/src/accumulator/module.js +++ b/packages/dock-blockchain-modules/src/accumulator/module.js @@ -27,16 +27,6 @@ export default class DockAccumulatorModule extends withParams( ) { static DockOnly = DockInternalAccumulatorModule; - async addPublicKeyTx(...args) { - if (args.length === 4) { - const [id, publicKey, targetDid, didKeypair] = args; - - return await super.addPublicKeyTx(id, publicKey, targetDid, didKeypair); - } else { - return await super.addPublicKeyTx(...args); - } - } - /** * Add a positive (add-only) accumulator * @param id - Unique accumulator id @@ -108,25 +98,87 @@ export default class DockAccumulatorModule extends withParams( } /** - * Update existing accumulator - * @param id - * @param newAccumulated - Accumulated value after the update - * @param additions - * @param removals - * @param witnessUpdateInfo + * Update a positive (add-only) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. + * @param signerDid - Signer of the transaction payload + * @param signingKeyRef - Signer's keypair reference + * @returns {Promise<*>} + */ + async updatePositiveAccumulatorTx( + id, + accumulated, + { additions, removals, witnessUpdateInfo }, + _publicKeyRef, + didKeypair, + ) { + return await this.dockOnly.tx.updateAccumulator( + id, + accumulated, + { + additions, + removals, + witnessUpdateInfo, + }, + didKeypair, + ); + } + + /** + * Update universal (supports add/remove) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. + * @param maxSize - Maximum size of the accumulator * @param signerDid - Signer of the transaction payload * @param signingKeyRef - Signer's keypair reference - * @returns {Promise< object>} + * @returns {Promise<*>} */ - async updateAccumulatorTx( + // eslint-disable-next-line sonarjs/no-identical-functions + async updateUniversalAccumulatorTx( id, - newAccumulated, + accumulated, + { additions, removals, witnessUpdateInfo }, + _publicKeyRef, + _maxSize, + didKeypair, + ) { + return await this.dockOnly.tx.updateAccumulator( + id, + accumulated, + { + additions, + removals, + witnessUpdateInfo, + }, + didKeypair, + ); + } + + /** + * Update KB universal (supports add/remove) accumulator + * @param id - Unique accumulator id + * @param accumulated - Current accumulated value. + * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not + * have any public key on the chain. This is useful for KVAC. + * @param signerDid - Signer of the transaction payload + * @param signingKeyRef - Signer's keypair reference + * @returns {Promise<*>} + */ + // eslint-disable-next-line sonarjs/no-identical-functions + async updateKBUniversalAccumulatorTx( + id, + accumulated, { additions, removals, witnessUpdateInfo }, + _publicKeyRef, didKeypair, ) { return await this.dockOnly.tx.updateAccumulator( id, - newAccumulated, + accumulated, { additions, removals, diff --git a/packages/dock-blockchain-modules/tests/integration/anoncreds/accumulator.test.js b/packages/dock-blockchain-modules/tests/integration/anoncreds/accumulator.test.js index 8ed2e9a83..7b3617d92 100644 --- a/packages/dock-blockchain-modules/tests/integration/anoncreds/accumulator.test.js +++ b/packages/dock-blockchain-modules/tests/integration/anoncreds/accumulator.test.js @@ -24,6 +24,7 @@ import { TestKeyringOpts, } from "../../test-constants"; import { DockDid, DockAccumulatorId } from "@docknetwork/credential-sdk/types"; +import { ArrayOfByteArrays } from "@docknetwork/credential-sdk/types/generic"; import { AccumulatorType } from "@docknetwork/credential-sdk/modules/abstract/accumulator"; import { AbstractAccumulatorModule } from "@docknetwork/credential-sdk/modules"; @@ -34,7 +35,6 @@ import { DidKeypair, } from "@docknetwork/credential-sdk/keypairs"; import { DockAccumulatorPublicKeyRef } from "@docknetwork/credential-sdk/types"; -import { ListOfByteArrays } from "../../../src/accumulator/actions"; import { DockAccumulatorModule } from "../../../src"; describe("Accumulator Module", () => { @@ -73,10 +73,15 @@ describe("Accumulator Module", () => { let params = Accumulator.generateParams(hexToU8a(label)); const bytes1 = u8aToHex(params.bytes); const params1 = chainModuleClass.prepareAddParameters(bytes1, label); - await chainModule.addParams(await chainModule.nextParamsId(did1), params1, did1, pair1); + await chainModule.addParams( + await chainModule.nextParamsId(did1), + params1, + did1, + pair1 + ); const paramsWritten1 = await chainModule.getParams( did1, - await chainModule.dockOnly.paramsCounter(did1) + await chainModule.lastParamsId(did1) ); expect(paramsWritten1.bytes).toEqual(params1.bytes); expect(paramsWritten1.label).toEqual(params1.label); @@ -88,10 +93,15 @@ describe("Accumulator Module", () => { params = Accumulator.generateParams(hexToU8a(label)); const bytes2 = u8aToHex(params.bytes); const params2 = chainModuleClass.prepareAddParameters(bytes2); - await chainModule.addParams(await chainModule.nextParamsId(did2), params2, did2, pair2); + await chainModule.addParams( + await chainModule.nextParamsId(did2), + params2, + did2, + pair2 + ); const paramsWritten2 = await chainModule.getParams( did2, - await chainModule.dockOnly.paramsCounter(did2) + await chainModule.lastParamsId(did2) ); expect(paramsWritten2.bytes).toEqual(params2.bytes); expect(paramsWritten2.label).toBe(null); @@ -103,10 +113,15 @@ describe("Accumulator Module", () => { params = Accumulator.generateParams(hexToU8a(label)); const bytes3 = u8aToHex(params.bytes); const params3 = chainModuleClass.prepareAddParameters(bytes3); - await chainModule.addParams(await chainModule.nextParamsId(did1), params3, did1, pair1); + await chainModule.addParams( + await chainModule.nextParamsId(did1), + params3, + did1, + pair1 + ); const paramsWritten3 = await chainModule.getParams( did1, - await chainModule.dockOnly.paramsCounter(did1) + await chainModule.lastParamsId(did1) ); expect(paramsWritten3.bytes).toEqual(params3.bytes); expect(paramsWritten3.label).toBe(null); @@ -131,7 +146,12 @@ describe("Accumulator Module", () => { let keypair = Accumulator.generateKeypair(params); const bytes1 = u8aToHex(keypair.publicKey.bytes); const pk1 = chainModuleClass.prepareAddPublicKey(bytes1); - await chainModule.addPublicKey(await chainModule.nextPublicKeyId(did2), pk1, did1, pair1); + await chainModule.addPublicKey( + await chainModule.nextPublicKeyId(did2), + pk1, + did1, + pair1 + ); const queriedPk1 = await chainModule.getPublicKey(did1, 1); expect(queriedPk1.bytes).toEqual(pk1.bytes); @@ -142,7 +162,12 @@ describe("Accumulator Module", () => { keypair = Accumulator.generateKeypair(aparams1, hexToU8a(seedAccum)); const bytes2 = u8aToHex(keypair.publicKey.bytes); const pk2 = chainModuleClass.prepareAddPublicKey(bytes2, [did1, 1]); - await chainModule.addPublicKey(await chainModule.nextPublicKeyId(did2), pk2, did2, pair2); + await chainModule.addPublicKey( + await chainModule.nextPublicKeyId(did2), + pk2, + did2, + pair2 + ); const queriedPk2 = await chainModule.getPublicKey(did2, 1); expect(queriedPk2.bytes).toEqual(pk2.bytes); @@ -158,7 +183,12 @@ describe("Accumulator Module", () => { keypair = Accumulator.generateKeypair(aparams2); const bytes3 = u8aToHex(keypair.publicKey.bytes); const pk3 = chainModuleClass.prepareAddPublicKey(bytes3, [did1, 2]); - await chainModule.addPublicKey(await chainModule.nextPublicKeyId(did2), pk3, did2, pair2); + await chainModule.addPublicKey( + await chainModule.nextPublicKeyId(did2), + pk3, + did2, + pair2 + ); const queriedPk3 = await chainModule.getPublicKey(did2, 2); expect(queriedPk3.bytes).toEqual(pk3.bytes); @@ -358,7 +388,10 @@ describe("Accumulator Module", () => { accumulator.accumulated, typ === 0 ? AccumulatorType.VBPos : AccumulatorType.KBUni ); - await chainModule.updateAccumulator(id, accumulated2, {}, pair2); + await (typ === 0 + ? chainModule.updatePositiveAccumulator + : chainModule.updateKBUniversalAccumulator + ).call(chainModule, id, accumulated2, {}, [did2, keyId], pair2); const accum2 = await chainModule.getAccumulator(id, false); expect(accum2.accumulated.value).toEqual(accumulated2); @@ -392,7 +425,11 @@ describe("Accumulator Module", () => { keypair.secretKey ).value ); - await chainModule.updateAccumulator( + await (typ === 0 + ? chainModule.updatePositiveAccumulator + : chainModule.updateKBUniversalAccumulator + ).call( + chainModule, id, accumulated3, { @@ -400,6 +437,7 @@ describe("Accumulator Module", () => { removals: removals1, witnessUpdateInfo: witUpd1, }, + [did2, keyId], pair2 ); const accum3 = await chainModule.getAccumulator(id, false); @@ -427,10 +465,15 @@ describe("Accumulator Module", () => { keypair.secretKey ).value ); - await chainModule.updateAccumulator( + await (typ === 0 + ? chainModule.updatePositiveAccumulator + : chainModule.updateKBUniversalAccumulator + ).call( + chainModule, id, accumulated4, { additions: additions2, witnessUpdateInfo: witUpd2 }, + [did2, keyId], pair2 ); const accum4 = await chainModule.getAccumulator(id, false); @@ -455,10 +498,15 @@ describe("Accumulator Module", () => { keypair.secretKey ).value ); - await chainModule.updateAccumulator( + await (typ === 0 + ? chainModule.updatePositiveAccumulator + : chainModule.updateKBUniversalAccumulator + ).call( + chainModule, id, accumulated5, { removals: removals3, witnessUpdateInfo: witUpd3 }, + [did2, keyId], pair2 ); const accum5 = await chainModule.getAccumulator(id, false); @@ -488,10 +536,10 @@ describe("Accumulator Module", () => { accum3.lastModified ); expect(updates2[0].newAccumulated.value).toEqual(accumulated3); - expect(updates2[0].additions.eq(ListOfByteArrays.from(additions1))).toBe( + expect(updates2[0].additions.eq(ArrayOfByteArrays.from(additions1))).toBe( true ); - expect(updates2[0].removals.eq(ListOfByteArrays.from(removals1))).toBe( + expect(updates2[0].removals.eq(ArrayOfByteArrays.from(removals1))).toBe( true ); expect(updates2[0].witnessUpdateInfo.value).toEqual(witUpd1); @@ -510,7 +558,7 @@ describe("Accumulator Module", () => { accum4.lastModified ); expect(updates3[0].newAccumulated.value).toEqual(accumulated4); - expect(updates3[0].additions.eq(ListOfByteArrays.from(additions2))).toBe( + expect(updates3[0].additions.eq(ArrayOfByteArrays.from(additions2))).toBe( true ); expect(updates3[0].removals).toEqual(null); @@ -522,7 +570,7 @@ describe("Accumulator Module", () => { ); expect(updates4[0].newAccumulated.value).toEqual(accumulated5); expect(updates4[0].additions).toEqual(null); - expect(updates4[0].removals.eq(ListOfByteArrays.from(removals3))).toBe( + expect(updates4[0].removals.eq(ArrayOfByteArrays.from(removals3))).toBe( true ); expect(updates4[0].witnessUpdateInfo.value).toEqual(witUpd3); diff --git a/packages/dock-blockchain-modules/tests/integration/anoncreds/demo.test.js b/packages/dock-blockchain-modules/tests/integration/anoncreds/demo.test.js index dbe8f2b38..390a2a1e3 100644 --- a/packages/dock-blockchain-modules/tests/integration/anoncreds/demo.test.js +++ b/packages/dock-blockchain-modules/tests/integration/anoncreds/demo.test.js @@ -381,10 +381,11 @@ for (const { ); const accumulated = u8aToHex(accumulator.accumulated); - await modules.accumulator.updateAccumulator( + await modules.accumulator.updatePositiveAccumulator( accumulatorId, accumulated, { additions: [u8aToHex(encodedAttrs[attributeCount - 1])] }, + [accumulatorManagerDid, 1], accumulatorManagerKeypair ); @@ -434,13 +435,14 @@ for (const { [], accumulatorKeypair.secretKey ); - await modules.accumulator.updateAccumulator( + await modules.accumulator.updatePositiveAccumulator( accumulatorId, u8aToHex(accumulator.accumulated), { additions: [u8aToHex(member1), u8aToHex(member2)], witnessUpdateInfo: u8aToHex(witnessUpdInfo.value), }, + [accumulatorManagerDid, 1], accumulatorManagerKeypair ); @@ -526,7 +528,7 @@ for (const { [member1, member2], accumulatorKeypair.secretKey ); - await modules.accumulator.updateAccumulator( + await modules.accumulator.updatePositiveAccumulator( accumulatorId, u8aToHex(accumulator.accumulated), { @@ -534,6 +536,7 @@ for (const { removals: [u8aToHex(member1), u8aToHex(member2)], witnessUpdateInfo: u8aToHex(witnessUpdInfo.value), }, + [accumulatorManagerDid, 1], accumulatorManagerKeypair ); @@ -556,7 +559,7 @@ for (const { [member4], accumulatorKeypair.secretKey ); - await modules.accumulator.updateAccumulator( + await modules.accumulator.updatePositiveAccumulator( accumulatorId, u8aToHex(accumulator.accumulated), { @@ -564,6 +567,7 @@ for (const { removals: [u8aToHex(member4)], witnessUpdateInfo: u8aToHex(witnessUpdInfo.value), }, + [accumulatorManagerDid, 1], accumulatorManagerKeypair ); @@ -587,7 +591,7 @@ for (const { [], accumulatorKeypair.secretKey ); - await modules.accumulator.updateAccumulator( + await modules.accumulator.updatePositiveAccumulator( accumulatorId, u8aToHex(accumulator.accumulated), { @@ -599,6 +603,7 @@ for (const { removals: [], witnessUpdateInfo: u8aToHex(witnessUpdInfo.value), }, + [accumulatorManagerDid, 1], accumulatorManagerKeypair ); @@ -638,7 +643,7 @@ for (const { accumulatorId, blockNo ); - console.log("!!!!", updates); + const wi = new VBWitnessUpdateInfo( updates[0].witnessUpdateInfo.bytes ); @@ -689,7 +694,7 @@ for (const { [encodedAttrs[attributeCount - 1]], accumulatorKeypair.secretKey ); - await modules.accumulator.updateAccumulator( + await modules.accumulator.updatePositiveAccumulator( accumulatorId, u8aToHex(accumulator.accumulated), { @@ -697,6 +702,7 @@ for (const { removals: [u8aToHex(encodedAttrs[attributeCount - 1])], witnessUpdateInfo: u8aToHex(witnessUpdInfo.value), }, + [accumulatorManagerDid, 1], accumulatorManagerKeypair ); }); diff --git a/packages/dock-blockchain-modules/tests/integration/anoncreds/prefilled-positive-accumulator.test.js b/packages/dock-blockchain-modules/tests/integration/anoncreds/prefilled-positive-accumulator.test.js deleted file mode 100644 index 5a858e43c..000000000 --- a/packages/dock-blockchain-modules/tests/integration/anoncreds/prefilled-positive-accumulator.test.js +++ /dev/null @@ -1,586 +0,0 @@ -import { hexToU8a, stringToHex, u8aToHex } from "@polkadot/util"; -import { - initializeWasm, - Accumulator, - AccumulatorParams, - AccumulatorPublicKey, - PositiveAccumulator, - VBWitnessUpdateInfo, - VBMembershipWitness, -} from "@docknetwork/crypto-wasm-ts"; -import { randomAsHex } from "@polkadot/util-crypto"; -import { InMemoryState } from "@docknetwork/crypto-wasm-ts/lib/accumulator/in-memory-persistence"; -import { DockAPI } from "@docknetwork/dock-blockchain-api"; -import { AccumulatorType } from "@docknetwork/credential-sdk/modules/abstract/accumulator/module"; -import { - FullNodeEndpoint, - TestAccountURI, - TestKeyringOpts, -} from "../../test-constants"; -import { DockDid, DockAccumulatorId } from "@docknetwork/credential-sdk/types"; -import { registerNewDIDUsingPair } from "../helpers"; -import { - getLastBlockNo, - waitForBlocks, -} from "@docknetwork/dock-blockchain-api/utils/chain-ops"; -import { - Ed25519Keypair, - DidKeypair, -} from "@docknetwork/credential-sdk/keypairs"; -import { DockCoreModules } from "../../../src"; -import { AbstractAccumulatorModule as AccumulatorModule } from "@docknetwork/credential-sdk/modules"; - -describe("Prefilled positive accumulator", () => { - // Incase updating an accumulator is expensive like making a blockchain txn, a cheaper strategy - // is to add the members to the accumulator beforehand but not giving out the witnesses yet. - // Eg. accumulator manager wants to add a million members over an year, rather than publishing - // the new accumulator after each addition, the manager can initialize the accumulator with a million - // member ids (member ids are either predictable like monotonically increasing numbers or the manager - // can internally keep a map of random ids like UUIDs to a number). Now when the manager actually - // wants to allow a member to prove membership, he can create a witness for that member but the - // accumulator value remains same and thus the witness for existing members also remain same. It - // should be noted though that changing the accumulator - // value causes change in all existing witnesses and thus its better to make a good estimate - // of the number of members during prefill stage - - // Manager estimates that he will have `total_members` members over the course of time - const totalMembers = 100; - const members = []; - const seed1 = randomAsHex(32); - const seedAccum = randomAsHex(32); - - const dock = new DockAPI(); - const modules = new DockCoreModules(dock); - let account; - const did = DockDid.random(); - const pair = new DidKeypair([did, 1], new Ed25519Keypair(seed1)); - - let chainModule; - const chainModuleClass = AccumulatorModule; - - let keypair; - let accumulatorId; - let accumulator; - const accumState = new InMemoryState(); - - beforeAll(async () => { - await dock.init({ - keyring: TestKeyringOpts, - address: FullNodeEndpoint, - }); - chainModule = modules.accumulator; - account = dock.keyring.addFromUri(TestAccountURI); - dock.setAccount(account); - - await registerNewDIDUsingPair(dock, did, pair); - await initializeWasm(); - }, 20000); - - test("Prefill", async () => { - const label = stringToHex("accumulator-params-label"); - const params = Accumulator.generateParams(hexToU8a(label)); - const bytes1 = u8aToHex(params.bytes); - const params1 = chainModuleClass.prepareAddParameters(bytes1, label); - await chainModule.addParams(null, params1, did, pair); - - keypair = Accumulator.generateKeypair(params, seedAccum); - const bytes2 = u8aToHex(keypair.publicKey.bytes); - const pk1 = chainModuleClass.prepareAddPublicKey(bytes2, [did, 1]); - await chainModule.addPublicKey(null, pk1, did, pair); - - accumulator = PositiveAccumulator.initialize(params, keypair.secretKey); - - for (let i = 1; i <= totalMembers; i++) { - members.push(Accumulator.encodePositiveNumberAsAccumulatorMember(i)); - } - await accumulator.addBatch(members, keypair.secretKey, accumState); - expect(accumState.state.size).toEqual(totalMembers); - - accumulatorId = DockAccumulatorId.random(did); - const accumulated = AccumulatorModule.accumulatedAsHex( - accumulator.accumulated, - AccumulatorType.VBPos - ); - await modules.accumulator.addPositiveAccumulator( - accumulatorId, - accumulated, - [did, 1], - pair - ); - const queriedAccum = await modules.accumulator.getAccumulator( - accumulatorId, - true, - true - ); - expect(queriedAccum.accumulated.value).toEqual(accumulated); - }); - - test("Witness creation, verification should work", async () => { - let queriedAccum = await modules.accumulator.getAccumulator( - accumulatorId, - true, - true - ); - let verifAccumulator = PositiveAccumulator.fromAccumulated( - AccumulatorModule.accumulatedFromHex( - queriedAccum.accumulated, - AccumulatorType.VBPos - ) - ); - - // Witness created for member 1 - const member1 = members[10]; - const witness1 = await accumulator.membershipWitness( - member1, - keypair.secretKey, - accumState - ); - let accumPk = new AccumulatorPublicKey(queriedAccum.publicKey.bytes.bytes); - let accumParams = new AccumulatorParams( - queriedAccum.publicKey.params.bytes.bytes - ); - expect( - verifAccumulator.verifyMembershipWitness( - member1, - witness1, - accumPk, - accumParams - ) - ).toEqual(true); - - // Witness created for member 2 - const member2 = members[25]; - const witness2 = await accumulator.membershipWitness( - member2, - keypair.secretKey, - accumState - ); - expect( - verifAccumulator.verifyMembershipWitness( - member2, - witness2, - accumPk, - accumParams - ) - ).toEqual(true); - - // Witness created for member 3 - const member3 = members[79]; - const witness3 = await accumulator.membershipWitness( - member3, - keypair.secretKey, - accumState - ); - expect( - verifAccumulator.verifyMembershipWitness( - member3, - witness3, - accumPk, - accumParams - ) - ).toEqual(true); - - // Previous users' witness still works - expect( - verifAccumulator.verifyMembershipWitness( - member1, - witness1, - accumPk, - accumParams - ) - ).toEqual(true); - expect( - verifAccumulator.verifyMembershipWitness( - member2, - witness2, - accumPk, - accumParams - ) - ).toEqual(true); - - // Manager decides to remove a member, the new accumulated value will be published along with witness update info - const witnessUpdInfo = VBWitnessUpdateInfo.new( - accumulator.accumulated, - [], - [member2], - keypair.secretKey - ); - await accumulator.remove(member2, keypair.secretKey, accumState); - - let accum = await modules.accumulator.getAccumulator(accumulatorId, false); - const accumulated = AccumulatorModule.accumulatedAsHex( - accumulator.accumulated, - AccumulatorType.VBPos - ); - const witUpdBytes = u8aToHex(witnessUpdInfo.value); - await modules.accumulator.updateAccumulator( - accumulatorId, - accumulated, - { removals: [u8aToHex(member2)], witnessUpdateInfo: witUpdBytes }, - pair - ); - - queriedAccum = await modules.accumulator.getAccumulator( - accumulatorId, - true, - true - ); - expect(queriedAccum.accumulated.value).toEqual(accumulated); - - verifAccumulator = PositiveAccumulator.fromAccumulated( - AccumulatorModule.accumulatedFromHex( - queriedAccum.accumulated, - AccumulatorType.VBPos - ) - ); - - accumPk = new AccumulatorPublicKey(queriedAccum.publicKey.bytes.bytes); - accumParams = new AccumulatorParams( - queriedAccum.publicKey.params.bytes.bytes - ); - - // Witness created for member 3 - const member4 = members[52]; - const witness4 = await accumulator.membershipWitness( - member4, - keypair.secretKey, - accumState - ); - expect( - verifAccumulator.verifyMembershipWitness( - member4, - witness4, - accumPk, - accumParams - ) - ).toEqual(true); - - // Older witnesses need to be updated - accum = await modules.accumulator.getAccumulator(accumulatorId); - const updates = await modules.accumulator.dockOnly.getUpdatesFromBlock( - accumulatorId, - accum.lastModified - ); - const additions = []; - const removals = []; - if (updates[0].additions !== null) { - for (const a of updates[0].additions) { - additions.push(a); - } - } - if (updates[0].removals !== null) { - for (const a of updates[0].removals) { - removals.push(a); - } - } - const queriedWitnessInfo = new VBWitnessUpdateInfo( - updates[0].witnessUpdateInfo.bytes - ); - - witness1.updateUsingPublicInfoPostBatchUpdate( - member1, - additions, - removals, - queriedWitnessInfo - ); - expect( - verifAccumulator.verifyMembershipWitness( - member1, - witness1, - accumPk, - accumParams - ) - ).toEqual(true); - - witness3.updateUsingPublicInfoPostBatchUpdate( - member3, - additions, - removals, - queriedWitnessInfo - ); - expect( - verifAccumulator.verifyMembershipWitness( - member3, - witness3, - accumPk, - accumParams - ) - ).toEqual(true); - }); - - test("Witness update after several batch upgrades", async () => { - let queriedAccum = await modules.accumulator.getAccumulator( - accumulatorId, - true, - true - ); - let verifAccumulator = PositiveAccumulator.fromAccumulated( - AccumulatorModule.accumulatedFromHex( - queriedAccum.accumulated, - AccumulatorType.VBPos - ) - ); - const member = members[10]; - let witness = await accumulator.membershipWitness( - member, - keypair.secretKey, - accumState - ); - - const accumPk = new AccumulatorPublicKey( - queriedAccum.publicKey.bytes.bytes - ); - const accumParams = new AccumulatorParams( - queriedAccum.publicKey.params.bytes.bytes - ); - expect( - verifAccumulator.verifyMembershipWitness( - member, - witness, - accumPk, - accumParams - ) - ).toEqual(true); - - await waitForBlocks(dock.api, 2); - - // Do some updates to the accumulator - - const removals1 = [members[85], members[86]]; - const witnessUpdInfo1 = VBWitnessUpdateInfo.new( - accumulator.accumulated, - [], - removals1, - keypair.secretKey - ); - await accumulator.removeBatch(removals1, keypair.secretKey, accumState); - const accumulated1 = AccumulatorModule.accumulatedAsHex( - accumulator.accumulated, - AccumulatorType.VBPos - ); - const witUpdBytes1 = u8aToHex(witnessUpdInfo1.value); - await modules.accumulator.updateAccumulator( - accumulatorId, - accumulated1, - { - removals: removals1.map((r) => u8aToHex(r)), - witnessUpdateInfo: witUpdBytes1, - }, - pair - ); - - await waitForBlocks(dock.api, 5); - - const removals2 = [members[87], members[88]]; - const witnessUpdInfo2 = VBWitnessUpdateInfo.new( - accumulator.accumulated, - [], - removals2, - keypair.secretKey - ); - await accumulator.removeBatch(removals2, keypair.secretKey, accumState); - const accumulated2 = AccumulatorModule.accumulatedAsHex( - accumulator.accumulated, - AccumulatorType.VBPos - ); - const witUpdBytes2 = u8aToHex(witnessUpdInfo2.value); - await modules.accumulator.updateAccumulator( - accumulatorId, - accumulated2, - { - removals: removals2.map((r) => u8aToHex(r)), - witnessUpdateInfo: witUpdBytes2, - }, - pair - ); - - await waitForBlocks(dock.api, 5); - - const removals3 = [members[89], members[90], members[91]]; - const witnessUpdInfo3 = VBWitnessUpdateInfo.new( - accumulator.accumulated, - [], - removals3, - keypair.secretKey - ); - await accumulator.removeBatch(removals3, keypair.secretKey, accumState); - const accumulated3 = AccumulatorModule.accumulatedAsHex( - accumulator.accumulated, - AccumulatorType.VBPos - ); - const witUpdBytes3 = u8aToHex(witnessUpdInfo3.value); - await modules.accumulator.updateAccumulator( - accumulatorId, - accumulated3, - { - removals: removals3.map((r) => u8aToHex(r)), - witnessUpdateInfo: witUpdBytes3, - }, - pair - ); - - // Get a witness from the accumulator manager. This will be updated after the following updates. - witness = await accumulator.membershipWitness( - member, - keypair.secretKey, - accumState - ); - // The user should be told the block number from which he is supposed to update the witnesses from. It will be one block - // ahead from where the last update was posted. - const blockNoToUpdateFrom = (await getLastBlockNo(dock.api)) + 1; - - await waitForBlocks(dock.api, 5); - - // Do some more updates to the accumulator - const removals4 = [members[92], members[93]]; - const witnessUpdInfo4 = VBWitnessUpdateInfo.new( - accumulator.accumulated, - [], - removals4, - keypair.secretKey - ); - await accumulator.removeBatch(removals4, keypair.secretKey, accumState); - const accumulated4 = AccumulatorModule.accumulatedAsHex( - accumulator.accumulated, - AccumulatorType.VBPos - ); - const witUpdBytes4 = u8aToHex(witnessUpdInfo4.value); - await modules.accumulator.updateAccumulator( - accumulatorId, - accumulated4, - { - removals: removals4.map((r) => u8aToHex(r)), - witnessUpdateInfo: witUpdBytes4, - }, - pair - ); - console.log(`Updated witness at block ${await getLastBlockNo(dock.api)}`); - await waitForBlocks(dock.api, 5); - - const removals5 = [members[94], members[95], members[96]]; - const witnessUpdInfo5 = VBWitnessUpdateInfo.new( - accumulator.accumulated, - [], - removals5, - keypair.secretKey - ); - await accumulator.removeBatch(removals5, keypair.secretKey, accumState); - const accumulated5 = AccumulatorModule.accumulatedAsHex( - accumulator.accumulated, - AccumulatorType.VBPos - ); - const witUpdBytes5 = u8aToHex(witnessUpdInfo5.value); - await modules.accumulator.updateAccumulator( - accumulatorId, - accumulated5, - { - removals: removals5.map((r) => u8aToHex(r)), - witnessUpdateInfo: witUpdBytes5, - }, - pair - ); - console.log(`Updated witness at block ${await getLastBlockNo(dock.api)}`); - await waitForBlocks(dock.api, 5); - - queriedAccum = await modules.accumulator.getAccumulator( - accumulatorId, - true, - true - ); - verifAccumulator = PositiveAccumulator.fromAccumulated( - AccumulatorModule.accumulatedFromHex( - queriedAccum.accumulated, - AccumulatorType.VBPos - ) - ); - // Old witness doesn't verify with new accumulator - expect( - verifAccumulator.verifyMembershipWitness( - member, - witness, - accumPk, - accumParams - ) - ).toEqual(false); - - const oldWitness1 = new VBMembershipWitness(witness.value); - const oldWitness2 = new VBMembershipWitness(witness.value); - const oldWitness3 = new VBMembershipWitness(witness.value); - - // Update witness by downloading necessary blocks and applying the updates if found - await modules.accumulator.updateWitness( - accumulatorId, - member, - witness, - blockNoToUpdateFrom, - queriedAccum.lastModified - ); - - // Updated witness verifies with new accumulator - expect( - verifAccumulator.verifyMembershipWitness( - member, - witness, - accumPk, - accumParams - ) - ).toEqual(true); - - // Test again with a batch size bigger than the total number of blocks - await modules.accumulator.updateWitness( - accumulatorId, - member, - oldWitness1, - blockNoToUpdateFrom, - queriedAccum.lastModified, - queriedAccum.lastModified - blockNoToUpdateFrom + 10 - ); - expect( - verifAccumulator.verifyMembershipWitness( - member, - oldWitness1, - accumPk, - accumParams - ) - ).toEqual(true); - - // Test again with few other batch sizes - await modules.accumulator.updateWitness( - accumulatorId, - member, - oldWitness2, - blockNoToUpdateFrom, - queriedAccum.lastModified, - 3 - ); - expect( - verifAccumulator.verifyMembershipWitness( - member, - oldWitness2, - accumPk, - accumParams - ) - ).toEqual(true); - - await modules.accumulator.updateWitness( - accumulatorId, - member, - oldWitness3, - blockNoToUpdateFrom, - queriedAccum.lastModified, - 4 - ); - expect( - verifAccumulator.verifyMembershipWitness( - member, - oldWitness3, - accumPk, - accumParams - ) - ).toEqual(true); - }, 60000); - - afterAll(async () => { - await dock.disconnect(); - }, 10000); -}); diff --git a/packages/dock-blockchain-modules/tests/integration/did/service-endpoint.test.js b/packages/dock-blockchain-modules/tests/integration/did/service-endpoint.test.js index f7090e30d..403ed6f36 100644 --- a/packages/dock-blockchain-modules/tests/integration/did/service-endpoint.test.js +++ b/packages/dock-blockchain-modules/tests/integration/did/service-endpoint.test.js @@ -175,7 +175,6 @@ describe("DID service endpoints", () => { }); test("Remove service endpoint", async () => { - console.log(spId2); // `dockDid1` removes service endpoint of `dockDid2` await modules.did.dockOnly.removeServiceEndpoint(spId2, pair1); await expect( diff --git a/packages/dock-blockchain-modules/tests/integration/modules/accumulator-module.test.js b/packages/dock-blockchain-modules/tests/integration/modules/accumulator-module.test.js new file mode 100644 index 000000000..9b66868f7 --- /dev/null +++ b/packages/dock-blockchain-modules/tests/integration/modules/accumulator-module.test.js @@ -0,0 +1,65 @@ +import { DockAPI } from "@docknetwork/dock-blockchain-api"; +import { + DockAccumulatorId, + DockAccumulatorPublicKey, + DockAccumulatorCommon, + DockDid, +} from "@docknetwork/credential-sdk/types"; +import { + MultiApiDIDModule, + MultiApiAccumulatorModule, +} from "@docknetwork/credential-sdk/modules"; +import generateAccumulatorModuleTests from "@docknetwork/credential-sdk/modules/tests/accumulator-module"; +import { DockDIDModule, DockAccumulatorModule } from "../../../src"; +import { + FullNodeEndpoint, + TestAccountURI, + TestKeyringOpts, +} from "../../test-constants"; +import { initializeWasm } from "@docknetwork/credential-sdk/crypto"; + +describe("AccumulatorModule", () => { + const dock = new DockAPI(); + + beforeAll(async () => { + await dock.init({ + keyring: TestKeyringOpts, + address: FullNodeEndpoint, + }); + const account = dock.keyring.addFromUri(TestAccountURI); + dock.setAccount(account); + await initializeWasm(); + }); + + afterAll(async () => { + await dock.disconnect(); + }); + + generateAccumulatorModuleTests( + { + did: new DockDIDModule(dock), + accumulator: new DockAccumulatorModule(dock), + }, + { + DID: DockDid, + PublicKey: DockAccumulatorPublicKey, + AccumulatorId: DockAccumulatorId, + AccumulatorCommon: DockAccumulatorCommon, + } + ); + + generateAccumulatorModuleTests( + { + did: new MultiApiDIDModule([new DockDIDModule(dock)]), + accumulator: new MultiApiAccumulatorModule([ + new DockAccumulatorModule(dock), + ]), + }, + { + DID: DockDid, + PublicKey: DockAccumulatorPublicKey, + AccumulatorId: DockAccumulatorId, + AccumulatorCommon: DockAccumulatorCommon, + } + ); +}); diff --git a/packages/dock-blockchain-modules/tests/integration/status-list-credential.test.js b/packages/dock-blockchain-modules/tests/integration/status-list-credential.test.js index b5d633f98..4ffcc66a0 100644 --- a/packages/dock-blockchain-modules/tests/integration/status-list-credential.test.js +++ b/packages/dock-blockchain-modules/tests/integration/status-list-credential.test.js @@ -21,7 +21,10 @@ import { DockCoreModules } from "../../src"; import { getUnsignedCred, registerNewDIDUsingPair } from "./helpers"; import { getKeyDoc } from "@docknetwork/credential-sdk/vc/helpers"; import { DockDid } from "@docknetwork/credential-sdk/types"; -import { DockStatusList2021Credential, DockStatusListCredentialId } from "@docknetwork/credential-sdk/types"; +import { + DockStatusList2021Credential, + DockStatusListCredentialId, +} from "@docknetwork/credential-sdk/types"; import { addStatusList21EntryToCredential } from "@docknetwork/credential-sdk/vc/credentials"; import { Ed25519Keypair, @@ -135,7 +138,6 @@ describe("DockStatusList2021Credential", () => { compactProof: true, }); - console.log(result.error); expect(result.verified).toBe(true); // Revoke the credential