@zkproofport-app/sdk 0.2.7 → 0.2.8

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
@@ -78,6 +78,26 @@ const CIRCUIT_METADATA = {
78
78
  publicInputsCount: 148,
79
79
  publicInputNames: ['pubkey_modulus_limbs', 'domain', 'scope', 'nullifier', 'provider'],
80
80
  },
81
+ // Korea Mobile ID (mDL) circuits. Counts are byte-flattened field totals
82
+ // from the compiled circuit ABIs: [u8; 32] arrays contribute 32 inputs each.
83
+ mdl_kr_ownership: {
84
+ name: 'Korea Mobile ID — Ownership',
85
+ description: "Prove valid Korean mobile driver's license ownership with selective disclosure",
86
+ publicInputsCount: 97,
87
+ publicInputNames: ['scope', 'nullifier_value', 'disclose_flags', 'owner_commit'],
88
+ },
89
+ mdl_kr_age: {
90
+ name: 'Korea Mobile ID — Age',
91
+ description: "Prove minimum age from Korean mobile driver's license without revealing birth date",
92
+ publicInputsCount: 66,
93
+ publicInputNames: ['scope', 'nullifier_value', 'age_threshold', 'current_year'],
94
+ },
95
+ mdl_kr_region: {
96
+ name: 'Korea Mobile ID — Region',
97
+ description: "Prove si/do residency from Korean mobile driver's license without revealing address",
98
+ publicInputsCount: 96,
99
+ publicInputNames: ['scope', 'nullifier_value', 'region_code'],
100
+ },
81
101
  };
82
102
  /**
83
103
  * Standard verifier contract ABI shared across all Barretenberg-generated verifiers.
@@ -209,6 +229,36 @@ const COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT = {
209
229
  NULLIFIER_START: 118,
210
230
  NULLIFIER_END: 149,
211
231
  };
232
+ /**
233
+ * Korea Mobile ID (mDL) circuit public input layout (field offsets).
234
+ * All three mDL circuits share the same prefix: scope [0..31] and
235
+ * nullifier_value [32..63], each stored one byte per field element.
236
+ * The suffix differs per predicate circuit:
237
+ * - mdl_kr_ownership (97 inputs): disclose_flags [64], owner_commit [65..96]
238
+ * - mdl_kr_age (66 inputs): age_threshold [64], current_year [65]
239
+ * - mdl_kr_region (96 inputs): region_code [64..95]
240
+ *
241
+ * @example
242
+ * ```typescript
243
+ * const nullifier = publicInputs.slice(
244
+ * MDL_KR_PUBLIC_INPUT_LAYOUT.NULLIFIER_START,
245
+ * MDL_KR_PUBLIC_INPUT_LAYOUT.NULLIFIER_END + 1
246
+ * );
247
+ * ```
248
+ */
249
+ const MDL_KR_PUBLIC_INPUT_LAYOUT = {
250
+ SCOPE_START: 0,
251
+ SCOPE_END: 31,
252
+ NULLIFIER_START: 32,
253
+ NULLIFIER_END: 63,
254
+ OWNERSHIP_DISCLOSE_FLAGS: 64,
255
+ OWNERSHIP_OWNER_COMMIT_START: 65,
256
+ OWNERSHIP_OWNER_COMMIT_END: 96,
257
+ AGE_THRESHOLD: 64,
258
+ AGE_CURRENT_YEAR: 65,
259
+ REGION_CODE_START: 64,
260
+ REGION_CODE_END: 95,
261
+ };
212
262
  /**
213
263
  * OIDC Domain Attestation circuit public input layout (field offsets).
214
264
  * Defines the field positions in the flattened public inputs array (148 fields total).
@@ -489,6 +539,78 @@ function isProofportDeepLink(url, scheme = DEFAULT_SCHEME) {
489
539
  * @param request - Proof request to validate
490
540
  * @returns Validation result with `valid` flag and optional `error` message
491
541
  */
