@coordination-network/credential 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,6 +11,7 @@ yarn add @coordination-network/credential
11
11
  ## Supported credential flow
12
12
 
13
13
  - `issueZkCredential` validates the holder bindings and signs the credential commitments.
14
+ - `issuanceRequestCommitment` hashes the issuer and the approval scope into the value the wallet's device signature covers; `issueZkCredential` recomputes it from its own id and the scope it signs.
14
15
  - `buildZkPresentationInputs` constructs private circuit inputs on the holder device.
15
16
  - `ZkCredentialProofSystem` creates and verifies the zero-knowledge presentation.
16
17
  - `ApprovedIssuerSet` and the revocation helpers provide the membership witnesses used by the circuit.
@@ -1,9 +1,13 @@
1
- import { Jwk, SerializedZkCredential, ZkCredential, ZkCredentialBody } from "./types";
1
+ import { ApprovalScope, Hex32, Jwk, SerializedZkCredential, ZkCredential, ZkCredentialBody } from "./types";
2
2
  export declare const CREDENTIAL_DOMAIN = 363566488821926773809509531830640889725124777157045803797738936823559174026n;
3
+ export declare const ISSUANCE_REQUEST_DOMAIN = "urn:cn:issuance-request:v1";
4
+ export declare const DEVICE_ATTESTATION_CHALLENGE = "urn:cn:device-attestation:v1";
3
5
  export declare const DEVICE_KEY_DOMAIN = 320507632879070882430325612169297945123468706597652794395401074724051126576n;
4
6
  export declare function stringToField(value: string): bigint;
5
7
  export declare function programmeHash(programme: string): bigint;
6
8
  export declare function countryCode(country: string): number;
9
+ /** What the device signature commits to besides the holder key: the issuer and the scope shown at consent. */
10
+ export declare function issuanceRequestCommitment(issuerNetworkId: Hex32, approvalScope: ApprovalScope): Uint8Array;
7
11
  export declare function deriveDeviceKeyCommitment(publicKey: Jwk): bigint;
8
12
  export declare function zkCredentialCommitment(body: ZkCredentialBody): bigint;
9
13
  export declare function serializeZkCredential(credential: ZkCredential): SerializedZkCredential;
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEVICE_KEY_DOMAIN = exports.CREDENTIAL_DOMAIN = void 0;
3
+ exports.DEVICE_KEY_DOMAIN = exports.DEVICE_ATTESTATION_CHALLENGE = exports.ISSUANCE_REQUEST_DOMAIN = exports.CREDENTIAL_DOMAIN = void 0;
4
4
  exports.stringToField = stringToField;
5
5
  exports.programmeHash = programmeHash;
6
6
  exports.countryCode = countryCode;
7
+ exports.issuanceRequestCommitment = issuanceRequestCommitment;
7
8
  exports.deriveDeviceKeyCommitment = deriveDeviceKeyCommitment;
8
9
  exports.zkCredentialCommitment = zkCredentialCommitment;
9
10
  exports.serializeZkCredential = serializeZkCredential;
@@ -18,6 +19,10 @@ const utils_1 = require("@noble/hashes/utils");
18
19
  const crypto_1 = require("./crypto");
19
20
  // keccak256(utf8("urn:cn:credential:v1")) >> 8
20
21
  exports.CREDENTIAL_DOMAIN = 363566488821926773809509531830640889725124777157045803797738936823559174026n;
22
+ exports.ISSUANCE_REQUEST_DOMAIN = "urn:cn:issuance-request:v1";
23
+ // Android Key Attestation challenge for the device signing key, as UTF-8 bytes. Fixed because the key is
24
+ // attested once, at generation; the device signature over the issuer nonce supplies freshness.
25
+ exports.DEVICE_ATTESTATION_CHALLENGE = "urn:cn:device-attestation:v1";
21
26
  exports.DEVICE_KEY_DOMAIN = 320507632879070882430325612169297945123468706597652794395401074724051126576n;
22
27
  const FIELD_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
