@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.js CHANGED
@@ -72,6 +72,12 @@ const CIRCUIT_METADATA = {
72
72
  publicInputsCount: 14,
73
73
  publicInputNames: ['signal_hash', 'signer_list_merkle_root', 'country_list', 'country_list_length', 'is_included'],
74
74
  },
75
+ oidc_domain_attestation: {
76
+ name: 'OIDC Domain',
77
+ description: 'Prove email domain affiliation via OIDC JWT',
78
+ publicInputsCount: 420,
79
+ publicInputNames: ['pubkey_modulus_limbs', 'domain', 'scope', 'nullifier'],
80
+ },
75
81
  };
76
82
  /**
77
83
  * Standard verifier contract ABI shared across all Barretenberg-generated verifiers.
@@ -140,6 +146,97 @@ const DEFAULT_REQUEST_EXPIRY_MS = 10 * 60 * 1000;
140
146
  * ```
141
147
  */
142
148
  const MAX_QR_DATA_SIZE = 2953; // Version 40 with L error correction
149
+ /**
150
+ * Coinbase Attestation circuit public input layout (byte offsets).
151
+ * Defines the byte positions of each field in the flattened public inputs array.
152
+ *
153
+ * Public inputs are packed as bytes32 values:
154
+ * - signal_hash: bytes 0-31
155
+ * - merkle_root: bytes 32-63
156
+ * - scope: bytes 64-95
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * const publicInputs = response.publicInputs;
161
+ * const signalHash = publicInputs.slice(
162
+ * COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT.SIGNAL_HASH_START,
163
+ * COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT.SIGNAL_HASH_END + 1
164
+ * );
165
+ * ```
166
+ */
167
+ const COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT = {
168
+ SIGNAL_HASH_START: 0,
169
+ SIGNAL_HASH_END: 31,
170
+ MERKLE_ROOT_START: 32,
171
+ MERKLE_ROOT_END: 63,
172
+ SCOPE_START: 64,
173
+ SCOPE_END: 95,
174
+ NULLIFIER_START: 96,
175
+ NULLIFIER_END: 127,
176
+ };
177
+ /**
178
+ * Coinbase Country Attestation circuit public input layout (byte offsets).
179
+ * Defines the byte positions of each field in the flattened public inputs array.
180
+ *
181
+ * Public inputs are packed as bytes32 values:
182
+ * - signal_hash: bytes 0-31
183
+ * - merkle_root: bytes 32-63
184
+ * - country_list: bytes 64-83 (20 bytes for 10 countries)
185
+ * - country_list_length: byte 84
186
+ * - is_included: byte 85
187
+ * - scope: bytes 86-117
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * const publicInputs = response.publicInputs;
192
+ * const countryList = publicInputs.slice(
193
+ * COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT.COUNTRY_LIST_START,
194
+ * COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT.COUNTRY_LIST_END + 1
195
+ * );
196
+ * ```
197
+ */
198
+ const COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT = {
199
+ SIGNAL_HASH_START: 0,
200
+ SIGNAL_HASH_END: 31,
201
+ MERKLE_ROOT_START: 32,
202
+ MERKLE_ROOT_END: 63,
203
+ COUNTRY_LIST_START: 64,
204
+ COUNTRY_LIST_END: 83,
205
+ COUNTRY_LIST_LENGTH: 84,
206
+ IS_INCLUDED: 85,
207
+ SCOPE_START: 86,
208
+ SCOPE_END: 117,
209
+ NULLIFIER_START: 118,
210
+ NULLIFIER_END: 149,
211
+ };
212
+ /**
213
+ * OIDC Domain Attestation circuit public input layout (byte offsets).
214
+ * Defines the byte positions of each field in the flattened public inputs array.
215
+ *
216
+ * Public inputs are packed as individual field elements (one byte per element):
217
+ * - pubkey_modulus_limbs: 18 x u128 = 18 x 16 bytes = 288 bytes → fields 0–287
218
+ * - domain (BoundedVec<u8, 64>): 4-byte length (u32) + 64-byte storage = 68 fields → fields 288–355
219
+ * - scope: 32 bytes → fields 356–387
220
+ * - nullifier: 32 bytes → fields 388–419
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * const scope = publicInputs.slice(
225
+ * OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT.SCOPE_START,
226
+ * OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT.SCOPE_END + 1
227
+ * );
228
+ * ```
229
+ */
230
+ const OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT = {
231
+ PUBKEY_MODULUS_START: 0,
232
+ PUBKEY_MODULUS_END: 287,
233
+ DOMAIN_START: 288,
234
+ DOMAIN_END: 355,
235
+ SCOPE_START: 356,
236
+ SCOPE_END: 387,
237
+ NULLIFIER_START: 388,
238
+ NULLIFIER_END: 419,
239
+ };
143
240
 
