@zkproofport-app/sdk 0.2.1 → 0.2.3

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/dist/index.mjs CHANGED
@@ -67,6 +67,12 @@ const CIRCUIT_METADATA = {
67
67
  publicInputsCount: 14,
68
68
  publicInputNames: ['signal_hash', 'signer_list_merkle_root', 'country_list', 'country_list_length', 'is_included'],
69
69
  },
70
+ oidc_domain_attestation: {
71
+ name: 'OIDC Domain',
72
+ description: 'Prove email domain affiliation via OIDC JWT',
73
+ publicInputsCount: 420,
74
+ publicInputNames: ['pubkey_modulus_limbs', 'domain', 'scope', 'nullifier'],
75
+ },
70
76
  };
71
77
  /**
72
78
  * Standard verifier contract ABI shared across all Barretenberg-generated verifiers.
@@ -135,6 +141,97 @@ const DEFAULT_REQUEST_EXPIRY_MS = 10 * 60 * 1000;
135
141
  * ```
136
142
  */
137
143
  const MAX_QR_DATA_SIZE = 2953; // Version 40 with L error correction
144
+ /**
145
+ * Coinbase Attestation circuit public input layout (byte offsets).
146
+ * Defines the byte positions of each field in the flattened public inputs array.
147
+ *
148
+ * Public inputs are packed as bytes32 values:
149
+ * - signal_hash: bytes 0-31
150
+ * - merkle_root: bytes 32-63
151
+ * - scope: bytes 64-95
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * const publicInputs = response.publicInputs;
156
+ * const signalHash = publicInputs.slice(
157
+ * COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT.SIGNAL_HASH_START,
158
+ * COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT.SIGNAL_HASH_END + 1
159
+ * );
160
+ * ```
161
+ */
162
+ const COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT = {
163
+ SIGNAL_HASH_START: 0,
164
+ SIGNAL_HASH_END: 31,
165
+ MERKLE_ROOT_START: 32,
166
+ MERKLE_ROOT_END: 63,
167
+ SCOPE_START: 64,
168
+ SCOPE_END: 95,
169
+ NULLIFIER_START: 96,
170
+ NULLIFIER_END: 127,
171
+ };
172
+ /**
173
+ * Coinbase Country Attestation circuit public input layout (byte offsets).
174
+ * Defines the byte positions of each field in the flattened public inputs array.
175
+ *
176
+ * Public inputs are packed as bytes32 values:
177
+ * - signal_hash: bytes 0-31
178
+ * - merkle_root: bytes 32-63
179
+ * - country_list: bytes 64-83 (20 bytes for 10 countries)
180
+ * - country_list_length: byte 84
181
+ * - is_included: byte 85
182
+ * - scope: bytes 86-117
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * const publicInputs = response.publicInputs;
187
+ * const countryList = publicInputs.slice(
188
+ * COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT.COUNTRY_LIST_START,
189
+ * COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT.COUNTRY_LIST_END + 1
190
+ * );
191
+ * ```
192
+ */
193
+ const COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT = {
194
+ SIGNAL_HASH_START: 0,
195
+ SIGNAL_HASH_END: 31,
196
+ MERKLE_ROOT_START: 32,
197
+ MERKLE_ROOT_END: 63,
198
+ COUNTRY_LIST_START: 64,
199
+ COUNTRY_LIST_END: 83,
200
+ COUNTRY_LIST_LENGTH: 84,
201
+ IS_INCLUDED: 85,
202
+ SCOPE_START: 86,
203
+ SCOPE_END: 117,
204
+ NULLIFIER_START: 118,
205
+ NULLIFIER_END: 149,
206
+ };
207
+ /**
208
+ * OIDC Domain Attestation circuit public input layout (byte offsets).
209
+ * Defines the byte positions of each field in the flattened public inputs array.
210
+ *
211
+ * Public inputs are packed as individual field elements (one byte per element):
212
+ * - pubkey_modulus_limbs: 18 x u128 = 18 x 16 bytes = 288 bytes → fields 0–287
213
+ * - domain (BoundedVec<u8, 64>): 4-byte length (u32) + 64-byte storage = 68 fields → fields 288–355
214
+ * - scope: 32 bytes → fields 356–387
215
+ * - nullifier: 32 bytes → fields 388–419
216
+ *
217
+ * @example
218
+ * ```typescript
219
+ * const scope = publicInputs.slice(
220
+ * OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT.SCOPE_START,
221
+ * OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT.SCOPE_END + 1
222
+ * );
223
+ * ```
224
+ */
225
+ const OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT = {
226
+ PUBKEY_MODULUS_START: 0,
227
+ PUBKEY_MODULUS_END: 287,
228
+ DOMAIN_START: 288,
229
+ DOMAIN_END: 355,
230
+ SCOPE_START: 356,
231
+ SCOPE_END: 387,
232
+ NULLIFIER_START: 388,
233
+ NULLIFIER_END: 419,
234
+ };
138
235
 
