|
1 | | -import { type Proof, type Context, RECLAIM_EXTENSION_ACTIONS, ExtensionMessage } from './utils/interfaces' |
| 1 | +import { type Proof, type Context, RECLAIM_EXTENSION_ACTIONS, ExtensionMessage, ProviderVersionInfo } from './utils/interfaces' |
2 | 2 | import { |
3 | 3 | ProofRequestOptions, |
4 | 4 | StartSessionParams, |
@@ -34,69 +34,83 @@ import { |
34 | 34 | SignatureGeneratingError, |
35 | 35 | SignatureNotFoundError, |
36 | 36 | ErrorDuringVerificationError, |
37 | | - CallbackUrlRequiredError |
38 | | -} from './utils/errors' |
| 37 | + CallbackUrlRequiredError, |
| 38 | + ProofNotValidatedError |
| 39 | +} from './utils/errors'; |
39 | 40 | import { validateContext, validateFunctionParams, validateParameters, validateSignature, validateURL, validateModalOptions, validateFunctionParamsWithFn, validateRedirectionMethod, validateRedirectionBody } from './utils/validationUtils' |
40 | 41 | import { fetchStatusUrl, initSession, updateSession } from './utils/sessionUtils' |
41 | | -import { createLinkWithTemplateData, getAttestors, recoverSignersOfSignedClaim } from './utils/proofUtils' |
| 42 | +import { assertVerifiedProof, createLinkWithTemplateData, getAttestors } from './utils/proofUtils' |
42 | 43 | import { QRCodeModal } from './utils/modalUtils' |
43 | 44 | import loggerModule from './utils/logger'; |
44 | 45 | import { getDeviceType, getMobileDeviceType } from './utils/device' |
45 | 46 | import { canonicalStringify } from './utils/strings' |
| 47 | +import { assertValidateProof, VerificationConfig } from './utils/proofValidationUtils' |
| 48 | +import { fetchProviderHashRequirementsBy, ProviderHashRequirementsConfig } from './utils/providerUtils' |
46 | 49 |
|
47 | 50 | const logger = loggerModule.logger |
48 | 51 |
|
49 | 52 | const sdkVersion = require('../package.json').version; |
50 | 53 |
|
51 | 54 | /** |
52 | | - * Verifies one or more Reclaim proofs by validating signatures and witness information |
| 55 | + * Verifies one or more Reclaim proofs by validating signatures, verifying witness information, |
| 56 | + * and performing content validation against the expected configuration. |
| 57 | + * |
| 58 | + * See also: |
| 59 | + * |
| 60 | + * * `ReclaimProofRequest.getProviderHashRequirements()` - To get the expected proof hash requirements for a proof request. |
| 61 | + * * `fetchProviderHashRequirementsBy()` - To get the expected proof hash requirements for a provider version by providing providerId and exactProviderVersionString. |
| 62 | + * * `getProviderHashRequirementsFromSpec()` - To get the expected proof hash requirements from a provider spec. |
| 63 | + * * All 3 functions above are alternatives of each other and result from these functions can be directly used as `config` parameter in this function for proof validation. |
53 | 64 | * |
54 | | - * @param proofOrProofs - A single proof object or an array of proof objects to verify |
55 | | - * @param allowAiWitness - Optional flag to allow AI witness verification. Defaults to false |
56 | | - * @returns Promise<boolean> - Returns true if all proofs are valid, false otherwise |
57 | | - * @throws {SignatureNotFoundError} When proof has no signatures |
58 | | - * @throws {ProofNotVerifiedError} When identifier mismatch occurs |
| 65 | + * @param proofOrProofs - A single proof object or an array of proof objects to be verified. |
| 66 | + * @param config - Verification configuration that specifies required hashes, allowed extra hashes, or disables content validation. |
| 67 | + * @returns Promise<boolean> - Returns `true` if all proofs are successfully verified and validated, `false` otherwise. |
| 68 | + * @throws {ProofNotVerifiedError} When signature validation or identifier mismatch occurs. |
| 69 | + * @throws {ProofNotValidatedError} When no proofs are provided, when the configuration is missing, or when proof content does not match the expectations set in the config. |
59 | 70 | * |
60 | 71 | * @example |
61 | 72 | * ```typescript |
62 | | - * const isValid = await verifyProof(proof); |
63 | | - * const areAllValid = await verifyProof([proof1, proof2, proof3]); |
64 | | - * const isValidWithAI = await verifyProof(proof, true); |
| 73 | + * // Validate a single proof against expected hash |
| 74 | + * const isValid = await verifyProof(proof, { hashes: ['0xAbC...'] }); |
| 75 | + * |
| 76 | + * // Validate multiple proofs |
| 77 | + * const areAllValid = await verifyProof([proof1, proof2], { |
| 78 | + * hashes: ['0xAbC...', '0xF22..'], |
| 79 | + * }); |
| 80 | + * |
| 81 | + * // Validate 1 required proofs, any number of multiple with same hash, and one optional |
| 82 | + * const areAllValid = await verifyProof([proof1, proof2, sameAsProof2], { |
| 83 | + * hashes: ['0xAbC...', { value: '0xF22..', multiple: true }, { value: '0xE33..', required: false }], |
| 84 | + * }); |
65 | 85 | * ``` |
66 | 86 | */ |
67 | 87 | export async function verifyProof( |
68 | | - proofOrProofs: Proof | Proof[], |
69 | | - allowAiWitness = false |
70 | | -) { |
71 | | - try { |
72 | | - await assertValidProof(proofOrProofs, allowAiWitness) |
73 | | - return true |
74 | | - } catch (error) { |
75 | | - logger.error('error in validating proof', error) |
76 | | - return false |
77 | | - } |
78 | | -} |
| 88 | + proofOrProofs: Proof | Proof[], |
| 89 | + config: VerificationConfig |
| 90 | +): Promise<boolean> { |
| 91 | + try { |
| 92 | + const proofs = Array.isArray(proofOrProofs) ? proofOrProofs : [proofOrProofs]; |
| 93 | + |
| 94 | + if (proofs.length === 0) { |
| 95 | + throw new ProofNotValidatedError('No proofs provided'); |
| 96 | + } |
79 | 97 |
|
80 | | -export async function assertValidProof( |
81 | | - proofOrProofs: Proof | Proof[], |
82 | | - allowAiWitness = false |
83 | | -) { |
84 | | - const attestors = await getAttestors() |
85 | | - proofOrProofs = Array.isArray(proofOrProofs) ? proofOrProofs : [proofOrProofs] |
86 | | - for (const proof of proofOrProofs) { |
87 | | - const signers = recoverSignersOfSignedClaim({ |
88 | | - claim: proof.claimData, |
89 | | - signatures: proof.signatures |
90 | | - .map(signature => ethers.getBytes(signature)) |
91 | | - }) |
92 | | - // ensure at least one signer is an attestor |
93 | | - if ( |
94 | | - !attestors |
95 | | - .some(attestor => signers.includes(attestor.id.toLowerCase())) |
96 | | - ) { |
97 | | - throw new ProofNotVerifiedError('Identifier mismatch') |
98 | | - } |
99 | | - } |
| 98 | + if (!config) { |
| 99 | + throw new ProofNotValidatedError('Verification configuration is required for `verifyProof(proof, config)`'); |
| 100 | + } |
| 101 | + |
| 102 | + const attestors = await getAttestors() |
| 103 | + for (const proof of proofs) { |
| 104 | + await assertVerifiedProof(proof, attestors) |
| 105 | + } |
| 106 | + |
| 107 | + await assertValidateProof(proofs, config); |
| 108 | + |
| 109 | + return true; |
| 110 | + } catch (error) { |
| 111 | + logger.error('Error in validating proof:', error); |
| 112 | + return false; |
| 113 | + } |
100 | 114 | } |
101 | 115 |
|
102 | 116 | /** |
@@ -159,7 +173,7 @@ export class ReclaimProofRequest { |
159 | 173 | private appCallbackUrl?: string; |
160 | 174 | private sessionId: string; |
161 | 175 | private options?: ProofRequestOptions; |
162 | | - private context: Context = { contextAddress: '0x0', contextMessage: 'sample context' }; |
| 176 | + private context: Context = { contextAddress: '0x0', contextMessage: 'sample context', reclaimSessionId: '' }; |
163 | 177 | private claimCreationType?: ClaimCreationType = ClaimCreationType.STANDALONE; |
164 | 178 | private providerId: string; |
165 | 179 | private resolvedProviderVersion?: string; |
@@ -337,6 +351,7 @@ export class ReclaimProofRequest { |
337 | 351 | const data: InitSessionResponse = await initSession(providerId, applicationId, proofRequestInstance.timeStamp, signature, options?.providerVersion); |
338 | 352 | proofRequestInstance.sessionId = data.sessionId |
339 | 353 | proofRequestInstance.resolvedProviderVersion = data.resolvedProviderVersion |
| 354 | + proofRequestInstance.context.reclaimSessionId = data.sessionId |
340 | 355 |
|
341 | 356 | return proofRequestInstance |
342 | 357 | } catch (error) { |
@@ -699,7 +714,7 @@ export class ReclaimProofRequest { |
699 | 714 | { input: context, paramName: 'context', isString: false } |
700 | 715 | ], 'setJsonContext'); |
701 | 716 | // ensure context is canonically json serializable |
702 | | - this.context = JSON.parse(canonicalStringify(context)); |
| 717 | + this.context = JSON.parse(canonicalStringify({ ...context, reclaimSessionId: this.sessionId })); |
703 | 718 | } catch (error) { |
704 | 719 | logger.info("Error setting context", error) |
705 | 720 | throw new SetContextError("Error setting context", error as Error) |
@@ -731,7 +746,7 @@ export class ReclaimProofRequest { |
731 | 746 | { input: address, paramName: 'address', isString: true }, |
732 | 747 | { input: message, paramName: 'message', isString: true } |
733 | 748 | ], 'setContext'); |
734 | | - this.context = { contextAddress: address, contextMessage: message }; |
| 749 | + this.context = { contextAddress: address, contextMessage: message, reclaimSessionId: this.sessionId }; |
735 | 750 | } catch (error) { |
736 | 751 | logger.info("Error setting context", error) |
737 | 752 | throw new SetContextError("Error setting context", error as Error) |
@@ -1336,6 +1351,39 @@ export class ReclaimProofRequest { |
1336 | 1351 | } |
1337 | 1352 | } |
1338 | 1353 |
|
| 1354 | + /** |
| 1355 | + * Returns the provider id and exact version of the provider that was used in the verification session of this request. |
| 1356 | + * |
| 1357 | + * This can be provided as a config parameter to the `verifyProof` function to verify the proof. |
| 1358 | + * |
| 1359 | + * See also: |
| 1360 | + * * `verifyProof()` - Verifies a proof against the expected provider configuration. |
| 1361 | + * * `getProviderHashRequirements()` - An alternative of this function to get the expected hashes for a provider version by providing providerId and exactProviderVersionString. The result can be provided in verifyProof function's `config` parameter for proof validation. |
| 1362 | + * * `getProviderHashRequirementsFromSpec()` - An alternative of this function to get the expected hashes from a provider spec. The result can be provided in verifyProof function's `config` parameter for proof validation. |
| 1363 | + */ |
| 1364 | + public getProviderVersion(): ProviderVersionInfo { |
| 1365 | + // This should be exact version and not a version constraint/expression. This cannot be blank. |
| 1366 | + const exactProviderVersionString = this.resolvedProviderVersion ?? ''; |
| 1367 | + return { |
| 1368 | + providerId: this.providerId, |
| 1369 | + providerVersion: exactProviderVersionString, |
| 1370 | + } |
| 1371 | + } |
| 1372 | + |
| 1373 | + /** |
| 1374 | + * Fetches the provider config that was used for this session and returns the hash requirements |
| 1375 | + * |
| 1376 | + * See also: |
| 1377 | + * * `verifyProof()` - Verifies a proof against the expected provider configuration. |
| 1378 | + * * `fetchProviderHashRequirementsBy()` - An alternative of this function to get the expected hashes for a provider version by providing providerId and exactProviderVersionString. The result can be provided in verifyProof function's `config` parameter for proof validation. |
| 1379 | + * * `getProviderHashRequirementsFromSpec()` - An alternative of this function to get the expected hashes from a provider spec. The result can be provided in verifyProof function's `config` parameter for proof validation. |
| 1380 | + * |
| 1381 | + * @returns A promise that resolves to a ProviderHashRequirementsConfig |
| 1382 | + */ |
| 1383 | + getProviderHashRequirements(proofs: Proof[]): Promise<ProviderHashRequirementsConfig> { |
| 1384 | + return fetchProviderHashRequirementsBy(this.providerId, this.resolvedProviderVersion ?? '', proofs); |
| 1385 | + } |
| 1386 | + |
1339 | 1387 | /** |
1340 | 1388 | * Starts the proof request session and monitors for proof submission |
1341 | 1389 | * |
@@ -1418,7 +1466,7 @@ export class ReclaimProofRequest { |
1418 | 1466 | if (statusUrlResponse.session.proofs && statusUrlResponse.session.proofs.length > 0) { |
1419 | 1467 | const proofs = statusUrlResponse.session.proofs; |
1420 | 1468 | if (this.claimCreationType === ClaimCreationType.STANDALONE) { |
1421 | | - const verified = await verifyProof(proofs, this.options?.acceptAiProviders); |
| 1469 | + const verified = await verifyProof(proofs, this.getProviderVersion()); |
1422 | 1470 | if (!verified) { |
1423 | 1471 | logger.info(`Proofs not verified: count=${proofs?.length}`); |
1424 | 1472 | throw new ProofNotVerifiedError(); |
|
0 commit comments