@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.cjs CHANGED
@@ -86,6 +86,7 @@ __export(index_exports, {
86
86
  OAC_DEFAULT_CUMULATIVE_KOBO: () => OAC_DEFAULT_CUMULATIVE_KOBO,
87
87
  OAC_DEFAULT_PER_TX_KOBO: () => OAC_DEFAULT_PER_TX_KOBO,
88
88
  OAC_DEFAULT_VALIDITY_MS: () => OAC_DEFAULT_VALIDITY_MS,
89
+ OFFLINE_CLAIM_SMS_PREFIX: () => OFFLINE_CLAIM_SMS_PREFIX,
89
90
  OfflineClaimArtifactSchema: () => OfflineClaimArtifactSchema,
90
91
  OfflineHoldRecordSchema: () => OfflineHoldRecordSchema,
91
92
  OfflinePaymentAuthorizationArtifactSchema: () => OfflinePaymentAuthorizationArtifactSchema,
@@ -181,20 +182,21 @@ __export(index_exports, {
181
182
  createPassesClient: () => createPassesClient,
182
183
  createReceiptArtifactUri: () => createReceiptArtifactUri,
183
184
  createReceiptsClient: () => createReceiptsClient,
184
- createSoftwareEd25519Signer: () => createSoftwareEd25519Signer,
185
185
  createSoftwareP256Signer: () => createSoftwareP256Signer,
186
186
  decodeArtifactUri: () => decodeArtifactUri,
187
187
  decodeAuthorizationQR: () => decodeAuthorizationQR,
188
188
  decodeBase45: () => decodeBase45,
189
+ decodeOfflineClaimSmsMessage: () => decodeOfflineClaimSmsMessage,
189
190
  decodePaymentRequestQR: () => decodePaymentRequestQR,
190
191
  encodeArtifactUri: () => encodeArtifactUri,
191
192
  encodeAuthorizationQR: () => encodeAuthorizationQR,
192
193
  encodeBase45: () => encodeBase45,
193
194
  encodeNQR: () => encodeNQR,
195
+ encodeOfflineClaimSmsMessage: () => encodeOfflineClaimSmsMessage,
194
196
  encodePaymentRequestQR: () => encodePaymentRequestQR,
197
+ extractOfflineClaimSmsToken: () => extractOfflineClaimSmsToken,
195
198
  formatAmount: () => formatAmount,
196
199
  generateDynamicQR: () => generateDynamicQR,
197
- generateKeyPair: () => generateKeyPair,
198
200
  generateStaticQR: () => generateStaticQR,
199
201
  init: () => init,
200
202
  isHardenedArtifactType: () => isHardenedArtifactType,
@@ -205,13 +207,10 @@ __export(index_exports, {
205
207
  parseAmountInput: () => parseAmountInput,
206
208
  parseNQR: () => parseNQR,
207
209
  parseQR: () => parseQR,
208
- publicKeyFromPrivate: () => publicKeyFromPrivate,
209
210
  readTLV: () => readTLV,
210
211
  routingHint: () => routingHint,
211
- sign: () => sign,
212
212
  signArtifact: () => signArtifact,
213
213
  signAuthorization: () => signAuthorization,
214
- signCanonical: () => signCanonical,
215
214
  signOAC: () => signOAC,
216
215
  signPartnerRequest: () => signPartnerRequest,
217
216
  signPass: () => signPass,
@@ -219,11 +218,9 @@ __export(index_exports, {
219
218
  signReceipt: () => signReceipt,
220
219
  signRedemption: () => signRedemption,
221
220
  signRequestHMAC: () => signRequestHMAC,
222
- verify: () => verify,
223
221
  verifyArtifactSignature: () => verifyArtifactSignature,
224
222
  verifyArtifactUri: () => verifyArtifactUri,
225
223
  verifyAuthorization: () => verifyAuthorization,
226
- verifyCanonical: () => verifyCanonical,
227
224
  verifyClaimSignature: () => verifyClaimSignature,
228
225
  verifyOAC: () => verifyOAC,
229
226
  verifyPass: () => verifyPass,
@@ -1800,64 +1797,113 @@ function constantTimeEqual(a, b) {
1800
1797
  return diff === 0;
1801
1798
  }
1802
1799
 
1803
- // src/crypto/ed25519.ts
1804
- var import_ed25519 = require("@noble/curves/ed25519");
1805
- function generateKeyPair() {
1806
- const privateKey = import_ed25519.ed25519.utils.randomPrivateKey();
1807
- const publicKey = import_ed25519.ed25519.getPublicKey(privateKey);
1808
- return { privateKey, publicKey };
1800
+ // src/offline/oac.ts
1801
+ var import_zod5 = require("zod");
1802
+
1803
+ // src/crypto/p256-issuer.ts
1804
+ var import_nist = require("@noble/curves/nist");
1805
+ function bytesToBase64(bytes) {
1806
+ if (typeof Buffer !== "undefined") {
1807
+ return Buffer.from(bytes).toString("base64");
1808
+ }
1809
+ let bin = "";
1810
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
1811
+ return btoa(bin);
1809
1812
  }
1810
- function publicKeyFromPrivate(privateKey) {
1811
- return import_ed25519.ed25519.getPublicKey(privateKey);
1813
+ function base64ToBytes(b64) {
1814
+ if (typeof Buffer !== "undefined") {
1815
+ return new Uint8Array(Buffer.from(b64, "base64"));
1816
+ }
1817
+ const bin = atob(b64);
1818
+ const out = new Uint8Array(bin.length);
1819
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
1820
+ return out;
1812
1821
  }
1813
- function sign(message, privateKey) {
1814
- return import_ed25519.ed25519.sign(message, privateKey);
1822
+ var P256_SPKI_HEADER = new Uint8Array([
1823
+ 48,
1824
+ 89,
1825
+ 48,
1826
+ 19,
1827
+ 6,
1828
+ 7,
1829
+ 42,
1830
+ 134,
1831
+ 72,
1832
+ 206,
1833
+ 61,
1834
+ 2,
1835
+ 1,
1836
+ 6,
1837
+ 8,
1838
+ 42,
1839
+ 134,
1840
+ 72,
1841
+ 206,
1842
+ 61,
1843
+ 3,
1844
+ 1,
1845
+ 7,
1846
+ 3,
1847
+ 66,
1848
+ 0
1849
+ ]);
1850
+ function p256SpkiB64ToRaw(spkiB64) {
1851
+ const spki = base64ToBytes(spkiB64);
1852
+ if (spki.length !== P256_SPKI_HEADER.length + 65) {
1853
+ throw new Error("p256: invalid SPKI length");
1854
+ }
1855
+ for (let i = 0; i < P256_SPKI_HEADER.length; i++) {
1856
+ if (spki[i] !== P256_SPKI_HEADER[i]) {
1857
+ throw new Error("p256: invalid SPKI header");
1858
+ }
1859
+ }
1860
+ return spki.slice(P256_SPKI_HEADER.length);
1815
1861
  }
1816
- function verify(message, signature, publicKey) {
1862
+ function signIssuerP256(bytes, issuerPrivateKey) {
1863
+ const sig = import_nist.p256.sign(bytes, issuerPrivateKey, { prehash: true });
1864
+ return bytesToBase64(sig.toBytes("der"));
1865
+ }
1866
+ function verifyIssuerP256(bytes, signatureB64, issuerPublicKeySpkiB64) {
1817
1867
  try {
1818
- return import_ed25519.ed25519.verify(signature, message, publicKey);
1868
+ const pubRaw = p256SpkiB64ToRaw(issuerPublicKeySpkiB64);
1869
+ const sigBytes = base64ToBytes(signatureB64);
1870
+ return import_nist.p256.verify(sigBytes, bytes, pubRaw, {
1871
+ prehash: true,
1872
+ format: "der"
1873
+ });
1819
1874
  } catch {
1820
1875
  return false;
1821
1876
  }
1822
1877
  }
1823
- function signCanonical(value, privateKey) {
1824
- return sign(canonicalJSONBytes(value), privateKey);
1825
- }
1826
- function verifyCanonical(value, signature, publicKey) {
1827
- return verify(canonicalJSONBytes(value), signature, publicKey);
1828
- }
1829
1878
 
1830
1879
  // src/offline/oac.ts
1831
- var import_zod5 = require("zod");
1832
1880
  var OAC_DEFAULT_PER_TX_KOBO = 5e5;
1833
1881
  var OAC_DEFAULT_CUMULATIVE_KOBO = 2e6;
1834
1882
  var OAC_DEFAULT_VALIDITY_MS = 24 * 60 * 60 * 1e3;
1835
- var HexString = (length) => import_zod5.z.string().regex(
1836
- new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
1837
- `expected ${length}-byte hex string`
1838
- );
1883
+ var Base64Std = import_zod5.z.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/, "expected base64 (standard) string");
1839
1884
  var OACSchema = import_zod5.z.object({
1840
1885
  userId: import_zod5.z.string().min(1),
1841
1886
  deviceId: import_zod5.z.string().min(1),
1842
- devicePublicKey: HexString(32),
1887
+ /** SubjectPublicKeyInfo DER, base64 (P-256). */
1888
+ devicePublicKey: Base64Std,
1843
1889
  perTxCapKobo: import_zod5.z.number().int().nonnegative(),
1844
1890
  cumulativeCapKobo: import_zod5.z.number().int().nonnegative(),
1845
1891
  validFromMs: import_zod5.z.number().int().nonnegative(),
1846
1892
  validUntilMs: import_zod5.z.number().int().positive(),
1847
1893
  counterSeed: import_zod5.z.number().int().nonnegative(),
1848
1894
  nonce: import_zod5.z.string().min(1),
1849
- issuerSig: HexString(64)
1895
+ /** ASN.1 DER ECDSA(SHA-256) signature, base64. */
1896
+ issuerSig: Base64Std
1850
1897
  }).refine((v) => v.validUntilMs > v.validFromMs, {
1851
1898
  message: "validUntilMs must be greater than validFromMs"
1852
1899
  }).refine((v) => v.perTxCapKobo <= v.cumulativeCapKobo, {
1853
1900
  message: "perTxCapKobo must not exceed cumulativeCapKobo"
1854
1901
  });
1855
1902
  function buildOAC(input) {
1856
- const devicePublicKey = typeof input.devicePublicKey === "string" ? input.devicePublicKey : bytesToHex(input.devicePublicKey);
1857
1903
  return {
1858
1904
  userId: input.userId,
1859
1905
  deviceId: input.deviceId,
1860
- devicePublicKey,
1906
+ devicePublicKey: input.devicePublicKey,
1861
1907
  perTxCapKobo: input.perTxCapKobo ?? OAC_DEFAULT_PER_TX_KOBO,
1862
1908
  cumulativeCapKobo: input.cumulativeCapKobo ?? OAC_DEFAULT_CUMULATIVE_KOBO,
1863
1909
  validFromMs: input.validFromMs,
@@ -1867,36 +1913,25 @@ function buildOAC(input) {
1867
1913
  };
1868
1914
  }
1869
1915
  function signOAC(unsigned, issuerPrivateKey) {
1870
- const issuerSig = bytesToHex(
1871
- sign(canonicalJSONBytes(unsigned), issuerPrivateKey)
1916
+ const issuerSig = signIssuerP256(
1917
+ canonicalJSONBytes(unsigned),
1918
+ issuerPrivateKey
1872
1919
  );
1873
1920
  return { ...unsigned, issuerSig };
1874
1921
  }
1875
- function verifyOAC(oac, issuerPublicKey) {
1922
+ function verifyOAC(oac, issuerPublicKeySpkiB64) {
1876
1923
  try {
1877
1924
  const parsed = OACSchema.parse(oac);
1878
1925
  const { issuerSig, ...unsigned } = parsed;
1879
- return verify(
1926
+ return verifyIssuerP256(
1880
1927
  canonicalJSONBytes(unsigned),
1881
- hexToBytes(issuerSig),
1882
- issuerPublicKey
1928
+ issuerSig,
1929
+ issuerPublicKeySpkiB64
1883
1930
  );
1884
1931
  } catch {
1885
1932
  return false;
1886
1933
  }
1887
1934
  }
1888
- function bytesToHex(b) {
1889
- let s = "";
1890
- for (let i = 0; i < b.length; i++) s += b[i].toString(16).padStart(2, "0");
1891
- return s;
1892
- }
1893
- function hexToBytes(s) {
1894
- if (s.length % 2 !== 0) throw new Error("hex: odd length");
1895
- const out = new Uint8Array(s.length / 2);
1896
- for (let i = 0; i < out.length; i++)
1897
- out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16);
1898
- return out;
1899
- }
1900
1935
 
1901
1936
  // src/offline/codec.ts
1902
1937
  var ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
@@ -1953,19 +1988,19 @@ function decodeBase45(s) {
1953
1988
 
1954
1989
  // src/offline/messages.ts
1955
1990
  var import_zod6 = require("zod");
1956
- var HexSig = import_zod6.z.string().regex(/^[0-9a-fA-F]{128}$/, "expected 64-byte hex signature");
1991
+ var Base64Sig = import_zod6.z.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/, "expected base64 (standard) signature");
1957
1992
  var OfflinePaymentRequestSchema = import_zod6.z.object({
1958
1993
  reference: import_zod6.z.string().min(1),
1959
1994
  amountKobo: import_zod6.z.number().int().positive(),
1960
1995
  merchantOAC: OACSchema,
1961
1996
  expiresAtMs: import_zod6.z.number().int().positive(),
1962
- merchantSig: HexSig
1997
+ merchantSig: Base64Sig
1963
1998
  });
1964
1999
  var OfflinePaymentAuthorizationSchema = import_zod6.z.object({
1965
2000
  request: OfflinePaymentRequestSchema,
1966
2001
  payerOAC: OACSchema,
1967
2002
  payerCounter: import_zod6.z.number().int().positive(),
1968
- payerSig: HexSig
2003
+ payerSig: Base64Sig
1969
2004
  });
1970
2005
  function buildPaymentRequest(input) {
1971
2006
  if (!Number.isInteger(input.amountKobo) || input.amountKobo <= 0) {
@@ -1982,27 +2017,28 @@ function buildPaymentRequest(input) {
1982
2017
  };
1983
2018
  }
1984
2019
  function signPaymentRequest(unsigned, merchantDevicePrivateKey) {
1985
- const merchantSig = bytesToHex(
1986
- sign(canonicalJSONBytes(unsigned), merchantDevicePrivateKey)
2020
+ const merchantSig = signIssuerP256(
2021
+ canonicalJSONBytes(unsigned),
2022
+ merchantDevicePrivateKey
1987
2023
  );
1988
2024
  return { ...unsigned, merchantSig };
1989
2025
  }
1990
- function verifyPaymentRequest(req, issuerPublicKey) {
2026
+ function verifyPaymentRequest(req, issuerPublicKeySpkiB64) {
1991
2027
  try {
1992
2028
  const parsed = OfflinePaymentRequestSchema.parse(req);
1993
2029
  const { issuerSig: merchantOacSig, ...merchantOacUnsigned } = parsed.merchantOAC;
1994
- if (!verify(
2030
+ if (!verifyIssuerP256(
1995
2031
  canonicalJSONBytes(merchantOacUnsigned),
1996
- hexToBytes(merchantOacSig),
1997
- issuerPublicKey
2032
+ merchantOacSig,
2033
+ issuerPublicKeySpkiB64
1998
2034
  )) {
1999
2035
  return false;
2000
2036
  }
2001
2037
  const { merchantSig, ...unsigned } = parsed;
2002
- return verify(
2038
+ return verifyIssuerP256(
2003
2039
  canonicalJSONBytes(unsigned),
2004
- hexToBytes(merchantSig),
2005
- hexToBytes(parsed.merchantOAC.devicePublicKey)
2040
+ merchantSig,
2041
+ parsed.merchantOAC.devicePublicKey
2006
2042
  );
2007
2043
  } catch {
2008
2044
  return false;
@@ -2022,28 +2058,30 @@ function buildAuthorization(input) {
2022
2058
  };
2023
2059
  }
2024
2060
  function signAuthorization(unsigned, payerDevicePrivateKey) {
2025
- const payerSig = bytesToHex(
2026
- sign(canonicalJSONBytes(unsigned), payerDevicePrivateKey)
2061
+ const payerSig = signIssuerP256(
2062
+ canonicalJSONBytes(unsigned),
2063
+ payerDevicePrivateKey
2027
2064
  );
2028
2065
  return { ...unsigned, payerSig };
2029
2066
  }
2030
- function verifyAuthorization(auth, issuerPublicKey) {
2067
+ function verifyAuthorization(auth, issuerPublicKeySpkiB64) {
2031
2068
  try {
2032
2069
  const parsed = OfflinePaymentAuthorizationSchema.parse(auth);
2033
- if (!verifyPaymentRequest(parsed.request, issuerPublicKey)) return false;
2070
+ if (!verifyPaymentRequest(parsed.request, issuerPublicKeySpkiB64))
2071
+ return false;
2034
2072
  const { issuerSig: payerOacSig, ...payerOacUnsigned } = parsed.payerOAC;
2035
- if (!verify(
2073
+ if (!verifyIssuerP256(
2036
2074
  canonicalJSONBytes(payerOacUnsigned),
2037
- hexToBytes(payerOacSig),
2038
- issuerPublicKey
2075
+ payerOacSig,
2076
+ issuerPublicKeySpkiB64
2039
2077
  )) {
2040
2078
  return false;
2041
2079
  }
2042
2080
  const { payerSig, ...unsigned } = parsed;
2043
- return verify(
2081
+ return verifyIssuerP256(
2044
2082
  canonicalJSONBytes(unsigned),
2045
- hexToBytes(payerSig),
2046
- hexToBytes(parsed.payerOAC.devicePublicKey)
2083
+ payerSig,
2084
+ parsed.payerOAC.devicePublicKey
2047
2085
  );
2048
2086
  } catch {
2049
2087
  return false;
@@ -2099,10 +2137,14 @@ var PaymentClaimSchema = import_zod7.z.object({
2099
2137
  occurredAtMs: import_zod7.z.number().int().nonnegative(),
2100
2138
  completedAtMs: import_zod7.z.number().int().nonnegative().optional(),
2101
2139
  contextId: import_zod7.z.string().optional(),
2102
- payerPubkey: import_zod7.z.string().regex(/^[0-9a-f]{64}$/i),
2103
- payerSignature: import_zod7.z.string().regex(/^[0-9a-f]+$/i),
2104
- payeePubkey: import_zod7.z.string().regex(/^[0-9a-f]{64}$/i).optional(),
2105
- payeeSignature: import_zod7.z.string().regex(/^[0-9a-f]+$/i).optional()
2140
+ // Stage 2c: P-256 device keys are now SubjectPublicKeyInfo DER, base64.
2141
+ // Signatures are ASN.1 DER ECDSA(SHA-256), base64. Backwards-incompatible
2142
+ // wire change; the backend has the matching widening in offline-settlements
2143
+ // service + zod schema.
2144
+ payerPubkey: import_zod7.z.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/),
2145
+ payerSignature: import_zod7.z.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/),
2146
+ payeePubkey: import_zod7.z.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/).optional(),
2147
+ payeeSignature: import_zod7.z.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/).optional()
2106
2148
  });
2107
2149
  var SettlementSchema = import_zod7.z.object({
2108
2150
  settlementId: import_zod7.z.string().uuid(),
@@ -2530,10 +2572,6 @@ var PASS_STATES = [
2530
2572
  "expired",
2531
2573
  "revoked"
2532
2574
  ];
2533
- var HexString2 = (length) => import_zod9.z.string().regex(
2534
- new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
2535
- `expected ${length}-byte hex string`
2536
- );
2537
2575
  var PassMetadataSchema = import_zod9.z.record(
2538
2576
  import_zod9.z.union([import_zod9.z.string(), import_zod9.z.number(), import_zod9.z.boolean(), import_zod9.z.null()])
2539
2577
  );
@@ -2554,9 +2592,9 @@ var PassSchema = import_zod9.z.object({
2554
2592
  nonce: import_zod9.z.string().min(1),
2555
2593
  /** Device id this pass is bound to (FK to backend `device_keys`). */
2556
2594
  holderDeviceId: import_zod9.z.string().min(1),
2557
- /** 32-byte hex Ed25519 public key of the bound device. The redemption signature
2558
- * is verified against this key — it is the security-critical binding. */
2559
- holderDevicePubkey: HexString2(32),
2595
+ /** SubjectPublicKeyInfo DER (P-256) of the bound device, base64. The redemption
2596
+ * signature is verified against this key — it is the security-critical binding. */
2597
+ holderDevicePubkey: import_zod9.z.string().min(64).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/),
2560
2598
  /** Optional fixed amount for monetary passes (vouchers, gift cards) in kobo. */
2561
2599
  amountKobo: import_zod9.z.number().int().nonnegative().optional(),
2562
2600
  /** ISO-4217-ish currency code; required on the wire. SDK builders default to NGN. */
@@ -2565,7 +2603,8 @@ var PassSchema = import_zod9.z.object({
2565
2603
  counterSeed: import_zod9.z.number().int().nonnegative(),
2566
2604
  /** Optional cumulative spend cap in kobo across all redemptions of this pass. */
2567
2605
  cumulativeCapKobo: import_zod9.z.number().int().nonnegative().optional(),
2568
- issuerSig: HexString2(64)
2606
+ /** ASN.1 DER ECDSA P-256 signature, base64. */
2607
+ issuerSig: import_zod9.z.string().min(64).max(2048).regex(/^[A-Za-z0-9+/]+={0,2}$/)
2569
2608
  }).refine((v) => v.validUntilMs > v.validFromMs, {
2570
2609
  message: "validUntilMs must be greater than validFromMs"
2571
2610
  });
@@ -2598,19 +2637,20 @@ function buildPass(input) {
2598
2637
  return out;
2599
2638
  }
2600
2639
  function signPass(unsigned, issuerPrivateKey) {
2601
- const issuerSig = bytesToHex(
2602
- sign(canonicalJSONBytes(unsigned), issuerPrivateKey)
2640
+ const issuerSig = signIssuerP256(
2641
+ canonicalJSONBytes(unsigned),
2642
+ issuerPrivateKey
2603
2643
  );
2604
2644
  return { ...unsigned, issuerSig };
2605
2645
  }
2606
- function verifyPass(pass, issuerPublicKey) {
2646
+ function verifyPass(pass, issuerPublicKeySpkiB64) {
2607
2647
  try {
2608
2648
  const parsed = PassSchema.parse(pass);
2609
2649
  const { issuerSig, ...unsigned } = parsed;
2610
- return verify(
2650
+ return verifyIssuerP256(
2611
2651
  canonicalJSONBytes(unsigned),
2612
- hexToBytes(issuerSig),
2613
- issuerPublicKey
2652
+ issuerSig,
2653
+ issuerPublicKeySpkiB64
2614
2654
  );
2615
2655
  } catch {
2616
2656
  return false;
@@ -2622,7 +2662,7 @@ function isPassWithinValidity(pass, nowMs) {
2622
2662
 
2623
2663
  // src/passes/redemption.ts
2624
2664
  var import_zod10 = require("zod");
2625
- var HexSig2 = import_zod10.z.string().regex(/^[0-9a-fA-F]{128}$/, "expected 64-byte hex signature");
2665
+ var Base64Std2 = import_zod10.z.string().min(16).max(2048).regex(/^[A-Za-z0-9+/]+={0,2}$/, "expected base64 (std)");
2626
2666
  var RedemptionSchema = import_zod10.z.object({
2627
2667
  pass: PassSchema,
2628
2668
  redeemerId: import_zod10.z.string().min(1),
@@ -2633,7 +2673,8 @@ var RedemptionSchema = import_zod10.z.object({
2633
2673
  /** Amount being redeemed in kobo (0 for non-monetary passes like ride tickets). */
2634
2674
  amountKobo: import_zod10.z.number().int().nonnegative(),
2635
2675
  nonce: import_zod10.z.string().min(1),
2636
- holderSig: HexSig2
2676
+ /** ASN.1 DER ECDSA P-256 signature over canonicalJSONBytes(unsigned), base64. */
2677
+ holderSig: Base64Std2
2637
2678
  });
2638
2679
  var REDEEMABLE_STATES = /* @__PURE__ */ new Set(["issued", "active"]);
2639
2680
  function buildRedemption(input) {
@@ -2687,31 +2728,28 @@ function buildRedemption(input) {
2687
2728
  };
2688
2729
  }
2689
2730
  function signRedemption(unsigned, holderDevicePrivateKey) {
2690
- const holderSig = bytesToHex(
2691
- sign(canonicalJSONBytes(unsigned), holderDevicePrivateKey)
2731
+ const holderSig = signIssuerP256(
2732
+ canonicalJSONBytes(unsigned),
2733
+ holderDevicePrivateKey
2692
2734
  );
2693
2735
  return { ...unsigned, holderSig };
2694
2736
  }
2695
- function verifyRedemption(r, issuerPublicKey) {
2737
+ function verifyRedemption(r, issuerPublicKeySpkiB64) {
2696
2738
  try {
2697
2739
  const parsed = RedemptionSchema.parse(r);
2698
2740
  if (parsed.counter <= parsed.pass.counterSeed) return false;
2699
2741
  const { issuerSig, ...passUnsigned } = parsed.pass;
2700
- if (!verify(
2742
+ if (!verifyIssuerP256(
2701
2743
  canonicalJSONBytes(passUnsigned),
2702
- hexToBytes(issuerSig),
2703
- issuerPublicKey
2744
+ issuerSig,
2745
+ issuerPublicKeySpkiB64
2704
2746
  )) {
2705
2747
  return false;
2706
2748
  }
2707
- const holderHex = parsed.pass.holderDevicePubkey;
2708
- if (typeof holderHex !== "string") return false;
2749
+ const holderPub = parsed.pass.holderDevicePubkey;
2750
+ if (typeof holderPub !== "string") return false;
2709
2751
  const { holderSig, ...unsigned } = parsed;
2710
- return verify(
2711
- canonicalJSONBytes(unsigned),
2712
- hexToBytes(holderSig),
2713
- hexToBytes(holderHex)
2714
- );
2752
+ return verifyIssuerP256(canonicalJSONBytes(unsigned), holderSig, holderPub);
2715
2753
  } catch {
2716
2754
  return false;
2717
2755
  }
@@ -2721,10 +2759,6 @@ function verifyRedemption(r, issuerPublicKey) {
2721
2759
  var import_zod11 = require("zod");
2722
2760
  var RECEIPT_CHANNELS = ["cash", "pass"];
2723
2761
  var RECEIPT_KINDS = RECEIPT_CHANNELS;
2724
- var HexString3 = (length) => import_zod11.z.string().regex(
2725
- new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
2726
- `expected ${length}-byte hex string`
2727
- );
2728
2762
  var ReceiptPayloadSchema = import_zod11.z.record(
2729
2763
  import_zod11.z.union([import_zod11.z.string(), import_zod11.z.number(), import_zod11.z.boolean(), import_zod11.z.null()])
2730
2764
  );
@@ -2742,7 +2776,8 @@ var ReceiptSchema = import_zod11.z.object({
2742
2776
  issuedAtMs: import_zod11.z.number().int().nonnegative(),
2743
2777
  issuerId: import_zod11.z.string().min(1),
2744
2778
  payload: ReceiptPayloadSchema,
2745
- issuerSig: HexString3(64)
2779
+ /** ASN.1 DER ECDSA P-256 signature, base64. */
2780
+ issuerSig: import_zod11.z.string().min(64).max(2048).regex(/^[A-Za-z0-9+/]+={0,2}$/)
2746
2781
  }).superRefine((v, ctx) => {
2747
2782
  if (v.channel === "cash") {
2748
2783
  if (!v.intentId) {
@@ -2808,19 +2843,20 @@ function buildReceipt(input) {
2808
2843
  return out;
2809
2844
  }
2810
2845
  function signReceipt(unsigned, issuerPrivateKey) {
2811
- const issuerSig = bytesToHex(
2812
- sign(canonicalJSONBytes(unsigned), issuerPrivateKey)
2846
+ const issuerSig = signIssuerP256(
2847
+ canonicalJSONBytes(unsigned),
2848
+ issuerPrivateKey
2813
2849
  );
2814
2850
  return { ...unsigned, issuerSig };
2815
2851
  }
2816
- function verifyReceipt(r, issuerPublicKey) {
2852
+ function verifyReceipt(r, issuerPublicKeySpkiB64) {
2817
2853
  try {
2818
2854
  const parsed = ReceiptSchema.parse(r);
2819
2855
  const { issuerSig, ...unsigned } = parsed;
2820
- return verify(
2856
+ return verifyIssuerP256(
2821
2857
  canonicalJSONBytes(unsigned),
2822
- hexToBytes(issuerSig),
2823
- issuerPublicKey
2858
+ issuerSig,
2859
+ issuerPublicKeySpkiB64
2824
2860
  );
2825
2861
  } catch {
2826
2862
  return false;
@@ -3155,9 +3191,8 @@ function createAccountsClient(opts) {
3155
3191
  // src/me-offline/client.ts
3156
3192
  var import_zod13 = require("zod");
3157
3193
  var Hex64 = import_zod13.z.string().regex(/^[0-9a-f]{64}$/i);
3158
- var HexAny = import_zod13.z.string().regex(/^[0-9a-f]+$/i);
3159
3194
  var Sha256Hex = import_zod13.z.string().regex(/^[0-9a-f]{64}$/i);
3160
- var Base64Std = import_zod13.z.string().regex(/^[A-Za-z0-9+/]+={0,2}$/);
3195
+ var Base64Std3 = import_zod13.z.string().regex(/^[A-Za-z0-9+/]+={0,2}$/);
3161
3196
  var RegisterDeviceKeyInputSchema = import_zod13.z.object({
3162
3197
  deviceId: import_zod13.z.string().min(1).max(128),
3163
3198
  publicKeyHex: Hex64
@@ -3168,15 +3203,15 @@ var AttestationSecurityLevelSchema = import_zod13.z.enum([
3168
3203
  "SECURE_ENCLAVE",
3169
3204
  "SOFTWARE"
3170
3205
  ]);
3171
- var DeviceKeyAlgSchema = import_zod13.z.enum(["ed25519", "p256"]);
3206
+ var DeviceKeyAlgSchema = import_zod13.z.literal("p256");
3172
3207
  var RegisterDeviceKeyP256InputSchema = import_zod13.z.object({
3173
3208
  deviceId: import_zod13.z.string().min(1).max(128),
3174
3209
  /** P-256 SubjectPublicKeyInfo DER, base64. */
3175
- publicKeySpkiB64: Base64Std.min(64).max(4096),
3210
+ publicKeySpkiB64: Base64Std3.min(64).max(4096),
3176
3211
  /** Base64 of the server-issued enrollment challenge string. */
3177
- challengeB64: Base64Std.min(8).max(1024),
3212
+ challengeB64: Base64Std3.min(8).max(1024),
3178
3213
  /** iOS App Attest payload or Android X.509 Key Attestation chain. */
3179
- attestationChainB64: import_zod13.z.array(Base64Std.min(16).max(16384)).min(1).max(16),
3214
+ attestationChainB64: import_zod13.z.array(Base64Std3.min(16).max(16384)).min(1).max(16),
3180
3215
  securityLevel: AttestationSecurityLevelSchema
3181
3216
  });
3182
3217
  var P256EnrollmentChallengeInputSchema = import_zod13.z.object({
@@ -3190,9 +3225,12 @@ var DeviceKeyRecordSchema = import_zod13.z.object({
3190
3225
  id: import_zod13.z.string().uuid(),
3191
3226
  userId: import_zod13.z.string().uuid(),
3192
3227
  deviceId: import_zod13.z.string(),
3193
- alg: DeviceKeyAlgSchema.default("ed25519"),
3228
+ /** Always 'p256' on the consumer offline rail. Field retained for forward-compat. */
3229
+ alg: DeviceKeyAlgSchema.default("p256"),
3230
+ /** Legacy ed25519 hex key. Always null on new records (kept for back-compat reads). */
3194
3231
  publicKeyHex: Hex64.nullable().default(null),
3195
- publicKeySpkiB64: Base64Std.nullable().default(null),
3232
+ /** P-256 SubjectPublicKeyInfo DER, base64. Required for new records. */
3233
+ publicKeySpkiB64: Base64Std3.nullable().default(null),
3196
3234
  securityLevel: AttestationSecurityLevelSchema.nullable().default(null),
3197
3235
  hardwareBacked: import_zod13.z.boolean().default(false),
3198
3236
  attestedAtMs: import_zod13.z.number().int().nonnegative().nullable().default(null),
@@ -3204,9 +3242,10 @@ var ConsumerOACSchema = import_zod13.z.object({
3204
3242
  issuerId: import_zod13.z.string().min(1).max(64),
3205
3243
  userId: import_zod13.z.string().uuid(),
3206
3244
  deviceId: import_zod13.z.string().min(1).max(128),
3207
- alg: import_zod13.z.enum(["ed25519", "p256"]).optional(),
3208
- devicePubkeyHex: Hex64.optional(),
3209
- devicePubkeySpkiB64: Base64Std.min(64).max(4096).optional(),
3245
+ /** Always 'p256'. Field retained for forward-compat. */
3246
+ alg: import_zod13.z.literal("p256").default("p256"),
3247
+ /** P-256 SubjectPublicKeyInfo DER, base64. */
3248
+ devicePubkeySpkiB64: Base64Std3.min(64).max(4096),
3210
3249
  perTxCapKobo: import_zod13.z.number().int().positive(),
3211
3250
  cumulativeCapKobo: import_zod13.z.number().int().positive(),
3212
3251
  currency: import_zod13.z.string().length(3),
@@ -3214,20 +3253,13 @@ var ConsumerOACSchema = import_zod13.z.object({
3214
3253
  validUntilMs: import_zod13.z.number().int().nonnegative(),
3215
3254
  counterSeed: import_zod13.z.number().int().nonnegative(),
3216
3255
  issuedAtMs: import_zod13.z.number().int().nonnegative()
3217
- }).refine(
3218
- (o) => {
3219
- const alg = o.alg ?? "ed25519";
3220
- if (alg === "ed25519") {
3221
- return Boolean(o.devicePubkeyHex) && !o.devicePubkeySpkiB64;
3222
- }
3223
- return Boolean(o.devicePubkeySpkiB64) && !o.devicePubkeyHex;
3224
- },
3225
- { message: "OAC device pubkey shape must match alg" }
3226
- );
3256
+ });
3227
3257
  var SignedConsumerOACSchema = import_zod13.z.object({
3228
3258
  oac: ConsumerOACSchema,
3229
- issuerSig: HexAny,
3230
- issuerPublicKeyHex: Hex64
3259
+ /** ASN.1 DER ECDSA P-256 issuer signature, base64. */
3260
+ issuerSig: Base64Std3.min(16).max(2048),
3261
+ /** Issuer's P-256 public key as SubjectPublicKeyInfo DER, base64. */
3262
+ issuerPublicKeySpkiB64: Base64Std3.min(64).max(4096)
3231
3263
  });
3232
3264
  var OACRecordSchema = SignedConsumerOACSchema.extend({
3233
3265
  currentOfflineSpentKobo: import_zod13.z.number().int().nonnegative(),
@@ -3310,10 +3342,9 @@ var OfflineStatusResultSchema = import_zod13.z.object({
3310
3342
  var OfflineStateResultSchema = import_zod13.z.object({
3311
3343
  active: OACRecordSchema.nullable()
3312
3344
  });
3313
- var ClaimAlgSchema = import_zod13.z.enum(["ed25519", "p256"]);
3314
3345
  var ConsumerPaymentClaimSchema = import_zod13.z.object({
3315
- /** Algorithm discriminator. Omit / 'ed25519' for V1 clients. */
3316
- alg: ClaimAlgSchema.optional(),
3346
+ /** Always 'p256'. Retained for forward-compat and as an explicit domain marker. */
3347
+ alg: import_zod13.z.literal("p256").default("p256"),
3317
3348
  oacId: import_zod13.z.string().uuid(),
3318
3349
  encounterId: Sha256Hex.optional(),
3319
3350
  payerUserId: import_zod13.z.string().uuid(),
@@ -3326,28 +3357,11 @@ var ConsumerPaymentClaimSchema = import_zod13.z.object({
3326
3357
  occurredAtMs: import_zod13.z.number().int().nonnegative(),
3327
3358
  completedAtMs: import_zod13.z.number().int().nonnegative().optional(),
3328
3359
  contextId: import_zod13.z.string().max(128).optional(),
3329
- // ed25519 path
3330
- payerPubkeyHex: Hex64.optional(),
3331
- payerSignature: HexAny.optional(),
3332
- payeePubkeyHex: Hex64.optional(),
3333
- payeeSignature: HexAny.optional(),
3334
- // p256 path
3335
- payerPubkeySpkiB64: import_zod13.z.string().min(64).max(4096).optional(),
3336
- payerSignatureDerB64: import_zod13.z.string().min(16).max(2048).optional(),
3337
- payeePubkeySpkiB64: import_zod13.z.string().min(64).max(4096).optional(),
3338
- payeeSignatureDerB64: import_zod13.z.string().min(16).max(2048).optional()
3339
- }).refine(
3340
- (c) => {
3341
- const alg = c.alg ?? "ed25519";
3342
- if (alg === "ed25519") {
3343
- return Boolean(c.payerPubkeyHex) && Boolean(c.payerSignature);
3344
- }
3345
- return Boolean(c.payerPubkeySpkiB64) && Boolean(c.payerSignatureDerB64);
3346
- },
3347
- {
3348
- message: "payer key/signature fields must match alg (ed25519: hex; p256: SPKI+DER b64)"
3349
- }
3350
- );
3360
+ payerPubkeySpkiB64: Base64Std3.min(64).max(4096),
3361
+ payerSignatureDerB64: Base64Std3.min(16).max(2048),
3362
+ payeePubkeySpkiB64: Base64Std3.min(64).max(4096).optional(),
3363
+ payeeSignatureDerB64: Base64Std3.min(16).max(2048).optional()
3364
+ });
3351
3365
  var ConsumerSettlementSchema = import_zod13.z.object({
3352
3366
  settlementId: import_zod13.z.string().uuid(),
3353
3367
  settlementKey: Sha256Hex,
@@ -3360,7 +3374,8 @@ var ConsumerSettlementSchema = import_zod13.z.object({
3360
3374
  status: import_zod13.z.enum(["SETTLED", "REVIEW"]),
3361
3375
  reviewReason: import_zod13.z.string().nullable(),
3362
3376
  ledgerRef: import_zod13.z.string().nullable(),
3363
- issuerSig: HexAny,
3377
+ /** ASN.1 DER ECDSA P-256 issuer signature, base64. */
3378
+ issuerSig: Base64Std3.min(16).max(2048),
3364
3379
  createdAtMs: import_zod13.z.number().int().nonnegative()
3365
3380
  });
3366
3381
  var ConsumerSettleResultSchema = import_zod13.z.object({
@@ -3488,9 +3503,7 @@ function createMeOfflineClient(opts) {
3488
3503
  }
3489
3504
 
3490
3505
  // src/me-offline/signer.ts
3491
- var import_ed255198 = require("@noble/curves/ed25519");
3492
- var import_nist = require("@noble/curves/nist");
3493
- var import_utils3 = require("@noble/hashes/utils");
3506
+ var import_nist2 = require("@noble/curves/nist");
3494
3507
  var CLAIM_DOMAIN_V2 = "flur:consumer-offline:v2:claim";
3495
3508
  function canonicalClaimSigningPayload(claim) {
3496
3509
  return {
@@ -3512,7 +3525,7 @@ function canonicalClaimSigningPayload(claim) {
3512
3525
  function canonicalClaimSigningBytes(claim) {
3513
3526
  return canonicalJSONBytes(canonicalClaimSigningPayload(claim));
3514
3527
  }
3515
- function bytesToBase64(bytes) {
3528
+ function bytesToBase642(bytes) {
3516
3529
  if (typeof Buffer !== "undefined") {
3517
3530
  return Buffer.from(bytes).toString("base64");
3518
3531
  }
@@ -3520,7 +3533,7 @@ function bytesToBase64(bytes) {
3520
3533
  for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
3521
3534
  return btoa(bin);
3522
3535
  }
3523
- function base64ToBytes(b64) {
3536
+ function base64ToBytes2(b64) {
3524
3537
  if (typeof Buffer !== "undefined") {
3525
3538
  return new Uint8Array(Buffer.from(b64, "base64"));
3526
3539
  }
@@ -3529,7 +3542,7 @@ function base64ToBytes(b64) {
3529
3542
  for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
3530
3543
  return out;
3531
3544
  }
3532
- var P256_SPKI_HEADER = new Uint8Array([
3545
+ var P256_SPKI_HEADER2 = new Uint8Array([
3533
3546
  48,
3534
3547
  89,
3535
3548
  48,
@@ -3561,38 +3574,25 @@ function p256PublicKeyToSpkiB64(rawUncompressed) {
3561
3574
  if (rawUncompressed.length !== 65 || rawUncompressed[0] !== 4) {
3562
3575
  throw new Error("p256: expected 65-byte uncompressed point");
3563
3576
  }
3564
- const out = new Uint8Array(P256_SPKI_HEADER.length + rawUncompressed.length);
3565
- out.set(P256_SPKI_HEADER, 0);
3566
- out.set(rawUncompressed, P256_SPKI_HEADER.length);
3567
- return bytesToBase64(out);
3577
+ const out = new Uint8Array(P256_SPKI_HEADER2.length + rawUncompressed.length);
3578
+ out.set(P256_SPKI_HEADER2, 0);
3579
+ out.set(rawUncompressed, P256_SPKI_HEADER2.length);
3580
+ return bytesToBase642(out);
3568
3581
  }
3569
3582
  function p256SpkiB64ToPublicKey(spkiB64) {
3570
- const spki = base64ToBytes(spkiB64);
3571
- if (spki.length !== P256_SPKI_HEADER.length + 65) {
3583
+ const spki = base64ToBytes2(spkiB64);
3584
+ if (spki.length !== P256_SPKI_HEADER2.length + 65) {
3572
3585
  throw new Error("p256: invalid SPKI length");
3573
3586
  }
3574
- for (let i = 0; i < P256_SPKI_HEADER.length; i++) {
3575
- if (spki[i] !== P256_SPKI_HEADER[i]) {
3587
+ for (let i = 0; i < P256_SPKI_HEADER2.length; i++) {
3588
+ if (spki[i] !== P256_SPKI_HEADER2[i]) {
3576
3589
  throw new Error("p256: invalid SPKI header");
3577
3590
  }
3578
3591
  }
3579
- return spki.slice(P256_SPKI_HEADER.length);
3580
- }
3581
- function createSoftwareEd25519Signer(privateKey) {
3582
- const pub = import_ed255198.ed25519.getPublicKey(privateKey);
3583
- return {
3584
- alg: "ed25519",
3585
- async getPublicKey() {
3586
- return { alg: "ed25519", publicKey: (0, import_utils3.bytesToHex)(pub) };
3587
- },
3588
- async sign(bytes) {
3589
- const sig = import_ed255198.ed25519.sign(bytes, privateKey);
3590
- return { alg: "ed25519", signature: (0, import_utils3.bytesToHex)(sig) };
3591
- }
3592
- };
3592
+ return spki.slice(P256_SPKI_HEADER2.length);
3593
3593
  }
3594
3594
  function createSoftwareP256Signer(privateKey) {
3595
- const raw = import_nist.p256.getPublicKey(privateKey, false);
3595
+ const raw = import_nist2.p256.getPublicKey(privateKey, false);
3596
3596
  const spkiB64 = p256PublicKeyToSpkiB64(raw);
3597
3597
  return {
3598
3598
  alg: "p256",
@@ -3600,35 +3600,87 @@ function createSoftwareP256Signer(privateKey) {
3600
3600
  return { alg: "p256", publicKey: spkiB64 };
3601
3601
  },
3602
3602
  async sign(bytes) {
3603
- const sig = import_nist.p256.sign(bytes, privateKey, { prehash: true });
3603
+ const sig = import_nist2.p256.sign(bytes, privateKey, { prehash: true });
3604
3604
  const der = sig.toBytes("der");
3605
- return { alg: "p256", signature: bytesToBase64(der) };
3605
+ return { alg: "p256", signature: bytesToBase642(der) };
3606
3606
  }
3607
3607
  };
3608
3608
  }
3609
3609
  function verifyClaimSignature(input) {
3610
3610
  try {
3611
- if (input.alg === "ed25519") {
3612
- return import_ed255198.ed25519.verify(
3613
- (0, import_utils3.hexToBytes)(input.signature),
3614
- input.bytes,
3615
- (0, import_utils3.hexToBytes)(input.publicKey)
3616
- );
3617
- }
3618
- if (input.alg === "p256") {
3619
- const sigDer = base64ToBytes(input.signature);
3620
- const pub = p256SpkiB64ToPublicKey(input.publicKey);
3621
- return import_nist.p256.verify(sigDer, input.bytes, pub, {
3622
- prehash: true,
3623
- format: "der"
3624
- });
3625
- }
3626
- return false;
3611
+ if (input.alg !== "p256") return false;
3612
+ const sigDer = base64ToBytes2(input.signature);
3613
+ const pub = p256SpkiB64ToPublicKey(input.publicKey);
3614
+ return import_nist2.p256.verify(sigDer, input.bytes, pub, {
3615
+ prehash: true,
3616
+ format: "der"
3617
+ });
3627
3618
  } catch {
3628
3619
  return false;
3629
3620
  }
3630
3621
  }
3631
3622
 
3623
+ // src/me-offline/sms.ts
3624
+ var OFFLINE_CLAIM_SMS_PREFIX = "FLURC1.";
3625
+ var TOKEN_RE = /(?:^|\s)(FLURC1\.[A-Za-z0-9_-]+={0,2})(?:\s|$)/;
3626
+ function encodeOfflineClaimSmsMessage(claim) {
3627
+ const parsed = ConsumerPaymentClaimSchema.parse(claim);
3628
+ const json = JSON.stringify(parsed);
3629
+ return `${OFFLINE_CLAIM_SMS_PREFIX}${base64UrlEncodeUtf8(json)}`;
3630
+ }
3631
+ function decodeOfflineClaimSmsMessage(message) {
3632
+ const token = extractOfflineClaimSmsToken(message);
3633
+ if (!token) {
3634
+ throw new Error("offline claim SMS token not found");
3635
+ }
3636
+ const encoded = token.slice(OFFLINE_CLAIM_SMS_PREFIX.length);
3637
+ let raw;
3638
+ try {
3639
+ raw = JSON.parse(base64UrlDecodeUtf8(encoded));
3640
+ } catch {
3641
+ throw new Error("offline claim SMS token is malformed");
3642
+ }
3643
+ const parsed = ConsumerPaymentClaimSchema.safeParse(raw);
3644
+ if (!parsed.success) {
3645
+ throw new Error("offline claim SMS token is invalid");
3646
+ }
3647
+ return parsed.data;
3648
+ }
3649
+ function extractOfflineClaimSmsToken(message) {
3650
+ const trimmed = message.trim();
3651
+ if (trimmed.startsWith(OFFLINE_CLAIM_SMS_PREFIX)) {
3652
+ return trimmed.split(/\s+/, 1)[0] ?? null;
3653
+ }
3654
+ return TOKEN_RE.exec(message)?.[1] ?? null;
3655
+ }
3656
+ function base64UrlEncodeUtf8(input) {
3657
+ const bytes = new TextEncoder().encode(input);
3658
+ let binary = "";
3659
+ for (const byte of bytes) binary += String.fromCharCode(byte);
3660
+ const base64 = typeof btoa === "function" ? btoa(binary) : typeof Buffer !== "undefined" ? Buffer.from(bytes).toString("base64") : void 0;
3661
+ if (!base64) throw new Error("base64 encoder unavailable");
3662
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
3663
+ }
3664
+ function base64UrlDecodeUtf8(input) {
3665
+ const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
3666
+ const padded = base64.padEnd(
3667
+ base64.length + (4 - base64.length % 4) % 4,
3668
+ "="
3669
+ );
3670
+ if (typeof atob === "function") {
3671
+ const binary = atob(padded);
3672
+ const bytes = new Uint8Array(binary.length);
3673
+ for (let index = 0; index < binary.length; index++) {
3674
+ bytes[index] = binary.charCodeAt(index);
3675
+ }
3676
+ return new TextDecoder().decode(bytes);
3677
+ }
3678
+ if (typeof Buffer !== "undefined") {
3679
+ return Buffer.from(padded, "base64").toString("utf8");
3680
+ }
3681
+ throw new Error("base64 decoder unavailable");
3682
+ }
3683
+
3632
3684
  // src/partner-funding/client.ts
3633
3685
  var import_zod14 = require("zod");
3634
3686
  var MinorString = import_zod14.z.string().regex(/^-?\d+$/);
@@ -4004,14 +4056,15 @@ function buildArtifactBody(input) {
4004
4056
  return { ...header, data: input.data };
4005
4057
  }
4006
4058
  function signArtifact(body, privateKey) {
4007
- const sig = bytesToHex(sign(canonicalJSONBytes(body), privateKey));
4059
+ const sig = signIssuerP256(canonicalJSONBytes(body), privateKey);
4008
4060
  return { body, sig };
4009
4061
  }
4010
4062
  function encodeArtifactUri(signed) {
4011
4063
  const bodyBytes = canonicalJSONBytes(signed.body);
4012
4064
  const bodyB64 = base64UrlEncode(bodyBytes);
4013
- const sigB64 = base64UrlEncode(hexToBytes(signed.sig));
4014
- return `${FLUR_ARTIFACT_URI_PREFIX}${signed.body.t}/${bodyB64}.${sigB64}`;
4065
+ const sigBytes = base64UrlDecode(signed.sig);
4066
+ const sigB64Url = base64UrlEncode(sigBytes);
4067
+ return `${FLUR_ARTIFACT_URI_PREFIX}${signed.body.t}/${bodyB64}.${sigB64Url}`;
4015
4068
  }
4016
4069
  function decodeArtifactUri(uri) {
4017
4070
  if (!uri.startsWith(FLUR_ARTIFACT_URI_PREFIX)) {
@@ -4042,9 +4095,9 @@ function decodeArtifactUri(uri) {
4042
4095
  }
4043
4096
  const bodyBytes = base64UrlDecode(payload.slice(0, dot));
4044
4097
  const sigBytes = base64UrlDecode(payload.slice(dot + 1));
4045
- if (sigBytes.length !== 64) {
4098
+ if (sigBytes.length < 64 || sigBytes.length > 80) {
4046
4099
  throw new FlurArtifactError(
4047
- `Signature must be 64 bytes, got ${sigBytes.length}`,
4100
+ `Signature length out of range: ${sigBytes.length}`,
4048
4101
  "INVALID_SIGNATURE"
4049
4102
  );
4050
4103
  }
@@ -4071,17 +4124,26 @@ function decodeArtifactUri(uri) {
4071
4124
  type,
4072
4125
  bodyBytes,
4073
4126
  body: bodyJson,
4074
- sig: bytesToHex(sigBytes)
4127
+ // Encode as standard base64 (not url-safe) for sig field consistency.
4128
+ sig: encodeStdBase64(sigBytes)
4075
4129
  };
4076
4130
  }
4077
- function verifyArtifactSignature(decoded, publicKey, options = {}) {
4131
+ function encodeStdBase64(bytes) {
4132
+ if (typeof Buffer !== "undefined") {
4133
+ return Buffer.from(bytes).toString("base64");
4134
+ }
4135
+ let bin = "";
4136
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
4137
+ return typeof btoa === "function" ? btoa(bin) : "";
4138
+ }
4139
+ function verifyArtifactSignature(decoded, publicKeySpkiB64, options = {}) {
4078
4140
  if (options.enforceExpiry !== false && decoded.body.exp !== void 0) {
4079
4141
  const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
4080
4142
  if (decoded.body.exp < now) {
4081
4143
  throw new FlurArtifactError("Artifact has expired", "EXPIRED");
4082
4144
  }
4083
4145
  }
4084
- return verify(decoded.bodyBytes, hexToBytes(decoded.sig), publicKey);
4146
+ return verifyIssuerP256(decoded.bodyBytes, decoded.sig, publicKeySpkiB64);
4085
4147
  }
4086
4148
 
4087
4149
  // src/artifacts/types.ts
@@ -4100,7 +4162,7 @@ var ARTIFACT_TYPES = {
4100
4162
  PASS: "pass",
4101
4163
  IDENTITY: "identity"
4102
4164
  };
4103
- var HexString4 = (length) => import_zod16.z.string().regex(
4165
+ var HexString = (length) => import_zod16.z.string().regex(
4104
4166
  new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
4105
4167
  `expected ${length}-byte hex string`
4106
4168
  );
@@ -4118,7 +4180,7 @@ var ReceiptArtifactSchema = import_zod16.z.object({
4118
4180
  settledAtMs: import_zod16.z.number().int().positive(),
4119
4181
  ledgerTxnId: import_zod16.z.string().min(1).max(64).optional(),
4120
4182
  memo: import_zod16.z.string().max(140).optional(),
4121
- hashChainPrev: HexString4(32).optional()
4183
+ hashChainPrev: HexString(32).optional()
4122
4184
  });
4123
4185
  var ShortId = import_zod16.z.string().min(1).max(64);
4124
4186
  var PositiveInt = import_zod16.z.number().int().positive();
@@ -4200,7 +4262,7 @@ var StatementArtifactSchema = import_zod16.z.object({
4200
4262
  closingBalanceKobo: import_zod16.z.number().int(),
4201
4263
  transactionCount: NonNegativeInt,
4202
4264
  currency: Currency2,
4203
- hashChainPrev: HexString4(32).optional()
4265
+ hashChainPrev: HexString(32).optional()
4204
4266
  }).refine((v) => v.periodEndMs > v.periodStartMs, {
4205
4267
  message: "periodEndMs must be greater than periodStartMs",
4206
4268
  path: ["periodEndMs"]
@@ -4233,7 +4295,7 @@ var IdentityArtifactSchema = import_zod16.z.object({
4233
4295
  "kyc_tier",
4234
4296
  "age_band"
4235
4297
  ]),
4236
- claimValueHash: HexString4(32),
4298
+ claimValueHash: HexString(32),
4237
4299
  attestedAtMs: PositiveInt
4238
4300
  });
4239
4301
  var ARTIFACT_BODY_SCHEMAS = {
@@ -4297,7 +4359,7 @@ function createArtifactUri(input) {
4297
4359
  const signed = signArtifact(body, input.privateKey);
4298
4360
  return { uri: encodeArtifactUri(signed), signed };
4299
4361
  }
4300
- function verifyArtifactUri(uri, publicKey, options = {}) {
4362
+ function verifyArtifactUri(uri, publicKeySpkiB64, options = {}) {
4301
4363
  const decoded = decodeArtifactUri(uri);
4302
4364
  if (!isKnownArtifactType(decoded.type)) {
4303
4365
  throw new FlurArtifactError(
@@ -4319,7 +4381,7 @@ function verifyArtifactUri(uri, publicKey, options = {}) {
4319
4381
  "INVALID_BODY"
4320
4382
  );
4321
4383
  }
4322
- const ok = verifyArtifactSignature(decoded, publicKey, options);
4384
+ const ok = verifyArtifactSignature(decoded, publicKeySpkiB64, options);
4323
4385
  if (!ok) {
4324
4386
  throw new FlurArtifactError(
4325
4387
  "Artifact signature verification failed",
@@ -4413,6 +4475,7 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4413
4475
  OAC_DEFAULT_CUMULATIVE_KOBO,
4414
4476
  OAC_DEFAULT_PER_TX_KOBO,
4415
4477
  OAC_DEFAULT_VALIDITY_MS,
4478
+ OFFLINE_CLAIM_SMS_PREFIX,
4416
4479
  OfflineClaimArtifactSchema,
4417
4480
  OfflineHoldRecordSchema,
4418
4481
  OfflinePaymentAuthorizationArtifactSchema,
@@ -4508,20 +4571,21 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4508
4571
  createPassesClient,
4509
4572
  createReceiptArtifactUri,
4510
4573
  createReceiptsClient,
4511
- createSoftwareEd25519Signer,
4512
4574
  createSoftwareP256Signer,
4513
4575
  decodeArtifactUri,
4514
4576
  decodeAuthorizationQR,
4515
4577
  decodeBase45,
4578
+ decodeOfflineClaimSmsMessage,
4516
4579
  decodePaymentRequestQR,
4517
4580
  encodeArtifactUri,
4518
4581
  encodeAuthorizationQR,
4519
4582
  encodeBase45,
4520
4583
  encodeNQR,
4584
+ encodeOfflineClaimSmsMessage,
4521
4585
  encodePaymentRequestQR,
4586
+ extractOfflineClaimSmsToken,
4522
4587
  formatAmount,
4523
4588
  generateDynamicQR,
4524
- generateKeyPair,
4525
4589
  generateStaticQR,
4526
4590
  init,
4527
4591
  isHardenedArtifactType,
@@ -4532,13 +4596,10 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4532
4596
  parseAmountInput,
4533
4597
  parseNQR,
4534
4598
  parseQR,
4535
- publicKeyFromPrivate,
4536
4599
  readTLV,
4537
4600
  routingHint,
4538
- sign,
4539
4601
  signArtifact,
4540
4602
  signAuthorization,
4541
- signCanonical,
4542
4603
  signOAC,
4543
4604
  signPartnerRequest,
4544
4605
  signPass,
@@ -4546,11 +4607,9 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4546
4607
  signReceipt,
4547
4608
  signRedemption,
4548
4609
  signRequestHMAC,
4549
- verify,
4550
4610
  verifyArtifactSignature,
4551
4611
  verifyArtifactUri,
4552
4612
  verifyAuthorization,
4553
- verifyCanonical,
4554
4613
  verifyClaimSignature,
4555
4614
  verifyOAC,
4556
4615
  verifyPass,