139
236
  /**
140
237
  * Deep Link utilities for ZKProofport SDK
@@ -387,7 +484,7 @@ function validateProofRequest(request) {
387
484
  if (!request.circuit) {
388
485
  return { valid: false, error: 'Missing circuit type' };
389
486
  }
390
- if (!['coinbase_attestation', 'coinbase_country_attestation'].includes(request.circuit)) {
487
+ if (!['coinbase_attestation', 'coinbase_country_attestation', 'oidc_domain_attestation'].includes(request.circuit)) {
391
488
  return { valid: false, error: `Invalid circuit type: ${request.circuit}` };
392
489
  }
393
490
  if (!request.callbackUrl) {
@@ -417,6 +514,15 @@ function validateProofRequest(request) {
417
514
  return { valid: false, error: 'isIncluded is required and must be a boolean' };
418
515
  }
419
516
  }
517
+ else if (request.circuit === 'oidc_domain_attestation') {
518
+ const inputs = request.inputs;
519
+ if (!inputs.domain || typeof inputs.domain !== 'string' || inputs.domain.trim() === '') {
520
+ return { valid: false, error: 'domain is required and must be a non-empty string' };
521
+ }
522
+ if (!inputs.scope || typeof inputs.scope !== 'string' || inputs.scope.trim() === '') {
523
+ return { valid: false, error: 'scope is required and must be a non-empty string' };
524
+ }
525
+ }
420
526
  // Check expiry
421
527
  if (request.expiresAt && Date.now() > request.expiresAt) {
422
528
  return { valid: false, error: 'Request has expired' };
@@ -3718,6 +3824,10 @@ function extractScopeFromPublicInputs(publicInputsHex, circuit) {
3718
3824
  start = 86;
3719
3825
  end = 117;
3720
3826
  }
3827
+ else if (circuit === 'oidc_domain_attestation') {
3828
+ start = 356;
3829
+ end = 387;
3830
+ }
3721
3831
  else {
3722
3832
  start = 64;
3723
3833
  end = 95;
@@ -3727,6 +3837,43 @@ function extractScopeFromPublicInputs(publicInputsHex, circuit) {
3727
3837
  const scopeFields = publicInputsHex.slice(start, end + 1);
3728
3838
  return reconstructBytes32FromFields(scopeFields);
3729
3839
  }
3840
+ /**
3841
+ * Extracts the nullifier (bytes32) from public inputs based on circuit type.
3842
+ *
3843
+ * The nullifier is a unique, deterministic hash derived from the user's attestation
3844
+ * and scope. It serves as a privacy-preserving user identifier — the same user
3845
+ * with the same scope always produces the same nullifier, enabling duplicate
3846
+ * detection without revealing the wallet address.
3847
+ *
3848
+ * @param publicInputsHex - Array of hex-encoded field elements
3849
+ * @param circuit - Circuit type (defaults to coinbase_attestation)
3850
+ * @returns Nullifier as hex string (bytes32), or null if publicInputs too short
3851
+ *
3852
+ * @example
3853
+ * ```typescript
3854
+ * const nullifier = extractNullifierFromPublicInputs(publicInputs, 'coinbase_attestation');
3855
+ * console.log(nullifier); // '0xabc123...'
3856
+ * ```
3857
+ */
3858
+ function extractNullifierFromPublicInputs(publicInputsHex, circuit) {
3859
+ let start, end;
3860
+ if (circuit === 'coinbase_country_attestation') {
3861
+ start = 118;
3862
+ end = 149;
3863
+ }
3864
+ else if (circuit === 'oidc_domain_attestation') {
3865
+ start = 388;
3866
+ end = 419;
3867
+ }
3868
+ else {
3869
+ start = 96;
3870
+ end = 127;
3871
+ }
3872
+ if (publicInputsHex.length <= end)
3873
+ return null;
3874
+ const nullifierFields = publicInputsHex.slice(start, end + 1);
3875
+ return reconstructBytes32FromFields(nullifierFields);
3876
+ }
3730
3877
  /** @internal Reconstruct a bytes32 value from 32 individual field elements */