23
28
  function stringToField(value) {
@@ -33,6 +38,17 @@ function countryCode(country) {
33
38
  throw new Error("country must be an ISO 3166-1 alpha-3 code");
34
39
  return [...country].reduce((value, character) => (value << 8) | character.charCodeAt(0), 0);
35
40
  }
41
+ /** What the device signature commits to besides the holder key: the issuer and the scope shown at consent. */
42
+ function issuanceRequestCommitment(issuerNetworkId, approvalScope) {
43
+ if (!/^0x[0-9a-fA-F]{64}$/.test(issuerNetworkId))
44
+ throw new Error("issuerNetworkId must be 32 bytes");
45
+ return (0, viem_1.hexToBytes)((0, viem_1.keccak256)((0, viem_1.concatHex)([
46
+ (0, viem_1.stringToHex)(exports.ISSUANCE_REQUEST_DOMAIN),
47
+ issuerNetworkId,
48
+ (0, viem_1.toHex)(programmeHash(approvalScope.programme), { size: 32 }),
49
+ (0, viem_1.toHex)(countryCode(approvalScope.country), { size: 32 })
50
+ ])));
51
+ }
36
52
  const bytesToBigint = (value) => BigInt(`0x${(0, utils_1.bytesToHex)(value)}`);
37
53
  function deriveDeviceKeyCommitment(publicKey) {
38
54
  const encoded = (0, crypto_1.jwkToPublicKeyBytes)(publicKey);
package/dist/errors.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type IssuanceErrorCode = "INVALID_PARAMS" | "ISSUER_NOT_APPROVED" | "SUBJECT_MISMATCH" | "BINDING_INVALID" | "COMMITMENT_OWNERSHIP_INVALID";
1
+ export type IssuanceErrorCode = "INVALID_PARAMS" | "ISSUER_NOT_APPROVED" | "SUBJECT_MISMATCH" | "BINDING_INVALID" | "HOLDER_KEY_OWNERSHIP_INVALID" | "COMMITMENT_OWNERSHIP_INVALID";
2
2
  export declare class IssuanceError extends Error {
3
3
  readonly code: IssuanceErrorCode;
4
4
  constructor(code: IssuanceErrorCode, message: string);
@@ -0,0 +1,12 @@
1
+ import type { EddsaPublicKey, EddsaSignature } from "./types";
2
+ /** Proves possession of the holder identity key bound into a credential request. */
3
+ export declare function signHolderKeyOwnership(identitySeed: Uint8Array, nonce: Uint8Array): {
4
+ publicKey: EddsaPublicKey;
5
+ signature: EddsaSignature;
6
+ };
7
+ /** Verifies that the requestor controls the supplied Baby Jubjub holder public key. */
8
+ export declare function verifyHolderKeyOwnership(params: {
9
+ publicKey: EddsaPublicKey;
10
+ signature: EddsaSignature;
11
+ nonce: Uint8Array;
12
+ }): boolean;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.signHolderKeyOwnership = signHolderKeyOwnership;
4
+ exports.verifyHolderKeyOwnership = verifyHolderKeyOwnership;
5
+ const identity_1 = require("@semaphore-protocol/identity");
6
+ const revocation_1 = require("./revocation");
7
+ /** Proves possession of the holder identity key bound into a credential request. */
8
+ function signHolderKeyOwnership(identitySeed, nonce) {
9
+ if (identitySeed.length !== 32)
10
+ throw new Error("identity seed must be 32 bytes");
11
+ const identity = new identity_1.Identity(identitySeed);
12
+ const signature = identity.signMessage((0, revocation_1.nonceToBigint)(nonce));
13
+ return {
14
+ publicKey: [identity.publicKey[0], identity.publicKey[1]],
15
+ signature: { R8: [signature.R8[0], signature.R8[1]], S: signature.S }
16
+ };
17
+ }
18
+ /** Verifies that the requestor controls the supplied Baby Jubjub holder public key. */
19
+ function verifyHolderKeyOwnership(params) {
20
+ try {
21
+ return identity_1.Identity.verifySignature((0, revocation_1.nonceToBigint)(params.nonce), params.signature, params.publicKey);
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
package/dist/index.d.ts CHANGED
@@ -12,3 +12,4 @@ export * from "./resolver";
12
12
  export * from "./registry-client";
13
13
  export * from "./crypto";
14
14
  export * from "./revocation";
15
+ export * from "./holder-ownership";
package/dist/index.js CHANGED
@@ -28,3 +28,4 @@ __exportStar(require("./resolver"), exports);
28
28
  __exportStar(require("./registry-client"), exports);
29
29
  __exportStar(require("./crypto"), exports);
30
30
  __exportStar(require("./revocation"), exports);
31
+ __exportStar(require("./holder-ownership"), exports);
@@ -11,6 +11,7 @@ const credential_message_1 = require("./credential-message");
11
11
  const crypto_1 = require("./crypto");
12
12
  const errors_1 = require("./errors");
13
13
  const revocation_1 = require("./revocation");
14
+ const holder_ownership_1 = require("./holder-ownership");
14
15
  /** Validates an issuance request without signing it. */
15
16
  async function validateZkCredentialIssue(params, approval) {
16
17
  if (!(await approval.isApprovedNetworkMember(params.issuerNetworkId))) {
@@ -31,6 +32,7 @@ async function validateZkCredentialIssue(params, approval) {
31
32
  accountPublicKey: params.accountPublicKey,
32
33
  confirmationKey: (0, crypto_1.jwkToPublicKeyBytes)(params.confirmationKey),
33
34
  holderKeyCommitment: holderKeyCommitmentBytes,
35
+ requestCommitment: (0, credential_message_1.issuanceRequestCommitment)(params.issuerNetworkId, params.approvalScope),
34
36
  accountSignature: params.accountPopSignature,
35
37
  deviceSignature: params.devicePopSignature
36
38
  });
@@ -47,6 +49,13 @@ async function validateZkCredentialIssue(params, approval) {
47
49
  if (!binding.valid) {
48
50
  throw new errors_1.IssuanceError("BINDING_INVALID", "credential binding failed");
49
51
  }
52
+ if (!(0, holder_ownership_1.verifyHolderKeyOwnership)({
53
+ publicKey: params.identityPublicKey,
54
+ signature: params.identityOwnershipSignature,
55
+ nonce: params.nonce
56
+ })) {
57
+ throw new errors_1.IssuanceError("HOLDER_KEY_OWNERSHIP_INVALID", "holder key ownership proof failed");
58
+ }
50
59
  const ownershipOk = (0, revocation_1.verifyCommitmentOwnership)({
51
60
  commitment: params.revocation.commitment,
52
61
  publicKey: params.revocation.publicKey,
@@ -1,4 +1,5 @@
1
1
  import { EddsaPublicKey, EddsaSignature } from "./types";
2
+ export declare function nonceToBigint(nonce: Uint8Array): bigint;
2
3
  /** Generates a fresh 32-byte revocation secret r. Device-side only. */
3
4
  export declare function generateRevocationSecret(): Uint8Array;
4
5
  /** commitment = new Identity(r).commitment (Semaphore V4). */
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nonceToBigint = nonceToBigint;
3
4
  exports.generateRevocationSecret = generateRevocationSecret;
4
5
  exports.deriveRevocationCommitment = deriveRevocationCommitment;
5
6
  exports.signCommitmentOwnership = signCommitmentOwnership;
package/dist/types.d.ts CHANGED
@@ -40,6 +40,8 @@ export interface IssuanceRequest {
40
40
  }
41
41
  export interface ZkCredentialIssueParams extends IssuanceRequest {
42
42
  identityPublicKey: EddsaPublicKey;
43
+ /** Signature over the issuer challenge proving control of `identityPublicKey`. */
44
+ identityOwnershipSignature: EddsaSignature;
43
45
  }
44
46
  export interface ZkCredentialBody {
45
47
  programmeHash: bigint;
package/dist/zk-proof.js CHANGED
@@ -77,9 +77,16 @@ class ZkCredentialProofSystem {
77
77
  return new ZkCredentialProofSystem(new Noir(circuit), new UltraHonkBackend(circuit.bytecode, api), api, options.proof);
78
78
  }
79
79
  async generateProof(inputs) {
80
- const { witness } = await this.noir.execute(inputs);
81
- const proof = await this.backend.generateProof(witness, this.proofOptions);
82
- return { proof: proof.proof, publicInputs: [...proof.publicInputs] };
80
+ try {
81
+ const { witness } = await this.noir.execute(inputs);
82
+ const proof = await this.backend.generateProof(witness, this.proofOptions);
83
+ return { proof: proof.proof, publicInputs: [...proof.publicInputs] };
84
+ }
85
+ catch {
86
+ // The circuit's assertion text distinguishes "not yet valid" from "expired".
87
+ // Both outcomes must be indistinguishable to the verifier that chose currentTime.
88
+ throw new Error("presentation proof failed");
89
+ }
83
90
  }
84
91
  async verifyProof(proof, expected) {
85
92
  const mismatch = publicInputMismatch(proof, expected);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coordination-network/credential",
3
- "version": "0.0.2",
3
+ "version": "0.1.0",
4
4
  "description": "Verifiable credential SDK for the coordination network: issuance, verification, holder binding and revocation helpers.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -30,7 +30,7 @@
30
30
  "test": "jest"
31
31
  },
32
32
  "devDependencies": {
33
- "@coordination-network/zk-artifacts": "^0.0.2",
33
+ "@coordination-network/zk-artifacts": "^0.1.0",
34
34
  "@types/jest": "^30.0.0",
35
35
  "@types/node": "^24.0.0",
36
36
  "@zk-kit/baby-jubjub": "1.0.3",
@@ -40,12 +40,12 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@aztec/bb.js": "5.0.0",
43
- "@coordination-network/identity": "^0.0.2",
44
- "@coordination-network/issuer-set": "^0.0.2",
43
+ "@coordination-network/identity": "^0.1.0",
44
+ "@coordination-network/issuer-set": "^0.1.0",
45
45
  "@noble/curves": "^1.9.7",
46
46
  "@noble/hashes": "^1.8.0",
47
47
  "@noir-lang/noir_js": "1.0.0-beta.22",
48
- "@semaphore-protocol/identity": "^4.14.3",
48
+ "@semaphore-protocol/identity": "^4.14.4",
49
49
  "poseidon-lite": "0.3.0",
50
50
  "viem": "^2.55.11"
51
51
  }
@@ -2,13 +2,17 @@ import { Identity } from "@semaphore-protocol/identity"
2
2
  import { poseidon3 } from "poseidon-lite/poseidon3"
3
3
  import { poseidon4 } from "poseidon-lite/poseidon4"
4
4
  import { poseidon5 } from "poseidon-lite/poseidon5"
5
- import { keccak256, stringToHex } from "viem"
5
+ import { concatHex, hexToBytes, keccak256, stringToHex, toHex } from "viem"
6
6
  import { bytesToHex } from "@noble/hashes/utils"
7
7
  import { jwkToPublicKeyBytes } from "./crypto"
8
- import { Jwk, SerializedZkCredential, ZkCredential, ZkCredentialBody } from "./types"
8
+ import { ApprovalScope, Hex32, Jwk, SerializedZkCredential, ZkCredential, ZkCredentialBody } from "./types"
9
9
 
10
10
  // keccak256(utf8("urn:cn:credential:v1")) >> 8
11
11
  export const CREDENTIAL_DOMAIN = 363566488821926773809509531830640889725124777157045803797738936823559174026n
12
+ export const ISSUANCE_REQUEST_DOMAIN = "urn:cn:issuance-request:v1"
13
+ // Android Key Attestation challenge for the device signing key, as UTF-8 bytes. Fixed because the key is
14
+ // attested once, at generation; the device signature over the issuer nonce supplies freshness.
15
+ export const DEVICE_ATTESTATION_CHALLENGE = "urn:cn:device-attestation:v1"
12
16
  export const DEVICE_KEY_DOMAIN = 320507632879070882430325612169297945123468706597652794395401074724051126576n
13
17
  const FIELD_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n
14
18
  export function stringToField(value: string): bigint {
@@ -25,6 +29,22 @@ export function countryCode(country: string): number {
25
29
  return [...country].reduce((value, character) => (value << 8) | character.charCodeAt(0), 0)
26
30
  }
27
31
 
32
+ /** What the device signature commits to besides the holder key: the issuer and the scope shown at consent. */
33
+ export function issuanceRequestCommitment(issuerNetworkId: Hex32, approvalScope: ApprovalScope): Uint8Array {
34
+ if (!/^0x[0-9a-fA-F]{64}$/.test(issuerNetworkId)) throw new Error("issuerNetworkId must be 32 bytes")
35
+
36
+ return hexToBytes(
37
+ keccak256(
38
+ concatHex([
39
+ stringToHex(ISSUANCE_REQUEST_DOMAIN),
40
+ issuerNetworkId,
41
+ toHex(programmeHash(approvalScope.programme), { size: 32 }),
42
+ toHex(countryCode(approvalScope.country), { size: 32 })
43
+ ])
44
+ )
45
+ )
46
+ }
47
+
28
48
  const bytesToBigint = (value: Uint8Array): bigint => BigInt(`0x${bytesToHex(value)}`)
29
49
 
30
50
  export function deriveDeviceKeyCommitment(publicKey: Jwk): bigint {
package/src/errors.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  export type IssuanceErrorCode =
2
- "INVALID_PARAMS" | "ISSUER_NOT_APPROVED" | "SUBJECT_MISMATCH" | "BINDING_INVALID" | "COMMITMENT_OWNERSHIP_INVALID"
2
+ | "INVALID_PARAMS"
3
+ | "ISSUER_NOT_APPROVED"
4
+ | "SUBJECT_MISMATCH"
5
+ | "BINDING_INVALID"
6
+ | "HOLDER_KEY_OWNERSHIP_INVALID"
7
+ | "COMMITMENT_OWNERSHIP_INVALID"
3
8
 
4
9
  export class IssuanceError extends Error {
5
10
  readonly code: IssuanceErrorCode
@@ -0,0 +1,30 @@
1
+ import { Identity } from "@semaphore-protocol/identity"
2
+ import { nonceToBigint } from "./revocation"
3
+ import type { EddsaPublicKey, EddsaSignature } from "./types"
4
+
5
+ /** Proves possession of the holder identity key bound into a credential request. */
6
+ export function signHolderKeyOwnership(
7
+ identitySeed: Uint8Array,
8
+ nonce: Uint8Array
9
+ ): { publicKey: EddsaPublicKey; signature: EddsaSignature } {
10
+ if (identitySeed.length !== 32) throw new Error("identity seed must be 32 bytes")
11
+ const identity = new Identity(identitySeed)
12
+ const signature = identity.signMessage(nonceToBigint(nonce))
13
+ return {
14
+ publicKey: [identity.publicKey[0], identity.publicKey[1]],
15
+ signature: { R8: [signature.R8[0], signature.R8[1]], S: signature.S }
16
+ }
17
+ }
18
+
19
+ /** Verifies that the requestor controls the supplied Baby Jubjub holder public key. */
20
+ export function verifyHolderKeyOwnership(params: {
21
+ publicKey: EddsaPublicKey
22
+ signature: EddsaSignature
23
+ nonce: Uint8Array
24
+ }): boolean {
25
+ try {
26
+ return Identity.verifySignature(nonceToBigint(params.nonce), params.signature, params.publicKey)
27
+ } catch {
28
+ return false
29
+ }
30
+ }
package/src/index.ts CHANGED
@@ -12,3 +12,4 @@ export * from "./resolver"
12
12
  export * from "./registry-client"
13
13
  export * from "./crypto"
14
14
  export * from "./revocation"
15
+ export * from "./holder-ownership"
@@ -3,11 +3,18 @@ import { inPrimeOrderSubgroup } from "@coordination-network/issuer-set"
3
3
  import { hexToBytes } from "@noble/hashes/utils"
4
4
  import { Identity } from "@semaphore-protocol/identity"
5
5
  import { CredentialSigner } from "./credential-signer"
6
- import { countryCode, deriveDeviceKeyCommitment, programmeHash, zkCredentialCommitment } from "./credential-message"
6
+ import {
7
+ countryCode,
8
+ deriveDeviceKeyCommitment,
9
+ issuanceRequestCommitment,
10
+ programmeHash,
11
+ zkCredentialCommitment
12
+ } from "./credential-message"
7
13
  import { jwkToPublicKeyBytes } from "./crypto"
8
14
  import { IssuanceError } from "./errors"
9
15
  import { ApprovalResolver } from "./resolver"
10
16
  import { verifyCommitmentOwnership } from "./revocation"
17
+ import { verifyHolderKeyOwnership } from "./holder-ownership"
11
18
  import { ZkCredential, ZkCredentialBody, ZkCredentialIssueParams } from "./types"
12
19
 
13
20
  /** Validates an issuance request without signing it. */
@@ -38,6 +45,7 @@ export async function validateZkCredentialIssue(
38
45
  accountPublicKey: params.accountPublicKey,
39
46
  confirmationKey: jwkToPublicKeyBytes(params.confirmationKey),
40
47
  holderKeyCommitment: holderKeyCommitmentBytes,
48
+ requestCommitment: issuanceRequestCommitment(params.issuerNetworkId, params.approvalScope),
41
49
  accountSignature: params.accountPopSignature,
42
50
  deviceSignature: params.devicePopSignature
43
51
  })
@@ -55,6 +63,16 @@ export async function validateZkCredentialIssue(
55
63
  throw new IssuanceError("BINDING_INVALID", "credential binding failed")
56
64
  }
57
65
 
66
+ if (
67
+ !verifyHolderKeyOwnership({
68
+ publicKey: params.identityPublicKey,
69
+ signature: params.identityOwnershipSignature,
70
+ nonce: params.nonce
71
+ })
72
+ ) {
73
+ throw new IssuanceError("HOLDER_KEY_OWNERSHIP_INVALID", "holder key ownership proof failed")
74
+ }
75
+
58
76
  const ownershipOk = verifyCommitmentOwnership({
59
77
  commitment: params.revocation.commitment,
60
78
  publicKey: params.revocation.publicKey,
package/src/revocation.ts CHANGED
@@ -16,7 +16,7 @@ function assertSecret(r: Uint8Array): void {
16
16
  }
17
17
  }
18
18
 
19
- function nonceToBigint(nonce: Uint8Array): bigint {
19
+ export function nonceToBigint(nonce: Uint8Array): bigint {
20
20
  if (nonce.length === 0) {
21
21
  throw new Error("nonce must not be empty")
22
22
  }
package/src/types.ts CHANGED
@@ -47,6 +47,8 @@ export interface IssuanceRequest {
47
47
 
48
48
  export interface ZkCredentialIssueParams extends IssuanceRequest {
49
49
  identityPublicKey: EddsaPublicKey
50
+ /** Signature over the issuer challenge proving control of `identityPublicKey`. */
51
+ identityOwnershipSignature: EddsaSignature
50
52
  }
51
53
 
52
54
  export interface ZkCredentialBody {
package/src/zk-proof.ts CHANGED
@@ -140,9 +140,15 @@ export class ZkCredentialProofSystem {
140
140
  }
141
141
 
142
142
  async generateProof(inputs: ZkPresentationInputs): Promise<ZkPresentationProof> {
143
- const { witness } = await this.noir.execute(inputs as unknown as InputMap)
144
- const proof = await this.backend.generateProof(witness, this.proofOptions)
145
- return { proof: proof.proof, publicInputs: [...proof.publicInputs] }
143
+ try {
144
+ const { witness } = await this.noir.execute(inputs as unknown as InputMap)
145
+ const proof = await this.backend.generateProof(witness, this.proofOptions)
146
+ return { proof: proof.proof, publicInputs: [...proof.publicInputs] }
147
+ } catch {
148
+ // The circuit's assertion text distinguishes "not yet valid" from "expired".
149
+ // Both outcomes must be indistinguishable to the verifier that chose currentTime.
150
+ throw new Error("presentation proof failed")
151
+ }
146
152
  }
147
153
 
148
154
  async verifyProof(