uvd-x402-sdk 2.53.0 → 2.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,8 +1,264 @@
1
1
  'use strict';
2
2
 
3
+ var aes = require('@noble/ciphers/aes');
4
+ var ed25519 = require('@noble/curves/ed25519');
5
+ var secp256k1 = require('@noble/curves/secp256k1');
6
+ var hkdf = require('@noble/hashes/hkdf');
7
+ var sha3 = require('@noble/hashes/sha3');
8
+ var sha2 = require('@noble/hashes/sha2');
9
+ var utils = require('@noble/hashes/utils');
3
10
  var ethers = require('ethers');
4
11
 
5
- // src/client/X402Client.ts
12
+ // src/dx402.ts
13
+ var EVIDENCE_HEADER = "X-Durable-Evidence";
14
+ var MAGIC = new Uint8Array([68, 88, 52, 48, 50]);
15
+ var FORMAT_VERSION = 1;
16
+ var NONCE_LEN = 12;
17
+ var CEK_LEN = 32;
18
+ var HKDF_INFO = new TextEncoder().encode("DX402-v1-wrap");
19
+ var DX402Error = class extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = "DX402Error";
23
+ }
24
+ };
25
+ var EvidenceSkipped = class extends DX402Error {
26
+ constructor(reason) {
27
+ super(`no durable evidence was anchored: ${reason}`);
28
+ this.reason = reason;
29
+ this.name = "EvidenceSkipped";
30
+ }
31
+ };
32
+ var ContentHashMismatch = class extends DX402Error {
33
+ constructor(anchored, actual) {
34
+ super(`content hash mismatch: anchored ${anchored}, decrypted ${actual}`);
35
+ this.anchored = anchored;
36
+ this.actual = actual;
37
+ this.name = "ContentHashMismatch";
38
+ }
39
+ };
40
+ function isEndToEnd(evidence) {
41
+ return evidence.mode === "direct";
42
+ }
43
+ function b64urlDecode(value) {
44
+ const normalized = value.trim().replace(/-/g, "+").replace(/_/g, "/");
45
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
46
+ return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
47
+ }
48
+ function hexToBytes(hex) {
49
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
50
+ if (clean.length % 2 !== 0) throw new DX402Error("odd-length hex string");
51
+ const out = new Uint8Array(clean.length / 2);
52
+ for (let i = 0; i < out.length; i++) {
53
+ out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
54
+ }
55
+ return out;
56
+ }
57
+ function bytesToHex(bytes) {
58
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
59
+ }
60
+ function contentHash(body) {
61
+ return "0x" + bytesToHex(sha3.keccak_256(body));
62
+ }
63
+ function paymentId(caip2Network, txHash) {
64
+ const enc = new TextEncoder();
65
+ const clean = txHash.startsWith("0x") ? txHash.slice(2) : txHash;
66
+ const preimage = new Uint8Array([...enc.encode(caip2Network), ...enc.encode(clean)]);
67
+ return "0x" + bytesToHex(sha3.keccak_256(preimage));
68
+ }
69
+ function parseEvidenceHeader(value) {
70
+ let payload;
71
+ try {
72
+ payload = JSON.parse(new TextDecoder().decode(b64urlDecode(value)));
73
+ } catch (err) {
74
+ throw new DX402Error(`malformed ${EVIDENCE_HEADER} header: ${String(err)}`);
75
+ }
76
+ if ("skipped" in payload) {
77
+ throw new EvidenceSkipped(String(payload.skipped));
78
+ }
79
+ if (!payload.paymentId || !payload.pointer || !payload.contentHash) {
80
+ throw new DX402Error(`malformed ${EVIDENCE_HEADER} header: missing required fields`);
81
+ }
82
+ return payload;
83
+ }
84
+ function evidenceFromHeaders(headers) {
85
+ let raw;
86
+ if (typeof headers.get === "function") {
87
+ raw = headers.get(EVIDENCE_HEADER);
88
+ } else {
89
+ const target = EVIDENCE_HEADER.toLowerCase();
90
+ for (const [key, value] of Object.entries(headers)) {
91
+ if (key.toLowerCase() === target) {
92
+ raw = value;
93
+ break;
94
+ }
95
+ }
96
+ }
97
+ if (!raw) throw new DX402Error(`no ${EVIDENCE_HEADER} header on this response`);
98
+ return parseEvidenceHeader(raw);
99
+ }
100
+ function dereferencePointer(pointer) {
101
+ if (pointer.startsWith("s3+")) return pointer.slice(3);
102
+ if (pointer.startsWith("ipfs://")) return `https://ipfs.io/ipfs/${pointer.slice(7)}`;
103
+ if (pointer.startsWith("ar://")) return `https://arweave.net/${pointer.slice(5)}`;
104
+ return pointer;
105
+ }
106
+ function parseSealed(raw) {
107
+ let pos = 0;
108
+ const take = (n, what) => {
109
+ if (raw.length < pos + n) throw new DX402Error(`truncated DX402 sealed blob at ${what}`);
110
+ const chunk = raw.subarray(pos, pos + n);
111
+ pos += n;
112
+ return chunk;
113
+ };
114
+ const magic = take(MAGIC.length, "magic");
115
+ for (let i = 0; i < MAGIC.length; i++) {
116
+ if (magic[i] !== MAGIC[i]) throw new DX402Error("not a DX402 sealed blob");
117
+ }
118
+ const version = take(1, "version")[0];
119
+ if (version !== FORMAT_VERSION) {
120
+ throw new DX402Error(`unsupported sealed-blob version ${version}`);
121
+ }
122
+ const algByte = take(1, "algorithm")[0];
123
+ if (algByte !== 1 && algByte !== 2) {
124
+ throw new DX402Error(`unknown key algorithm ${algByte}`);
125
+ }
126
+ const ephLen = take(1, "ephemeral key length")[0];
127
+ const ephemeral = take(ephLen, "ephemeral key");
128
+ const cekNonce = take(NONCE_LEN, "cek nonce");
129
+ const lenBytes = take(2, "wrapped key length");
130
+ const wrappedLen = lenBytes[0] << 8 | lenBytes[1];
131
+ const wrappedCek = take(wrappedLen, "wrapped cek");
132
+ const bodyNonce = take(NONCE_LEN, "body nonce");
133
+ const ciphertext = raw.subarray(pos);
134
+ return {
135
+ alg: algByte === 1 ? "secp256k1" : "x25519",
136
+ ephemeral,
137
+ cekNonce,
138
+ wrappedCek,
139
+ bodyNonce,
140
+ ciphertext
141
+ };
142
+ }
143
+ function sharedSecret(sealed, privateKey) {
144
+ if (sealed.alg === "secp256k1") {
145
+ const full = secp256k1.secp256k1.getSharedSecret(privateKey, sealed.ephemeral, true);
146
+ return full.subarray(1);
147
+ }
148
+ const scalar = sha2.sha512(privateKey).subarray(0, 32);
149
+ const shared = ed25519.x25519.getSharedSecret(scalar, sealed.ephemeral);
150
+ if (shared.every((b) => b === 0)) {
151
+ throw new DX402Error("degenerate ECDH result (small-order public key)");
152
+ }
153
+ return shared;
154
+ }
155
+ function unseal(sealed, privateKey, aad) {
156
+ const shared = sharedSecret(sealed, privateKey);
157
+ const wrapKey = hkdf.hkdf(sha2.sha256, shared, aad, HKDF_INFO, 32);
158
+ const cek = aes.gcm(wrapKey, sealed.cekNonce, aad).decrypt(sealed.wrappedCek);
159
+ if (cek.length !== CEK_LEN) {
160
+ throw new DX402Error(`unwrapped CEK is ${cek.length} bytes, expected ${CEK_LEN}`);
161
+ }
162
+ return aes.gcm(cek, sealed.bodyNonce, aad).decrypt(sealed.ciphertext);
163
+ }
164
+ async function recoverEvidence(evidence, privateKey, options = {}) {
165
+ const key = typeof privateKey === "string" ? hexToBytes(privateKey) : privateKey;
166
+ if (key.length !== 32) {
167
+ throw new DX402Error(`private key must be 32 bytes, got ${key.length}`);
168
+ }
169
+ const doFetch = options.fetch ?? fetch;
170
+ const url = dereferencePointer(evidence.pointer);
171
+ const res = await doFetch(url);
172
+ if (!res.ok) {
173
+ throw new DX402Error(`could not fetch sealed evidence: HTTP ${res.status}`);
174
+ }
175
+ const blob = new Uint8Array(await res.arrayBuffer());
176
+ const sealed = parseSealed(blob);
177
+ const aad = new TextEncoder().encode(evidence.paymentId);
178
+ let plaintext;
179
+ try {
180
+ plaintext = unseal(sealed, key, aad);
181
+ } catch (err) {
182
+ if (err instanceof DX402Error) throw err;
183
+ throw new DX402Error(
184
+ "decryption failed -- wrong key, or the blob belongs to another payment"
185
+ );
186
+ }
187
+ const actual = contentHash(plaintext);
188
+ if (actual.toLowerCase() !== evidence.contentHash.toLowerCase()) {
189
+ throw new ContentHashMismatch(evidence.contentHash, actual);
190
+ }
191
+ return plaintext;
192
+ }
193
+ function ed25519ToX25519(pubkey) {
194
+ if (pubkey.length !== 32) {
195
+ throw new DX402Error(`ed25519 public key must be 32 bytes, got ${pubkey.length}`);
196
+ }
197
+ return ed25519.edwardsToMontgomeryPub(pubkey);
198
+ }
199
+ function payerKeyFromEvmSignature(signature, digest) {
200
+ const sig = typeof signature === "string" ? hexToBytes(signature) : signature;
201
+ if (sig.length !== 65) {
202
+ throw new DX402Error(`signature must be 65 bytes, got ${sig.length}`);
203
+ }
204
+ let v = sig[65 - 1];
205
+ if (v === 27 || v === 28) v -= 27;
206
+ else if (v >= 35) v = (v - 35) % 2;
207
+ if (v !== 0 && v !== 1) throw new DX402Error(`invalid recovery id ${sig[64]}`);
208
+ const parsed = secp256k1.secp256k1.Signature.fromCompact(sig.subarray(0, 64)).addRecoveryBit(v);
209
+ return parsed.recoverPublicKey(digest).toRawBytes(true);
210
+ }
211
+ function sealEvidence(body, payerKey, paymentIdValue) {
212
+ const aad = new TextEncoder().encode(paymentIdValue);
213
+ const cek = utils.randomBytes(32);
214
+ const bodyNonce = utils.randomBytes(NONCE_LEN);
215
+ const cekNonce = utils.randomBytes(NONCE_LEN);
216
+ const ciphertext = aes.gcm(cek, bodyNonce, aad).encrypt(body);
217
+ let algByte;
218
+ let ephemeral;
219
+ let shared;
220
+ if (payerKey.length === 33) {
221
+ algByte = 1;
222
+ const ephPriv = secp256k1.secp256k1.utils.randomPrivateKey();
223
+ ephemeral = secp256k1.secp256k1.getPublicKey(ephPriv, true);
224
+ shared = secp256k1.secp256k1.getSharedSecret(ephPriv, payerKey, true).subarray(1);
225
+ } else if (payerKey.length === 32) {
226
+ algByte = 2;
227
+ const ephPriv = utils.randomBytes(32);
228
+ ephemeral = ed25519.x25519.getPublicKey(ephPriv);
229
+ shared = ed25519.x25519.getSharedSecret(ephPriv, payerKey);
230
+ if (shared.every((b) => b === 0)) {
231
+ throw new DX402Error("degenerate ECDH result (small-order public key)");
232
+ }
233
+ } else {
234
+ throw new DX402Error(
235
+ `payer key must be 33 bytes (secp256k1) or 32 (X25519), got ${payerKey.length}`
236
+ );
237
+ }
238
+ const wrapKey = hkdf.hkdf(sha2.sha256, shared, aad, HKDF_INFO, 32);
239
+ const wrappedCek = aes.gcm(wrapKey, cekNonce, aad).encrypt(cek);
240
+ const out = new Uint8Array(
241
+ MAGIC.length + 3 + ephemeral.length + NONCE_LEN + 2 + wrappedCek.length + NONCE_LEN + ciphertext.length
242
+ );
243
+ let pos = 0;
244
+ out.set(MAGIC, pos);
245
+ pos += MAGIC.length;
246
+ out[pos++] = FORMAT_VERSION;
247
+ out[pos++] = algByte;
248
+ out[pos++] = ephemeral.length;
249
+ out.set(ephemeral, pos);
250
+ pos += ephemeral.length;
251
+ out.set(cekNonce, pos);
252
+ pos += NONCE_LEN;
253
+ out[pos++] = wrappedCek.length >> 8 & 255;
254
+ out[pos++] = wrappedCek.length & 255;
255
+ out.set(wrappedCek, pos);
256
+ pos += wrappedCek.length;
257
+ out.set(bodyNonce, pos);
258
+ pos += NONCE_LEN;
259
+ out.set(ciphertext, pos);
260
+ return out;
261
+ }
6
262
 
