noah-avalanche-sdk 0.1.2

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.
Files changed (103) hide show
  1. package/README.md +892 -0
  2. package/dist/core/APIClient.d.ts +71 -0
  3. package/dist/core/APIClient.d.ts.map +1 -0
  4. package/dist/core/APIClient.js +92 -0
  5. package/dist/core/APIClient.js.map +1 -0
  6. package/dist/core/ContractClient.d.ts +38 -0
  7. package/dist/core/ContractClient.d.ts.map +1 -0
  8. package/dist/core/ContractClient.js +209 -0
  9. package/dist/core/ContractClient.js.map +1 -0
  10. package/dist/core/NoahSDK.d.ts +43 -0
  11. package/dist/core/NoahSDK.d.ts.map +1 -0
  12. package/dist/core/NoahSDK.js +93 -0
  13. package/dist/core/NoahSDK.js.map +1 -0
  14. package/dist/core/WalletAdapter.d.ts +188 -0
  15. package/dist/core/WalletAdapter.d.ts.map +1 -0
  16. package/dist/core/WalletAdapter.js +425 -0
  17. package/dist/core/WalletAdapter.js.map +1 -0
  18. package/dist/hooks/index.d.ts +18 -0
  19. package/dist/hooks/index.d.ts.map +1 -0
  20. package/dist/hooks/index.js +15 -0
  21. package/dist/hooks/index.js.map +1 -0
  22. package/dist/hooks/useCredentials.d.ts +136 -0
  23. package/dist/hooks/useCredentials.d.ts.map +1 -0
  24. package/dist/hooks/useCredentials.js +217 -0
  25. package/dist/hooks/useCredentials.js.map +1 -0
  26. package/dist/hooks/useProtocol.d.ts +117 -0
  27. package/dist/hooks/useProtocol.d.ts.map +1 -0
  28. package/dist/hooks/useProtocol.js +165 -0
  29. package/dist/hooks/useProtocol.js.map +1 -0
  30. package/dist/hooks/useUser.d.ts +159 -0
  31. package/dist/hooks/useUser.d.ts.map +1 -0
  32. package/dist/hooks/useUser.js +188 -0
  33. package/dist/hooks/useUser.js.map +1 -0
  34. package/dist/index.d.ts +28 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +26 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/issuer/IssuerClient.d.ts +98 -0
  39. package/dist/issuer/IssuerClient.d.ts.map +1 -0
  40. package/dist/issuer/IssuerClient.js +159 -0
  41. package/dist/issuer/IssuerClient.js.map +1 -0
  42. package/dist/protocol/ProtocolClient.d.ts +124 -0
  43. package/dist/protocol/ProtocolClient.d.ts.map +1 -0
  44. package/dist/protocol/ProtocolClient.js +265 -0
  45. package/dist/protocol/ProtocolClient.js.map +1 -0
  46. package/dist/protocol/RequirementsManager.d.ts +9 -0
  47. package/dist/protocol/RequirementsManager.d.ts.map +1 -0
  48. package/dist/protocol/RequirementsManager.js +9 -0
  49. package/dist/protocol/RequirementsManager.js.map +1 -0
  50. package/dist/user/ProofGenerator.d.ts +49 -0
  51. package/dist/user/ProofGenerator.d.ts.map +1 -0
  52. package/dist/user/ProofGenerator.js +80 -0
  53. package/dist/user/ProofGenerator.js.map +1 -0
  54. package/dist/user/UserClient.d.ts +191 -0
  55. package/dist/user/UserClient.d.ts.map +1 -0
  56. package/dist/user/UserClient.js +338 -0
  57. package/dist/user/UserClient.js.map +1 -0
  58. package/dist/utils/credentials.d.ts +47 -0
  59. package/dist/utils/credentials.d.ts.map +1 -0
  60. package/dist/utils/credentials.js +99 -0
  61. package/dist/utils/credentials.js.map +1 -0
  62. package/dist/utils/identity.d.ts +22 -0
  63. package/dist/utils/identity.d.ts.map +1 -0
  64. package/dist/utils/identity.js +35 -0
  65. package/dist/utils/identity.js.map +1 -0
  66. package/dist/utils/jurisdiction.d.ts +21 -0
  67. package/dist/utils/jurisdiction.d.ts.map +1 -0
  68. package/dist/utils/jurisdiction.js +64 -0
  69. package/dist/utils/jurisdiction.js.map +1 -0
  70. package/dist/utils/mrz.d.ts +16 -0
  71. package/dist/utils/mrz.d.ts.map +1 -0
  72. package/dist/utils/mrz.js +91 -0
  73. package/dist/utils/mrz.js.map +1 -0
  74. package/dist/utils/ocr.d.ts +31 -0
  75. package/dist/utils/ocr.d.ts.map +1 -0
  76. package/dist/utils/ocr.js +69 -0
  77. package/dist/utils/ocr.js.map +1 -0
  78. package/dist/utils/types.d.ts +122 -0
  79. package/dist/utils/types.d.ts.map +1 -0
  80. package/dist/utils/types.js +2 -0
  81. package/dist/utils/types.js.map +1 -0
  82. package/package.json +53 -0
  83. package/src/core/APIClient.ts +165 -0
  84. package/src/core/ContractClient.ts +266 -0
  85. package/src/core/NoahSDK.ts +123 -0
  86. package/src/core/WalletAdapter.ts +546 -0
  87. package/src/hooks/index.ts +31 -0
  88. package/src/hooks/types.d.ts +18 -0
  89. package/src/hooks/useCredentials.ts +359 -0
  90. package/src/hooks/useProtocol.ts +284 -0
  91. package/src/hooks/useUser.ts +331 -0
  92. package/src/index.ts +80 -0
  93. package/src/issuer/IssuerClient.ts +209 -0
  94. package/src/protocol/ProtocolClient.ts +330 -0
  95. package/src/protocol/RequirementsManager.ts +16 -0
  96. package/src/user/ProofGenerator.ts +113 -0
  97. package/src/user/UserClient.ts +440 -0
  98. package/src/utils/credentials.ts +122 -0
  99. package/src/utils/identity.ts +46 -0
  100. package/src/utils/jurisdiction.ts +83 -0
  101. package/src/utils/mrz.ts +113 -0
  102. package/src/utils/ocr.ts +84 -0
  103. package/src/utils/types.ts +144 -0
