@nokinc-flur/sdk 1.1.2 → 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
@@ -29,6 +29,8 @@ __export(index_exports, {
29
29
  AccountSchema: () => AccountSchema,
30
30
  ApiCredentialPublicSchema: () => ApiCredentialPublicSchema,
31
31
  ArtifactHeaderSchema: () => ArtifactHeaderSchema,
32
+ AttestationSecurityLevelSchema: () => AttestationSecurityLevelSchema,
33
+ CLAIM_DOMAIN_V2: () => CLAIM_DOMAIN_V2,
32
34
  COLLECTION_INTENT_STATUSES: () => COLLECTION_INTENT_STATUSES,
33
35
  COLLECTION_PAYMENT_STATUSES: () => COLLECTION_PAYMENT_STATUSES,
34
36
  CUSTODIAL_MODES: () => CUSTODIAL_MODES,
@@ -47,6 +49,7 @@ __export(index_exports, {
47
49
  CreatePayoutInputSchema: () => CreatePayoutInputSchema,
48
50
  CreateWithdrawalInputSchema: () => CreateWithdrawalInputSchema,
49
51
  CreateWithdrawalResultSchema: () => CreateWithdrawalResultSchema,
52
+ DeviceKeyAlgSchema: () => DeviceKeyAlgSchema,
50
53
  DeviceKeyRecordSchema: () => DeviceKeyRecordSchema,
51
54
  DisableOfflineInputSchema: () => DisableOfflineInputSchema,
52
55
  DisableOfflineResultSchema: () => DisableOfflineResultSchema,
@@ -83,6 +86,7 @@ __export(index_exports, {
83
86
  OAC_DEFAULT_CUMULATIVE_KOBO: () => OAC_DEFAULT_CUMULATIVE_KOBO,
84
87
  OAC_DEFAULT_PER_TX_KOBO: () => OAC_DEFAULT_PER_TX_KOBO,
85
88
  OAC_DEFAULT_VALIDITY_MS: () => OAC_DEFAULT_VALIDITY_MS,
89
+ OFFLINE_CLAIM_SMS_PREFIX: () => OFFLINE_CLAIM_SMS_PREFIX,
86
90
  OfflineClaimArtifactSchema: () => OfflineClaimArtifactSchema,
87
91
  OfflineHoldRecordSchema: () => OfflineHoldRecordSchema,
88
92
  OfflinePaymentAuthorizationArtifactSchema: () => OfflinePaymentAuthorizationArtifactSchema,
@@ -91,6 +95,8 @@ __export(index_exports, {
91
95
  OfflineStateResultSchema: () => OfflineStateResultSchema,
92
96
  OfflineStatusResultSchema: () => OfflineStatusResultSchema,
93
97
  OfflineTokenSchema: () => OfflineTokenSchema,
98
+ P256EnrollmentChallengeInputSchema: () => P256EnrollmentChallengeInputSchema,
99
+ P256EnrollmentChallengeResultSchema: () => P256EnrollmentChallengeResultSchema,
94
100
  PARTNER_FUNDING_DIRECTIONS: () => PARTNER_FUNDING_DIRECTIONS,
95
101
  PARTNER_FUNDING_STATUSES: () => PARTNER_FUNDING_STATUSES,
96
102
  PARTNER_KINDS: () => PARTNER_KINDS,
@@ -114,6 +120,8 @@ __export(index_exports, {
114
120
  PayoutEventInputSchema: () => PayoutEventInputSchema,
115
121
  ProviderEventInputSchema: () => ProviderEventInputSchema,
116
122
  ProviderEventRecordSchema: () => ProviderEventRecordSchema,
123
+ ProvisionOfflineAllowanceInputSchema: () => ProvisionOfflineAllowanceInputSchema,
124
+ ProvisionOfflineAllowanceResultSchema: () => ProvisionOfflineAllowanceResultSchema,
117
125
  PublicCollectionIntentSchema: () => PublicCollectionIntentSchema,
118
126
  RECEIPT_CHANNELS: () => RECEIPT_CHANNELS,
119
127
  RECEIPT_KINDS: () => RECEIPT_KINDS,
@@ -125,6 +133,7 @@ __export(index_exports, {
125
133
  RecordPayoutEventResultSchema: () => RecordPayoutEventResultSchema,
126
134
  RedemptionSchema: () => RedemptionSchema,
127
135
  RegisterDeviceKeyInputSchema: () => RegisterDeviceKeyInputSchema,
136
+ RegisterDeviceKeyP256InputSchema: () => RegisterDeviceKeyP256InputSchema,
128
137
  ReversalRecordArtifactSchema: () => ReversalRecordArtifactSchema,
129
138
  RevokeDeviceKeyInputSchema: () => RevokeDeviceKeyInputSchema,
130
139
  SETTLEMENT_SCHEDULES: () => SETTLEMENT_SCHEDULES,
@@ -147,6 +156,8 @@ __export(index_exports, {
147
156
  buildPaymentRequest: () => buildPaymentRequest,
148
157
  buildReceipt: () => buildReceipt,
149
158
  buildRedemption: () => buildRedemption,
159
+ canonicalClaimSigningBytes: () => canonicalClaimSigningBytes,
160
+ canonicalClaimSigningPayload: () => canonicalClaimSigningPayload,
150
161
  canonicalJSONBytes: () => canonicalJSONBytes,
151
162
  canonicalJSONStringify: () => canonicalJSONStringify,
152
163
  canonicalRequestString: () => canonicalRequestString,
@@ -171,18 +182,21 @@ __export(index_exports, {
171
182
  createPassesClient: () => createPassesClient,
172
183
  createReceiptArtifactUri: () => createReceiptArtifactUri,
173
184
  createReceiptsClient: () => createReceiptsClient,
185
+ createSoftwareP256Signer: () => createSoftwareP256Signer,
174
186
  decodeArtifactUri: () => decodeArtifactUri,
175
187
  decodeAuthorizationQR: () => decodeAuthorizationQR,
176
188
  decodeBase45: () => decodeBase45,
189
+ decodeOfflineClaimSmsMessage: () => decodeOfflineClaimSmsMessage,
177
190
  decodePaymentRequestQR: () => decodePaymentRequestQR,
178
191
  encodeArtifactUri: () => encodeArtifactUri,
179
192
  encodeAuthorizationQR: () => encodeAuthorizationQR,
180
193
  encodeBase45: () => encodeBase45,
181
194
  encodeNQR: () => encodeNQR,
195
+ encodeOfflineClaimSmsMessage: () => encodeOfflineClaimSmsMessage,
182
196
  encodePaymentRequestQR: () => encodePaymentRequestQR,
197
+ extractOfflineClaimSmsToken: () => extractOfflineClaimSmsToken,
183
198
  formatAmount: () => formatAmount,
184
199
  generateDynamicQR: () => generateDynamicQR,
185
- generateKeyPair: () => generateKeyPair,
186
200
  generateStaticQR: () => generateStaticQR,
187
201
  init: () => init,
188
202
  isHardenedArtifactType: () => isHardenedArtifactType,
@@ -193,13 +207,10 @@ __export(index_exports, {
193
207
  parseAmountInput: () => parseAmountInput,
194
208
  parseNQR: () => parseNQR,
195
209
  parseQR: () => parseQR,
196
- publicKeyFromPrivate: () => publicKeyFromPrivate,
197
210
  readTLV: () => readTLV,
198
211
  routingHint: () => routingHint,
199
- sign: () => sign,
200
212
  signArtifact: () => signArtifact,
201
213
  signAuthorization: () => signAuthorization,
202
- signCanonical: () => signCanonical,
203
214
  signOAC: () => signOAC,
204
215
  signPartnerRequest: () => signPartnerRequest,
205
216
  signPass: () => signPass,
@@ -207,11 +218,10 @@ __export(index_exports, {
207
218
  signReceipt: () => signReceipt,
208
219
  signRedemption: () => signRedemption,
209
220
  signRequestHMAC: () => signRequestHMAC,
210
- verify: () => verify,
211
221
  verifyArtifactSignature: () => verifyArtifactSignature,
212
222
  verifyArtifactUri: () => verifyArtifactUri,
213
223
  verifyAuthorization: () => verifyAuthorization,
214
- verifyCanonical: () => verifyCanonical,
224
+ verifyClaimSignature: () => verifyClaimSignature,
215
225
  verifyOAC: () => verifyOAC,
216
226
  verifyPass: () => verifyPass,
217
227
  verifyPaymentRequest: () => verifyPaymentRequest,
@@ -1787,64 +1797,113 @@ function constantTimeEqual(a, b) {
1787
1797
  return diff === 0;
1788
1798
  }
1789
1799
 
1790
- // src/crypto/ed25519.ts
1791
- var import_ed25519 = require("@noble/curves/ed25519");
1792
- function generateKeyPair() {
1793
- const privateKey = import_ed25519.ed25519.utils.randomPrivateKey();
1794
- const publicKey = import_ed25519.ed25519.getPublicKey(privateKey);
1795
- 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);
1796
1812
  }
1797
- function publicKeyFromPrivate(privateKey) {
1798
- 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;
1821
+ }
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);
1799
1861
  }
1800
- function sign(message, privateKey) {
1801
- return import_ed25519.ed25519.sign(message, privateKey);
1862
+ function signIssuerP256(bytes, issuerPrivateKey) {
1863
+ const sig = import_nist.p256.sign(bytes, issuerPrivateKey, { prehash: true });
1864
+ return bytesToBase64(sig.toBytes("der"));
1802
1865
  }
1803
- function verify(message, signature, publicKey) {
1866
+ function verifyIssuerP256(bytes, signatureB64, issuerPublicKeySpkiB64) {
1804
1867
  try {
1805
- 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
+ });
1806
1874
  } catch {
1807
1875
  return false;
1808
1876
  }
1809
1877
  }
1810
- function signCanonical(value, privateKey) {
1811
- return sign(canonicalJSONBytes(value), privateKey);
1812
- }
1813
- function verifyCanonical(value, signature, publicKey) {
1814
- return verify(canonicalJSONBytes(value), signature, publicKey);
1815
- }
1816
1878
 
1817
1879
  // src/offline/oac.ts
1818
- var import_zod5 = require("zod");
1819
1880
  var OAC_DEFAULT_PER_TX_KOBO = 5e5;
1820
1881
  var OAC_DEFAULT_CUMULATIVE_KOBO = 2e6;
1821
1882
  var OAC_DEFAULT_VALIDITY_MS = 24 * 60 * 60 * 1e3;
1822
- var HexString = (length) => import_zod5.z.string().regex(
1823
- new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
1824
- `expected ${length}-byte hex string`
1825
- );
1883
+ var Base64Std = import_zod5.z.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/, "expected base64 (standard) string");
1826
1884
  var OACSchema = import_zod5.z.object({
1827
1885
  userId: import_zod5.z.string().min(1),
1828
1886
  deviceId: import_zod5.z.string().min(1),
1829
- devicePublicKey: HexString(32),
1887
+ /** SubjectPublicKeyInfo DER, base64 (P-256). */
1888
+ devicePublicKey: Base64Std,
1830
1889
  perTxCapKobo: import_zod5.z.number().int().nonnegative(),
1831
1890
  cumulativeCapKobo: import_zod5.z.number().int().nonnegative(),
1832
1891
  validFromMs: import_zod5.z.number().int().nonnegative(),
1833
1892
  validUntilMs: import_zod5.z.number().int().positive(),
1834
1893
  counterSeed: import_zod5.z.number().int().nonnegative(),
1835
1894
  nonce: import_zod5.z.string().min(1),
1836
- issuerSig: HexString(64)
1895
+ /** ASN.1 DER ECDSA(SHA-256) signature, base64. */
1896
+ issuerSig: Base64Std
1837
1897
  }).refine((v) => v.validUntilMs > v.validFromMs, {
1838
1898
  message: "validUntilMs must be greater than validFromMs"
1839
1899
  }).refine((v) => v.perTxCapKobo <= v.cumulativeCapKobo, {
1840
1900
  message: "perTxCapKobo must not exceed cumulativeCapKobo"
1841
1901
  });
1842
1902
  function buildOAC(input) {
1843
- const devicePublicKey = typeof input.devicePublicKey === "string" ? input.devicePublicKey : bytesToHex(input.devicePublicKey);
1844
1903
  return {
1845
1904
  userId: input.userId,
1846
1905
  deviceId: input.deviceId,
1847
- devicePublicKey,
1906
+ devicePublicKey: input.devicePublicKey,
1848
1907
  perTxCapKobo: input.perTxCapKobo ?? OAC_DEFAULT_PER_TX_KOBO,
1849
1908
  cumulativeCapKobo: input.cumulativeCapKobo ?? OAC_DEFAULT_CUMULATIVE_KOBO,
1850
1909
  validFromMs: input.validFromMs,
@@ -1854,36 +1913,25 @@ function buildOAC(input) {
1854
1913
  };
1855
1914
  }
1856
1915
  function signOAC(unsigned, issuerPrivateKey) {
1857
- const issuerSig = bytesToHex(
1858
- sign(canonicalJSONBytes(unsigned), issuerPrivateKey)
1916
+ const issuerSig = signIssuerP256(
1917
+ canonicalJSONBytes(unsigned),
1918
+ issuerPrivateKey
1859
1919
  );
1860
1920
  return { ...unsigned, issuerSig };
1861
1921
  }
1862
- function verifyOAC(oac, issuerPublicKey) {
1922
+ function verifyOAC(oac, issuerPublicKeySpkiB64) {
1863
1923
  try {
1864
1924
  const parsed = OACSchema.parse(oac);
1865
1925
  const { issuerSig, ...unsigned } = parsed;
1866
- return verify(
1926
+ return verifyIssuerP256(
1867
1927
  canonicalJSONBytes(unsigned),
1868
- hexToBytes(issuerSig),
1869
- issuerPublicKey
1928
+ issuerSig,
1929
+ issuerPublicKeySpkiB64
1870
1930
  );
1871
1931
  } catch {
1872
1932
  return false;
1873
1933
  }
1874
1934
  }
1875
- function bytesToHex(b) {
1876
- let s = "";
1877
- for (let i = 0; i < b.length; i++) s += b[i].toString(16).padStart(2, "0");
1878
- return s;
1879
- }
1880
- function hexToBytes(s) {
1881
- if (s.length % 2 !== 0) throw new Error("hex: odd length");
1882
- const out = new Uint8Array(s.length / 2);
1883
- for (let i = 0; i < out.length; i++)
1884
- out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16);
1885
- return out;
1886
- }
1887
1935
 
1888
1936
  // src/offline/codec.ts
1889
1937
  var ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
@@ -1940,19 +1988,19 @@ function decodeBase45(s) {
1940
1988
 
1941
1989
  // src/offline/messages.ts
1942
1990
  var import_zod6 = require("zod");
1943
- 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");
1944
1992
  var OfflinePaymentRequestSchema = import_zod6.z.object({
1945
1993
  reference: import_zod6.z.string().min(1),
1946
1994
  amountKobo: import_zod6.z.number().int().positive(),
1947
1995
  merchantOAC: OACSchema,
1948
1996
  expiresAtMs: import_zod6.z.number().int().positive(),
1949
- merchantSig: HexSig
1997
+ merchantSig: Base64Sig
1950
1998
  });
1951
1999
  var OfflinePaymentAuthorizationSchema = import_zod6.z.object({
1952
2000
  request: OfflinePaymentRequestSchema,
1953
2001
  payerOAC: OACSchema,
1954
2002
  payerCounter: import_zod6.z.number().int().positive(),
1955
- payerSig: HexSig
2003
+ payerSig: Base64Sig
1956
2004
  });
1957
2005
  function buildPaymentRequest(input) {
1958
2006
  if (!Number.isInteger(input.amountKobo) || input.amountKobo <= 0) {
@@ -1969,27 +2017,28 @@ function buildPaymentRequest(input) {
1969
2017
  };
1970
2018
  }
1971
2019
  function signPaymentRequest(unsigned, merchantDevicePrivateKey) {
1972
- const merchantSig = bytesToHex(
1973
- sign(canonicalJSONBytes(unsigned), merchantDevicePrivateKey)
2020
+ const merchantSig = signIssuerP256(
2021
+ canonicalJSONBytes(unsigned),
2022
+ merchantDevicePrivateKey
1974
2023
  );
1975
2024
  return { ...unsigned, merchantSig };
1976
2025
  }
1977
- function verifyPaymentRequest(req, issuerPublicKey) {
2026
+ function verifyPaymentRequest(req, issuerPublicKeySpkiB64) {
1978
2027
  try {
1979
2028
  const parsed = OfflinePaymentRequestSchema.parse(req);
1980
2029
  const { issuerSig: merchantOacSig, ...merchantOacUnsigned } = parsed.merchantOAC;
1981
- if (!verify(
2030
+ if (!verifyIssuerP256(
1982
2031
  canonicalJSONBytes(merchantOacUnsigned),
1983
- hexToBytes(merchantOacSig),
1984
- issuerPublicKey
2032
+ merchantOacSig,
2033
+ issuerPublicKeySpkiB64
1985
2034
  )) {
1986
2035
  return false;
1987
2036
  }
1988
2037
  const { merchantSig, ...unsigned } = parsed;
1989
- return verify(
2038
+ return verifyIssuerP256(
1990
2039
  canonicalJSONBytes(unsigned),
1991
- hexToBytes(merchantSig),
1992
- hexToBytes(parsed.merchantOAC.devicePublicKey)
2040
+ merchantSig,
2041
+ parsed.merchantOAC.devicePublicKey
1993
2042
  );
1994
2043
  } catch {
1995
2044
  return false;
@@ -2009,28 +2058,30 @@ function buildAuthorization(input) {
2009
2058
  };
2010
2059
  }
2011
2060
  function signAuthorization(unsigned, payerDevicePrivateKey) {
2012
- const payerSig = bytesToHex(
2013
- sign(canonicalJSONBytes(unsigned), payerDevicePrivateKey)
2061
+ const payerSig = signIssuerP256(
2062
+ canonicalJSONBytes(unsigned),
2063
+ payerDevicePrivateKey
2014
2064
  );
2015
2065
  return { ...unsigned, payerSig };
2016
2066
  }
2017
- function verifyAuthorization(auth, issuerPublicKey) {
2067
+ function verifyAuthorization(auth, issuerPublicKeySpkiB64) {
2018
2068
  try {
2019
2069
  const parsed = OfflinePaymentAuthorizationSchema.parse(auth);
2020
- if (!verifyPaymentRequest(parsed.request, issuerPublicKey)) return false;
2070
+ if (!verifyPaymentRequest(parsed.request, issuerPublicKeySpkiB64))
2071
+ return false;
2021
2072
  const { issuerSig: payerOacSig, ...payerOacUnsigned } = parsed.payerOAC;
2022
- if (!verify(
2073
+ if (!verifyIssuerP256(
2023
2074
  canonicalJSONBytes(payerOacUnsigned),
2024
- hexToBytes(payerOacSig),
2025
- issuerPublicKey
2075
+ payerOacSig,
2076
+ issuerPublicKeySpkiB64
2026
2077
  )) {
2027
2078
  return false;
2028
2079
  }
2029
2080
  const { payerSig, ...unsigned } = parsed;
2030
- return verify(
2081
+ return verifyIssuerP256(
2031
2082
  canonicalJSONBytes(unsigned),
2032
- hexToBytes(payerSig),
2033
- hexToBytes(parsed.payerOAC.devicePublicKey)
2083
+ payerSig,
2084
+ parsed.payerOAC.devicePublicKey
2034
2085
  );
2035
2086
  } catch {
2036
2087
  return false;
@@ -2086,10 +2137,14 @@ var PaymentClaimSchema = import_zod7.z.object({
2086
2137
  occurredAtMs: import_zod7.z.number().int().nonnegative(),
2087
2138
  completedAtMs: import_zod7.z.number().int().nonnegative().optional(),
2088
2139
  contextId: import_zod7.z.string().optional(),
2089
- payerPubkey: import_zod7.z.string().regex(/^[0-9a-f]{64}$/i),
2090
- payerSignature: import_zod7.z.string().regex(/^[0-9a-f]+$/i),
2091
- payeePubkey: import_zod7.z.string().regex(/^[0-9a-f]{64}$/i).optional(),
2092
- 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()
2093
2148
  });
2094
2149
  var SettlementSchema = import_zod7.z.object({
2095
2150
  settlementId: import_zod7.z.string().uuid(),
@@ -2517,10 +2572,6 @@ var PASS_STATES = [
2517
2572
  "expired",
2518
2573
  "revoked"
2519
2574
  ];
2520
- var HexString2 = (length) => import_zod9.z.string().regex(
2521
- new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
2522
- `expected ${length}-byte hex string`
2523
- );
2524
2575
  var PassMetadataSchema = import_zod9.z.record(
2525
2576
  import_zod9.z.union([import_zod9.z.string(), import_zod9.z.number(), import_zod9.z.boolean(), import_zod9.z.null()])
2526
2577
  );
@@ -2541,9 +2592,9 @@ var PassSchema = import_zod9.z.object({
2541
2592
  nonce: import_zod9.z.string().min(1),
2542
2593
  /** Device id this pass is bound to (FK to backend `device_keys`). */
2543
2594
  holderDeviceId: import_zod9.z.string().min(1),
2544
- /** 32-byte hex Ed25519 public key of the bound device. The redemption signature
2545
- * is verified against this key — it is the security-critical binding. */
2546
- 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}$/),
2547
2598
  /** Optional fixed amount for monetary passes (vouchers, gift cards) in kobo. */
2548
2599
  amountKobo: import_zod9.z.number().int().nonnegative().optional(),
2549
2600
  /** ISO-4217-ish currency code; required on the wire. SDK builders default to NGN. */
@@ -2552,7 +2603,8 @@ var PassSchema = import_zod9.z.object({
2552
2603
  counterSeed: import_zod9.z.number().int().nonnegative(),
2553
2604
  /** Optional cumulative spend cap in kobo across all redemptions of this pass. */
2554
2605
  cumulativeCapKobo: import_zod9.z.number().int().nonnegative().optional(),
2555
- 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}$/)
2556
2608
  }).refine((v) => v.validUntilMs > v.validFromMs, {
2557
2609
  message: "validUntilMs must be greater than validFromMs"
2558
2610
  });
@@ -2585,19 +2637,20 @@ function buildPass(input) {
2585
2637
  return out;
2586
2638
  }
2587
2639
  function signPass(unsigned, issuerPrivateKey) {
2588
- const issuerSig = bytesToHex(
2589
- sign(canonicalJSONBytes(unsigned), issuerPrivateKey)
2640
+ const issuerSig = signIssuerP256(
2641
+ canonicalJSONBytes(unsigned),
2642
+ issuerPrivateKey
2590
2643
  );
2591
2644
  return { ...unsigned, issuerSig };
2592
2645
  }
2593
- function verifyPass(pass, issuerPublicKey) {
2646
+ function verifyPass(pass, issuerPublicKeySpkiB64) {
2594
2647
  try {
2595
2648
  const parsed = PassSchema.parse(pass);
2596
2649
  const { issuerSig, ...unsigned } = parsed;
2597
- return verify(
2650
+ return verifyIssuerP256(
2598
2651
  canonicalJSONBytes(unsigned),
2599
- hexToBytes(issuerSig),
2600
- issuerPublicKey
2652
+ issuerSig,
2653
+ issuerPublicKeySpkiB64
2601
2654
  );
2602
2655
  } catch {
2603
2656
  return false;
@@ -2609,7 +2662,7 @@ function isPassWithinValidity(pass, nowMs) {
2609
2662
 
2610
2663
  // src/passes/redemption.ts
2611
2664
  var import_zod10 = require("zod");
2612
- 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)");
2613
2666
  var RedemptionSchema = import_zod10.z.object({
2614
2667
  pass: PassSchema,
2615
2668
  redeemerId: import_zod10.z.string().min(1),
@@ -2620,7 +2673,8 @@ var RedemptionSchema = import_zod10.z.object({
2620
2673
  /** Amount being redeemed in kobo (0 for non-monetary passes like ride tickets). */
2621
2674
  amountKobo: import_zod10.z.number().int().nonnegative(),
2622
2675
  nonce: import_zod10.z.string().min(1),
2623
- holderSig: HexSig2
2676
+ /** ASN.1 DER ECDSA P-256 signature over canonicalJSONBytes(unsigned), base64. */
2677
+ holderSig: Base64Std2
2624
2678
  });
2625
2679
  var REDEEMABLE_STATES = /* @__PURE__ */ new Set(["issued", "active"]);
2626
2680
  function buildRedemption(input) {
@@ -2674,31 +2728,28 @@ function buildRedemption(input) {
2674
2728
  };
2675
2729
  }
2676
2730
  function signRedemption(unsigned, holderDevicePrivateKey) {
2677
- const holderSig = bytesToHex(
2678
- sign(canonicalJSONBytes(unsigned), holderDevicePrivateKey)
2731
+ const holderSig = signIssuerP256(
2732
+ canonicalJSONBytes(unsigned),
2733
+ holderDevicePrivateKey
2679
2734
  );
2680
2735
  return { ...unsigned, holderSig };
2681
2736
  }
2682
- function verifyRedemption(r, issuerPublicKey) {
2737
+ function verifyRedemption(r, issuerPublicKeySpkiB64) {
2683
2738
  try {
2684
2739
  const parsed = RedemptionSchema.parse(r);
2685
2740
  if (parsed.counter <= parsed.pass.counterSeed) return false;
2686
2741
  const { issuerSig, ...passUnsigned } = parsed.pass;
2687
- if (!verify(
2742
+ if (!verifyIssuerP256(
2688
2743
  canonicalJSONBytes(passUnsigned),
2689
- hexToBytes(issuerSig),
2690
- issuerPublicKey
2744
+ issuerSig,
2745
+ issuerPublicKeySpkiB64
2691
2746
  )) {
2692
2747
  return false;
2693
2748
  }
2694
- const holderHex = parsed.pass.holderDevicePubkey;
2695
- if (typeof holderHex !== "string") return false;
2749
+ const holderPub = parsed.pass.holderDevicePubkey;
2750
+ if (typeof holderPub !== "string") return false;
2696
2751
  const { holderSig, ...unsigned } = parsed;
2697
- return verify(
2698
- canonicalJSONBytes(unsigned),
2699
- hexToBytes(holderSig),
2700
- hexToBytes(holderHex)
2701
- );
2752
+ return verifyIssuerP256(canonicalJSONBytes(unsigned), holderSig, holderPub);
2702
2753
  } catch {
2703
2754
  return false;
2704
2755
  }
@@ -2708,10 +2759,6 @@ function verifyRedemption(r, issuerPublicKey) {
2708
2759
  var import_zod11 = require("zod");
2709
2760
  var RECEIPT_CHANNELS = ["cash", "pass"];
2710
2761
  var RECEIPT_KINDS = RECEIPT_CHANNELS;
2711
- var HexString3 = (length) => import_zod11.z.string().regex(
2712
- new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
2713
- `expected ${length}-byte hex string`
2714
- );
2715
2762
  var ReceiptPayloadSchema = import_zod11.z.record(
2716
2763
  import_zod11.z.union([import_zod11.z.string(), import_zod11.z.number(), import_zod11.z.boolean(), import_zod11.z.null()])
2717
2764
  );
@@ -2729,7 +2776,8 @@ var ReceiptSchema = import_zod11.z.object({
2729
2776
  issuedAtMs: import_zod11.z.number().int().nonnegative(),
2730
2777
  issuerId: import_zod11.z.string().min(1),
2731
2778
  payload: ReceiptPayloadSchema,
2732
- 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}$/)
2733
2781
  }).superRefine((v, ctx) => {
2734
2782
  if (v.channel === "cash") {
2735
2783
  if (!v.intentId) {
@@ -2795,19 +2843,20 @@ function buildReceipt(input) {
2795
2843
  return out;
2796
2844
  }
2797
2845
  function signReceipt(unsigned, issuerPrivateKey) {
2798
- const issuerSig = bytesToHex(
2799
- sign(canonicalJSONBytes(unsigned), issuerPrivateKey)
2846
+ const issuerSig = signIssuerP256(
2847
+ canonicalJSONBytes(unsigned),
2848
+ issuerPrivateKey
2800
2849
  );
2801
2850
  return { ...unsigned, issuerSig };
2802
2851
  }
2803
- function verifyReceipt(r, issuerPublicKey) {
2852
+ function verifyReceipt(r, issuerPublicKeySpkiB64) {
2804
2853
  try {
2805
2854
  const parsed = ReceiptSchema.parse(r);
2806
2855
  const { issuerSig, ...unsigned } = parsed;
2807
- return verify(
2856
+ return verifyIssuerP256(
2808
2857
  canonicalJSONBytes(unsigned),
2809
- hexToBytes(issuerSig),
2810
- issuerPublicKey
2858
+ issuerSig,
2859
+ issuerPublicKeySpkiB64
2811
2860
  );
2812
2861
  } catch {
2813
2862
  return false;
@@ -3142,17 +3191,49 @@ function createAccountsClient(opts) {
3142
3191
  // src/me-offline/client.ts
3143
3192
  var import_zod13 = require("zod");
3144
3193
  var Hex64 = import_zod13.z.string().regex(/^[0-9a-f]{64}$/i);
3145
- var HexAny = import_zod13.z.string().regex(/^[0-9a-f]+$/i);
3146
3194
  var Sha256Hex = import_zod13.z.string().regex(/^[0-9a-f]{64}$/i);
3195
+ var Base64Std3 = import_zod13.z.string().regex(/^[A-Za-z0-9+/]+={0,2}$/);
3147
3196
  var RegisterDeviceKeyInputSchema = import_zod13.z.object({
3148
3197
  deviceId: import_zod13.z.string().min(1).max(128),
3149
3198
  publicKeyHex: Hex64
3150
3199
  });
3200
+ var AttestationSecurityLevelSchema = import_zod13.z.enum([
3201
+ "STRONGBOX",
3202
+ "TEE",
3203
+ "SECURE_ENCLAVE",
3204
+ "SOFTWARE"
3205
+ ]);
3206
+ var DeviceKeyAlgSchema = import_zod13.z.literal("p256");
3207
+ var RegisterDeviceKeyP256InputSchema = import_zod13.z.object({
3208
+ deviceId: import_zod13.z.string().min(1).max(128),
3209
+ /** P-256 SubjectPublicKeyInfo DER, base64. */
3210
+ publicKeySpkiB64: Base64Std3.min(64).max(4096),
3211
+ /** Base64 of the server-issued enrollment challenge string. */
3212
+ challengeB64: Base64Std3.min(8).max(1024),
3213
+ /** iOS App Attest payload or Android X.509 Key Attestation chain. */
3214
+ attestationChainB64: import_zod13.z.array(Base64Std3.min(16).max(16384)).min(1).max(16),
3215
+ securityLevel: AttestationSecurityLevelSchema
3216
+ });
3217
+ var P256EnrollmentChallengeInputSchema = import_zod13.z.object({
3218
+ deviceId: import_zod13.z.string().min(1).max(128)
3219
+ });
3220
+ var P256EnrollmentChallengeResultSchema = import_zod13.z.object({
3221
+ challenge: import_zod13.z.string().min(16),
3222
+ expiresAtMs: import_zod13.z.number().int().positive()
3223
+ });
3151
3224
  var DeviceKeyRecordSchema = import_zod13.z.object({
3152
3225
  id: import_zod13.z.string().uuid(),
3153
3226
  userId: import_zod13.z.string().uuid(),
3154
3227
  deviceId: import_zod13.z.string(),
3155
- publicKeyHex: Hex64,
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). */
3231
+ publicKeyHex: Hex64.nullable().default(null),
3232
+ /** P-256 SubjectPublicKeyInfo DER, base64. Required for new records. */
3233
+ publicKeySpkiB64: Base64Std3.nullable().default(null),
3234
+ securityLevel: AttestationSecurityLevelSchema.nullable().default(null),
3235
+ hardwareBacked: import_zod13.z.boolean().default(false),
3236
+ attestedAtMs: import_zod13.z.number().int().nonnegative().nullable().default(null),
3156
3237
  createdAtMs: import_zod13.z.number().int().nonnegative(),
3157
3238
  revokedAtMs: import_zod13.z.number().int().nonnegative().nullable()
3158
3239
  });
@@ -3161,7 +3242,10 @@ var ConsumerOACSchema = import_zod13.z.object({
3161
3242
  issuerId: import_zod13.z.string().min(1).max(64),
3162
3243
  userId: import_zod13.z.string().uuid(),
3163
3244
  deviceId: import_zod13.z.string().min(1).max(128),
3164
- devicePubkeyHex: Hex64,
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),
3165
3249
  perTxCapKobo: import_zod13.z.number().int().positive(),
3166
3250
  cumulativeCapKobo: import_zod13.z.number().int().positive(),
3167
3251
  currency: import_zod13.z.string().length(3),
@@ -3172,8 +3256,10 @@ var ConsumerOACSchema = import_zod13.z.object({
3172
3256
  });
3173
3257
  var SignedConsumerOACSchema = import_zod13.z.object({
3174
3258
  oac: ConsumerOACSchema,
3175
- issuerSig: HexAny,
3176
- 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)
3177
3263
  });
3178
3264
  var OACRecordSchema = SignedConsumerOACSchema.extend({
3179
3265
  currentOfflineSpentKobo: import_zod13.z.number().int().nonnegative(),
@@ -3205,6 +3291,7 @@ var EnableOfflineInputSchema = import_zod13.z.object({
3205
3291
  installId: import_zod13.z.string().min(1).max(128),
3206
3292
  partnerId: import_zod13.z.string().min(1).max(64).optional()
3207
3293
  });
3294
+ var ProvisionOfflineAllowanceInputSchema = EnableOfflineInputSchema;
3208
3295
  var DisableOfflineInputSchema = import_zod13.z.object({
3209
3296
  deviceId: import_zod13.z.string().min(1).max(128),
3210
3297
  installId: import_zod13.z.string().min(1).max(128).optional(),
@@ -3242,6 +3329,7 @@ var EnableOfflineResultSchema = import_zod13.z.object({
3242
3329
  hold: OfflineHoldRecordSchema,
3243
3330
  oac: OACRecordSchema
3244
3331
  });
3332
+ var ProvisionOfflineAllowanceResultSchema = EnableOfflineResultSchema;
3245
3333
  var DisableOfflineResultSchema = import_zod13.z.object({
3246
3334
  hold: OfflineHoldRecordSchema,
3247
3335
  trusted: import_zod13.z.boolean(),
@@ -3255,6 +3343,8 @@ var OfflineStateResultSchema = import_zod13.z.object({
3255
3343
  active: OACRecordSchema.nullable()
3256
3344
  });
3257
3345
  var ConsumerPaymentClaimSchema = import_zod13.z.object({
3346
+ /** Always 'p256'. Retained for forward-compat and as an explicit domain marker. */
3347
+ alg: import_zod13.z.literal("p256").default("p256"),
3258
3348
  oacId: import_zod13.z.string().uuid(),
3259
3349
  encounterId: Sha256Hex.optional(),
3260
3350
  payerUserId: import_zod13.z.string().uuid(),
@@ -3267,10 +3357,10 @@ var ConsumerPaymentClaimSchema = import_zod13.z.object({
3267
3357
  occurredAtMs: import_zod13.z.number().int().nonnegative(),
3268
3358
  completedAtMs: import_zod13.z.number().int().nonnegative().optional(),
3269
3359
  contextId: import_zod13.z.string().max(128).optional(),
3270
- payerPubkeyHex: Hex64,
3271
- payerSignature: HexAny,
3272
- payeePubkeyHex: Hex64.optional(),
3273
- payeeSignature: HexAny.optional()
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()
3274
3364
  });
3275
3365
  var ConsumerSettlementSchema = import_zod13.z.object({
3276
3366
  settlementId: import_zod13.z.string().uuid(),
@@ -3284,7 +3374,8 @@ var ConsumerSettlementSchema = import_zod13.z.object({
3284
3374
  status: import_zod13.z.enum(["SETTLED", "REVIEW"]),
3285
3375
  reviewReason: import_zod13.z.string().nullable(),
3286
3376
  ledgerRef: import_zod13.z.string().nullable(),
3287
- issuerSig: HexAny,
3377
+ /** ASN.1 DER ECDSA P-256 issuer signature, base64. */
3378
+ issuerSig: Base64Std3.min(16).max(2048),
3288
3379
  createdAtMs: import_zod13.z.number().int().nonnegative()
3289
3380
  });
3290
3381
  var ConsumerSettleResultSchema = import_zod13.z.object({
@@ -3336,6 +3427,18 @@ function createMeOfflineClient(opts) {
3336
3427
  RegisterDeviceKeyInputSchema.parse(input),
3337
3428
  (raw) => DeviceKeyRecordSchema.parse(raw)
3338
3429
  ),
3430
+ issueP256EnrollmentChallenge: (input) => call(
3431
+ "POST",
3432
+ "/v1/me/offline/keys/p256/challenge",
3433
+ P256EnrollmentChallengeInputSchema.parse(input),
3434
+ (raw) => P256EnrollmentChallengeResultSchema.parse(raw)
3435
+ ),
3436
+ registerDeviceKeyP256: (input) => call(
3437
+ "POST",
3438
+ "/v1/me/offline/keys/p256",
3439
+ RegisterDeviceKeyP256InputSchema.parse(input),
3440
+ (raw) => DeviceKeyRecordSchema.parse(raw)
3441
+ ),
3339
3442
  listDeviceKeys: () => call(
3340
3443
  "GET",
3341
3444
  "/v1/me/offline/keys",
@@ -3348,6 +3451,12 @@ function createMeOfflineClient(opts) {
3348
3451
  RevokeDeviceKeyInputSchema.parse(input),
3349
3452
  () => void 0
3350
3453
  ),
3454
+ provisionAllowance: (input) => call(
3455
+ "POST",
3456
+ "/v1/me/offline/allowance",
3457
+ ProvisionOfflineAllowanceInputSchema.parse(input),
3458
+ (raw) => ProvisionOfflineAllowanceResultSchema.parse(raw)
3459
+ ),
3351
3460
  enable: (input) => call(
3352
3461
  "POST",
3353
3462
  "/v1/me/offline/enable",
@@ -3393,6 +3502,185 @@ function createMeOfflineClient(opts) {
3393
3502
  };
3394
3503
  }
3395
3504
 
3505
+ // src/me-offline/signer.ts
3506
+ var import_nist2 = require("@noble/curves/nist");
3507
+ var CLAIM_DOMAIN_V2 = "flur:consumer-offline:v2:claim";
3508
+ function canonicalClaimSigningPayload(claim) {
3509
+ return {
3510
+ domain: CLAIM_DOMAIN_V2,
3511
+ alg: claim.alg,
3512
+ oacId: claim.oacId,
3513
+ payerUserId: claim.payerUserId,
3514
+ payeeUserId: claim.payeeUserId,
3515
+ payerDeviceId: claim.payerDeviceId,
3516
+ payerNonce: claim.payerNonce,
3517
+ payeeNonce: claim.payeeNonce,
3518
+ amountKobo: claim.amountKobo,
3519
+ currency: claim.currency,
3520
+ occurredAtMs: claim.occurredAtMs,
3521
+ completedAtMs: claim.completedAtMs ?? null,
3522
+ contextId: claim.contextId ?? null
3523
+ };
3524
+ }
3525
+ function canonicalClaimSigningBytes(claim) {
3526
+ return canonicalJSONBytes(canonicalClaimSigningPayload(claim));
3527
+ }
3528
+ function bytesToBase642(bytes) {
3529
+ if (typeof Buffer !== "undefined") {
3530
+ return Buffer.from(bytes).toString("base64");
3531
+ }
3532
+ let bin = "";
3533
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
3534
+ return btoa(bin);
3535
+ }
3536
+ function base64ToBytes2(b64) {
3537
+ if (typeof Buffer !== "undefined") {
3538
+ return new Uint8Array(Buffer.from(b64, "base64"));
3539
+ }
3540
+ const bin = atob(b64);
3541
+ const out = new Uint8Array(bin.length);
3542
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
3543
+ return out;
3544
+ }
3545
+ var P256_SPKI_HEADER2 = new Uint8Array([
3546
+ 48,
3547
+ 89,
3548
+ 48,
3549
+ 19,
3550
+ 6,
3551
+ 7,
3552
+ 42,
3553
+ 134,
3554
+ 72,
3555
+ 206,
3556
+ 61,
3557
+ 2,
3558
+ 1,
3559
+ 6,
3560
+ 8,
3561
+ 42,
3562
+ 134,
3563
+ 72,
3564
+ 206,
3565
+ 61,
3566
+ 3,
3567
+ 1,
3568
+ 7,
3569
+ 3,
3570
+ 66,
3571
+ 0
3572
+ ]);
3573
+ function p256PublicKeyToSpkiB64(rawUncompressed) {
3574
+ if (rawUncompressed.length !== 65 || rawUncompressed[0] !== 4) {
3575
+ throw new Error("p256: expected 65-byte uncompressed point");
3576
+ }
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);
3581
+ }
3582
+ function p256SpkiB64ToPublicKey(spkiB64) {
3583
+ const spki = base64ToBytes2(spkiB64);
3584
+ if (spki.length !== P256_SPKI_HEADER2.length + 65) {
3585
+ throw new Error("p256: invalid SPKI length");
3586
+ }
3587
+ for (let i = 0; i < P256_SPKI_HEADER2.length; i++) {
3588
+ if (spki[i] !== P256_SPKI_HEADER2[i]) {
3589
+ throw new Error("p256: invalid SPKI header");
3590
+ }
3591
+ }
3592
+ return spki.slice(P256_SPKI_HEADER2.length);
3593
+ }
3594
+ function createSoftwareP256Signer(privateKey) {
3595
+ const raw = import_nist2.p256.getPublicKey(privateKey, false);
3596
+ const spkiB64 = p256PublicKeyToSpkiB64(raw);
3597
+ return {
3598
+ alg: "p256",
3599
+ async getPublicKey() {
3600
+ return { alg: "p256", publicKey: spkiB64 };
3601
+ },
3602
+ async sign(bytes) {
3603
+ const sig = import_nist2.p256.sign(bytes, privateKey, { prehash: true });
3604
+ const der = sig.toBytes("der");
3605
+ return { alg: "p256", signature: bytesToBase642(der) };
3606
+ }
3607
+ };
3608
+ }
3609
+ function verifyClaimSignature(input) {
3610
+ try {
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
+ });
3618
+ } catch {
3619
+ return false;
3620
+ }
3621
+ }
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
+
3396
3684
  // src/partner-funding/client.ts
3397
3685
  var import_zod14 = require("zod");
3398
3686
  var MinorString = import_zod14.z.string().regex(/^-?\d+$/);
@@ -3768,14 +4056,15 @@ function buildArtifactBody(input) {
3768
4056
  return { ...header, data: input.data };
3769
4057
  }
3770
4058
  function signArtifact(body, privateKey) {
3771
- const sig = bytesToHex(sign(canonicalJSONBytes(body), privateKey));
4059
+ const sig = signIssuerP256(canonicalJSONBytes(body), privateKey);
3772
4060
  return { body, sig };
3773
4061
  }
3774
4062
  function encodeArtifactUri(signed) {
3775
4063
  const bodyBytes = canonicalJSONBytes(signed.body);
3776
4064
  const bodyB64 = base64UrlEncode(bodyBytes);
3777
- const sigB64 = base64UrlEncode(hexToBytes(signed.sig));
3778
- 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}`;
3779
4068
  }
3780
4069
  function decodeArtifactUri(uri) {
3781
4070
  if (!uri.startsWith(FLUR_ARTIFACT_URI_PREFIX)) {
@@ -3806,9 +4095,9 @@ function decodeArtifactUri(uri) {
3806
4095
  }
3807
4096
  const bodyBytes = base64UrlDecode(payload.slice(0, dot));
3808
4097
  const sigBytes = base64UrlDecode(payload.slice(dot + 1));
3809
- if (sigBytes.length !== 64) {
4098
+ if (sigBytes.length < 64 || sigBytes.length > 80) {
3810
4099
  throw new FlurArtifactError(
3811
- `Signature must be 64 bytes, got ${sigBytes.length}`,
4100
+ `Signature length out of range: ${sigBytes.length}`,
3812
4101
  "INVALID_SIGNATURE"
3813
4102
  );
3814
4103
  }
@@ -3835,17 +4124,26 @@ function decodeArtifactUri(uri) {
3835
4124
  type,
3836
4125
  bodyBytes,
3837
4126
  body: bodyJson,
3838
- sig: bytesToHex(sigBytes)
4127
+ // Encode as standard base64 (not url-safe) for sig field consistency.
4128
+ sig: encodeStdBase64(sigBytes)
3839
4129
  };
3840
4130
  }
3841
- 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 = {}) {
3842
4140
  if (options.enforceExpiry !== false && decoded.body.exp !== void 0) {
3843
4141
  const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
3844
4142
  if (decoded.body.exp < now) {
3845
4143
  throw new FlurArtifactError("Artifact has expired", "EXPIRED");
3846
4144
  }
3847
4145
  }
3848
- return verify(decoded.bodyBytes, hexToBytes(decoded.sig), publicKey);
4146
+ return verifyIssuerP256(decoded.bodyBytes, decoded.sig, publicKeySpkiB64);
3849
4147
  }
3850
4148
 
3851
4149
  // src/artifacts/types.ts
@@ -3864,7 +4162,7 @@ var ARTIFACT_TYPES = {
3864
4162
  PASS: "pass",
3865
4163
  IDENTITY: "identity"
3866
4164
  };
3867
- var HexString4 = (length) => import_zod16.z.string().regex(
4165
+ var HexString = (length) => import_zod16.z.string().regex(
3868
4166
  new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
3869
4167
  `expected ${length}-byte hex string`
3870
4168
  );
@@ -3882,7 +4180,7 @@ var ReceiptArtifactSchema = import_zod16.z.object({
3882
4180
  settledAtMs: import_zod16.z.number().int().positive(),
3883
4181
  ledgerTxnId: import_zod16.z.string().min(1).max(64).optional(),
3884
4182
  memo: import_zod16.z.string().max(140).optional(),
3885
- hashChainPrev: HexString4(32).optional()
4183
+ hashChainPrev: HexString(32).optional()
3886
4184
  });
3887
4185
  var ShortId = import_zod16.z.string().min(1).max(64);
3888
4186
  var PositiveInt = import_zod16.z.number().int().positive();
@@ -3964,7 +4262,7 @@ var StatementArtifactSchema = import_zod16.z.object({
3964
4262
  closingBalanceKobo: import_zod16.z.number().int(),
3965
4263
  transactionCount: NonNegativeInt,
3966
4264
  currency: Currency2,
3967
- hashChainPrev: HexString4(32).optional()
4265
+ hashChainPrev: HexString(32).optional()
3968
4266
  }).refine((v) => v.periodEndMs > v.periodStartMs, {
3969
4267
  message: "periodEndMs must be greater than periodStartMs",
3970
4268
  path: ["periodEndMs"]
@@ -3997,7 +4295,7 @@ var IdentityArtifactSchema = import_zod16.z.object({
3997
4295
  "kyc_tier",
3998
4296
  "age_band"
3999
4297
  ]),
4000
- claimValueHash: HexString4(32),
4298
+ claimValueHash: HexString(32),
4001
4299
  attestedAtMs: PositiveInt
4002
4300
  });
4003
4301
  var ARTIFACT_BODY_SCHEMAS = {
@@ -4061,7 +4359,7 @@ function createArtifactUri(input) {
4061
4359
  const signed = signArtifact(body, input.privateKey);
4062
4360
  return { uri: encodeArtifactUri(signed), signed };
4063
4361
  }
4064
- function verifyArtifactUri(uri, publicKey, options = {}) {
4362
+ function verifyArtifactUri(uri, publicKeySpkiB64, options = {}) {
4065
4363
  const decoded = decodeArtifactUri(uri);
4066
4364
  if (!isKnownArtifactType(decoded.type)) {
4067
4365
  throw new FlurArtifactError(
@@ -4083,7 +4381,7 @@ function verifyArtifactUri(uri, publicKey, options = {}) {
4083
4381
  "INVALID_BODY"
4084
4382
  );
4085
4383
  }
4086
- const ok = verifyArtifactSignature(decoded, publicKey, options);
4384
+ const ok = verifyArtifactSignature(decoded, publicKeySpkiB64, options);
4087
4385
  if (!ok) {
4088
4386
  throw new FlurArtifactError(
4089
4387
  "Artifact signature verification failed",
@@ -4120,6 +4418,8 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4120
4418
  AccountSchema,
4121
4419
  ApiCredentialPublicSchema,
4122
4420
  ArtifactHeaderSchema,
4421
+ AttestationSecurityLevelSchema,
4422
+ CLAIM_DOMAIN_V2,
4123
4423
  COLLECTION_INTENT_STATUSES,
4124
4424
  COLLECTION_PAYMENT_STATUSES,
4125
4425
  CUSTODIAL_MODES,
@@ -4138,6 +4438,7 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4138
4438
  CreatePayoutInputSchema,
4139
4439
  CreateWithdrawalInputSchema,
4140
4440
  CreateWithdrawalResultSchema,
4441
+ DeviceKeyAlgSchema,
4141
4442
  DeviceKeyRecordSchema,
4142
4443
  DisableOfflineInputSchema,
4143
4444
  DisableOfflineResultSchema,
@@ -4174,6 +4475,7 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4174
4475
  OAC_DEFAULT_CUMULATIVE_KOBO,
4175
4476
  OAC_DEFAULT_PER_TX_KOBO,
4176
4477
  OAC_DEFAULT_VALIDITY_MS,
4478
+ OFFLINE_CLAIM_SMS_PREFIX,
4177
4479
  OfflineClaimArtifactSchema,
4178
4480
  OfflineHoldRecordSchema,
4179
4481
  OfflinePaymentAuthorizationArtifactSchema,
@@ -4182,6 +4484,8 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4182
4484
  OfflineStateResultSchema,
4183
4485
  OfflineStatusResultSchema,
4184
4486
  OfflineTokenSchema,
4487
+ P256EnrollmentChallengeInputSchema,
4488
+ P256EnrollmentChallengeResultSchema,
4185
4489
  PARTNER_FUNDING_DIRECTIONS,
4186
4490
  PARTNER_FUNDING_STATUSES,
4187
4491
  PARTNER_KINDS,
@@ -4205,6 +4509,8 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4205
4509
  PayoutEventInputSchema,
4206
4510
  ProviderEventInputSchema,
4207
4511
  ProviderEventRecordSchema,
4512
+ ProvisionOfflineAllowanceInputSchema,
4513
+ ProvisionOfflineAllowanceResultSchema,
4208
4514
  PublicCollectionIntentSchema,
4209
4515
  RECEIPT_CHANNELS,
4210
4516
  RECEIPT_KINDS,
@@ -4216,6 +4522,7 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4216
4522
  RecordPayoutEventResultSchema,
4217
4523
  RedemptionSchema,
4218
4524
  RegisterDeviceKeyInputSchema,
4525
+ RegisterDeviceKeyP256InputSchema,
4219
4526
  ReversalRecordArtifactSchema,
4220
4527
  RevokeDeviceKeyInputSchema,
4221
4528
  SETTLEMENT_SCHEDULES,
@@ -4238,6 +4545,8 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4238
4545
  buildPaymentRequest,
4239
4546
  buildReceipt,
4240
4547
  buildRedemption,
4548
+ canonicalClaimSigningBytes,
4549
+ canonicalClaimSigningPayload,
4241
4550
  canonicalJSONBytes,
4242
4551
  canonicalJSONStringify,
4243
4552
  canonicalRequestString,
@@ -4262,18 +4571,21 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4262
4571
  createPassesClient,
4263
4572
  createReceiptArtifactUri,
4264
4573
  createReceiptsClient,
4574
+ createSoftwareP256Signer,
4265
4575
  decodeArtifactUri,
4266
4576
  decodeAuthorizationQR,
4267
4577
  decodeBase45,
4578
+ decodeOfflineClaimSmsMessage,
4268
4579
  decodePaymentRequestQR,
4269
4580
  encodeArtifactUri,
4270
4581
  encodeAuthorizationQR,
4271
4582
  encodeBase45,
4272
4583
  encodeNQR,
4584
+ encodeOfflineClaimSmsMessage,
4273
4585
  encodePaymentRequestQR,
4586
+ extractOfflineClaimSmsToken,
4274
4587
  formatAmount,
4275
4588
  generateDynamicQR,
4276
- generateKeyPair,
4277
4589
  generateStaticQR,
4278
4590
  init,
4279
4591
  isHardenedArtifactType,
@@ -4284,13 +4596,10 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4284
4596
  parseAmountInput,
4285
4597
  parseNQR,
4286
4598
  parseQR,
4287
- publicKeyFromPrivate,
4288
4599
  readTLV,
4289
4600
  routingHint,
4290
- sign,
4291
4601
  signArtifact,
4292
4602
  signAuthorization,
4293
- signCanonical,
4294
4603
  signOAC,
4295
4604
  signPartnerRequest,
4296
4605
  signPass,
@@ -4298,11 +4607,10 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4298
4607
  signReceipt,
4299
4608
  signRedemption,
4300
4609
  signRequestHMAC,
4301
- verify,
4302
4610
  verifyArtifactSignature,
4303
4611
  verifyArtifactUri,
4304
4612
  verifyAuthorization,
4305
- verifyCanonical,
4613
+ verifyClaimSignature,
4306
4614
  verifyOAC,
4307
4615
  verifyPass,
4308
4616
  verifyPaymentRequest,