uvd-x402-sdk 2.55.0 → 2.57.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.d.mts +59 -3
- package/dist/index.d.ts +59 -3
- package/dist/index.js +134 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +131 -34
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/dx402.ts +232 -34
- package/src/dx402.vectors.json +23 -0
- package/src/index.ts +6 -1
package/dist/index.d.mts
CHANGED
|
@@ -29,6 +29,8 @@ export { FacilitatorClient, FacilitatorClientOptions, HonoMiddlewareOptions, Pay
|
|
|
29
29
|
* Specification: `docs/plans/dx402/02-SPEC-v0.1.md` in x402-rs.
|
|
30
30
|
*/
|
|
31
31
|
declare const EVIDENCE_HEADER = "X-Durable-Evidence";
|
|
32
|
+
/** Why a party holds a key to this evidence. */
|
|
33
|
+
type RecipientRole = 'payer' | 'seller' | 'auditor';
|
|
32
34
|
declare class DX402Error extends Error {
|
|
33
35
|
constructor(message: string);
|
|
34
36
|
}
|
|
@@ -103,14 +105,26 @@ declare function evidenceFromHeaders(headers: Headers | Record<string, string>):
|
|
|
103
105
|
* a caller with their own resolver is not blocked by this function.
|
|
104
106
|
*/
|
|
105
107
|
declare function dereferencePointer(pointer: string): string;
|
|
106
|
-
interface
|
|
108
|
+
interface Recipient {
|
|
109
|
+
role: RecipientRole;
|
|
107
110
|
alg: 'secp256k1' | 'x25519';
|
|
108
111
|
ephemeral: Uint8Array;
|
|
109
112
|
cekNonce: Uint8Array;
|
|
110
113
|
wrappedCek: Uint8Array;
|
|
114
|
+
}
|
|
115
|
+
interface SealedEnvelope {
|
|
116
|
+
recipients: Recipient[];
|
|
111
117
|
bodyNonce: Uint8Array;
|
|
112
118
|
ciphertext: Uint8Array;
|
|
113
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Who can open this blob, without decrypting anything.
|
|
122
|
+
*
|
|
123
|
+
* Worth surfacing: a buyer has to be able to see that the seller — or a
|
|
124
|
+
* designated auditor — also holds a key to what they bought. Finding that out
|
|
125
|
+
* afterwards would destroy the privacy property.
|
|
126
|
+
*/
|
|
127
|
+
declare function sealedRoles(raw: Uint8Array): RecipientRole[];
|
|
114
128
|
/**
|
|
115
129
|
* Parse the sealed-blob layout.
|
|
116
130
|
*
|
|
@@ -121,7 +135,13 @@ interface SealedEnvelope {
|
|
|
121
135
|
* rather than an out-of-range surprise later.
|
|
122
136
|
*/
|
|
123
137
|
declare function parseSealed(raw: Uint8Array): SealedEnvelope;
|
|
124
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* Decrypt a sealed envelope with whichever recipient slot belongs to `privateKey`.
|
|
140
|
+
*
|
|
141
|
+
* Tries every slot: a holder does not necessarily know which one is theirs, and
|
|
142
|
+
* in a multi-recipient envelope the payer is not always first. A slot that does
|
|
143
|
+
* not open is skipped, not reported — "that one was not for me" is not an error.
|
|
144
|
+
*/
|
|
125
145
|
declare function unseal(sealed: SealedEnvelope, privateKey: Uint8Array, aad: Uint8Array): Uint8Array;
|
|
126
146
|
/**
|
|
127
147
|
* Fetch, decrypt and verify the body behind `evidence`.
|
|
@@ -173,6 +193,42 @@ declare function payerKeyFromEvmSignature(signature: Uint8Array | string, digest
|
|
|
173
193
|
* Returns the bytes to upload. Nothing here touches the network.
|
|
174
194
|
*/
|
|
175
195
|
declare function sealEvidence(body: Uint8Array, payerKey: Uint8Array, paymentIdValue: string): Uint8Array;
|
|
196
|
+
/** The zero address, for the ed25519 form of the digest. */
|
|
197
|
+
declare const ZERO_ADDRESS: string;
|
|
198
|
+
/**
|
|
199
|
+
* The 32-byte digest a seller signs to prove an anchor is theirs.
|
|
200
|
+
*
|
|
201
|
+
* One canonical message across every curve. `payee` is the EVM address for a
|
|
202
|
+
* secp256k1 payee and the **zero address** for an ed25519 one — an ed25519
|
|
203
|
+
* address does not fit the `address` field, and the binding is already
|
|
204
|
+
* established by which key verifies the signature.
|
|
205
|
+
*
|
|
206
|
+
* `pointer` is whatever you send in the anchor, or the **empty string** when you
|
|
207
|
+
* send `sealed` and the facilitator issues the pointer itself: you cannot sign a
|
|
208
|
+
* value you have not seen.
|
|
209
|
+
*
|
|
210
|
+
* Getting this wrong throws nothing — it produces a signature that simply never
|
|
211
|
+
* verifies, and the anchor stays provisional with no clue why. The tests pin it
|
|
212
|
+
* against digests emitted by the facilitator's own Rust implementation.
|
|
213
|
+
*/
|
|
214
|
+
declare function anchorDigest(paymentId: string, contentHash: string, pointer: string, payee: string, chainId: number): Uint8Array;
|
|
215
|
+
/**
|
|
216
|
+
* Sign an anchor authorization with a Solana / Stellar ed25519 key.
|
|
217
|
+
*
|
|
218
|
+
* A Solana payee cannot produce an EIP-712 signature at all — its address is an
|
|
219
|
+
* ed25519 key — so requiring one would leave that chain unable to prove
|
|
220
|
+
* authorship even once the on-chain gate is enforced. This closes it today, with
|
|
221
|
+
* no RPC.
|
|
222
|
+
*/
|
|
223
|
+
declare function signAnchorEd25519(privateKey: Uint8Array, paymentId: string, contentHash: string, pointer?: string): string;
|
|
224
|
+
/**
|
|
225
|
+
* Sign an anchor authorization with an EVM secp256k1 key.
|
|
226
|
+
*
|
|
227
|
+
* `payee` must be the address of `privateKey` — the facilitator recovers the
|
|
228
|
+
* signer and compares, so declaring somebody else's address simply leaves the
|
|
229
|
+
* anchor provisional.
|
|
230
|
+
*/
|
|
231
|
+
declare function signAnchorEvm(privateKey: Uint8Array, paymentId: string, contentHash: string, pointer: string, payee: string, chainId: number): string;
|
|
176
232
|
|
|
177
233
|
/**
|
|
178
234
|
* Facilitator wallet addresses by chain type
|
|
@@ -821,4 +877,4 @@ interface EscrowPreAuthParams {
|
|
|
821
877
|
*/
|
|
822
878
|
declare function buildEscrowPreAuth(wallet: EscrowPreAuthSigner, params: EscrowPreAuthParams): Promise<string>;
|
|
823
879
|
|
|
824
|
-
export { type AnchoredEvidence, ContentHashMismatch, type CreateSignedFetchConfig, DX402Error, type ERC8128RequestOptions, ESCROW_DEPOSIT_LIMIT_USD, ESCROW_TIER_WINDOWS, EVENT_KINDS, EVIDENCE_HEADER, type EscrowNetworkConfig, type EscrowPaymentInfo, type EscrowPreAuthParams, type EscrowPreAuthSigner, type EscrowTierWindows, type EvidenceMode, EvidenceSkipped, FACILITATOR_ADDRESSES, type FacilitatorAddresses, KEEPALIVE_INTERVAL_MS, OPERATOR_FEE_BPS, type SSEFrame, SSEParser, type SignRequestOptions, type SignRequestWithSignerOptions, type SignatureBaseParams, type SignatureHeaders, type SignatureParamsInput, SigningWalletAdapter, type StreamTrafficEventsOptions, type TrafficEvent, type TrafficEventKind, TrafficStreamError, buildEscrowPreAuth, buildSignatureBase, buildSignatureParams, computeEscrowNonce, contentHash, createSignedFetch, dereferencePointer, paymentId as dx402PaymentId, ed25519ToX25519, evidenceFromHeaders, fetchNonce, getFacilitatorAddress, isEndToEnd, matchesFilters, parseEvidenceHeader, parseSealed, parseTrafficEvent, payerKeyFromEvmSignature, recoverEvidence, sealEvidence, signRequest, signRequestWithSigner, signRequestWithWallet, streamTrafficEvents, unseal };
|
|
880
|
+
export { type AnchoredEvidence, ContentHashMismatch, type CreateSignedFetchConfig, DX402Error, type ERC8128RequestOptions, ESCROW_DEPOSIT_LIMIT_USD, ESCROW_TIER_WINDOWS, EVENT_KINDS, EVIDENCE_HEADER, type EscrowNetworkConfig, type EscrowPaymentInfo, type EscrowPreAuthParams, type EscrowPreAuthSigner, type EscrowTierWindows, type EvidenceMode, EvidenceSkipped, FACILITATOR_ADDRESSES, type FacilitatorAddresses, KEEPALIVE_INTERVAL_MS, OPERATOR_FEE_BPS, type RecipientRole, type SSEFrame, SSEParser, type SignRequestOptions, type SignRequestWithSignerOptions, type SignatureBaseParams, type SignatureHeaders, type SignatureParamsInput, SigningWalletAdapter, type StreamTrafficEventsOptions, type TrafficEvent, type TrafficEventKind, TrafficStreamError, ZERO_ADDRESS, anchorDigest, buildEscrowPreAuth, buildSignatureBase, buildSignatureParams, computeEscrowNonce, contentHash, createSignedFetch, dereferencePointer, paymentId as dx402PaymentId, ed25519ToX25519, evidenceFromHeaders, fetchNonce, getFacilitatorAddress, isEndToEnd, matchesFilters, parseEvidenceHeader, parseSealed, parseTrafficEvent, payerKeyFromEvmSignature, recoverEvidence, sealEvidence, sealedRoles, signAnchorEd25519, signAnchorEvm, signRequest, signRequestWithSigner, signRequestWithWallet, streamTrafficEvents, unseal };
|
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export { FacilitatorClient, FacilitatorClientOptions, HonoMiddlewareOptions, Pay
|
|
|
29
29
|
* Specification: `docs/plans/dx402/02-SPEC-v0.1.md` in x402-rs.
|
|
30
30
|
*/
|
|
31
31
|
declare const EVIDENCE_HEADER = "X-Durable-Evidence";
|
|
32
|
+
/** Why a party holds a key to this evidence. */
|
|
33
|
+
type RecipientRole = 'payer' | 'seller' | 'auditor';
|
|
32
34
|
declare class DX402Error extends Error {
|
|
33
35
|
constructor(message: string);
|
|
34
36
|
}
|
|
@@ -103,14 +105,26 @@ declare function evidenceFromHeaders(headers: Headers | Record<string, string>):
|
|
|
103
105
|
* a caller with their own resolver is not blocked by this function.
|
|
104
106
|
*/
|
|
105
107
|
declare function dereferencePointer(pointer: string): string;
|
|
106
|
-
interface
|
|
108
|
+
interface Recipient {
|
|
109
|
+
role: RecipientRole;
|
|
107
110
|
alg: 'secp256k1' | 'x25519';
|
|
108
111
|
ephemeral: Uint8Array;
|
|
109
112
|
cekNonce: Uint8Array;
|
|
110
113
|
wrappedCek: Uint8Array;
|
|
114
|
+
}
|
|
115
|
+
interface SealedEnvelope {
|
|
116
|
+
recipients: Recipient[];
|
|
111
117
|
bodyNonce: Uint8Array;
|
|
112
118
|
ciphertext: Uint8Array;
|
|
113
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Who can open this blob, without decrypting anything.
|
|
122
|
+
*
|
|
123
|
+
* Worth surfacing: a buyer has to be able to see that the seller — or a
|
|
124
|
+
* designated auditor — also holds a key to what they bought. Finding that out
|
|
125
|
+
* afterwards would destroy the privacy property.
|
|
126
|
+
*/
|
|
127
|
+
declare function sealedRoles(raw: Uint8Array): RecipientRole[];
|
|
114
128
|
/**
|
|
115
129
|
* Parse the sealed-blob layout.
|
|
116
130
|
*
|
|
@@ -121,7 +135,13 @@ interface SealedEnvelope {
|
|
|
121
135
|
* rather than an out-of-range surprise later.
|
|
122
136
|
*/
|
|
123
137
|
declare function parseSealed(raw: Uint8Array): SealedEnvelope;
|
|
124
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* Decrypt a sealed envelope with whichever recipient slot belongs to `privateKey`.
|
|
140
|
+
*
|
|
141
|
+
* Tries every slot: a holder does not necessarily know which one is theirs, and
|
|
142
|
+
* in a multi-recipient envelope the payer is not always first. A slot that does
|
|
143
|
+
* not open is skipped, not reported — "that one was not for me" is not an error.
|
|
144
|
+
*/
|
|
125
145
|
declare function unseal(sealed: SealedEnvelope, privateKey: Uint8Array, aad: Uint8Array): Uint8Array;
|
|
126
146
|
/**
|
|
127
147
|
* Fetch, decrypt and verify the body behind `evidence`.
|
|
@@ -173,6 +193,42 @@ declare function payerKeyFromEvmSignature(signature: Uint8Array | string, digest
|
|
|
173
193
|
* Returns the bytes to upload. Nothing here touches the network.
|
|
174
194
|
*/
|
|
175
195
|
declare function sealEvidence(body: Uint8Array, payerKey: Uint8Array, paymentIdValue: string): Uint8Array;
|
|
196
|
+
/** The zero address, for the ed25519 form of the digest. */
|
|
197
|
+
declare const ZERO_ADDRESS: string;
|
|
198
|
+
/**
|
|
199
|
+
* The 32-byte digest a seller signs to prove an anchor is theirs.
|
|
200
|
+
*
|
|
201
|
+
* One canonical message across every curve. `payee` is the EVM address for a
|
|
202
|
+
* secp256k1 payee and the **zero address** for an ed25519 one — an ed25519
|
|
203
|
+
* address does not fit the `address` field, and the binding is already
|
|
204
|
+
* established by which key verifies the signature.
|
|
205
|
+
*
|
|
206
|
+
* `pointer` is whatever you send in the anchor, or the **empty string** when you
|
|
207
|
+
* send `sealed` and the facilitator issues the pointer itself: you cannot sign a
|
|
208
|
+
* value you have not seen.
|
|
209
|
+
*
|
|
210
|
+
* Getting this wrong throws nothing — it produces a signature that simply never
|
|
211
|
+
* verifies, and the anchor stays provisional with no clue why. The tests pin it
|
|
212
|
+
* against digests emitted by the facilitator's own Rust implementation.
|
|
213
|
+
*/
|
|
214
|
+
declare function anchorDigest(paymentId: string, contentHash: string, pointer: string, payee: string, chainId: number): Uint8Array;
|
|
215
|
+
/**
|
|
216
|
+
* Sign an anchor authorization with a Solana / Stellar ed25519 key.
|
|
217
|
+
*
|
|
218
|
+
* A Solana payee cannot produce an EIP-712 signature at all — its address is an
|
|
219
|
+
* ed25519 key — so requiring one would leave that chain unable to prove
|
|
220
|
+
* authorship even once the on-chain gate is enforced. This closes it today, with
|
|
221
|
+
* no RPC.
|
|
222
|
+
*/
|
|
223
|
+
declare function signAnchorEd25519(privateKey: Uint8Array, paymentId: string, contentHash: string, pointer?: string): string;
|
|
224
|
+
/**
|
|
225
|
+
* Sign an anchor authorization with an EVM secp256k1 key.
|
|
226
|
+
*
|
|
227
|
+
* `payee` must be the address of `privateKey` — the facilitator recovers the
|
|
228
|
+
* signer and compares, so declaring somebody else's address simply leaves the
|
|
229
|
+
* anchor provisional.
|
|
230
|
+
*/
|
|
231
|
+
declare function signAnchorEvm(privateKey: Uint8Array, paymentId: string, contentHash: string, pointer: string, payee: string, chainId: number): string;
|
|
176
232
|
|
|
177
233
|
/**
|
|
178
234
|
* Facilitator wallet addresses by chain type
|
|
@@ -821,4 +877,4 @@ interface EscrowPreAuthParams {
|
|
|
821
877
|
*/
|
|
822
878
|
declare function buildEscrowPreAuth(wallet: EscrowPreAuthSigner, params: EscrowPreAuthParams): Promise<string>;
|
|
823
879
|
|
|
824
|
-
export { type AnchoredEvidence, ContentHashMismatch, type CreateSignedFetchConfig, DX402Error, type ERC8128RequestOptions, ESCROW_DEPOSIT_LIMIT_USD, ESCROW_TIER_WINDOWS, EVENT_KINDS, EVIDENCE_HEADER, type EscrowNetworkConfig, type EscrowPaymentInfo, type EscrowPreAuthParams, type EscrowPreAuthSigner, type EscrowTierWindows, type EvidenceMode, EvidenceSkipped, FACILITATOR_ADDRESSES, type FacilitatorAddresses, KEEPALIVE_INTERVAL_MS, OPERATOR_FEE_BPS, type SSEFrame, SSEParser, type SignRequestOptions, type SignRequestWithSignerOptions, type SignatureBaseParams, type SignatureHeaders, type SignatureParamsInput, SigningWalletAdapter, type StreamTrafficEventsOptions, type TrafficEvent, type TrafficEventKind, TrafficStreamError, buildEscrowPreAuth, buildSignatureBase, buildSignatureParams, computeEscrowNonce, contentHash, createSignedFetch, dereferencePointer, paymentId as dx402PaymentId, ed25519ToX25519, evidenceFromHeaders, fetchNonce, getFacilitatorAddress, isEndToEnd, matchesFilters, parseEvidenceHeader, parseSealed, parseTrafficEvent, payerKeyFromEvmSignature, recoverEvidence, sealEvidence, signRequest, signRequestWithSigner, signRequestWithWallet, streamTrafficEvents, unseal };
|
|
880
|
+
export { type AnchoredEvidence, ContentHashMismatch, type CreateSignedFetchConfig, DX402Error, type ERC8128RequestOptions, ESCROW_DEPOSIT_LIMIT_USD, ESCROW_TIER_WINDOWS, EVENT_KINDS, EVIDENCE_HEADER, type EscrowNetworkConfig, type EscrowPaymentInfo, type EscrowPreAuthParams, type EscrowPreAuthSigner, type EscrowTierWindows, type EvidenceMode, EvidenceSkipped, FACILITATOR_ADDRESSES, type FacilitatorAddresses, KEEPALIVE_INTERVAL_MS, OPERATOR_FEE_BPS, type RecipientRole, type SSEFrame, SSEParser, type SignRequestOptions, type SignRequestWithSignerOptions, type SignatureBaseParams, type SignatureHeaders, type SignatureParamsInput, SigningWalletAdapter, type StreamTrafficEventsOptions, type TrafficEvent, type TrafficEventKind, TrafficStreamError, ZERO_ADDRESS, anchorDigest, buildEscrowPreAuth, buildSignatureBase, buildSignatureParams, computeEscrowNonce, contentHash, createSignedFetch, dereferencePointer, paymentId as dx402PaymentId, ed25519ToX25519, evidenceFromHeaders, fetchNonce, getFacilitatorAddress, isEndToEnd, matchesFilters, parseEvidenceHeader, parseSealed, parseTrafficEvent, payerKeyFromEvmSignature, recoverEvidence, sealEvidence, sealedRoles, signAnchorEd25519, signAnchorEvm, signRequest, signRequestWithSigner, signRequestWithWallet, streamTrafficEvents, unseal };
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,9 @@ var ethers = require('ethers');
|
|
|
12
12
|
// src/dx402.ts
|
|
13
13
|
var EVIDENCE_HEADER = "X-Durable-Evidence";
|
|
14
14
|
var MAGIC = new Uint8Array([68, 88, 52, 48, 50]);
|
|
15
|
-
var
|
|
15
|
+
var FORMAT_V1 = 1;
|
|
16
|
+
var FORMAT_V2 = 2;
|
|
17
|
+
var ROLE_NAMES = ["payer", "seller", "auditor"];
|
|
16
18
|
var NONCE_LEN = 12;
|
|
17
19
|
var CEK_LEN = 32;
|
|
18
20
|
var HKDF_INFO = new TextEncoder().encode("DX402-v1-wrap");
|
|
@@ -103,6 +105,9 @@ function dereferencePointer(pointer) {
|
|
|
103
105
|
if (pointer.startsWith("ar://")) return `https://arweave.net/${pointer.slice(5)}`;
|
|
104
106
|
return pointer;
|
|
105
107
|
}
|
|
108
|
+
function sealedRoles(raw) {
|
|
109
|
+
return parseSealed(raw).recipients.map((r) => r.role);
|
|
110
|
+
}
|
|
106
111
|
function parseSealed(raw) {
|
|
107
112
|
let pos = 0;
|
|
108
113
|
const take = (n, what) => {
|
|
@@ -116,29 +121,38 @@ function parseSealed(raw) {
|
|
|
116
121
|
if (magic[i] !== MAGIC[i]) throw new DX402Error("not a DX402 sealed blob");
|
|
117
122
|
}
|
|
118
123
|
const version = take(1, "version")[0];
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
124
|
+
let count;
|
|
125
|
+
if (version === FORMAT_V1) count = 1;
|
|
126
|
+
else if (version === FORMAT_V2) count = take(1, "recipient count")[0];
|
|
127
|
+
else throw new DX402Error(`unsupported sealed-blob version ${version}`);
|
|
128
|
+
if (count === 0) throw new DX402Error("DX402 sealed blob has no recipients");
|
|
129
|
+
const recipients = [];
|
|
130
|
+
for (let i = 0; i < count; i++) {
|
|
131
|
+
let role = "payer";
|
|
132
|
+
if (version === FORMAT_V2) {
|
|
133
|
+
const b = take(1, "role")[0];
|
|
134
|
+
if (b >= ROLE_NAMES.length) throw new DX402Error(`unknown role ${b}`);
|
|
135
|
+
role = ROLE_NAMES[b];
|
|
136
|
+
}
|
|
137
|
+
const algByte = take(1, "algorithm")[0];
|
|
138
|
+
if (algByte !== 1 && algByte !== 2) {
|
|
139
|
+
throw new DX402Error(`unknown key algorithm ${algByte}`);
|
|
140
|
+
}
|
|
141
|
+
const ephLen = take(1, "ephemeral key length")[0];
|
|
142
|
+
const ephemeral = take(ephLen, "ephemeral key");
|
|
143
|
+
const cekNonce = take(NONCE_LEN, "cek nonce");
|
|
144
|
+
const lenBytes = take(2, "wrapped key length");
|
|
145
|
+
const wrappedCek = take(lenBytes[0] << 8 | lenBytes[1], "wrapped cek");
|
|
146
|
+
recipients.push({
|
|
147
|
+
role,
|
|
148
|
+
alg: algByte === 1 ? "secp256k1" : "x25519",
|
|
149
|
+
ephemeral,
|
|
150
|
+
cekNonce,
|
|
151
|
+
wrappedCek
|
|
152
|
+
});
|
|
153
|
+
}
|
|
132
154
|
const bodyNonce = take(NONCE_LEN, "body nonce");
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
alg: algByte === 1 ? "secp256k1" : "x25519",
|
|
136
|
-
ephemeral,
|
|
137
|
-
cekNonce,
|
|
138
|
-
wrappedCek,
|
|
139
|
-
bodyNonce,
|
|
140
|
-
ciphertext
|
|
141
|
-
};
|
|
155
|
+
return { recipients, bodyNonce, ciphertext: raw.subarray(pos) };
|
|
142
156
|
}
|
|
143
157
|
function sharedSecret(sealed, privateKey) {
|
|
144
158
|
if (sealed.alg === "secp256k1") {
|
|
@@ -153,13 +167,29 @@ function sharedSecret(sealed, privateKey) {
|
|
|
153
167
|
return shared;
|
|
154
168
|
}
|
|
155
169
|
function unseal(sealed, privateKey, aad) {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
for (const recipient of sealed.recipients) {
|
|
171
|
+
let shared;
|
|
172
|
+
try {
|
|
173
|
+
shared = sharedSecret(recipient, privateKey);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
if (err instanceof DX402Error) throw err;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const wrapKey = hkdf.hkdf(sha2.sha256, shared, aad, HKDF_INFO, 32);
|
|
179
|
+
let cek;
|
|
180
|
+
try {
|
|
181
|
+
cek = aes.gcm(wrapKey, recipient.cekNonce, aad).decrypt(recipient.wrappedCek);
|
|
182
|
+
} catch {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (cek.length !== CEK_LEN) {
|
|
186
|
+
throw new DX402Error(`unwrapped CEK is ${cek.length} bytes, expected ${CEK_LEN}`);
|
|
187
|
+
}
|
|
188
|
+
return aes.gcm(cek, sealed.bodyNonce, aad).decrypt(sealed.ciphertext);
|
|
161
189
|
}
|
|
162
|
-
|
|
190
|
+
throw new DX402Error(
|
|
191
|
+
"no recipient slot opened -- wrong key, or the blob belongs to another payment"
|
|
192
|
+
);
|
|
163
193
|
}
|
|
164
194
|
async function recoverEvidence(evidence, privateKey, options = {}) {
|
|
165
195
|
const key = typeof privateKey === "string" ? hexToBytes(privateKey) : privateKey;
|
|
@@ -243,7 +273,7 @@ function sealEvidence(body, payerKey, paymentIdValue) {
|
|
|
243
273
|
let pos = 0;
|
|
244
274
|
out.set(MAGIC, pos);
|
|
245
275
|
pos += MAGIC.length;
|
|
246
|
-
out[pos++] =
|
|
276
|
+
out[pos++] = FORMAT_V1;
|
|
247
277
|
out[pos++] = algByte;
|
|
248
278
|
out[pos++] = ephemeral.length;
|
|
249
279
|
out.set(ephemeral, pos);
|
|
@@ -259,6 +289,73 @@ function sealEvidence(body, payerKey, paymentIdValue) {
|
|
|
259
289
|
out.set(ciphertext, pos);
|
|
260
290
|
return out;
|
|
261
291
|
}
|
|
292
|
+
var ANCHOR_DOMAIN_NAME = "DX402 Anchor";
|
|
293
|
+
var ANCHOR_DOMAIN_VERSION = "1";
|
|
294
|
+
var ANCHOR_TYPE = "Dx402AnchorAuthorization(bytes32 paymentId,bytes32 contentHash,string pointer,address payee)";
|
|
295
|
+
var EIP712_DOMAIN_TYPE = "EIP712Domain(string name,string version,uint256 chainId)";
|
|
296
|
+
var ZERO_ADDRESS = "0x" + "00".repeat(20);
|
|
297
|
+
function concatBytes(...parts) {
|
|
298
|
+
const total = parts.reduce((n, p) => n + p.length, 0);
|
|
299
|
+
const out = new Uint8Array(total);
|
|
300
|
+
let pos = 0;
|
|
301
|
+
for (const p of parts) {
|
|
302
|
+
out.set(p, pos);
|
|
303
|
+
pos += p.length;
|
|
304
|
+
}
|
|
305
|
+
return out;
|
|
306
|
+
}
|
|
307
|
+
function uint256(n) {
|
|
308
|
+
const out = new Uint8Array(32);
|
|
309
|
+
let v = BigInt(n);
|
|
310
|
+
for (let i = 31; i >= 0 && v > 0n; i--) {
|
|
311
|
+
out[i] = Number(v & 0xffn);
|
|
312
|
+
v >>= 8n;
|
|
313
|
+
}
|
|
314
|
+
return out;
|
|
315
|
+
}
|
|
316
|
+
function anchorDigest(paymentId2, contentHash2, pointer, payee, chainId) {
|
|
317
|
+
const b32 = (value, field) => {
|
|
318
|
+
const raw = hexToBytes(value);
|
|
319
|
+
if (raw.length !== 32) throw new DX402Error(`${field} must be 32 bytes, got ${raw.length}`);
|
|
320
|
+
return raw;
|
|
321
|
+
};
|
|
322
|
+
const addr = hexToBytes(payee);
|
|
323
|
+
if (addr.length !== 20) {
|
|
324
|
+
throw new DX402Error(`payee must be a 20-byte address, got ${addr.length}`);
|
|
325
|
+
}
|
|
326
|
+
const enc = new TextEncoder();
|
|
327
|
+
const domainSeparator = sha3.keccak_256(
|
|
328
|
+
concatBytes(
|
|
329
|
+
sha3.keccak_256(enc.encode(EIP712_DOMAIN_TYPE)),
|
|
330
|
+
sha3.keccak_256(enc.encode(ANCHOR_DOMAIN_NAME)),
|
|
331
|
+
sha3.keccak_256(enc.encode(ANCHOR_DOMAIN_VERSION)),
|
|
332
|
+
uint256(chainId)
|
|
333
|
+
)
|
|
334
|
+
);
|
|
335
|
+
const structHash = sha3.keccak_256(
|
|
336
|
+
concatBytes(
|
|
337
|
+
sha3.keccak_256(enc.encode(ANCHOR_TYPE)),
|
|
338
|
+
b32(paymentId2, "paymentId"),
|
|
339
|
+
b32(contentHash2, "contentHash"),
|
|
340
|
+
sha3.keccak_256(enc.encode(pointer)),
|
|
341
|
+
new Uint8Array(12),
|
|
342
|
+
addr
|
|
343
|
+
)
|
|
344
|
+
);
|
|
345
|
+
return sha3.keccak_256(concatBytes(new Uint8Array([25, 1]), domainSeparator, structHash));
|
|
346
|
+
}
|
|
347
|
+
function signAnchorEd25519(privateKey, paymentId2, contentHash2, pointer = "") {
|
|
348
|
+
if (privateKey.length !== 32) {
|
|
349
|
+
throw new DX402Error(`ed25519 seed must be 32 bytes, got ${privateKey.length}`);
|
|
350
|
+
}
|
|
351
|
+
const digest = anchorDigest(paymentId2, contentHash2, pointer, ZERO_ADDRESS, 0);
|
|
352
|
+
return "0x" + bytesToHex(ed25519.ed25519.sign(digest, privateKey));
|
|
353
|
+
}
|
|
354
|
+
function signAnchorEvm(privateKey, paymentId2, contentHash2, pointer, payee, chainId) {
|
|
355
|
+
const digest = anchorDigest(paymentId2, contentHash2, pointer, payee, chainId);
|
|
356
|
+
const sig = secp256k1.secp256k1.sign(digest, privateKey);
|
|
357
|
+
return "0x" + bytesToHex(sig.toCompactRawBytes()) + (sig.recovery === 1 ? "01" : "00");
|
|
358
|
+
}
|
|
262
359
|
|
|
263
360
|
// src/types/index.ts
|
|
264
361
|
var CAIP2_IDENTIFIERS = {
|
|
@@ -3088,7 +3185,7 @@ function buildSignatureParams(params) {
|
|
|
3088
3185
|
parts.push(`alg="${ALG}"`);
|
|
3089
3186
|
return parts.join(";");
|
|
3090
3187
|
}
|
|
3091
|
-
var
|
|
3188
|
+
var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
|
|
3092
3189
|
var USDC_DECIMALS = 6;
|
|
3093
3190
|
var ESCROW_DEPOSIT_LIMIT_USD = 100;
|
|
3094
3191
|
var OPERATOR_FEE_BPS = 1300;
|
|
@@ -3137,7 +3234,7 @@ function computeEscrowNonce(chainId, escrowAddress, paymentInfoTypehash, pi) {
|
|
|
3137
3234
|
const coder = ethers.ethers.AbiCoder.defaultAbiCoder();
|
|
3138
3235
|
const piTuple = [
|
|
3139
3236
|
ethers.ethers.getAddress(pi.operator),
|
|
3140
|
-
|
|
3237
|
+
ZERO_ADDRESS2,
|
|
3141
3238
|
// payer = 0 for the payer-agnostic hash
|
|
3142
3239
|
ethers.ethers.getAddress(pi.receiver),
|
|
3143
3240
|
ethers.ethers.getAddress(pi.token),
|
|
@@ -4062,6 +4159,8 @@ exports.X402Client = X402Client;
|
|
|
4062
4159
|
exports.X402Error = X402Error;
|
|
4063
4160
|
exports.X402_CORS_HEADERS = X402_CORS_HEADERS;
|
|
4064
4161
|
exports.X402_HEADER_NAMES = X402_HEADER_NAMES;
|
|
4162
|
+
exports.ZERO_ADDRESS = ZERO_ADDRESS;
|
|
4163
|
+
exports.anchorDigest = anchorDigest;
|
|
4065
4164
|
exports.buildEscrowPreAuth = buildEscrowPreAuth;
|
|
4066
4165
|
exports.buildPaymentRequirements = buildPaymentRequirements;
|
|
4067
4166
|
exports.buildSettleRequest = buildSettleRequest;
|
|
@@ -4132,6 +4231,9 @@ exports.parseTrafficEvent = parseTrafficEvent;
|
|
|
4132
4231
|
exports.payerKeyFromEvmSignature = payerKeyFromEvmSignature;
|
|
4133
4232
|
exports.recoverEvidence = recoverEvidence;
|
|
4134
4233
|
exports.sealEvidence = sealEvidence;
|
|
4234
|
+
exports.sealedRoles = sealedRoles;
|
|
4235
|
+
exports.signAnchorEd25519 = signAnchorEd25519;
|
|
4236
|
+
exports.signAnchorEvm = signAnchorEvm;
|
|
4135
4237
|
exports.signRequest = signRequest;
|
|
4136
4238
|
exports.signRequestWithSigner = signRequestWithSigner;
|
|
4137
4239
|
exports.signRequestWithWallet = signRequestWithWallet;
|