@nokinc-flur/sdk 1.1.3 → 1.1.4

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
@@ -1563,64 +1563,113 @@ function constantTimeEqual(a, b) {
1563
1563
  return diff === 0;
1564
1564
  }
1565
1565
 
1566
- // src/crypto/ed25519.ts
1567
- import { ed25519 } from "@noble/curves/ed25519";
1568
- function generateKeyPair() {
1569
- const privateKey = ed25519.utils.randomPrivateKey();
1570
- const publicKey = ed25519.getPublicKey(privateKey);
1571
- return { privateKey, publicKey };
1566
+ // src/offline/oac.ts
1567
+ import { z as z5 } from "zod";
1568
+
1569
+ // src/crypto/p256-issuer.ts
1570
+ import { p256 } from "@noble/curves/nist";
1571
+ function bytesToBase64(bytes) {
1572
+ if (typeof Buffer !== "undefined") {
1573
+ return Buffer.from(bytes).toString("base64");
1574
+ }
1575
+ let bin = "";
1576
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
1577
+ return btoa(bin);
1572
1578
  }
1573
- function publicKeyFromPrivate(privateKey) {
1574
- return ed25519.getPublicKey(privateKey);
1579
+ function base64ToBytes(b64) {
1580
+ if (typeof Buffer !== "undefined") {
1581
+ return new Uint8Array(Buffer.from(b64, "base64"));
1582
+ }
1583
+ const bin = atob(b64);
1584
+ const out = new Uint8Array(bin.length);
1585
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
1586
+ return out;
1575
1587
  }
1576
- function sign(message, privateKey) {
1577
- return ed25519.sign(message, privateKey);
1588
+ var P256_SPKI_HEADER = new Uint8Array([
1589
+ 48,
1590
+ 89,
1591
+ 48,
1592
+ 19,
1593
+ 6,
1594
+ 7,
1595
+ 42,
1596
+ 134,
1597
+ 72,
1598
+ 206,
1599
+ 61,
1600
+ 2,
1601
+ 1,
1602
+ 6,
1603
+ 8,
1604
+ 42,
1605
+ 134,
1606
+ 72,
1607
+ 206,
1608
+ 61,
1609
+ 3,
1610
+ 1,
1611
+ 7,
1612
+ 3,
1613
+ 66,
1614
+ 0
1615
+ ]);
1616
+ function p256SpkiB64ToRaw(spkiB64) {
1617
+ const spki = base64ToBytes(spkiB64);
1618
+ if (spki.length !== P256_SPKI_HEADER.length + 65) {
1619
+ throw new Error("p256: invalid SPKI length");
1620
+ }
1621
+ for (let i = 0; i < P256_SPKI_HEADER.length; i++) {
1622
+ if (spki[i] !== P256_SPKI_HEADER[i]) {
1623
+ throw new Error("p256: invalid SPKI header");
1624
+ }
1625
+ }
1626
+ return spki.slice(P256_SPKI_HEADER.length);
1578
1627
  }
1579
- function verify(message, signature, publicKey) {
1628
+ function signIssuerP256(bytes, issuerPrivateKey) {
1629
+ const sig = p256.sign(bytes, issuerPrivateKey, { prehash: true });
1630
+ return bytesToBase64(sig.toBytes("der"));
1631
+ }
1632
+ function verifyIssuerP256(bytes, signatureB64, issuerPublicKeySpkiB64) {
1580
1633
  try {
1581
- return ed25519.verify(signature, message, publicKey);
1634
+ const pubRaw = p256SpkiB64ToRaw(issuerPublicKeySpkiB64);
1635
+ const sigBytes = base64ToBytes(signatureB64);
1636
+ return p256.verify(sigBytes, bytes, pubRaw, {
1637
+ prehash: true,
1638
+ format: "der"
1639
+ });
1582
1640
  } catch {
1583
1641
  return false;
1584
1642
  }
1585
1643
  }
1586
- function signCanonical(value, privateKey) {
1587
- return sign(canonicalJSONBytes(value), privateKey);
1588
- }
1589
- function verifyCanonical(value, signature, publicKey) {
1590
- return verify(canonicalJSONBytes(value), signature, publicKey);
1591
- }
1592
1644
 
1593
1645
  // src/offline/oac.ts
1594
- import { z as z5 } from "zod";
1595
1646
  var OAC_DEFAULT_PER_TX_KOBO = 5e5;
1596
1647
  var OAC_DEFAULT_CUMULATIVE_KOBO = 2e6;
1597
1648
  var OAC_DEFAULT_VALIDITY_MS = 24 * 60 * 60 * 1e3;
1598
- var HexString = (length) => z5.string().regex(
1599
- new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
1600
- `expected ${length}-byte hex string`
1601
- );
1649
+ var Base64Std = z5.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/, "expected base64 (standard) string");
1602
1650
  var OACSchema = z5.object({
1603
1651
  userId: z5.string().min(1),
1604
1652
  deviceId: z5.string().min(1),
1605
- devicePublicKey: HexString(32),
1653
+ /** SubjectPublicKeyInfo DER, base64 (P-256). */
1654
+ devicePublicKey: Base64Std,
1606
1655
  perTxCapKobo: z5.number().int().nonnegative(),
1607
1656
  cumulativeCapKobo: z5.number().int().nonnegative(),
1608
1657
  validFromMs: z5.number().int().nonnegative(),
1609
1658
  validUntilMs: z5.number().int().positive(),
1610
1659
  counterSeed: z5.number().int().nonnegative(),
1611
1660
  nonce: z5.string().min(1),
1612
- issuerSig: HexString(64)
1661
+ /** ASN.1 DER ECDSA(SHA-256) signature, base64. */
1662
+ issuerSig: Base64Std
1613
1663
  }).refine((v) => v.validUntilMs > v.validFromMs, {
1614
1664
  message: "validUntilMs must be greater than validFromMs"
1615
1665
  }).refine((v) => v.perTxCapKobo <= v.cumulativeCapKobo, {
1616
1666
  message: "perTxCapKobo must not exceed cumulativeCapKobo"
1617
1667
  });
1618
1668
  function buildOAC(input) {
1619
- const devicePublicKey = typeof input.devicePublicKey === "string" ? input.devicePublicKey : bytesToHex(input.devicePublicKey);
1620
1669
  return {
1621
1670
  userId: input.userId,
1622
1671
  deviceId: input.deviceId,
1623
- devicePublicKey,
1672
+ devicePublicKey: input.devicePublicKey,
1624
1673
  perTxCapKobo: input.perTxCapKobo ?? OAC_DEFAULT_PER_TX_KOBO,
1625
1674
  cumulativeCapKobo: input.cumulativeCapKobo ?? OAC_DEFAULT_CUMULATIVE_KOBO,
1626
1675
  validFromMs: input.validFromMs,
@@ -1630,36 +1679,25 @@ function buildOAC(input) {
1630
1679
  };
1631
1680
  }
1632
1681
  function signOAC(unsigned, issuerPrivateKey) {
1633
- const issuerSig = bytesToHex(
1634
- sign(canonicalJSONBytes(unsigned), issuerPrivateKey)
1682
+ const issuerSig = signIssuerP256(
1683
+ canonicalJSONBytes(unsigned),
1684
+ issuerPrivateKey
1635
1685
  );
1636
1686
  return { ...unsigned, issuerSig };
1637
1687
  }
1638
- function verifyOAC(oac, issuerPublicKey) {
1688
+ function verifyOAC(oac, issuerPublicKeySpkiB64) {
1639
1689
  try {
1640
1690
  const parsed = OACSchema.parse(oac);
1641
1691
  const { issuerSig, ...unsigned } = parsed;
1642
- return verify(
1692
+ return verifyIssuerP256(
1643
1693
  canonicalJSONBytes(unsigned),
1644
- hexToBytes(issuerSig),
1645
- issuerPublicKey
1694
+ issuerSig,
1695
+ issuerPublicKeySpkiB64
1646
1696
  );
1647
1697
  } catch {
1648
1698
  return false;
1649
1699
  }
1650
1700
  }
1651
- function bytesToHex(b) {
1652
- let s = "";
1653
- for (let i = 0; i < b.length; i++) s += b[i].toString(16).padStart(2, "0");
1654
- return s;
1655
- }
1656
- function hexToBytes(s) {
1657
- if (s.length % 2 !== 0) throw new Error("hex: odd length");
1658
- const out = new Uint8Array(s.length / 2);
1659
- for (let i = 0; i < out.length; i++)
1660
- out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16);
1661
- return out;
1662
- }
1663
1701
 
1664
1702
  // src/offline/codec.ts
1665
1703
  var ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
@@ -1716,19 +1754,19 @@ function decodeBase45(s) {
1716
1754
 
1717
1755
  // src/offline/messages.ts
1718
1756
  import { z as z6 } from "zod";
1719
- var HexSig = z6.string().regex(/^[0-9a-fA-F]{128}$/, "expected 64-byte hex signature");
1757
+ var Base64Sig = z6.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/, "expected base64 (standard) signature");
1720
1758
  var OfflinePaymentRequestSchema = z6.object({
1721
1759
  reference: z6.string().min(1),
1722
1760
  amountKobo: z6.number().int().positive(),
1723
1761
  merchantOAC: OACSchema,
1724
1762
  expiresAtMs: z6.number().int().positive(),
1725
- merchantSig: HexSig
1763
+ merchantSig: Base64Sig
1726
1764
  });
1727
1765
  var OfflinePaymentAuthorizationSchema = z6.object({
1728
1766
  request: OfflinePaymentRequestSchema,
1729
1767
  payerOAC: OACSchema,
1730
1768
  payerCounter: z6.number().int().positive(),
1731
- payerSig: HexSig
1769
+ payerSig: Base64Sig
1732
1770
  });
1733
1771
  function buildPaymentRequest(input) {
1734
1772
  if (!Number.isInteger(input.amountKobo) || input.amountKobo <= 0) {
@@ -1745,27 +1783,28 @@ function buildPaymentRequest(input) {
1745
1783
  };
1746
1784
  }
1747
1785
  function signPaymentRequest(unsigned, merchantDevicePrivateKey) {
1748
- const merchantSig = bytesToHex(
1749
- sign(canonicalJSONBytes(unsigned), merchantDevicePrivateKey)
1786
+ const merchantSig = signIssuerP256(
1787
+ canonicalJSONBytes(unsigned),
1788
+ merchantDevicePrivateKey
1750
1789
  );
1751
1790
  return { ...unsigned, merchantSig };
1752
1791
  }
1753
- function verifyPaymentRequest(req, issuerPublicKey) {
1792
+ function verifyPaymentRequest(req, issuerPublicKeySpkiB64) {
1754
1793
  try {
1755
1794
  const parsed = OfflinePaymentRequestSchema.parse(req);
1756
1795
  const { issuerSig: merchantOacSig, ...merchantOacUnsigned } = parsed.merchantOAC;
1757
- if (!verify(
1796
+ if (!verifyIssuerP256(
1758
1797
  canonicalJSONBytes(merchantOacUnsigned),
1759
- hexToBytes(merchantOacSig),
1760
- issuerPublicKey
1798
+ merchantOacSig,
1799
+ issuerPublicKeySpkiB64
1761
1800
  )) {
1762
1801
  return false;
1763
1802
  }
1764
1803
  const { merchantSig, ...unsigned } = parsed;
1765
- return verify(
1804
+ return verifyIssuerP256(
1766
1805
  canonicalJSONBytes(unsigned),
1767
- hexToBytes(merchantSig),
1768
- hexToBytes(parsed.merchantOAC.devicePublicKey)
1806
+ merchantSig,
1807
+ parsed.merchantOAC.devicePublicKey
1769
1808
  );
1770
1809
  } catch {
1771
1810
  return false;
@@ -1785,28 +1824,30 @@ function buildAuthorization(input) {
1785
1824
  };
1786
1825
  }
1787
1826
  function signAuthorization(unsigned, payerDevicePrivateKey) {
1788
- const payerSig = bytesToHex(
1789
- sign(canonicalJSONBytes(unsigned), payerDevicePrivateKey)
1827
+ const payerSig = signIssuerP256(
1828
+ canonicalJSONBytes(unsigned),
1829
+ payerDevicePrivateKey
1790
1830
  );
1791
1831
  return { ...unsigned, payerSig };
1792
1832
  }
1793
- function verifyAuthorization(auth, issuerPublicKey) {
1833
+ function verifyAuthorization(auth, issuerPublicKeySpkiB64) {
1794
1834
  try {
1795
1835
  const parsed = OfflinePaymentAuthorizationSchema.parse(auth);
1796
- if (!verifyPaymentRequest(parsed.request, issuerPublicKey)) return false;
1836
+ if (!verifyPaymentRequest(parsed.request, issuerPublicKeySpkiB64))
1837
+ return false;
1797
1838
  const { issuerSig: payerOacSig, ...payerOacUnsigned } = parsed.payerOAC;
1798
- if (!verify(
1839
+ if (!verifyIssuerP256(
1799
1840
  canonicalJSONBytes(payerOacUnsigned),
1800
- hexToBytes(payerOacSig),
1801
- issuerPublicKey
1841
+ payerOacSig,
1842
+ issuerPublicKeySpkiB64
1802
1843
  )) {
1803
1844
  return false;
1804
1845
  }
1805
1846
  const { payerSig, ...unsigned } = parsed;
1806
- return verify(
1847
+ return verifyIssuerP256(
1807
1848
  canonicalJSONBytes(unsigned),
1808
- hexToBytes(payerSig),
1809
- hexToBytes(parsed.payerOAC.devicePublicKey)
1849
+ payerSig,
1850
+ parsed.payerOAC.devicePublicKey
1810
1851
  );
1811
1852
  } catch {
1812
1853
  return false;
@@ -1838,7 +1879,7 @@ function decodeAuthorizationQR(s) {
1838
1879
  // src/offline/settlements.ts
1839
1880
  import { z as z7 } from "zod";
1840
1881
  import { sha256 } from "@noble/hashes/sha256";
1841
- import { bytesToHex as bytesToHex2 } from "@noble/hashes/utils";
1882
+ import { bytesToHex } from "@noble/hashes/utils";
1842
1883
  var OfflineTokenSchema = z7.object({
1843
1884
  tokenId: z7.string().uuid(),
1844
1885
  tokenSerial: z7.string(),
@@ -1862,10 +1903,14 @@ var PaymentClaimSchema = z7.object({
1862
1903
  occurredAtMs: z7.number().int().nonnegative(),
1863
1904
  completedAtMs: z7.number().int().nonnegative().optional(),
1864
1905
  contextId: z7.string().optional(),
1865
- payerPubkey: z7.string().regex(/^[0-9a-f]{64}$/i),
1866
- payerSignature: z7.string().regex(/^[0-9a-f]+$/i),
1867
- payeePubkey: z7.string().regex(/^[0-9a-f]{64}$/i).optional(),
1868
- payeeSignature: z7.string().regex(/^[0-9a-f]+$/i).optional()
1906
+ // Stage 2c: P-256 device keys are now SubjectPublicKeyInfo DER, base64.
1907
+ // Signatures are ASN.1 DER ECDSA(SHA-256), base64. Backwards-incompatible
1908
+ // wire change; the backend has the matching widening in offline-settlements
1909
+ // service + zod schema.
1910
+ payerPubkey: z7.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/),
1911
+ payerSignature: z7.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/),
1912
+ payeePubkey: z7.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/).optional(),
1913
+ payeeSignature: z7.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/).optional()
1869
1914
  });
1870
1915
  var SettlementSchema = z7.object({
1871
1916
  settlementId: z7.string().uuid(),
@@ -1889,7 +1934,7 @@ var SettleResponseSchema = z7.object({
1889
1934
  });
1890
1935
  var ENCOUNTER_DOMAIN = "offline:v1:encounter";
1891
1936
  async function sha256Hex(input) {
1892
- return bytesToHex2(sha256(new TextEncoder().encode(input)));
1937
+ return bytesToHex(sha256(new TextEncoder().encode(input)));
1893
1938
  }
1894
1939
  async function computeEncounterId(input) {
1895
1940
  return sha256Hex(
@@ -2293,10 +2338,6 @@ var PASS_STATES = [
2293
2338
  "expired",
2294
2339
  "revoked"
2295
2340
  ];
2296
- var HexString2 = (length) => z9.string().regex(
2297
- new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
2298
- `expected ${length}-byte hex string`
2299
- );
2300
2341
  var PassMetadataSchema = z9.record(
2301
2342
  z9.union([z9.string(), z9.number(), z9.boolean(), z9.null()])
2302
2343
  );
@@ -2317,9 +2358,9 @@ var PassSchema = z9.object({
2317
2358
  nonce: z9.string().min(1),
2318
2359
  /** Device id this pass is bound to (FK to backend `device_keys`). */
2319
2360
  holderDeviceId: z9.string().min(1),
2320
- /** 32-byte hex Ed25519 public key of the bound device. The redemption signature
2321
- * is verified against this key — it is the security-critical binding. */
2322
- holderDevicePubkey: HexString2(32),
2361
+ /** SubjectPublicKeyInfo DER (P-256) of the bound device, base64. The redemption
2362
+ * signature is verified against this key — it is the security-critical binding. */
2363
+ holderDevicePubkey: z9.string().min(64).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/),
2323
2364
  /** Optional fixed amount for monetary passes (vouchers, gift cards) in kobo. */
2324
2365
  amountKobo: z9.number().int().nonnegative().optional(),
2325
2366
  /** ISO-4217-ish currency code; required on the wire. SDK builders default to NGN. */
@@ -2328,7 +2369,8 @@ var PassSchema = z9.object({
2328
2369
  counterSeed: z9.number().int().nonnegative(),
2329
2370
  /** Optional cumulative spend cap in kobo across all redemptions of this pass. */
2330
2371
  cumulativeCapKobo: z9.number().int().nonnegative().optional(),
2331
- issuerSig: HexString2(64)
2372
+ /** ASN.1 DER ECDSA P-256 signature, base64. */
2373
+ issuerSig: z9.string().min(64).max(2048).regex(/^[A-Za-z0-9+/]+={0,2}$/)
2332
2374
  }).refine((v) => v.validUntilMs > v.validFromMs, {
2333
2375
  message: "validUntilMs must be greater than validFromMs"
2334
2376
  });
@@ -2361,19 +2403,20 @@ function buildPass(input) {
2361
2403
  return out;
2362
2404
  }
2363
2405
  function signPass(unsigned, issuerPrivateKey) {
2364
- const issuerSig = bytesToHex(
2365
- sign(canonicalJSONBytes(unsigned), issuerPrivateKey)
2406
+ const issuerSig = signIssuerP256(
2407
+ canonicalJSONBytes(unsigned),
2408
+ issuerPrivateKey
2366
2409
  );
2367
2410
  return { ...unsigned, issuerSig };
2368
2411
  }
2369
- function verifyPass(pass, issuerPublicKey) {
2412
+ function verifyPass(pass, issuerPublicKeySpkiB64) {
2370
2413
  try {
2371
2414
  const parsed = PassSchema.parse(pass);
2372
2415
  const { issuerSig, ...unsigned } = parsed;
2373
- return verify(
2416
+ return verifyIssuerP256(
2374
2417
  canonicalJSONBytes(unsigned),
2375
- hexToBytes(issuerSig),
2376
- issuerPublicKey
2418
+ issuerSig,
2419
+ issuerPublicKeySpkiB64
2377
2420
  );
2378
2421
  } catch {
2379
2422
  return false;
@@ -2385,7 +2428,7 @@ function isPassWithinValidity(pass, nowMs) {
2385
2428
 
2386
2429
  // src/passes/redemption.ts
2387
2430
  import { z as z10 } from "zod";
2388
- var HexSig2 = z10.string().regex(/^[0-9a-fA-F]{128}$/, "expected 64-byte hex signature");
2431
+ var Base64Std2 = z10.string().min(16).max(2048).regex(/^[A-Za-z0-9+/]+={0,2}$/, "expected base64 (std)");
2389
2432
  var RedemptionSchema = z10.object({
2390
2433
  pass: PassSchema,
2391
2434
  redeemerId: z10.string().min(1),
@@ -2396,7 +2439,8 @@ var RedemptionSchema = z10.object({
2396
2439
  /** Amount being redeemed in kobo (0 for non-monetary passes like ride tickets). */
2397
2440
  amountKobo: z10.number().int().nonnegative(),
2398
2441
  nonce: z10.string().min(1),
2399
- holderSig: HexSig2
2442
+ /** ASN.1 DER ECDSA P-256 signature over canonicalJSONBytes(unsigned), base64. */
2443
+ holderSig: Base64Std2
2400
2444
  });
2401
2445
  var REDEEMABLE_STATES = /* @__PURE__ */ new Set(["issued", "active"]);
2402
2446
  function buildRedemption(input) {
@@ -2450,31 +2494,28 @@ function buildRedemption(input) {
2450
2494
  };
2451
2495
  }
2452
2496
  function signRedemption(unsigned, holderDevicePrivateKey) {
2453
- const holderSig = bytesToHex(
2454
- sign(canonicalJSONBytes(unsigned), holderDevicePrivateKey)
2497
+ const holderSig = signIssuerP256(
2498
+ canonicalJSONBytes(unsigned),
2499
+ holderDevicePrivateKey
2455
2500
  );
2456
2501
  return { ...unsigned, holderSig };
2457
2502
  }
2458
- function verifyRedemption(r, issuerPublicKey) {
2503
+ function verifyRedemption(r, issuerPublicKeySpkiB64) {
2459
2504
  try {
2460
2505
  const parsed = RedemptionSchema.parse(r);
2461
2506
  if (parsed.counter <= parsed.pass.counterSeed) return false;
2462
2507
  const { issuerSig, ...passUnsigned } = parsed.pass;
2463
- if (!verify(
2508
+ if (!verifyIssuerP256(
2464
2509
  canonicalJSONBytes(passUnsigned),
2465
- hexToBytes(issuerSig),
2466
- issuerPublicKey
2510
+ issuerSig,
2511
+ issuerPublicKeySpkiB64
2467
2512
  )) {
2468
2513
  return false;
2469
2514
  }
2470
- const holderHex = parsed.pass.holderDevicePubkey;
2471
- if (typeof holderHex !== "string") return false;
2515
+ const holderPub = parsed.pass.holderDevicePubkey;
2516
+ if (typeof holderPub !== "string") return false;
2472
2517
  const { holderSig, ...unsigned } = parsed;
2473
- return verify(
2474
- canonicalJSONBytes(unsigned),
2475
- hexToBytes(holderSig),
2476
- hexToBytes(holderHex)
2477
- );
2518
+ return verifyIssuerP256(canonicalJSONBytes(unsigned), holderSig, holderPub);
2478
2519
  } catch {
2479
2520
  return false;
2480
2521
  }
@@ -2484,10 +2525,6 @@ function verifyRedemption(r, issuerPublicKey) {
2484
2525
  import { z as z11 } from "zod";
2485
2526
  var RECEIPT_CHANNELS = ["cash", "pass"];
2486
2527
  var RECEIPT_KINDS = RECEIPT_CHANNELS;
2487
- var HexString3 = (length) => z11.string().regex(
2488
- new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
2489
- `expected ${length}-byte hex string`
2490
- );
2491
2528
  var ReceiptPayloadSchema = z11.record(
2492
2529
  z11.union([z11.string(), z11.number(), z11.boolean(), z11.null()])
2493
2530
  );
@@ -2505,7 +2542,8 @@ var ReceiptSchema = z11.object({
2505
2542
  issuedAtMs: z11.number().int().nonnegative(),
2506
2543
  issuerId: z11.string().min(1),
2507
2544
  payload: ReceiptPayloadSchema,
2508
- issuerSig: HexString3(64)
2545
+ /** ASN.1 DER ECDSA P-256 signature, base64. */
2546
+ issuerSig: z11.string().min(64).max(2048).regex(/^[A-Za-z0-9+/]+={0,2}$/)
2509
2547
  }).superRefine((v, ctx) => {
2510
2548
  if (v.channel === "cash") {
2511
2549
  if (!v.intentId) {
@@ -2571,19 +2609,20 @@ function buildReceipt(input) {
2571
2609
  return out;
2572
2610
  }
2573
2611
  function signReceipt(unsigned, issuerPrivateKey) {
2574
- const issuerSig = bytesToHex(
2575
- sign(canonicalJSONBytes(unsigned), issuerPrivateKey)
2612
+ const issuerSig = signIssuerP256(
2613
+ canonicalJSONBytes(unsigned),
2614
+ issuerPrivateKey
2576
2615
  );
2577
2616
  return { ...unsigned, issuerSig };
2578
2617
  }
2579
- function verifyReceipt(r, issuerPublicKey) {
2618
+ function verifyReceipt(r, issuerPublicKeySpkiB64) {
2580
2619
  try {
2581
2620
  const parsed = ReceiptSchema.parse(r);
2582
2621
  const { issuerSig, ...unsigned } = parsed;
2583
- return verify(
2622
+ return verifyIssuerP256(
2584
2623
  canonicalJSONBytes(unsigned),
2585
- hexToBytes(issuerSig),
2586
- issuerPublicKey
2624
+ issuerSig,
2625
+ issuerPublicKeySpkiB64
2587
2626
  );
2588
2627
  } catch {
2589
2628
  return false;
@@ -2918,9 +2957,8 @@ function createAccountsClient(opts) {
2918
2957
  // src/me-offline/client.ts
2919
2958
  import { z as z13 } from "zod";
2920
2959
  var Hex64 = z13.string().regex(/^[0-9a-f]{64}$/i);
2921
- var HexAny = z13.string().regex(/^[0-9a-f]+$/i);
2922
2960
  var Sha256Hex = z13.string().regex(/^[0-9a-f]{64}$/i);
2923
- var Base64Std = z13.string().regex(/^[A-Za-z0-9+/]+={0,2}$/);
2961
+ var Base64Std3 = z13.string().regex(/^[A-Za-z0-9+/]+={0,2}$/);
2924
2962
  var RegisterDeviceKeyInputSchema = z13.object({
2925
2963
  deviceId: z13.string().min(1).max(128),
2926
2964
  publicKeyHex: Hex64
@@ -2931,15 +2969,15 @@ var AttestationSecurityLevelSchema = z13.enum([
2931
2969
  "SECURE_ENCLAVE",
2932
2970
  "SOFTWARE"
2933
2971
  ]);
2934
- var DeviceKeyAlgSchema = z13.enum(["ed25519", "p256"]);
2972
+ var DeviceKeyAlgSchema = z13.literal("p256");
2935
2973
  var RegisterDeviceKeyP256InputSchema = z13.object({
2936
2974
  deviceId: z13.string().min(1).max(128),
2937
2975
  /** P-256 SubjectPublicKeyInfo DER, base64. */
2938
- publicKeySpkiB64: Base64Std.min(64).max(4096),
2976
+ publicKeySpkiB64: Base64Std3.min(64).max(4096),
2939
2977
  /** Base64 of the server-issued enrollment challenge string. */
2940
- challengeB64: Base64Std.min(8).max(1024),
2978
+ challengeB64: Base64Std3.min(8).max(1024),
2941
2979
  /** iOS App Attest payload or Android X.509 Key Attestation chain. */
2942
- attestationChainB64: z13.array(Base64Std.min(16).max(16384)).min(1).max(16),
2980
+ attestationChainB64: z13.array(Base64Std3.min(16).max(16384)).min(1).max(16),
2943
2981
  securityLevel: AttestationSecurityLevelSchema
2944
2982
  });
2945
2983
  var P256EnrollmentChallengeInputSchema = z13.object({
@@ -2953,9 +2991,12 @@ var DeviceKeyRecordSchema = z13.object({
2953
2991
  id: z13.string().uuid(),
2954
2992
  userId: z13.string().uuid(),
2955
2993
  deviceId: z13.string(),
2956
- alg: DeviceKeyAlgSchema.default("ed25519"),
2994
+ /** Always 'p256' on the consumer offline rail. Field retained for forward-compat. */
2995
+ alg: DeviceKeyAlgSchema.default("p256"),
2996
+ /** Legacy ed25519 hex key. Always null on new records (kept for back-compat reads). */
2957
2997
  publicKeyHex: Hex64.nullable().default(null),
2958
- publicKeySpkiB64: Base64Std.nullable().default(null),
2998
+ /** P-256 SubjectPublicKeyInfo DER, base64. Required for new records. */
2999
+ publicKeySpkiB64: Base64Std3.nullable().default(null),
2959
3000
  securityLevel: AttestationSecurityLevelSchema.nullable().default(null),
2960
3001
  hardwareBacked: z13.boolean().default(false),
2961
3002
  attestedAtMs: z13.number().int().nonnegative().nullable().default(null),
@@ -2967,9 +3008,10 @@ var ConsumerOACSchema = z13.object({
2967
3008
  issuerId: z13.string().min(1).max(64),
2968
3009
  userId: z13.string().uuid(),
2969
3010
  deviceId: z13.string().min(1).max(128),
2970
- alg: z13.enum(["ed25519", "p256"]).optional(),
2971
- devicePubkeyHex: Hex64.optional(),
2972
- devicePubkeySpkiB64: Base64Std.min(64).max(4096).optional(),
3011
+ /** Always 'p256'. Field retained for forward-compat. */
3012
+ alg: z13.literal("p256").default("p256"),
3013
+ /** P-256 SubjectPublicKeyInfo DER, base64. */
3014
+ devicePubkeySpkiB64: Base64Std3.min(64).max(4096),
2973
3015
  perTxCapKobo: z13.number().int().positive(),
2974
3016
  cumulativeCapKobo: z13.number().int().positive(),
2975
3017
  currency: z13.string().length(3),
@@ -2977,20 +3019,13 @@ var ConsumerOACSchema = z13.object({
2977
3019
  validUntilMs: z13.number().int().nonnegative(),
2978
3020
  counterSeed: z13.number().int().nonnegative(),
2979
3021
  issuedAtMs: z13.number().int().nonnegative()
2980
- }).refine(
2981
- (o) => {
2982
- const alg = o.alg ?? "ed25519";
2983
- if (alg === "ed25519") {
2984
- return Boolean(o.devicePubkeyHex) && !o.devicePubkeySpkiB64;
2985
- }
2986
- return Boolean(o.devicePubkeySpkiB64) && !o.devicePubkeyHex;
2987
- },
2988
- { message: "OAC device pubkey shape must match alg" }
2989
- );
3022
+ });
2990
3023
  var SignedConsumerOACSchema = z13.object({
2991
3024
  oac: ConsumerOACSchema,
2992
- issuerSig: HexAny,
2993
- issuerPublicKeyHex: Hex64
3025
+ /** ASN.1 DER ECDSA P-256 issuer signature, base64. */
3026
+ issuerSig: Base64Std3.min(16).max(2048),
3027
+ /** Issuer's P-256 public key as SubjectPublicKeyInfo DER, base64. */
3028
+ issuerPublicKeySpkiB64: Base64Std3.min(64).max(4096)
2994
3029
  });
2995
3030
  var OACRecordSchema = SignedConsumerOACSchema.extend({
2996
3031
  currentOfflineSpentKobo: z13.number().int().nonnegative(),
@@ -3073,10 +3108,9 @@ var OfflineStatusResultSchema = z13.object({
3073
3108
  var OfflineStateResultSchema = z13.object({
3074
3109
  active: OACRecordSchema.nullable()
3075
3110
  });
3076
- var ClaimAlgSchema = z13.enum(["ed25519", "p256"]);
3077
3111
  var ConsumerPaymentClaimSchema = z13.object({
3078
- /** Algorithm discriminator. Omit / 'ed25519' for V1 clients. */
3079
- alg: ClaimAlgSchema.optional(),
3112
+ /** Always 'p256'. Retained for forward-compat and as an explicit domain marker. */
3113
+ alg: z13.literal("p256").default("p256"),
3080
3114
  oacId: z13.string().uuid(),
3081
3115
  encounterId: Sha256Hex.optional(),
3082
3116
  payerUserId: z13.string().uuid(),
@@ -3089,28 +3123,11 @@ var ConsumerPaymentClaimSchema = z13.object({
3089
3123
  occurredAtMs: z13.number().int().nonnegative(),
3090
3124
  completedAtMs: z13.number().int().nonnegative().optional(),
3091
3125
  contextId: z13.string().max(128).optional(),
3092
- // ed25519 path
3093
- payerPubkeyHex: Hex64.optional(),
3094
- payerSignature: HexAny.optional(),
3095
- payeePubkeyHex: Hex64.optional(),
3096
- payeeSignature: HexAny.optional(),
3097
- // p256 path
3098
- payerPubkeySpkiB64: z13.string().min(64).max(4096).optional(),
3099
- payerSignatureDerB64: z13.string().min(16).max(2048).optional(),
3100
- payeePubkeySpkiB64: z13.string().min(64).max(4096).optional(),
3101
- payeeSignatureDerB64: z13.string().min(16).max(2048).optional()
3102
- }).refine(
3103
- (c) => {
3104
- const alg = c.alg ?? "ed25519";
3105
- if (alg === "ed25519") {
3106
- return Boolean(c.payerPubkeyHex) && Boolean(c.payerSignature);
3107
- }
3108
- return Boolean(c.payerPubkeySpkiB64) && Boolean(c.payerSignatureDerB64);
3109
- },
3110
- {
3111
- message: "payer key/signature fields must match alg (ed25519: hex; p256: SPKI+DER b64)"
3112
- }
3113
- );
3126
+ payerPubkeySpkiB64: Base64Std3.min(64).max(4096),
3127
+ payerSignatureDerB64: Base64Std3.min(16).max(2048),
3128
+ payeePubkeySpkiB64: Base64Std3.min(64).max(4096).optional(),
3129
+ payeeSignatureDerB64: Base64Std3.min(16).max(2048).optional()
3130
+ });
3114
3131
  var ConsumerSettlementSchema = z13.object({
3115
3132
  settlementId: z13.string().uuid(),
3116
3133
  settlementKey: Sha256Hex,
@@ -3123,7 +3140,8 @@ var ConsumerSettlementSchema = z13.object({
3123
3140
  status: z13.enum(["SETTLED", "REVIEW"]),
3124
3141
  reviewReason: z13.string().nullable(),
3125
3142
  ledgerRef: z13.string().nullable(),
3126
- issuerSig: HexAny,
3143
+ /** ASN.1 DER ECDSA P-256 issuer signature, base64. */
3144
+ issuerSig: Base64Std3.min(16).max(2048),
3127
3145
  createdAtMs: z13.number().int().nonnegative()
3128
3146
  });
3129
3147
  var ConsumerSettleResultSchema = z13.object({
@@ -3251,9 +3269,7 @@ function createMeOfflineClient(opts) {
3251
3269
  }
3252
3270
 
3253
3271
  // src/me-offline/signer.ts
3254
- import { ed25519 as ed255192 } from "@noble/curves/ed25519";
3255
- import { p256 } from "@noble/curves/nist";
3256
- import { bytesToHex as bytesToHex5, hexToBytes as hexToBytes2 } from "@noble/hashes/utils";
3272
+ import { p256 as p2562 } from "@noble/curves/nist";
3257
3273
  var CLAIM_DOMAIN_V2 = "flur:consumer-offline:v2:claim";
3258
3274
  function canonicalClaimSigningPayload(claim) {
3259
3275
  return {
@@ -3275,7 +3291,7 @@ function canonicalClaimSigningPayload(claim) {
3275
3291
  function canonicalClaimSigningBytes(claim) {
3276
3292
  return canonicalJSONBytes(canonicalClaimSigningPayload(claim));
3277
3293
  }
3278
- function bytesToBase64(bytes) {
3294
+ function bytesToBase642(bytes) {
3279
3295
  if (typeof Buffer !== "undefined") {
3280
3296
  return Buffer.from(bytes).toString("base64");
3281
3297
  }
@@ -3283,7 +3299,7 @@ function bytesToBase64(bytes) {
3283
3299
  for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
3284
3300
  return btoa(bin);
3285
3301
  }
3286
- function base64ToBytes(b64) {
3302
+ function base64ToBytes2(b64) {
3287
3303
  if (typeof Buffer !== "undefined") {
3288
3304
  return new Uint8Array(Buffer.from(b64, "base64"));
3289
3305
  }
@@ -3292,7 +3308,7 @@ function base64ToBytes(b64) {
3292
3308
  for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
3293
3309
  return out;
3294
3310
  }
3295
- var P256_SPKI_HEADER = new Uint8Array([
3311
+ var P256_SPKI_HEADER2 = new Uint8Array([
3296
3312
  48,
3297
3313
  89,
3298
3314
  48,
@@ -3324,38 +3340,25 @@ function p256PublicKeyToSpkiB64(rawUncompressed) {
3324
3340
  if (rawUncompressed.length !== 65 || rawUncompressed[0] !== 4) {
3325
3341
  throw new Error("p256: expected 65-byte uncompressed point");
3326
3342
  }
3327
- const out = new Uint8Array(P256_SPKI_HEADER.length + rawUncompressed.length);
3328
- out.set(P256_SPKI_HEADER, 0);
3329
- out.set(rawUncompressed, P256_SPKI_HEADER.length);
3330
- return bytesToBase64(out);
3343
+ const out = new Uint8Array(P256_SPKI_HEADER2.length + rawUncompressed.length);
3344
+ out.set(P256_SPKI_HEADER2, 0);
3345
+ out.set(rawUncompressed, P256_SPKI_HEADER2.length);
3346
+ return bytesToBase642(out);
3331
3347
  }
3332
3348
  function p256SpkiB64ToPublicKey(spkiB64) {
3333
- const spki = base64ToBytes(spkiB64);
3334
- if (spki.length !== P256_SPKI_HEADER.length + 65) {
3349
+ const spki = base64ToBytes2(spkiB64);
3350
+ if (spki.length !== P256_SPKI_HEADER2.length + 65) {
3335
3351
  throw new Error("p256: invalid SPKI length");
3336
3352
  }
3337
- for (let i = 0; i < P256_SPKI_HEADER.length; i++) {
3338
- if (spki[i] !== P256_SPKI_HEADER[i]) {
3353
+ for (let i = 0; i < P256_SPKI_HEADER2.length; i++) {
3354
+ if (spki[i] !== P256_SPKI_HEADER2[i]) {
3339
3355
  throw new Error("p256: invalid SPKI header");
3340
3356
  }
3341
3357
  }
3342
- return spki.slice(P256_SPKI_HEADER.length);
3343
- }
3344
- function createSoftwareEd25519Signer(privateKey) {
3345
- const pub = ed255192.getPublicKey(privateKey);
3346
- return {
3347
- alg: "ed25519",
3348
- async getPublicKey() {
3349
- return { alg: "ed25519", publicKey: bytesToHex5(pub) };
3350
- },
3351
- async sign(bytes) {
3352
- const sig = ed255192.sign(bytes, privateKey);
3353
- return { alg: "ed25519", signature: bytesToHex5(sig) };
3354
- }
3355
- };
3358
+ return spki.slice(P256_SPKI_HEADER2.length);
3356
3359
  }
3357
3360
  function createSoftwareP256Signer(privateKey) {
3358
- const raw = p256.getPublicKey(privateKey, false);
3361
+ const raw = p2562.getPublicKey(privateKey, false);
3359
3362
  const spkiB64 = p256PublicKeyToSpkiB64(raw);
3360
3363
  return {
3361
3364
  alg: "p256",
@@ -3363,35 +3366,87 @@ function createSoftwareP256Signer(privateKey) {
3363
3366
  return { alg: "p256", publicKey: spkiB64 };
3364
3367
  },
3365
3368
  async sign(bytes) {
3366
- const sig = p256.sign(bytes, privateKey, { prehash: true });
3369
+ const sig = p2562.sign(bytes, privateKey, { prehash: true });
3367
3370
  const der = sig.toBytes("der");
3368
- return { alg: "p256", signature: bytesToBase64(der) };
3371
+ return { alg: "p256", signature: bytesToBase642(der) };
3369
3372
  }
3370
3373
  };
3371
3374
  }
3372
3375
  function verifyClaimSignature(input) {
3373
3376
  try {
3374
- if (input.alg === "ed25519") {
3375
- return ed255192.verify(
3376
- hexToBytes2(input.signature),
3377
- input.bytes,
3378
- hexToBytes2(input.publicKey)
3379
- );
3380
- }
3381
- if (input.alg === "p256") {
3382
- const sigDer = base64ToBytes(input.signature);
3383
- const pub = p256SpkiB64ToPublicKey(input.publicKey);
3384
- return p256.verify(sigDer, input.bytes, pub, {
3385
- prehash: true,
3386
- format: "der"
3387
- });
3388
- }
3389
- return false;
3377
+ if (input.alg !== "p256") return false;
3378
+ const sigDer = base64ToBytes2(input.signature);
3379
+ const pub = p256SpkiB64ToPublicKey(input.publicKey);
3380
+ return p2562.verify(sigDer, input.bytes, pub, {
3381
+ prehash: true,
3382
+ format: "der"
3383
+ });
3390
3384
  } catch {
3391
3385
  return false;
3392
3386
  }
3393
3387
  }
3394
3388
 
3389
+ // src/me-offline/sms.ts
3390
+ var OFFLINE_CLAIM_SMS_PREFIX = "FLURC1.";
3391
+ var TOKEN_RE = /(?:^|\s)(FLURC1\.[A-Za-z0-9_-]+={0,2})(?:\s|$)/;
3392
+ function encodeOfflineClaimSmsMessage(claim) {
3393
+ const parsed = ConsumerPaymentClaimSchema.parse(claim);
3394
+ const json = JSON.stringify(parsed);
3395
+ return `${OFFLINE_CLAIM_SMS_PREFIX}${base64UrlEncodeUtf8(json)}`;
3396
+ }
3397
+ function decodeOfflineClaimSmsMessage(message) {
3398
+ const token = extractOfflineClaimSmsToken(message);
3399
+ if (!token) {
3400
+ throw new Error("offline claim SMS token not found");
3401
+ }
3402
+ const encoded = token.slice(OFFLINE_CLAIM_SMS_PREFIX.length);
3403
+ let raw;
3404
+ try {
3405
+ raw = JSON.parse(base64UrlDecodeUtf8(encoded));
3406
+ } catch {
3407
+ throw new Error("offline claim SMS token is malformed");
3408
+ }
3409
+ const parsed = ConsumerPaymentClaimSchema.safeParse(raw);
3410
+ if (!parsed.success) {
3411
+ throw new Error("offline claim SMS token is invalid");
3412
+ }
3413
+ return parsed.data;
3414
+ }
3415
+ function extractOfflineClaimSmsToken(message) {
3416
+ const trimmed = message.trim();
3417
+ if (trimmed.startsWith(OFFLINE_CLAIM_SMS_PREFIX)) {
3418
+ return trimmed.split(/\s+/, 1)[0] ?? null;
3419
+ }
3420
+ return TOKEN_RE.exec(message)?.[1] ?? null;
3421
+ }
3422
+ function base64UrlEncodeUtf8(input) {
3423
+ const bytes = new TextEncoder().encode(input);
3424
+ let binary = "";
3425
+ for (const byte of bytes) binary += String.fromCharCode(byte);
3426
+ const base64 = typeof btoa === "function" ? btoa(binary) : typeof Buffer !== "undefined" ? Buffer.from(bytes).toString("base64") : void 0;
3427
+ if (!base64) throw new Error("base64 encoder unavailable");
3428
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
3429
+ }
3430
+ function base64UrlDecodeUtf8(input) {
3431
+ const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
3432
+ const padded = base64.padEnd(
3433
+ base64.length + (4 - base64.length % 4) % 4,
3434
+ "="
3435
+ );
3436
+ if (typeof atob === "function") {
3437
+ const binary = atob(padded);
3438
+ const bytes = new Uint8Array(binary.length);
3439
+ for (let index = 0; index < binary.length; index++) {
3440
+ bytes[index] = binary.charCodeAt(index);
3441
+ }
3442
+ return new TextDecoder().decode(bytes);
3443
+ }
3444
+ if (typeof Buffer !== "undefined") {
3445
+ return Buffer.from(padded, "base64").toString("utf8");
3446
+ }
3447
+ throw new Error("base64 decoder unavailable");
3448
+ }
3449
+
3395
3450
  // src/partner-funding/client.ts
3396
3451
  import { z as z14 } from "zod";
3397
3452
  var MinorString = z14.string().regex(/^-?\d+$/);
@@ -3767,14 +3822,15 @@ function buildArtifactBody(input) {
3767
3822
  return { ...header, data: input.data };
3768
3823
  }
3769
3824
  function signArtifact(body, privateKey) {
3770
- const sig = bytesToHex(sign(canonicalJSONBytes(body), privateKey));
3825
+ const sig = signIssuerP256(canonicalJSONBytes(body), privateKey);
3771
3826
  return { body, sig };
3772
3827
  }
3773
3828
  function encodeArtifactUri(signed) {
3774
3829
  const bodyBytes = canonicalJSONBytes(signed.body);
3775
3830
  const bodyB64 = base64UrlEncode(bodyBytes);
3776
- const sigB64 = base64UrlEncode(hexToBytes(signed.sig));
3777
- return `${FLUR_ARTIFACT_URI_PREFIX}${signed.body.t}/${bodyB64}.${sigB64}`;
3831
+ const sigBytes = base64UrlDecode(signed.sig);
3832
+ const sigB64Url = base64UrlEncode(sigBytes);
3833
+ return `${FLUR_ARTIFACT_URI_PREFIX}${signed.body.t}/${bodyB64}.${sigB64Url}`;
3778
3834
  }
3779
3835
  function decodeArtifactUri(uri) {
3780
3836
  if (!uri.startsWith(FLUR_ARTIFACT_URI_PREFIX)) {
@@ -3805,9 +3861,9 @@ function decodeArtifactUri(uri) {
3805
3861
  }
3806
3862
  const bodyBytes = base64UrlDecode(payload.slice(0, dot));
3807
3863
  const sigBytes = base64UrlDecode(payload.slice(dot + 1));
3808
- if (sigBytes.length !== 64) {
3864
+ if (sigBytes.length < 64 || sigBytes.length > 80) {
3809
3865
  throw new FlurArtifactError(
3810
- `Signature must be 64 bytes, got ${sigBytes.length}`,
3866
+ `Signature length out of range: ${sigBytes.length}`,
3811
3867
  "INVALID_SIGNATURE"
3812
3868
  );
3813
3869
  }
@@ -3834,17 +3890,26 @@ function decodeArtifactUri(uri) {
3834
3890
  type,
3835
3891
  bodyBytes,
3836
3892
  body: bodyJson,
3837
- sig: bytesToHex(sigBytes)
3893
+ // Encode as standard base64 (not url-safe) for sig field consistency.
3894
+ sig: encodeStdBase64(sigBytes)
3838
3895
  };
3839
3896
  }
3840
- function verifyArtifactSignature(decoded, publicKey, options = {}) {
3897
+ function encodeStdBase64(bytes) {
3898
+ if (typeof Buffer !== "undefined") {
3899
+ return Buffer.from(bytes).toString("base64");
3900
+ }
3901
+ let bin = "";
3902
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
3903
+ return typeof btoa === "function" ? btoa(bin) : "";
3904
+ }
3905
+ function verifyArtifactSignature(decoded, publicKeySpkiB64, options = {}) {
3841
3906
  if (options.enforceExpiry !== false && decoded.body.exp !== void 0) {
3842
3907
  const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
3843
3908
  if (decoded.body.exp < now) {
3844
3909
  throw new FlurArtifactError("Artifact has expired", "EXPIRED");
3845
3910
  }
3846
3911
  }
3847
- return verify(decoded.bodyBytes, hexToBytes(decoded.sig), publicKey);
3912
+ return verifyIssuerP256(decoded.bodyBytes, decoded.sig, publicKeySpkiB64);
3848
3913
  }
3849
3914
 
3850
3915
  // src/artifacts/types.ts
@@ -3863,7 +3928,7 @@ var ARTIFACT_TYPES = {
3863
3928
  PASS: "pass",
3864
3929
  IDENTITY: "identity"
3865
3930
  };
3866
- var HexString4 = (length) => z16.string().regex(
3931
+ var HexString = (length) => z16.string().regex(
3867
3932
  new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
3868
3933
  `expected ${length}-byte hex string`
3869
3934
  );
@@ -3881,7 +3946,7 @@ var ReceiptArtifactSchema = z16.object({
3881
3946
  settledAtMs: z16.number().int().positive(),
3882
3947
  ledgerTxnId: z16.string().min(1).max(64).optional(),
3883
3948
  memo: z16.string().max(140).optional(),
3884
- hashChainPrev: HexString4(32).optional()
3949
+ hashChainPrev: HexString(32).optional()
3885
3950
  });
3886
3951
  var ShortId = z16.string().min(1).max(64);
3887
3952
  var PositiveInt = z16.number().int().positive();
@@ -3963,7 +4028,7 @@ var StatementArtifactSchema = z16.object({
3963
4028
  closingBalanceKobo: z16.number().int(),
3964
4029
  transactionCount: NonNegativeInt,
3965
4030
  currency: Currency2,
3966
- hashChainPrev: HexString4(32).optional()
4031
+ hashChainPrev: HexString(32).optional()
3967
4032
  }).refine((v) => v.periodEndMs > v.periodStartMs, {
3968
4033
  message: "periodEndMs must be greater than periodStartMs",
3969
4034
  path: ["periodEndMs"]
@@ -3996,7 +4061,7 @@ var IdentityArtifactSchema = z16.object({
3996
4061
  "kyc_tier",
3997
4062
  "age_band"
3998
4063
  ]),
3999
- claimValueHash: HexString4(32),
4064
+ claimValueHash: HexString(32),
4000
4065
  attestedAtMs: PositiveInt
4001
4066
  });
4002
4067
  var ARTIFACT_BODY_SCHEMAS = {
@@ -4060,7 +4125,7 @@ function createArtifactUri(input) {
4060
4125
  const signed = signArtifact(body, input.privateKey);
4061
4126
  return { uri: encodeArtifactUri(signed), signed };
4062
4127
  }
4063
- function verifyArtifactUri(uri, publicKey, options = {}) {
4128
+ function verifyArtifactUri(uri, publicKeySpkiB64, options = {}) {
4064
4129
  const decoded = decodeArtifactUri(uri);
4065
4130
  if (!isKnownArtifactType(decoded.type)) {
4066
4131
  throw new FlurArtifactError(
@@ -4082,7 +4147,7 @@ function verifyArtifactUri(uri, publicKey, options = {}) {
4082
4147
  "INVALID_BODY"
4083
4148
  );
4084
4149
  }
4085
- const ok = verifyArtifactSignature(decoded, publicKey, options);
4150
+ const ok = verifyArtifactSignature(decoded, publicKeySpkiB64, options);
4086
4151
  if (!ok) {
4087
4152
  throw new FlurArtifactError(
4088
4153
  "Artifact signature verification failed",
@@ -4175,6 +4240,7 @@ export {
4175
4240
  OAC_DEFAULT_CUMULATIVE_KOBO,
4176
4241
  OAC_DEFAULT_PER_TX_KOBO,
4177
4242
  OAC_DEFAULT_VALIDITY_MS,
4243
+ OFFLINE_CLAIM_SMS_PREFIX,
4178
4244
  OfflineClaimArtifactSchema,
4179
4245
  OfflineHoldRecordSchema,
4180
4246
  OfflinePaymentAuthorizationArtifactSchema,
@@ -4270,20 +4336,21 @@ export {
4270
4336
  createPassesClient,
4271
4337
  createReceiptArtifactUri,
4272
4338
  createReceiptsClient,
4273
- createSoftwareEd25519Signer,
4274
4339
  createSoftwareP256Signer,
4275
4340
  decodeArtifactUri,
4276
4341
  decodeAuthorizationQR,
4277
4342
  decodeBase45,
4343
+ decodeOfflineClaimSmsMessage,
4278
4344
  decodePaymentRequestQR,
4279
4345
  encodeArtifactUri,
4280
4346
  encodeAuthorizationQR,
4281
4347
  encodeBase45,
4282
4348
  encodeNQR,
4349
+ encodeOfflineClaimSmsMessage,
4283
4350
  encodePaymentRequestQR,
4351
+ extractOfflineClaimSmsToken,
4284
4352
  formatAmount,
4285
4353
  generateDynamicQR,
4286
- generateKeyPair,
4287
4354
  generateStaticQR,
4288
4355
  init,
4289
4356
  isHardenedArtifactType,
@@ -4294,13 +4361,10 @@ export {
4294
4361
  parseAmountInput,
4295
4362
  parseNQR,
4296
4363
  parseQR,
4297
- publicKeyFromPrivate,
4298
4364
  readTLV,
4299
4365
  routingHint,
4300
- sign,
4301
4366
  signArtifact,
4302
4367
  signAuthorization,
4303
- signCanonical,
4304
4368
  signOAC,
4305
4369
  signPartnerRequest,
4306
4370
  signPass,
@@ -4308,11 +4372,9 @@ export {
4308
4372
  signReceipt,
4309
4373
  signRedemption,
4310
4374
  signRequestHMAC,
4311
- verify,
4312
4375
  verifyArtifactSignature,
4313
4376
  verifyArtifactUri,
4314
4377
  verifyAuthorization,
4315
- verifyCanonical,
4316
4378
  verifyClaimSignature,
4317
4379
  verifyOAC,
4318
4380
  verifyPass,