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.
- package/README.md +892 -0
- package/dist/core/APIClient.d.ts +71 -0
- package/dist/core/APIClient.d.ts.map +1 -0
- package/dist/core/APIClient.js +92 -0
- package/dist/core/APIClient.js.map +1 -0
- package/dist/core/ContractClient.d.ts +38 -0
- package/dist/core/ContractClient.d.ts.map +1 -0
- package/dist/core/ContractClient.js +209 -0
- package/dist/core/ContractClient.js.map +1 -0
- package/dist/core/NoahSDK.d.ts +43 -0
- package/dist/core/NoahSDK.d.ts.map +1 -0
- package/dist/core/NoahSDK.js +93 -0
- package/dist/core/NoahSDK.js.map +1 -0
- package/dist/core/WalletAdapter.d.ts +188 -0
- package/dist/core/WalletAdapter.d.ts.map +1 -0
- package/dist/core/WalletAdapter.js +425 -0
- package/dist/core/WalletAdapter.js.map +1 -0
- package/dist/hooks/index.d.ts +18 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +15 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useCredentials.d.ts +136 -0
- package/dist/hooks/useCredentials.d.ts.map +1 -0
- package/dist/hooks/useCredentials.js +217 -0
- package/dist/hooks/useCredentials.js.map +1 -0
- package/dist/hooks/useProtocol.d.ts +117 -0
- package/dist/hooks/useProtocol.d.ts.map +1 -0
- package/dist/hooks/useProtocol.js +165 -0
- package/dist/hooks/useProtocol.js.map +1 -0
- package/dist/hooks/useUser.d.ts +159 -0
- package/dist/hooks/useUser.d.ts.map +1 -0
- package/dist/hooks/useUser.js +188 -0
- package/dist/hooks/useUser.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/issuer/IssuerClient.d.ts +98 -0
- package/dist/issuer/IssuerClient.d.ts.map +1 -0
- package/dist/issuer/IssuerClient.js +159 -0
- package/dist/issuer/IssuerClient.js.map +1 -0
- package/dist/protocol/ProtocolClient.d.ts +124 -0
- package/dist/protocol/ProtocolClient.d.ts.map +1 -0
- package/dist/protocol/ProtocolClient.js +265 -0
- package/dist/protocol/ProtocolClient.js.map +1 -0
- package/dist/protocol/RequirementsManager.d.ts +9 -0
- package/dist/protocol/RequirementsManager.d.ts.map +1 -0
- package/dist/protocol/RequirementsManager.js +9 -0
- package/dist/protocol/RequirementsManager.js.map +1 -0
- package/dist/user/ProofGenerator.d.ts +49 -0
- package/dist/user/ProofGenerator.d.ts.map +1 -0
- package/dist/user/ProofGenerator.js +80 -0
- package/dist/user/ProofGenerator.js.map +1 -0
- package/dist/user/UserClient.d.ts +191 -0
- package/dist/user/UserClient.d.ts.map +1 -0
- package/dist/user/UserClient.js +338 -0
- package/dist/user/UserClient.js.map +1 -0
- package/dist/utils/credentials.d.ts +47 -0
- package/dist/utils/credentials.d.ts.map +1 -0
- package/dist/utils/credentials.js +99 -0
- package/dist/utils/credentials.js.map +1 -0
- package/dist/utils/identity.d.ts +22 -0
- package/dist/utils/identity.d.ts.map +1 -0
- package/dist/utils/identity.js +35 -0
- package/dist/utils/identity.js.map +1 -0
- package/dist/utils/jurisdiction.d.ts +21 -0
- package/dist/utils/jurisdiction.d.ts.map +1 -0
- package/dist/utils/jurisdiction.js +64 -0
- package/dist/utils/jurisdiction.js.map +1 -0
- package/dist/utils/mrz.d.ts +16 -0
- package/dist/utils/mrz.d.ts.map +1 -0
- package/dist/utils/mrz.js +91 -0
- package/dist/utils/mrz.js.map +1 -0
- package/dist/utils/ocr.d.ts +31 -0
- package/dist/utils/ocr.d.ts.map +1 -0
- package/dist/utils/ocr.js +69 -0
- package/dist/utils/ocr.js.map +1 -0
- package/dist/utils/types.d.ts +122 -0
- package/dist/utils/types.d.ts.map +1 -0
- package/dist/utils/types.js +2 -0
- package/dist/utils/types.js.map +1 -0
- package/package.json +53 -0
- package/src/core/APIClient.ts +165 -0
- package/src/core/ContractClient.ts +266 -0
- package/src/core/NoahSDK.ts +123 -0
- package/src/core/WalletAdapter.ts +546 -0
- package/src/hooks/index.ts +31 -0
- package/src/hooks/types.d.ts +18 -0
- package/src/hooks/useCredentials.ts +359 -0
- package/src/hooks/useProtocol.ts +284 -0
- package/src/hooks/useUser.ts +331 -0
- package/src/index.ts +80 -0
- package/src/issuer/IssuerClient.ts +209 -0
- package/src/protocol/ProtocolClient.ts +330 -0
- package/src/protocol/RequirementsManager.ts +16 -0
- package/src/user/ProofGenerator.ts +113 -0
- package/src/user/UserClient.ts +440 -0
- package/src/utils/credentials.ts +122 -0
- package/src/utils/identity.ts +46 -0
- package/src/utils/jurisdiction.ts +83 -0
- package/src/utils/mrz.ts +113 -0
- package/src/utils/ocr.ts +84 -0
- 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
|
+
|