144
241
  /**
145
242
  * Deep Link utilities for ZKProofport SDK
@@ -392,7 +489,7 @@ function validateProofRequest(request) {
392
489
  if (!request.circuit) {
393
490
  return { valid: false, error: 'Missing circuit type' };
394
491
  }
395
- if (!['coinbase_attestation', 'coinbase_country_attestation'].includes(request.circuit)) {
492
+ if (!['coinbase_attestation', 'coinbase_country_attestation', 'oidc_domain_attestation'].includes(request.circuit)) {
396
493
  return { valid: false, error: `Invalid circuit type: ${request.circuit}` };
397
494
  }
398
495
  if (!request.callbackUrl) {
@@ -422,6 +519,15 @@ function validateProofRequest(request) {
422
519
  return { valid: false, error: 'isIncluded is required and must be a boolean' };
423
520
  }
424
521
  }
522
+ else if (request.circuit === 'oidc_domain_attestation') {
523
+ const inputs = request.inputs;
524
+ if (!inputs.domain || typeof inputs.domain !== 'string' || inputs.domain.trim() === '') {
525
+ return { valid: false, error: 'domain is required and must be a non-empty string' };
526
+ }
527
+ if (!inputs.scope || typeof inputs.scope !== 'string' || inputs.scope.trim() === '') {
528
+ return { valid: false, error: 'scope is required and must be a non-empty string' };
529
+ }
530
+ }
425
531
  // Check expiry
426
532
  if (request.expiresAt && Date.now() > request.expiresAt) {
427
533
  return { valid: false, error: 'Request has expired' };
@@ -856,6 +962,10 @@ function extractScopeFromPublicInputs(publicInputsHex, circuit) {
856
962
  start = 86;
857
963
  end = 117;
858
964
  }
965
+ else if (circuit === 'oidc_domain_attestation') {
966
+ start = 356;
967
+ end = 387;
968
+ }
859
969
  else {
860
970
  start = 64;
861
971
  end = 95;
@@ -865,6 +975,43 @@ function extractScopeFromPublicInputs(publicInputsHex, circuit) {
865
975
  const scopeFields = publicInputsHex.slice(start, end + 1);
866
976
  return reconstructBytes32FromFields(scopeFields);
867
977
  }
978
+ /**
979
+ * Extracts the nullifier (bytes32) from public inputs based on circuit type.
980
+ *
981
+ * The nullifier is a unique, deterministic hash derived from the user's attestation
982
+ * and scope. It serves as a privacy-preserving user identifier — the same user
983
+ * with the same scope always produces the same nullifier, enabling duplicate
984
+ * detection without revealing the wallet address.
985
+ *
986
+ * @param publicInputsHex - Array of hex-encoded field elements
987
+ * @param circuit - Circuit type (defaults to coinbase_attestation)
988
+ * @returns Nullifier as hex string (bytes32), or null if publicInputs too short
989
+ *
990
+ * @example
991
+ * ```typescript
992
+ * const nullifier = extractNullifierFromPublicInputs(publicInputs, 'coinbase_attestation');
993
+ * console.log(nullifier); // '0xabc123...'
994
+ * ```
995
+ */
996
+ function extractNullifierFromPublicInputs(publicInputsHex, circuit) {
997
+ let start, end;
998
+ if (circuit === 'coinbase_country_attestation') {
999
+ start = 118;
1000
+ end = 149;
1001
+ }
1002
+ else if (circuit === 'oidc_domain_attestation') {
1003
+ start = 388;
1004
+ end = 419;
1005
+ }
1006
+ else {
1007
+ start = 96;
1008
+ end = 127;
1009
+ }
1010
+ if (publicInputsHex.length <= end)
1011
+ return null;
1012
+ const nullifierFields = publicInputsHex.slice(start, end + 1);
1013
+ return reconstructBytes32FromFields(nullifierFields);
1014
+ }
868
1015
  /** @internal Reconstruct a bytes32 value from 32 individual field elements */