7
263
  // src/types/index.ts
8
264
  var CAIP2_IDENTIFIERS = {
@@ -3781,14 +4037,18 @@ var ESCROW_TIMEOUT_MS = {
3781
4037
 
3782
4038
  exports.CAIP2_IDENTIFIERS = CAIP2_IDENTIFIERS;
3783
4039
  exports.CAIP2_TO_CHAIN = CAIP2_TO_CHAIN;
4040
+ exports.ContentHashMismatch = ContentHashMismatch;
3784
4041
  exports.DEFAULT_CHAIN = DEFAULT_CHAIN;
3785
4042
  exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
3786
4043
  exports.DEFAULT_FACILITATOR_URL = DEFAULT_FACILITATOR_URL;
3787
4044
  exports.DEFAULT_PAYMENT_HEADER = DEFAULT_PAYMENT_HEADER;
4045
+ exports.DX402Error = DX402Error;
3788
4046
  exports.ESCROW_DEPOSIT_LIMIT_USD = ESCROW_DEPOSIT_LIMIT_USD;
3789
4047
  exports.ESCROW_TIER_WINDOWS = ESCROW_TIER_WINDOWS;
3790
4048
  exports.EVENT_KINDS = EVENT_KINDS;
4049
+ exports.EVIDENCE_HEADER = EVIDENCE_HEADER;
3791
4050
  exports.EnvKeyAdapter = EnvKeyAdapter;
4051
+ exports.EvidenceSkipped = EvidenceSkipped;
3792
4052
  exports.FACILITATOR_ADDRESSES = FACILITATOR_ADDRESSES;
3793
4053
  exports.FacilitatorClient = FacilitatorClient;
3794
4054
  exports.KEEPALIVE_INTERVAL_MS = KEEPALIVE_INTERVAL_MS;
@@ -3814,6 +4074,7 @@ exports.buildVerifyRequestV2 = buildVerifyRequestV2;
3814
4074
  exports.caip2ToChain = caip2ToChain;
3815
4075
  exports.chainToCAIP2 = chainToCAIP2;
3816
4076
  exports.computeEscrowNonce = computeEscrowNonce;
4077
+ exports.contentHash = contentHash;
3817
4078
  exports.convertX402Header = convertX402Header;
3818
4079
  exports.create402Response = create402Response;
3819
4080
  exports.createHonoMiddleware = createHonoMiddleware;
@@ -3825,10 +4086,14 @@ exports.createX402V1Header = createX402V1Header;
3825
4086
  exports.createX402V2Header = createX402V2Header;
3826
4087
  exports.decodeBase64Utf8 = decodeBase64Utf8;
3827
4088
  exports.decodeX402Header = decodeX402Header;
4089
+ exports.dereferencePointer = dereferencePointer;
3828
4090
  exports.detectX402Version = detectX402Version;
4091
+ exports.dx402PaymentId = paymentId;
4092
+ exports.ed25519ToX25519 = ed25519ToX25519;
3829
4093
  exports.encodeBase64Json = encodeBase64Json;
3830
4094
  exports.encodeBase64Utf8 = encodeBase64Utf8;
3831
4095
  exports.encodeX402Header = encodeX402Header;
4096
+ exports.evidenceFromHeaders = evidenceFromHeaders;
3832
4097
  exports.extractPaymentFromHeaders = extractPaymentFromHeaders;
3833
4098
  exports.fetchNonce = fetchNonce;
3834
4099
  exports.generatePaymentOptions = generatePaymentOptions;
@@ -3854,17 +4119,24 @@ exports.getXRPLChains = getXRPLChains;
3854
4119
  exports.isAlgorandChain = isAlgorandChain;
3855
4120
  exports.isCAIP2Format = isCAIP2Format;
3856
4121
  exports.isChainSupported = isChainSupported;
4122
+ exports.isEndToEnd = isEndToEnd;
3857
4123
  exports.isSVMChain = isSVMChain;
3858
4124
  exports.isSuiChain = isSuiChain;
3859
4125
  exports.isTokenSupported = isTokenSupported;
3860
4126
  exports.isXRPLChain = isXRPLChain;
3861
4127
  exports.matchesFilters = matchesFilters;
4128
+ exports.parseEvidenceHeader = parseEvidenceHeader;
3862
4129
  exports.parseNetworkIdentifier = parseNetworkIdentifier;
4130
+ exports.parseSealed = parseSealed;
3863
4131
  exports.parseTrafficEvent = parseTrafficEvent;
4132
+ exports.payerKeyFromEvmSignature = payerKeyFromEvmSignature;
4133
+ exports.recoverEvidence = recoverEvidence;
4134
+ exports.sealEvidence = sealEvidence;
3864
4135
  exports.signRequest = signRequest;
3865
4136
  exports.signRequestWithSigner = signRequestWithSigner;
3866
4137
  exports.signRequestWithWallet = signRequestWithWallet;
3867
4138
  exports.streamTrafficEvents = streamTrafficEvents;
4139
+ exports.unseal = unseal;
3868
4140
  exports.validateAmount = validateAmount;
3869
4141
  exports.validateRecipient = validateRecipient;
3870
4142
  //# sourceMappingURL=index.js.map