@zkproofport-app/sdk 0.2.0 → 0.2.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 CHANGED
@@ -159,6 +159,19 @@ interface WalletSigner {
159
159
 
160
160
  Any ethers v5/v6 `Signer` is compatible.
161
161
 
162
+ #### About challenge-signature
163
+
164
+ The challenge-signature mechanism was developed **for relay nonce replay prevention**. Each challenge is one-time use and consumed immediately. The signer's recovered address is recorded as `clientId` in relay server logs, which helps the relay operator track requests.
165
+
166
+ For server-side or headless environments, using an ephemeral random wallet is fine. A persistent wallet (fixed private key) is **not recommended** as it adds unnecessary key management overhead with no functional benefit.
167
+
168
+ ```typescript
169
+ import { Wallet } from 'ethers';
170
+
171
+ // Server-side: ephemeral wallet per request
172
+ sdk.setSigner(Wallet.createRandom());
173
+ ```
174
+
162
175
  ### Step 3: Create Request (via Relay)
163
176
 
164
177
  `createRelayRequest` authenticates with the relay (challenge-signature), creates a tracked proof request, and returns a deep link.
@@ -281,6 +294,41 @@ if (result.status === 'completed') {
281
294
  const verification = await sdk.verifyResponseOnChain(response);
282
295
  ```
283
296
 
297
+ ### Step 7: Extract Scope and Nullifier
298
+
299
+ After verification, extract the scope and nullifier from the public inputs:
300
+
301
+ ```typescript
302
+ if (result.status === 'completed') {
303
+ // Extract scope — the keccak256 hash of the scope string you provided
304
+ const scope = sdk.extractScope(result.publicInputs, result.circuit);
305
+
306
+ // Extract nullifier — a unique, deterministic hash per user + scope
307
+ // Same user with the same scope always produces the same nullifier
308
+ const nullifier = sdk.extractNullifier(result.publicInputs, result.circuit);
309
+
310
+ console.log('Scope:', scope); // '0x7a6b70726f...'
311
+ console.log('Nullifier:', nullifier); // '0xabc123...'
312
+ }
313
+ ```
314
+
315
+ The **nullifier** serves as a privacy-preserving user identifier:
316
+ - Deterministic: same user + same scope = same nullifier (enables duplicate detection)
317
+ - Privacy-preserving: the wallet address is never revealed
318
+ - Scope-bound: different scopes produce different nullifiers for the same user
319
+
320
+ **Standalone utility functions** are also available for use outside the SDK class:
321
+
322
+ ```typescript
323
+ import {
324
+ extractScopeFromPublicInputs,
325
+ extractNullifierFromPublicInputs,
326
+ } from '@zkproofport-app/sdk';
327
+
328
+ const scope = extractScopeFromPublicInputs(publicInputs, 'coinbase_attestation');
329
+ const nullifier = extractNullifierFromPublicInputs(publicInputs, 'coinbase_attestation');
330
+ ```
331
+
284
332
  ## Complete Example
285
333
 
286
334
  End-to-end integration using the relay flow:
@@ -893,5 +893,27 @@ export declare class ProofportSDK {
893
893
  * ```
894
894
  */
895
895
  extractScope(publicInputs: string[], circuit: CircuitType): string | null;
896
+ /**
897
+ * Extracts the nullifier (bytes32) from proof public inputs.
898
+ *
899
+ * The nullifier is a unique, deterministic hash derived from the user's attestation
900
+ * and scope. It serves as a privacy-preserving user identifier — the same user
901
+ * with the same scope always produces the same nullifier, enabling duplicate
902
+ * detection without revealing the wallet address.
903
+ *
904
+ * @param publicInputs - Array of hex-encoded field elements from proof result
905
+ * @param circuit - Circuit type that produced the public inputs
906
+ * @returns Nullifier as hex string (bytes32), or null if publicInputs too short
907
+ *
908
+ * @example
909
+ * ```typescript
910
+ * const result = await sdk.waitForProof(relay.requestId);
911
+ * if (result.status === 'completed') {
912
+ * const nullifier = sdk.extractNullifier(result.publicInputs, result.circuit);
913
+ * console.log('Nullifier:', nullifier); // '0xabc123...'
914
+ * }
915
+ * ```
916
+ */
917
+ extractNullifier(publicInputs: string[], circuit: CircuitType): string | null;
896
918
  }
897
919
  export default ProofportSDK;
@@ -145,6 +145,8 @@ export declare const COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT: {
145
145
  readonly MERKLE_ROOT_END: 63;
146
146
  readonly SCOPE_START: 64;
147
147
  readonly SCOPE_END: 95;
148
+ readonly NULLIFIER_START: 96;
149
+ readonly NULLIFIER_END: 127;
148
150
  };
149
151
  /**
150
152
  * Coinbase Country Attestation circuit public input layout (byte offsets).
@@ -178,4 +180,6 @@ export declare const COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT: {
178
180
  readonly IS_INCLUDED: 85;
179
181
  readonly SCOPE_START: 86;
180
182
  readonly SCOPE_END: 117;
183
+ readonly NULLIFIER_START: 118;
184
+ readonly NULLIFIER_END: 149;
181
185
  };
package/dist/index.d.ts CHANGED
@@ -26,4 +26,6 @@
26
26
  * ```
27
27
  */
28
28
  export { ProofportSDK, default } from './ProofportSDK';
29
+ export { extractScopeFromPublicInputs, extractNullifierFromPublicInputs, } from './verifier';
30
+ export { COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT, } from './constants';
29
31
  export type { CircuitType, ProofRequestStatus, CoinbaseKycInputs, CoinbaseCountryInputs, CircuitInputs, ProofRequest, ProofResponse, QRCodeOptions, VerifierContract, ProofportConfig, ChallengeResponse, WalletSigner, RelayProofRequest, RelayProofResult, SDKEnvironment, } from './types';
package/dist/index.esm.js CHANGED
@@ -135,6 +135,69 @@ const DEFAULT_REQUEST_EXPIRY_MS = 10 * 60 * 1000;
135
135
  * ```
136
136
  */
137
137
  const MAX_QR_DATA_SIZE = 2953; // Version 40 with L error correction
138
+ /**
139
+ * Coinbase Attestation circuit public input layout (byte offsets).
140
+ * Defines the byte positions of each field in the flattened public inputs array.
141
+ *
142
+ * Public inputs are packed as bytes32 values:
143
+ * - signal_hash: bytes 0-31
144
+ * - merkle_root: bytes 32-63
145
+ * - scope: bytes 64-95
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * const publicInputs = response.publicInputs;
150
+ * const signalHash = publicInputs.slice(
151
+ * COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT.SIGNAL_HASH_START,
152
+ * COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT.SIGNAL_HASH_END + 1
153
+ * );
154
+ * ```
155
+ */
156
+ const COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT = {
157
+ SIGNAL_HASH_START: 0,
158
+ SIGNAL_HASH_END: 31,
159
+ MERKLE_ROOT_START: 32,
160
+ MERKLE_ROOT_END: 63,
161
+ SCOPE_START: 64,
162
+ SCOPE_END: 95,
163
+ NULLIFIER_START: 96,
164
+ NULLIFIER_END: 127,
165
+ };
166
+ /**
167
+ * Coinbase Country Attestation circuit public input layout (byte offsets).
168
+ * Defines the byte positions of each field in the flattened public inputs array.
169
+ *
170
+ * Public inputs are packed as bytes32 values:
171
+ * - signal_hash: bytes 0-31
172
+ * - merkle_root: bytes 32-63
173
+ * - country_list: bytes 64-83 (20 bytes for 10 countries)
174
+ * - country_list_length: byte 84
175
+ * - is_included: byte 85
176
+ * - scope: bytes 86-117
177
+ *
178
+ * @example
179
+ * ```typescript
180
+ * const publicInputs = response.publicInputs;
181
+ * const countryList = publicInputs.slice(
182
+ * COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT.COUNTRY_LIST_START,
183
+ * COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT.COUNTRY_LIST_END + 1
184
+ * );
185
+ * ```
186
+ */
187
+ const COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT = {
188
+ SIGNAL_HASH_START: 0,
189
+ SIGNAL_HASH_END: 31,
190
+ MERKLE_ROOT_START: 32,
191
+ MERKLE_ROOT_END: 63,
192
+ COUNTRY_LIST_START: 64,
193
+ COUNTRY_LIST_END: 83,
194
+ COUNTRY_LIST_LENGTH: 84,
195
+ IS_INCLUDED: 85,
196
+ SCOPE_START: 86,
197
+ SCOPE_END: 117,
198
+ NULLIFIER_START: 118,
199
+ NULLIFIER_END: 149,
200
+ };
138
201
 
139
202
  /**
140
203
  * Deep Link utilities for ZKProofport SDK
@@ -3727,6 +3790,39 @@ function extractScopeFromPublicInputs(publicInputsHex, circuit) {
3727
3790
  const scopeFields = publicInputsHex.slice(start, end + 1);
3728
3791
  return reconstructBytes32FromFields(scopeFields);
3729
3792
  }
3793
+ /**
3794
+ * Extracts the nullifier (bytes32) from public inputs based on circuit type.
3795
+ *
3796
+ * The nullifier is a unique, deterministic hash derived from the user's attestation
3797
+ * and scope. It serves as a privacy-preserving user identifier — the same user
3798
+ * with the same scope always produces the same nullifier, enabling duplicate
3799
+ * detection without revealing the wallet address.
3800
+ *
3801
+ * @param publicInputsHex - Array of hex-encoded field elements
3802
+ * @param circuit - Circuit type (defaults to coinbase_attestation)
3803
+ * @returns Nullifier as hex string (bytes32), or null if publicInputs too short
3804
+ *
3805
+ * @example
3806
+ * ```typescript
3807
+ * const nullifier = extractNullifierFromPublicInputs(publicInputs, 'coinbase_attestation');
3808
+ * console.log(nullifier); // '0xabc123...'
3809
+ * ```
3810
+ */
3811
+ function extractNullifierFromPublicInputs(publicInputsHex, circuit) {
3812
+ let start, end;
3813
+ if (circuit === 'coinbase_country_attestation') {
3814
+ start = 118;
3815
+ end = 149;
3816
+ }
3817
+ else {
3818
+ start = 96;
3819
+ end = 127;
3820
+ }
3821
+ if (publicInputsHex.length <= end)
3822
+ return null;
3823
+ const nullifierFields = publicInputsHex.slice(start, end + 1);
3824
+ return reconstructBytes32FromFields(nullifierFields);
3825
+ }
3730
3826
  /** @internal Reconstruct a bytes32 value from 32 individual field elements */
3731
3827
  function reconstructBytes32FromFields(fields) {
3732
3828
  if (fields.length !== 32) {
@@ -4907,7 +5003,31 @@ class ProofportSDK {
4907
5003
  extractScope(publicInputs, circuit) {
4908
5004
  return extractScopeFromPublicInputs(publicInputs, circuit);
4909
5005
  }
5006
+ /**
5007
+ * Extracts the nullifier (bytes32) from proof public inputs.
5008
+ *
5009
+ * The nullifier is a unique, deterministic hash derived from the user's attestation
5010
+ * and scope. It serves as a privacy-preserving user identifier — the same user
5011
+ * with the same scope always produces the same nullifier, enabling duplicate
5012
+ * detection without revealing the wallet address.
5013
+ *
5014
+ * @param publicInputs - Array of hex-encoded field elements from proof result
5015
+ * @param circuit - Circuit type that produced the public inputs
5016
+ * @returns Nullifier as hex string (bytes32), or null if publicInputs too short
5017
+ *
5018
+ * @example
5019
+ * ```typescript
5020
+ * const result = await sdk.waitForProof(relay.requestId);
5021
+ * if (result.status === 'completed') {
5022
+ * const nullifier = sdk.extractNullifier(result.publicInputs, result.circuit);
5023
+ * console.log('Nullifier:', nullifier); // '0xabc123...'
5024
+ * }
5025
+ * ```
5026
+ */
5027
+ extractNullifier(publicInputs, circuit) {
5028
+ return extractNullifierFromPublicInputs(publicInputs, circuit);
5029
+ }
4910
5030
  }
4911
5031
 
4912
- export { ProofportSDK, ProofportSDK as default };
5032
+ export { COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT, ProofportSDK, ProofportSDK as default, extractNullifierFromPublicInputs, extractScopeFromPublicInputs };
4913
5033
  //# sourceMappingURL=index.esm.js.map