3731
3878
  function reconstructBytes32FromFields(fields) {
3732
3879
  if (fields.length !== 32) {
@@ -4609,53 +4756,26 @@ class ProofportSDK {
4609
4756
  }
4610
4757
  return await response.json();
4611
4758
  }
4612
- /**
4613
- * Creates a proof request through the relay server.
4614
- *
4615
- * This is the recommended way to create proof requests. The relay server:
4616
- * - Issues a server-side requestId (validated by the mobile app)
4617
- * - Tracks request status in Redis
4618
- * - Builds the deep link with relay callback URL
4619
- * - Stores inputs hash for deep link integrity verification
4620
- *
4621
- * @param circuit - Circuit type identifier
4622
- * @param inputs - Circuit-specific inputs
4623
- * @param options - Request options (message, dappName, dappIcon, nonce)
4624
- * @returns Promise resolving to RelayProofRequest with requestId, deepLink, pollUrl
4625
- * @throws Error if signer not set or relay request fails
4626
- *
4627
- * @example
4628
- * ```typescript
4629
- * const sdk = ProofportSDK.create();
4630
- * sdk.setSigner(signer);
4631
- *
4632
- * const relay = await sdk.createRelayRequest('coinbase_attestation', {
4633
- * scope: 'myapp.com'
4634
- * }, { dappName: 'My DApp' });
4635
- *
4636
- * // Generate QR code from relay deep link
4637
- * const qr = await sdk.generateQRCode(relay.deepLink);
4638
- *
4639
- * // Wait for proof (WebSocket primary, polling fallback)
4640
- * const result = await sdk.waitForProof(relay.requestId);
4641
- * ```
4642
- */
4643
4759
  async createRelayRequest(circuit, inputs, options = {}) {
4644
- if (!this.signer) {
4645
- throw new Error('Signer not set. Call setSigner() first.');
4646
- }
4647
4760
  if (!this.relayUrl) {
4648
4761
  throw new Error('relayUrl is required. Set it in ProofportSDK config.');
4649
4762
  }
4650
- // Get challenge from relay and sign it
4651
- const { challenge } = await this.getChallenge();
4652
- const signature = await this.signer.signMessage(challenge);
4763
+ const needsSignature = ProofportSDK.WALLET_SIGNATURE_CIRCUITS.includes(circuit);
4764
+ if (needsSignature && !this.signer) {
4765
+ throw new Error('Signer not set. Call setSigner() first. Wallet signature is required for this circuit.');
4766
+ }
4767
+ // Get challenge + requestId from relay
4768
+ const { requestId, challenge } = await this.getChallenge();
4653
4769
  const body = {
4770
+ requestId,
4654
4771
  circuitId: circuit,
4655
4772
  inputs,
4656
4773
  challenge,
4657
- signature,
4658
4774
  };
4775
+ // Sign challenge for circuits that require wallet signature
4776
+ if (needsSignature && this.signer) {
4777
+ body.signature = await this.signer.signMessage(challenge);
4778
+ }
4659
4779
  if (options.message)
4660
4780
  body.message = options.message;
4661
4781
  if (options.dappName)
@@ -4907,7 +5027,67 @@ class ProofportSDK {
4907
5027
  extractScope(publicInputs, circuit) {
4908
5028
  return extractScopeFromPublicInputs(publicInputs, circuit);
4909
5029
  }
5030
+ /**
5031
+ * Extracts the nullifier (bytes32) from proof public inputs.
5032
+ *
5033
+ * The nullifier is a unique, deterministic hash derived from the user's attestation
5034
+ * and scope. It serves as a privacy-preserving user identifier — the same user
5035
+ * with the same scope always produces the same nullifier, enabling duplicate
5036
+ * detection without revealing the wallet address.
5037
+ *
5038
+ * @param publicInputs - Array of hex-encoded field elements from proof result
5039
+ * @param circuit - Circuit type that produced the public inputs
5040
+ * @returns Nullifier as hex string (bytes32), or null if publicInputs too short
5041
+ *
5042
+ * @example
5043
+ * ```typescript
5044
+ * const result = await sdk.waitForProof(relay.requestId);
5045
+ * if (result.status === 'completed') {
5046
+ * const nullifier = sdk.extractNullifier(result.publicInputs, result.circuit);
5047
+ * console.log('Nullifier:', nullifier); // '0xabc123...'
5048
+ * }
5049
+ * ```
5050
+ */
5051
+ extractNullifier(publicInputs, circuit) {
5052
+ return extractNullifierFromPublicInputs(publicInputs, circuit);
5053
+ }
4910
5054
  }
5055
+ /**
5056
+ * Creates a proof request through the relay server.
5057
+ *
5058
+ * This is the recommended way to create proof requests. The relay server:
5059
+ * - Issues a server-side requestId (validated by the mobile app)
5060
+ * - Tracks request status in Redis
5061
+ * - Builds the deep link with relay callback URL
5062
+ * - Stores inputs hash for deep link integrity verification
5063
+ *
5064
+ * @param circuit - Circuit type identifier
5065
+ * @param inputs - Circuit-specific inputs
5066
+ * @param options - Request options (message, dappName, dappIcon, nonce)
5067
+ * @returns Promise resolving to RelayProofRequest with requestId, deepLink, pollUrl
5068
+ * @throws Error if signer not set or relay request fails
5069
+ *
5070
+ * @example
5071
+ * ```typescript
5072
+ * const sdk = ProofportSDK.create();
5073
+ * sdk.setSigner(signer);
5074
+ *
5075
+ * const relay = await sdk.createRelayRequest('coinbase_attestation', {
5076
+ * scope: 'myapp.com'
5077
+ * }, { dappName: 'My DApp' });
5078
+ *
5079
+ * // Generate QR code from relay deep link
5080
+ * const qr = await sdk.generateQRCode(relay.deepLink);
5081
+ *
5082
+ * // Wait for proof (WebSocket primary, polling fallback)
5083
+ * const result = await sdk.waitForProof(relay.requestId);
5084
+ * ```
5085
+ */
5086
+ // Circuits that require wallet signature (used as circuit input)
5087
+ ProofportSDK.WALLET_SIGNATURE_CIRCUITS = [
5088
+ 'coinbase_attestation',
5089
+ 'coinbase_country_attestation',
5090
+ ];
4911
5091
 
4912
- export { ProofportSDK, ProofportSDK as default };
5092
+ export { COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT, OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT, ProofportSDK, ProofportSDK as default, extractNullifierFromPublicInputs, extractScopeFromPublicInputs };
4913
5093
  //# sourceMappingURL=index.mjs.map