542
+ /**
543
+ * Validates a free-text mDL input (scope / targetRegion).
544
+ * Rejects empty or whitespace-only values, control characters (which break
545
+ * deep-link query encoding and downstream logging), and values over 256 chars
546
+ * (server-side length cap mirror). UTF-8 text such as Korean region names is
547
+ * allowed.
548
+ *
549
+ * @returns Error message string, or null when valid.
550
+ */
551
+ function validateMdlTextInput(value, fieldName) {
552
+ if (typeof value !== 'string' || value.trim() === '') {
553
+ return `${fieldName} is required and must be a non-empty string`;
554
+ }
555
+ if (value.length > 256) {
556
+ return `${fieldName} must be 256 characters or fewer`;
557
+ }
558
+ // eslint-disable-next-line no-control-regex
559
+ if (/[\u0000-\u001F\u007F]/.test(value)) {
560
+ return `${fieldName} must not contain control characters`;
561
+ }
562
+ return null;
563
+ }
564
+ /**
565
+ * Validates dapp-supplied inputs for the Korea Mobile ID (mDL) circuits.
566
+ * Shared by deep-link request validation and relay request creation so the
567
+ * same guards apply on every request path.
568
+ *
569
+ * Rules:
570
+ * - `scope` — required non-empty string, ≤256 chars, no control characters
571
+ * - `mdl_kr_ownership.discloseFlags` — optional integer 0–15 (0x0F bitmask)
572
+ * - `mdl_kr_age.ageThreshold` — required integer 1–150
573
+ * - `mdl_kr_region.targetRegion` — required non-empty string (Korean si/do),
574
+ * ≤256 chars, no control characters
575
+ *
576
+ * @param circuit - One of the three mDL circuit identifiers
577
+ * @param inputs - Dapp-supplied circuit inputs
578
+ * @returns Error message string, or null when the inputs are valid
579
+ */
580
+ function validateMdlInputs(circuit, inputs) {
581
+ const scopeError = validateMdlTextInput(inputs.scope, 'scope');
582
+ if (scopeError) {
583
+ return scopeError;
584
+ }
585
+ if (circuit === 'mdl_kr_ownership') {
586
+ const { discloseFlags } = inputs;
587
+ if (discloseFlags !== undefined) {
588
+ if (typeof discloseFlags !== 'number' ||
589
+ !Number.isInteger(discloseFlags) ||
590
+ discloseFlags < 0 ||
591
+ discloseFlags > 0x0f) {
592
+ return 'discloseFlags must be an integer between 0 and 15';
593
+ }
594
+ }
595
+ }
596
+ else if (circuit === 'mdl_kr_age') {
597
+ const { ageThreshold } = inputs;
598
+ if (typeof ageThreshold !== 'number' ||
599
+ !Number.isInteger(ageThreshold) ||
600
+ ageThreshold < 1 ||
601
+ ageThreshold > 150) {
602
+ return 'ageThreshold is required and must be an integer between 1 and 150';
603
+ }
604
+ }
605
+ else {
606
+ const { targetRegion } = inputs;
607
+ const regionError = validateMdlTextInput(targetRegion, 'targetRegion');
608
+ if (regionError) {
609
+ return regionError;
610
+ }
611
+ }
612
+ return null;
613
+ }
492
614
  function validateProofRequest(request) {
493
615
  if (!request.requestId) {
494
616
  return { valid: false, error: 'Missing requestId' };
@@ -496,7 +618,14 @@ function validateProofRequest(request) {
496
618
  if (!request.circuit) {
497
619
  return { valid: false, error: 'Missing circuit type' };
498
620
  }
499
- if (!['coinbase_attestation', 'coinbase_country_attestation', 'oidc_domain_attestation'].includes(request.circuit)) {
621
+ if (![
622
+ 'coinbase_attestation',
623
+ 'coinbase_country_attestation',
624
+ 'oidc_domain_attestation',
625
+ 'mdl_kr_ownership',
626
+ 'mdl_kr_age',
627
+ 'mdl_kr_region',
628
+ ].includes(request.circuit)) {
500
629
  return { valid: false, error: `Invalid circuit type: ${request.circuit}` };
501
630
  }
502
631
  if (!request.callbackUrl) {
@@ -538,6 +667,14 @@ function validateProofRequest(request) {
538
667
  return { valid: false, error: 'provider must be a non-empty string when specified' };
539
668
  }
540
669
  }
670
+ else if (request.circuit === 'mdl_kr_ownership' ||
671
+ request.circuit === 'mdl_kr_age' ||
672
+ request.circuit === 'mdl_kr_region') {
673
+ const mdlError = validateMdlInputs(request.circuit, request.inputs);
674
+ if (mdlError) {
675
+ return { valid: false, error: mdlError };
676
+ }
677
+ }
541
678
  // Check expiry
542
679
  if (request.expiresAt && Date.now() > request.expiresAt) {
543
680
  return { valid: false, error: 'Request has expired' };
@@ -976,6 +1113,11 @@ function extractScopeFromPublicInputs(publicInputsHex, circuit) {
976
1113
  start = 83;
977
1114
  end = 114;
978
1115
  }
1116
+ else if (circuit !== undefined && circuit.startsWith('mdl_kr_')) {
1117
+ // All Korea mDL circuits: scope [0..31], nullifier_value [32..63]
1118
+ start = 0;
1119
+ end = 31;
1120
+ }
979
1121
  else {
980
1122
  start = 64;
981
1123
  end = 95;
@@ -1013,6 +1155,11 @@ function extractNullifierFromPublicInputs(publicInputsHex, circuit) {
1013
1155
  start = 115;
1014
1156
  end = 146;
1015
1157
  }
1158
+ else if (circuit !== undefined && circuit.startsWith('mdl_kr_')) {
1159
+ // All Korea mDL circuits: scope [0..31], nullifier_value [32..63]
1160
+ start = 32;
1161
+ end = 63;
1162
+ }
1016
1163
  else {
1017
1164
  start = 96;
1018
1165
  end = 127;
@@ -1280,10 +1427,48 @@ class ProofportSDK {
1280
1427
  if (circuit === 'coinbase_country_attestation') {
1281
1428
  return this.createCoinbaseCountryRequest(inputs, options);
1282
1429
  }
1430
+ else if (circuit === 'mdl_kr_ownership' ||
1431
+ circuit === 'mdl_kr_age' ||
1432
+ circuit === 'mdl_kr_region') {
1433
+ return this.createMdlKrRequest(circuit, inputs, options);
1434
+ }
1283
1435
  else {
1284
1436
  return this.createCoinbaseKycRequest(inputs, options);
1285
1437
  }
1286
1438
  }
1439
+ /**
1440
+ * @internal
1441
+ * Creates a Korea Mobile ID (mDL) proof request for any of the three
1442
+ * mDL predicate circuits (ownership / age / region).
1443
+ *
1444
+ * The mobile app collects the license data on-device; the dapp only
1445
+ * supplies the predicate parameters validated here. No wallet signature
1446
+ * is required for mDL circuits.
1447
+ *
1448
+ * @param circuit - 'mdl_kr_ownership' | 'mdl_kr_age' | 'mdl_kr_region'
1449
+ * @param inputs - Predicate inputs (see MdlKr*Inputs types)
1450
+ * @param options - Request configuration options
1451
+ *
1452
+ * @throws Error if the predicate inputs fail validation
1453
+ */
1454
+ createMdlKrRequest(circuit, inputs, options = {}) {
1455
+ const mdlError = validateMdlInputs(circuit, inputs);
1456
+ if (mdlError) {
1457
+ throw new Error(`${mdlError} (circuit: ${circuit})`);
1458
+ }
1459
+ const request = {
1460
+ requestId: generateRequestId(),
1461
+ circuit,
1462
+ inputs,
1463
+ message: options.message,
1464
+ dappName: options.dappName,
1465
+ dappIcon: options.dappIcon,
1466
+ createdAt: Date.now(),
1467
+ expiresAt: Date.now() + (options.expiresInMs || DEFAULT_REQUEST_EXPIRY_MS),
1468
+ };
1469
+ this.pendingRequests.set(request.requestId, request);
1470
+ return request;
1471
+ }
1287
1472
  // ============ Deep Link Generation ============
1288
1473
  /**
1289
1474
  * @internal
@@ -1944,6 +2129,14 @@ class ProofportSDK {
1944
2129
  if (needsSignature && !this.signer) {
1945
2130
  throw new Error('Signer not set. Call setSigner() first. Wallet signature is required for this circuit.');
1946
2131
  }
2132
+ // mDL circuits: validate predicate inputs client-side before hitting the
2133
+ // relay, so a bad ageThreshold / targetRegion fails fast with a clear error.
2134
+ if (circuit === 'mdl_kr_ownership' || circuit === 'mdl_kr_age' || circuit === 'mdl_kr_region') {
2135
+ const mdlError = validateMdlInputs(circuit, inputs);
2136
+ if (mdlError) {
2137
+ throw new Error(`${mdlError} (circuit: ${circuit})`);
2138
+ }
2139
+ }
1947
2140
  // Get challenge + requestId from relay
1948
2141
  const { requestId, challenge } = await this.getChallenge();
1949
2142
  const body = {
@@ -2292,6 +2485,7 @@ ProofportSDK.WALLET_SIGNATURE_CIRCUITS = [
2292
2485
 
2293
2486
  exports.COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT = COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT;
2294
2487
  exports.COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT = COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT;
2488
+ exports.MDL_KR_PUBLIC_INPUT_LAYOUT = MDL_KR_PUBLIC_INPUT_LAYOUT;
2295
2489
  exports.OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT = OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT;
2296
2490
  exports.ProofportSDK = ProofportSDK;
2297
2491
  exports.default = ProofportSDK;