@@ -0,0 +1,440 @@
1
+ import type { Signer } from 'ethers';
2
+ import type { Requirements, ZKProof, TransactionResult, ContractAddresses } from '../utils/types';
3
+ import { ContractClient } from '../core/ContractClient';
4
+ import { ProofGenerator, type ProverInput } from './ProofGenerator';
5
+ import { IdentityManager } from '../utils/identity';
6
+ import { jurisdictionStringToHash } from '../utils/jurisdiction';
7
+ import { generateCredentialHash } from '../utils/credentials';
8
+
9
+ /**
10
+ * Credential data structure for proof generation
11
+ */
12
+ export interface Credential {
13
+ credentialHash: string;
14
+ age: number;
15
+ jurisdiction: string | number;
16
+ accredited: number; // 0 or 1
17
+ passportNumber?: string;
18
+ expiryDate?: number;
19
+ userAddress?: string;
20
+ }
21
+
22
+ /**
23
+ * Proof generation result
24
+ */
25
+ export interface ProofResult {
26
+ proof: ZKProof;
27
+ publicSignals: string[]; // 28 elements
28
+ nullifier: string;
29
+ packedFlags: number;
30
+ credentialHash: string;
31
+ success: boolean;
32
+ }
33
+
34
+ /**
35
+ * UserClient configuration options
36
+ */
37
+ export interface UserClientConfig {
38
+ contractAddresses?: Partial<ContractAddresses>;
39
+ rpcUrl?: string;
40
+ wasmUrl?: string;
41
+ mockMode?: boolean;
42
+ }
43
+
44
+ /**
45
+ * UserClient - High-level API for end-user applications
46
+ *
47
+ * Provides a simple interface for users to:
48
+ * - Generate ZK proofs from credentials
49
+ * - Verify and grant access to protocols
50
+ * - Check credential validity
51
+ * - Get protocol requirements
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * import { UserClient } from '@noah-protocol/sdk';
56
+ * import { ethers } from 'ethers';
57
+ *
58
+ * const provider = new ethers.BrowserProvider(window.ethereum);
59
+ * const signer = await provider.getSigner();
60
+ * const user = new UserClient(signer);
61
+ *
62
+ * // Generate proof
63
+ * const proof = await user.generateProof(credential, requirements);
64
+ *
65
+ * // Verify and grant access
66
+ * await user.verifyAndGrantAccess(proof, protocolAddress);
67
+ * ```
68
+ */
69
+ export class UserClient {
70
+ private signer: Signer;
71
+ private contractClient: ContractClient;
72
+ private proofGenerator: ProofGenerator;
73
+ private mockMode: boolean;
74
+ private wasmUrl?: string;
75
+ private identityManager: IdentityManager;
76
+
77
+ constructor(signer: Signer, config: UserClientConfig = {}) {
78
+ if (!signer) {
79
+ throw new Error('Signer is required');
80
+ }
81
+
82
+ this.signer = signer;
83
+ this.mockMode = config.mockMode || false;
84
+ this.wasmUrl = config.wasmUrl;
85
+
86
+ this.contractClient = new ContractClient({
87
+ provider: signer.provider || undefined,
88
+ contractAddresses: config.contractAddresses as ContractAddresses | undefined,
89
+ rpcUrl: config.rpcUrl,
90
+ });
91
+
92
+ this.proofGenerator = new ProofGenerator();
93
+ this.identityManager = new IdentityManager();
94
+ }
95
+
96
+ /**
97
+ * Generate a ZK proof from credential data and protocol requirements
98
+ *
99
+ * @param credential - Credential data (age, jurisdiction, accredited, credentialHash)
100
+ * @param requirements - Protocol requirements (minAge, allowedJurisdictions, requireAccredited)
101
+ * @returns Promise resolving to proof result with proof, publicSignals, and credentialHash
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * const credential = {
106
+ * credentialHash: '0x1234...',
107
+ * age: 25,
108
+ * jurisdiction: 'US',
109
+ * accredited: 1,
110
+ * userAddress: '0x...'
111
+ * };
112
+ *
113
+ * const requirements = {
114
+ * protocolAddress: '0x...',
115
+ * minAge: 21,
116
+ * allowedJurisdictions: ['US', 'UK'],
117
+ * requireAccredited: true
118
+ * };
119
+ *
120
+ * const proof = await user.generateProof(credential, requirements);
121
+ * ```
122
+ */
123
+ async generateProof(
124
+ credential: Credential,
125
+ requirements: Requirements & { protocolAddress: string }
126
+ ): Promise<ProofResult> {
127
+ if (this.mockMode) {
128
+ return this.generateMockProof(credential, requirements);
129
+ }
130
+
131
+ // 1. Pre-flight checks
132
+ await this.performPreFlightChecks(credential, requirements);
133
+
134
+ try {
135
+ // 2. Prepare inputs for Gnark circuit
136
+ const jurisdictionValue = typeof credential.jurisdiction === 'string'
137
+ ? BigInt(jurisdictionStringToHash(credential.jurisdiction))
138
+ : BigInt(credential.jurisdiction);
139
+
140
+ const userAddr = await this.signer.getAddress();
141
+ const passportNum = BigInt('0x' + Buffer.from(credential.passportNumber || '0').toString('hex'));
142
+
143
+ const input: ProverInput = {
144
+ actualAge: credential.age,
145
+ actualJurisdiction: Number(jurisdictionValue),
146
+ actualAccredited: credential.accredited,
147
+ credentialHash: credential.credentialHash,
148
+ passportNumber: passportNum.toString(),
149
+ expiryDate: 20300101, // Placeholder: YYYYMMDD
150
+ minAge: requirements.minAge,
151
+ recipientAddress: BigInt(userAddr).toString(),
152
+ currentDate: Math.floor(Date.now() / 1000),
153
+ allowedJurisdictions: (requirements.allowedJurisdictions || []).map(j => Number(BigInt(j))),
154
+ sanctionedCountries: [], // To be populated from on-chain or config
155
+ requireAccredited: requirements.requireAccredited ? 1 : 0,
156
+ credentialHashPublic: credential.credentialHash
157
+ };
158
+
159
+ // 3. Load and execute WASM prover
160
+ await this.proofGenerator.loadProver(this.wasmUrl);
161
+ const result = await this.proofGenerator.generateProof(input);
162
+
163
+ return {
164
+ proof: result.proof,
165
+ publicSignals: result.publicSignals,
166
+ nullifier: result.nullifier,
167
+ packedFlags: result.packedFlags,
168
+ credentialHash: credential.credentialHash,
169
+ success: result.success,
170
+ };
171
+ } catch (error: any) {
172
+ throw new Error(`Failed to generate proof: ${error.message || 'Unknown error'}`);
173
+ }
174
+ }
175
+
176
+ /**
177
+ * High-level method to generate proof directly from an image (OCR -> MRZ -> ZK)
178
+ *
179
+ * @param imageSource - URL, File, or Blob of the document image
180
+ * @param requirements - Protocol requirements
181
+ * @returns Promise resolving to proof result
182
+ */
183
+ async proveFromImage(
184
+ imageSource: string | File | Blob,
185
+ requirements: Requirements & { protocolAddress: string }
186
+ ): Promise<ProofResult> {
187
+ if (this.mockMode) {
188
+ // Mock credential for testing
189
+ const mockCredential: Credential = {
190
+ credentialHash: generateCredentialHash({
191
+ userAddress: 'mock-user',
192
+ age: 25,
193
+ jurisdiction: 'US',
194
+ accredited: true
195
+ }).credentialHash,
196
+ age: 25,
197
+ jurisdiction: 'US',
198
+ accredited: 1,
199
+ passportNumber: 'P12345678',
200
+ };
201
+ return this.generateMockProof(mockCredential, requirements);
202
+ }
203
+
204
+ try {
205
+ // 1. Extract data via OCR
206
+ const profile = await this.identityManager.extractFromImage(imageSource);
207
+
208
+ // 2. Prepare credential
209
+ const credential: Credential = {
210
+ credentialHash: generateCredentialHash({
211
+ userAddress: await this.signer.getAddress(),
212
+ age: profile.age,
213
+ jurisdiction: profile.nationality,
214
+ accredited: false // Default non-accredited, can be updated if logic allows
215
+ }).credentialHash,
216
+ age: profile.age,
217
+ jurisdiction: profile.nationality,
218
+ accredited: 0,
219
+ passportNumber: profile.passportNumber,
220
+ expiryDate: Math.floor(profile.expiryDate.getTime() / 1000)
221
+ };
222
+
223
+ // 3. Generate proof
224
+ return await this.generateProof(credential, requirements);
225
+ } catch (error: any) {
226
+ throw new Error(`Failed to prove from image: ${error.message}`);
227
+ } finally {
228
+ await this.identityManager.cleanup();
229
+ }
230
+ }
231
+
232
+ private async performPreFlightChecks(credential: Credential, requirements: Requirements) {
233
+ // Check if credential is valid on-chain
234
+ const isValid = await this.contractClient.isCredentialValid(credential.credentialHash);
235
+ if (!isValid) throw new Error('Credential is not valid or has been revoked');
236
+
237
+ // Check if nullifier is already used (if we can derive it here)
238
+ // In production, the nullifier should be derived the same way as in the circuit
239
+ // const nullifier = deriveNullifier(credential.passportNumber, requirements.protocolAddress);
240
+ // const isUsed = await this.contractClient.isNullifierUsed(nullifier);
241
+ // if (isUsed) throw new Error('This document has already been used for this protocol');
242
+ }
243
+
244
+ private async generateMockProof(credential: Credential, requirements: Requirements): Promise<ProofResult> {
245
+ console.log('[MockMode] Generating proof for', credential.credentialHash);
246
+ await new Promise(resolve => setTimeout(resolve, 800));
247
+ return {
248
+ proof: { a: ["0", "0"], b: [["0", "0"], ["0", "0"]], c: ["0", "0"] } as ZKProof,
249
+ publicSignals: new Array(28).fill("0"),
250
+ nullifier: "0xmocknullifier" + Math.random().toString(16).substring(2, 8),
251
+ packedFlags: 15,
252
+ credentialHash: credential.credentialHash,
253
+ success: true
254
+ };
255
+ }
256
+
257
+ /**
258
+ * Verify proof and grant access to a protocol
259
+ *
260
+ * This method calls the smart contract's verifyAndGrantAccess function,
261
+ * which verifies the ZK proof and grants the user access to the protocol.
262
+ *
263
+ * @param proofResult - Proof result from generateProof()
264
+ * @param protocolAddress - Protocol contract address (optional, can be inferred from proof)
265
+ * @param userAddress - User's wallet address (optional, defaults to signer address)
266
+ * @returns Promise resolving to transaction result with hash and receipt
267
+ *
268
+ * @example
269
+ * ```typescript
270
+ * const proof = await user.generateProof(credential, requirements);
271
+ * const tx = await user.verifyAndGrantAccess(proof, protocolAddress);
272
+ * console.log('Transaction hash:', tx.transactionHash);
273
+ * ```
274
+ */
275
+ async verifyAndGrantAccess(
276
+ proofResult: ProofResult,
277
+ protocolAddress?: string,
278
+ userAddress?: string
279
+ ): Promise<TransactionResult> {
280
+ // Remove unused protocolAddress parameter warning
281
+ void protocolAddress;
282
+ if (!proofResult || !proofResult.proof) {
283
+ throw new Error('Proof result is required');
284
+ }
285
+
286
+ if (!proofResult.publicSignals || proofResult.publicSignals.length < 13) {
287
+ throw new Error('Public signals are required and must have at least 13 elements');
288
+ }
289
+
290
+ if (!proofResult.credentialHash) {
291
+ throw new Error('Credential hash is required in proof result');
292
+ }
293
+
294
+ // Get user address from signer if not provided
295
+ const finalUserAddress = userAddress || (await this.signer.getAddress());
296
+
297
+ // Note: The contract uses msg.sender (the signer's address) as the protocol address
298
+ // The protocolAddress parameter is kept for API consistency but is not used in the contract call
299
+
300
+ try {
301
+ const result = await this.contractClient.verifyAndGrantAccess(
302
+ this.signer,
303
+ proofResult.proof,
304
+ proofResult.publicSignals,
305
+ proofResult.credentialHash,
306
+ finalUserAddress
307
+ );
308
+
309
+ return result;
310
+ } catch (error: any) {
311
+ throw new Error(
312
+ `Failed to verify proof and grant access: ${error.message || 'Unknown error'}`
313
+ );
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Check if a credential is valid (exists and not revoked)
319
+ *
320
+ * @param credentialHash - The credential hash to check (bytes32)
321
+ * @returns Promise resolving to true if credential is valid, false otherwise
322
+ *
323
+ * @example
324
+ * ```typescript
325
+ * const isValid = await user.checkCredentialValidity('0x1234...');
326
+ * if (isValid) {
327
+ * console.log('Credential is valid');
328
+ * }
329
+ * ```
330
+ */
331
+ async checkCredentialValidity(credentialHash: string): Promise<boolean> {
332
+ if (!credentialHash) {
333
+ throw new Error('Credential hash is required');
334
+ }
335
+
336
+ try {
337
+ return await this.contractClient.isCredentialValid(credentialHash);
338
+ } catch (error: any) {
339
+ throw new Error(
340
+ `Failed to check credential validity: ${error.message || 'Unknown error'}`
341
+ );
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Get protocol requirements
347
+ *
348
+ * @param protocolAddress - The protocol contract address
349
+ * @returns Promise resolving to requirements object (minAge, allowedJurisdictions, requireAccredited)
350
+ *
351
+ * @example
352
+ * ```typescript
353
+ * const requirements = await user.getProtocolRequirements('0x...');
354
+ * console.log('Min age:', requirements.minAge);
355
+ * console.log('Allowed jurisdictions:', requirements.allowedJurisdictions);
356
+ * ```
357
+ */
358
+ async getProtocolRequirements(protocolAddress: string): Promise<Requirements> {
359
+ if (!protocolAddress) {
360
+ throw new Error('Protocol address is required');
361
+ }
362
+
363
+ try {
364
+ return await this.contractClient.getRequirements(protocolAddress);
365
+ } catch (error: any) {
366
+ throw new Error(
367
+ `Failed to get protocol requirements: ${error.message || 'Unknown error'}`
368
+ );
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Check if user has access to a protocol
374
+ *
375
+ * @param protocolAddress - The protocol contract address
376
+ * @param userAddress - The user's wallet address (optional, defaults to signer address)
377
+ * @returns Promise resolving to true if user has access, false otherwise
378
+ *
379
+ * @example
380
+ * ```typescript
381
+ * const hasAccess = await user.hasAccess('0x...');
382
+ * if (hasAccess) {
383
+ * console.log('User has access to protocol');
384
+ * }
385
+ * ```
386
+ */
387
+ async hasAccess(
388
+ protocolAddress: string,
389
+ userAddress?: string
390
+ ): Promise<boolean> {
391
+ if (!protocolAddress) {
392
+ throw new Error('Protocol address is required');
393
+ }
394
+
395
+ const finalUserAddress = userAddress || (await this.signer.getAddress());
396
+
397
+ try {
398
+ return await this.contractClient.hasAccess(protocolAddress, finalUserAddress);
399
+ } catch (error: any) {
400
+ throw new Error(
401
+ `Failed to check access: ${error.message || 'Unknown error'}`
402
+ );
403
+ }
404
+ }
405
+
406
+ /**
407
+ * Get user's credential hash for a protocol
408
+ *
409
+ * @param protocolAddress - The protocol contract address
410
+ * @param userAddress - The user's wallet address (optional, defaults to signer address)
411
+ * @returns Promise resolving to credential hash (bytes32) or empty string if not set
412
+ *
413
+ * @example
414
+ * ```typescript
415
+ * const credentialHash = await user.getUserCredential('0x...');
416
+ * if (credentialHash) {
417
+ * console.log('User credential:', credentialHash);
418
+ * }
419
+ * ```
420
+ */
421
+ async getUserCredential(
422
+ protocolAddress: string,
423
+ userAddress?: string
424
+ ): Promise<string> {
425
+ if (!protocolAddress) {
426
+ throw new Error('Protocol address is required');
427
+ }
428
+
429
+ const finalUserAddress = userAddress || (await this.signer.getAddress());
430
+
431
+ try {
432
+ return await this.contractClient.getUserCredential(protocolAddress, finalUserAddress);
433
+ } catch (error: any) {
434
+ throw new Error(
435
+ `Failed to get user credential: ${error.message || 'Unknown error'}`
436
+ );
437
+ }
438
+ }
439
+ }
440
+
@@ -0,0 +1,122 @@
1
+ import { ethers } from 'ethers';
2
+ import { jurisdictionStringToHash } from './jurisdiction.js';
3
+ import type { CredentialData, CredentialHashResult } from './types.js';
4
+
5
+ /**
6
+ * Generate a credential hash from user data
7
+ *
8
+ * The credential hash is computed using Keccak256 from a formatted string containing:
9
+ * - User address
10
+ * - Age
11
+ * - Jurisdiction (converted to hash)
12
+ * - Accredited status
13
+ * - Timestamp
14
+ *
15
+ * @param userData - The credential data to hash
16
+ * @returns Object containing the credential hash, jurisdiction hash, credential data string, and timestamp
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const credential = {
21
+ * userAddress: "0x1234...",
22
+ * age: 25,
23
+ * jurisdiction: "US",
24
+ * accredited: true
25
+ * };
26
+ *
27
+ * const result = generateCredentialHash(credential);
28
+ * console.log(result.credentialHash); // "0x..."
29
+ * ```
30
+ */
31
+ export function generateCredentialHash(
32
+ userData: CredentialData
33
+ ): CredentialHashResult {
34
+ const { userAddress, age, jurisdiction, accredited } = userData;
35
+
36
+ // Validate required fields
37
+ if (!userAddress || typeof userAddress !== 'string') {
38
+ throw new Error('userAddress must be a non-empty string');
39
+ }
40
+
41
+ if (typeof age !== 'number' || age < 0 || !Number.isInteger(age)) {
42
+ throw new Error('age must be a non-negative integer');
43
+ }
44
+
45
+ if (!jurisdiction || typeof jurisdiction !== 'string') {
46
+ throw new Error('jurisdiction must be a non-empty string');
47
+ }
48
+
49
+ if (typeof accredited !== 'boolean') {
50
+ throw new Error('accredited must be a boolean');
51
+ }
52
+
53
+ // Convert jurisdiction string to hash
54
+ const jurisdictionHash = jurisdictionStringToHash(jurisdiction, true);
55
+
56
+ // Create credential data string
57
+ // Format: user:address,age:number,jurisdiction:hash,accredited:number,timestamp:timestamp
58
+ const timestamp = userData.timestamp ?? Date.now();
59
+ const accreditedValue = accredited ? 1 : 0;
60
+
61
+ const credentialData = `user:${userAddress},age:${age},jurisdiction:${jurisdictionHash},accredited:${accreditedValue},timestamp:${timestamp}`;
62
+
63
+ // Hash the credential data
64
+ const credentialHash = ethers.keccak256(ethers.toUtf8Bytes(credentialData));
65
+
66
+ return {
67
+ credentialHash,
68
+ jurisdictionHash,
69
+ credentialData,
70
+ timestamp,
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Validate a credential hash format
76
+ * @param hash - The credential hash to validate
77
+ * @returns True if the hash is valid (starts with 0x and is 66 characters)
78
+ */
79
+ export function isValidCredentialHash(hash: string): boolean {
80
+ if (typeof hash !== 'string') {
81
+ return false;
82
+ }
83
+
84
+ // Must start with 0x and be 66 characters (0x + 64 hex chars)
85
+ return /^0x[a-fA-F0-9]{64}$/.test(hash);
86
+ }
87
+
88
+ /**
89
+ * Validate a user address format
90
+ * @param address - The Ethereum address to validate
91
+ * @returns True if the address is valid
92
+ */
93
+ export function isValidAddress(address: string): boolean {
94
+ if (typeof address !== 'string') {
95
+ return false;
96
+ }
97
+
98
+ try {
99
+ return ethers.isAddress(address);
100
+ } catch {
101
+ return false;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Normalize an Ethereum address to checksum format
107
+ * @param address - The address to normalize
108
+ * @returns The checksummed address
109
+ */
110
+ export function toChecksumAddress(address: string): string {
111
+ if (!isValidAddress(address)) {
112
+ throw new Error('Invalid Ethereum address');
113
+ }
114
+
115
+ return ethers.getAddress(address);
116
+ }
117
+
118
+
119
+
120
+
121
+
122
+
@@ -0,0 +1,46 @@
1
+ import { OCRExtractor } from './ocr.js';
2
+ import { parseTD3, type MRZData } from './mrz.js';
3
+
4
+ export interface IdentityProfile extends MRZData {
5
+ confidence: number;
6
+ }
7
+
8
+ /**
9
+ * IdentityManager - Orchestrates OCR and MRZ parsing for Noah SDK
10
+ */
11
+ export class IdentityManager {
12
+ private ocr: OCRExtractor;
13
+
14
+ constructor() {
15
+ this.ocr = new OCRExtractor();
16
+ }
17
+
18
+ /**
19
+ * Extract identity profile from a document image
20
+ * @param imageSource - URL, File, or Blob of the document
21
+ * @returns IdentityProfile containing parsed data and OCR confidence
22
+ */
23
+ async extractFromImage(imageSource: string | File | Blob): Promise<IdentityProfile> {
24
+ const ocrResult = await this.ocr.extractMRZ(imageSource);
25
+
26
+ if (ocrResult.mrzLines.length < 2) {
27
+ throw new Error(`Failed to detect MRZ lines in image. Raw text: ${ocrResult.rawText.substring(0, 100)}...`);
28
+ }
29
+
30
+ // Attempt to parse the detected lines (assuming TD3/Passport for now)
31
+ // TD3 expects two lines of 44 characters
32
+ const mrzData = parseTD3(ocrResult.mrzLines[0], ocrResult.mrzLines[1]);
33
+
34
+ return {
35
+ ...mrzData,
36
+ confidence: ocrResult.confidence,
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Cleanup resources
42
+ */
43
+ async cleanup() {
44
+ await this.ocr.terminate();
45
+ }
46
+ }
@@ -0,0 +1,83 @@
1
+ import { ethers } from 'ethers';
2
+
3
+ /**
4
+ * Convert a jurisdiction string (e.g., "US", "UK", "CA") to a hash number
5
+ * Uses keccak256 to hash the string, then converts to a BigInt
6
+ * @param jurisdiction - Jurisdiction string (e.g., "US", "UK", "CA")
7
+ * @param returnHex - If true, return hex string instead of decimal string
8
+ * @returns Hash as a string number or hex string (for contract compatibility)
9
+ */
10
+ export function jurisdictionStringToHash(
11
+ jurisdiction: string,
12
+ returnHex: boolean = false
13
+ ): string {
14
+ if (!jurisdiction || typeof jurisdiction !== 'string') {
15
+ throw new Error('Jurisdiction must be a non-empty string');
16
+ }
17
+
18
+ // Normalize the jurisdiction string (uppercase, trim)
19
+ const normalized = jurisdiction.trim().toUpperCase();
20
+
21
+ // Hash using keccak256
22
+ const hash = ethers.keccak256(ethers.toUtf8Bytes(normalized));
23
+
24
+ if (returnHex) {
25
+ // Return as hex string (backend can convert to BigInt)
26
+ return hash;
27
+ }
28
+
29
+ // Convert hex hash to BigInt, then to string for JSON serialization
30
+ // The contract expects uint256, so we use the full hash as a number
31
+ const hashBigInt = BigInt(hash);
32
+
33
+ // Return as string to avoid precision issues with large numbers
34
+ return hashBigInt.toString();
35
+ }
36
+
37
+ /**
38
+ * Convert multiple jurisdiction strings to hash numbers
39
+ * @param jurisdictions - Array of jurisdiction strings
40
+ * @returns Array of hash numbers as strings
41
+ */
42
+ export function jurisdictionStringsToHashes(jurisdictions: string[]): string[] {
43
+ if (!Array.isArray(jurisdictions)) {
44
+ throw new Error('Jurisdictions must be an array');
45
+ }
46
+
47
+ return jurisdictions
48
+ .map((j) => j.trim())
49
+ .filter((j) => j.length > 0)
50
+ .map((j) => {
51
+ // If it's already a number string, return as-is
52
+ // This allows mixing strings and numbers
53
+ if (/^\d+$/.test(j)) {
54
+ return j;
55
+ }
56
+ // Otherwise, convert string to hash
57
+ return jurisdictionStringToHash(j);
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Parse jurisdiction input (comma-separated string) and convert to hashes
63
+ * @param input - Comma-separated jurisdiction strings or numbers
64
+ * @returns Array of hash numbers as strings
65
+ */
66
+ export function parseJurisdictions(input: string): string[] {
67
+ if (!input || typeof input !== 'string') {
68
+ return [];
69
+ }
70
+
71
+ const jurisdictions = input
72
+ .split(',')
73
+ .map((j) => j.trim())
74
+ .filter((j) => j.length > 0);
75
+
76
+ return jurisdictionStringsToHashes(jurisdictions);
77
+ }
78
+
79
+
80
+
81
+
82
+
83
+