869
1016
  function reconstructBytes32FromFields(fields) {
870
1017
  if (fields.length !== 32) {
@@ -1747,53 +1894,26 @@ class ProofportSDK {
1747
1894
  }
1748
1895
  return await response.json();
1749
1896
  }
1750
- /**
1751
- * Creates a proof request through the relay server.
1752
- *
1753
- * This is the recommended way to create proof requests. The relay server:
1754
- * - Issues a server-side requestId (validated by the mobile app)
1755
- * - Tracks request status in Redis
1756
- * - Builds the deep link with relay callback URL
1757
- * - Stores inputs hash for deep link integrity verification
1758
- *
1759
- * @param circuit - Circuit type identifier
1760
- * @param inputs - Circuit-specific inputs
1761
- * @param options - Request options (message, dappName, dappIcon, nonce)
1762
- * @returns Promise resolving to RelayProofRequest with requestId, deepLink, pollUrl
1763
- * @throws Error if signer not set or relay request fails
1764
- *
1765
- * @example
1766
- * ```typescript
1767
- * const sdk = ProofportSDK.create();
1768
- * sdk.setSigner(signer);
1769
- *
1770
- * const relay = await sdk.createRelayRequest('coinbase_attestation', {
1771
- * scope: 'myapp.com'
1772
- * }, { dappName: 'My DApp' });
1773
- *
1774
- * // Generate QR code from relay deep link
1775
- * const qr = await sdk.generateQRCode(relay.deepLink);
1776
- *
1777
- * // Wait for proof (WebSocket primary, polling fallback)
1778
- * const result = await sdk.waitForProof(relay.requestId);
1779
- * ```
1780
- */
1781
1897
  async createRelayRequest(circuit, inputs, options = {}) {
1782
- if (!this.signer) {
1783
- throw new Error('Signer not set. Call setSigner() first.');
1784
- }
1785
1898
  if (!this.relayUrl) {
1786
1899
  throw new Error('relayUrl is required. Set it in ProofportSDK config.');
1787
1900
  }
1788
- // Get challenge from relay and sign it
1789
- const { challenge } = await this.getChallenge();
1790
- const signature = await this.signer.signMessage(challenge);
1901
+ const needsSignature = ProofportSDK.WALLET_SIGNATURE_CIRCUITS.includes(circuit);
1902
+ if (needsSignature && !this.signer) {
1903
+ throw new Error('Signer not set. Call setSigner() first. Wallet signature is required for this circuit.');
1904
+ }
1905
+ // Get challenge + requestId from relay
1906
+ const { requestId, challenge } = await this.getChallenge();
1791
1907
  const body = {
1908
+ requestId,
1792
1909
  circuitId: circuit,
1793
1910
  inputs,
1794
1911
  challenge,
1795
- signature,
1796
1912
  };
1913
+ // Sign challenge for circuits that require wallet signature
1914
+ if (needsSignature && this.signer) {
1915
+ body.signature = await this.signer.signMessage(challenge);
1916
+ }
1797
1917
  if (options.message)
1798
1918
  body.message = options.message;
1799
1919
  if (options.dappName)
@@ -2045,8 +2165,73 @@ class ProofportSDK {
2045
2165
  extractScope(publicInputs, circuit) {
2046
2166
  return extractScopeFromPublicInputs(publicInputs, circuit);
2047
2167
  }
2168
+ /**
2169
+ * Extracts the nullifier (bytes32) from proof public inputs.
2170
+ *
2171
+ * The nullifier is a unique, deterministic hash derived from the user's attestation
2172
+ * and scope. It serves as a privacy-preserving user identifier — the same user
2173
+ * with the same scope always produces the same nullifier, enabling duplicate
2174
+ * detection without revealing the wallet address.
2175
+ *
2176
+ * @param publicInputs - Array of hex-encoded field elements from proof result
2177
+ * @param circuit - Circuit type that produced the public inputs
2178
+ * @returns Nullifier as hex string (bytes32), or null if publicInputs too short
2179
+ *
2180
+ * @example
2181
+ * ```typescript
2182
+ * const result = await sdk.waitForProof(relay.requestId);
2183
+ * if (result.status === 'completed') {
2184
+ * const nullifier = sdk.extractNullifier(result.publicInputs, result.circuit);
2185
+ * console.log('Nullifier:', nullifier); // '0xabc123...'
2186
+ * }
2187
+ * ```
2188
+ */
2189
+ extractNullifier(publicInputs, circuit) {
2190
+ return extractNullifierFromPublicInputs(publicInputs, circuit);
2191
+ }
2048
2192
  }
2193
+ /**
2194
+ * Creates a proof request through the relay server.
2195
+ *
2196
+ * This is the recommended way to create proof requests. The relay server:
2197
+ * - Issues a server-side requestId (validated by the mobile app)
2198
+ * - Tracks request status in Redis
2199
+ * - Builds the deep link with relay callback URL
2200
+ * - Stores inputs hash for deep link integrity verification
2201
+ *
2202
+ * @param circuit - Circuit type identifier
2203
+ * @param inputs - Circuit-specific inputs
2204
+ * @param options - Request options (message, dappName, dappIcon, nonce)
2205
+ * @returns Promise resolving to RelayProofRequest with requestId, deepLink, pollUrl
2206
+ * @throws Error if signer not set or relay request fails
2207
+ *
2208
+ * @example
2209
+ * ```typescript
2210
+ * const sdk = ProofportSDK.create();
2211
+ * sdk.setSigner(signer);
2212
+ *
2213
+ * const relay = await sdk.createRelayRequest('coinbase_attestation', {
2214
+ * scope: 'myapp.com'
2215
+ * }, { dappName: 'My DApp' });
2216
+ *
2217
+ * // Generate QR code from relay deep link
2218
+ * const qr = await sdk.generateQRCode(relay.deepLink);
2219
+ *
2220
+ * // Wait for proof (WebSocket primary, polling fallback)
2221
+ * const result = await sdk.waitForProof(relay.requestId);
2222
+ * ```
2223
+ */
2224
+ // Circuits that require wallet signature (used as circuit input)
2225
+ ProofportSDK.WALLET_SIGNATURE_CIRCUITS = [
2226
+ 'coinbase_attestation',
2227
+ 'coinbase_country_attestation',
2228
+ ];
2049
2229
 
2230
+ exports.COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT = COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT;
2231
+ exports.COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT = COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT;
2232
+ exports.OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT = OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT;
2050
2233
  exports.ProofportSDK = ProofportSDK;
2051
2234
  exports.default = ProofportSDK;
2235
+ exports.extractNullifierFromPublicInputs = extractNullifierFromPublicInputs;
2236
+ exports.extractScopeFromPublicInputs = extractScopeFromPublicInputs;
2052
2237
  //# sourceMappingURL=index.js.map