@heyanon-arp/sdk 0.0.4 → 0.0.5
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/did/document.d.ts +4 -6
- package/dist/did/index.d.ts +1 -1
- package/dist/escrow/condition-hash.d.ts +41 -54
- package/dist/escrow/create-lock.d.ts +1 -1
- package/dist/escrow/index.d.ts +1 -1
- package/dist/escrow/lock-id.d.ts +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +20 -79
- package/dist/index.mjs +20 -73
- package/dist/purpose.d.ts +1 -3
- package/dist/settlement/index.d.ts +1 -1
- package/dist/settlement/token-program.d.ts +22 -35
- package/dist/types/body.d.ts +14 -51
- package/dist/types/envelope.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
- package/dist/webhook/index.d.ts +0 -3
- package/dist/webhook/payload-types.d.ts +0 -198
- package/dist/webhook/webhook.d.ts +0 -158
package/dist/did/document.d.ts
CHANGED
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
* `identity_pubkey` from the document (not `decoded(did)`) because
|
|
6
6
|
* `identity_pubkey` may have rotated since registration while the DID
|
|
7
7
|
* itself stays stable.
|
|
8
|
+
*
|
|
9
|
+
* No `service[]` endpoint: delivery is server-mediated and agents
|
|
10
|
+
* pull (poll/SSE inbox), so there is no per-agent inbound endpoint
|
|
11
|
+
* to advertise.
|
|
8
12
|
*/
|
|
9
13
|
export interface DidDocument {
|
|
10
14
|
id: string;
|
|
11
15
|
verificationMethod: VerificationMethod[];
|
|
12
|
-
service: ServiceEndpoint[];
|
|
13
16
|
metadata: {
|
|
14
17
|
key_mode: KeyMode;
|
|
15
18
|
owner_attestation_id: string;
|
|
@@ -23,8 +26,3 @@ export interface VerificationMethod {
|
|
|
23
26
|
controller: string;
|
|
24
27
|
publicKeyMultibase: string;
|
|
25
28
|
}
|
|
26
|
-
export interface ServiceEndpoint {
|
|
27
|
-
id: string;
|
|
28
|
-
type: 'ARPEndpoint';
|
|
29
|
-
serviceEndpoint: string;
|
|
30
|
-
}
|
package/dist/did/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { formatDid, parseDid, isValidDid } from './format';
|
|
2
|
-
export type { DidDocument, KeyMode, VerificationMethod
|
|
2
|
+
export type { DidDocument, KeyMode, VerificationMethod } from './document';
|
|
@@ -1,79 +1,66 @@
|
|
|
1
1
|
import type { AssetIdentifier } from '../types';
|
|
2
2
|
/**
|
|
3
|
-
* Subset of
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
3
|
+
* Subset of DELEGATION terms that the on-chain `condition_hash` binds:
|
|
4
|
+
* - `delegationId` is the identity binding — it is the root of the
|
|
5
|
+
* on-chain derivation chain (`lock_id = sha256("arp-lock-v1" ||
|
|
6
|
+
* delegationId_bytes16)`), so it is known before `create_lock` is
|
|
7
|
+
* built → NOT circular.
|
|
8
|
+
* - `currency` is the single `delegation.currency` field (the one
|
|
9
|
+
* asset for both rate + amount); the projection reads the REAL
|
|
10
|
+
* delegation field, so a raw delegation row binds currency with no
|
|
11
|
+
* caller pre-mapping.
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
13
|
+
* `amount` is intentionally NOT here — the concrete locked amount AND its
|
|
14
|
+
* mint are bound at settlement via the digest (`buildReleaseDigest` binds
|
|
15
|
+
* `amount` u64 + `mint` 32B) and on-chain in the Lock account. The
|
|
16
|
+
* condition_hash binds the agreed TERMS (scope / pricing / settlement /
|
|
17
|
+
* rate / unit / currency), not the amount.
|
|
18
|
+
*
|
|
19
|
+
* The shape MUST match server-side + CLI derivation byte-for-byte; the
|
|
20
|
+
* golden vectors in `condition-hash.test.ts` pin it.
|
|
15
21
|
*/
|
|
16
|
-
export interface
|
|
17
|
-
|
|
18
|
-
version: number;
|
|
22
|
+
export interface DelegationTermsSubset {
|
|
23
|
+
delegationId: string;
|
|
19
24
|
scopeSummary: string;
|
|
20
25
|
pricingModel: string;
|
|
21
26
|
settlementModel: string;
|
|
22
27
|
rateAmount?: string;
|
|
23
|
-
rateCurrency?: AssetIdentifier;
|
|
24
28
|
rateUnit?: string;
|
|
25
|
-
|
|
29
|
+
currency?: AssetIdentifier;
|
|
26
30
|
}
|
|
27
31
|
/**
|
|
28
|
-
* Loose input
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* contract shape grows new fields.
|
|
34
|
-
*
|
|
35
|
-
* All fields are typed as optional here so a row pulled directly from
|
|
36
|
-
* Mongoose (where most contract fields are optional in the schema)
|
|
37
|
-
* fits the shape. The actual validity check happens inside
|
|
38
|
-
* `deriveConditionHash`, which throws if a required projected field
|
|
39
|
-
* is undefined — that surface is the right place to fail because
|
|
40
|
-
* `condition_hash` cannot be derived without it.
|
|
32
|
+
* Loose input for `deriveDelegationConditionHash`. Callers pass a whole
|
|
33
|
+
* delegation row / DTO; we project onto `DelegationTermsSubset` so extra
|
|
34
|
+
* fields (`state`, `relationshipId`, `amount`, timestamps, decline
|
|
35
|
+
* metadata, …) can NEVER influence the hash. Defensive projection:
|
|
36
|
+
* extra fields can NEVER influence the hash.
|
|
41
37
|
*/
|
|
42
|
-
export interface
|
|
43
|
-
|
|
44
|
-
version?: number;
|
|
38
|
+
export interface DelegationTermsInput {
|
|
39
|
+
delegationId?: string;
|
|
45
40
|
scopeSummary?: string;
|
|
46
41
|
pricingModel?: string;
|
|
47
42
|
settlementModel?: string;
|
|
48
43
|
rateAmount?: string;
|
|
49
|
-
rateCurrency?: AssetIdentifier;
|
|
50
44
|
rateUnit?: string;
|
|
51
|
-
|
|
45
|
+
currency?: AssetIdentifier;
|
|
52
46
|
[other: string]: unknown;
|
|
53
47
|
}
|
|
54
48
|
/**
|
|
55
|
-
* Compute the 32-byte sha256 condition_hash that binds an on-chain
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* The function performs an EXPLICIT FIELD PROJECTION before
|
|
59
|
-
* canonicalisation. Even if the caller passes a full contract row
|
|
60
|
-
* with `state`, `createdAt`, `declineReason`, etc., only the eight
|
|
61
|
-
* whitelisted fields are hashed. This is critical for digest
|
|
62
|
-
* stability across SDK versions: adding a field to the public
|
|
63
|
-
* contract shape MUST NOT change `condition_hash` values for existing
|
|
64
|
-
* contracts.
|
|
49
|
+
* Compute the 32-byte sha256 condition_hash that binds an on-chain lock
|
|
50
|
+
* to the off-chain DELEGATION terms.
|
|
65
51
|
*
|
|
66
|
-
* Wire procedure
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
52
|
+
* Wire procedure — explicit field
|
|
53
|
+
* projection → JCS canonicalize (`canonicalBytes`) → sha256. The
|
|
54
|
+
* projection is the security boundary: adding a field to the public
|
|
55
|
+
* delegation shape MUST NOT change `condition_hash` for existing
|
|
56
|
+
* delegations.
|
|
70
57
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* on-chain.
|
|
58
|
+
* Runs in THREE places that MUST agree byte-for-byte: the buyer's
|
|
59
|
+
* offer-prep (CLI), the server's `delegation.offer` validation
|
|
60
|
+
* (recompute + compare to the on-chain bound hash), and the settlement
|
|
61
|
+
* digest reconstruction. Any field-set drift produces a different hash
|
|
62
|
+
* → Ed25519 settlement verify fails on-chain.
|
|
76
63
|
*
|
|
77
64
|
* @returns 32-byte condition_hash
|
|
78
65
|
*/
|
|
79
|
-
export declare function
|
|
66
|
+
export declare function deriveDelegationConditionHash(delegation: DelegationTermsInput): Uint8Array;
|
|
@@ -4,7 +4,7 @@ export interface CreateLockArgs {
|
|
|
4
4
|
lockId: Uint8Array;
|
|
5
5
|
/** u64 amount in base units. */
|
|
6
6
|
amount: bigint;
|
|
7
|
-
/** 32-byte condition_hash (
|
|
7
|
+
/** 32-byte condition_hash (deriveDelegationConditionHash output). */
|
|
8
8
|
conditionHash: Uint8Array;
|
|
9
9
|
/** u64 unix-seconds expiry. */
|
|
10
10
|
expiry: bigint;
|
package/dist/escrow/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { deriveLockId, delegationIdToBytes16, bytes16ToDelegationId } from './lock-id';
|
|
2
|
-
export {
|
|
2
|
+
export { deriveDelegationConditionHash, type DelegationTermsSubset, type DelegationTermsInput } from './condition-hash';
|
|
3
3
|
export { parseCaip19SolanaAssetId, type ParsedSolanaAssetId } from './caip19';
|
|
4
4
|
export { buildCreateLockIxData, computeCreateLockDiscriminator, CREATE_LOCK_DISCRIMINATOR, type CreateLockArgs, } from './create-lock';
|
package/dist/escrow/lock-id.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export declare function deriveLockId(delegationId: string): Uint8Array;
|
|
|
41
41
|
* Either way, the same 16-byte representation comes out (UUID body is
|
|
42
42
|
* the lock-id substrate; the `del_` prefix is a wrapper that exists
|
|
43
43
|
* only so the wire shape is unambiguously a delegation reference vs.
|
|
44
|
-
* a
|
|
44
|
+
* a receipt id elsewhere in the canonical JSON). Pushing
|
|
45
45
|
* this normalization down into the SDK gives every caller — CLI,
|
|
46
46
|
* server, future TypeScript clients — a single source of truth.
|
|
47
47
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
* - envelope — sign / verify envelope (steps 4-6 of protocol verification)
|
|
10
10
|
* - cosignature — receipt + dispute-response co-signatures
|
|
11
11
|
* - challenge — ARP-CHALLENGE-v1 ownership proof (registration / rotation)
|
|
12
|
-
* - webhook — ARP-WEBHOOK-v1 HMAC header (outbox delivery)
|
|
13
12
|
* - attestation — scrypt key-link + key-rotation attestations
|
|
14
13
|
* - server-chain — signed_message_hash, server_event_hash, audit walker
|
|
15
14
|
* - settlement — ARP-SOLANA-* digest stubs (V1.5)
|
|
@@ -27,7 +26,6 @@ export * from './did';
|
|
|
27
26
|
export * from './envelope';
|
|
28
27
|
export * from './cosignature';
|
|
29
28
|
export * from './challenge';
|
|
30
|
-
export * from './webhook';
|
|
31
29
|
export * from './attestation';
|
|
32
30
|
export * from './server-chain';
|
|
33
31
|
export * from './settlement';
|
package/dist/index.js
CHANGED
|
@@ -160,8 +160,6 @@ var Purpose = {
|
|
|
160
160
|
RECEIPT: "ARP-RECEIPT-v1",
|
|
161
161
|
/** Dispute response co-signature payload. */
|
|
162
162
|
DISPUTE_RESPONSE: "ARP-DISPUTE-RESPONSE-v1",
|
|
163
|
-
/** Webhook delivery HMAC signature payload. */
|
|
164
|
-
WEBHOOK: "ARP-WEBHOOK-v1",
|
|
165
163
|
/** Identity ownership challenge proof. */
|
|
166
164
|
CHALLENGE: "ARP-CHALLENGE-v1",
|
|
167
165
|
/** Verifiable Credential issued by the platform. */
|
|
@@ -267,54 +265,6 @@ function verifyChallenge(challengeBytes, signature, identityPubkey) {
|
|
|
267
265
|
if (signature.length !== 64) return false;
|
|
268
266
|
return verify2(signature, buildSigningInput(challengeBytes), identityPubkey);
|
|
269
267
|
}
|
|
270
|
-
function buildWebhookSignatureHeader(input, sharedSecret) {
|
|
271
|
-
const digest = sha2.sha256(canonicalBytes(input));
|
|
272
|
-
const mac = hmac.hmac(sha2.sha256, sharedSecret, digest);
|
|
273
|
-
return `${Purpose.WEBHOOK}=${base.base64.encode(mac)}`;
|
|
274
|
-
}
|
|
275
|
-
function verifyWebhookSignatureHeader(headerValue, input, sharedSecret) {
|
|
276
|
-
const secrets = Array.isArray(sharedSecret) ? sharedSecret : [sharedSecret];
|
|
277
|
-
if (secrets.length === 0) return false;
|
|
278
|
-
let matched = false;
|
|
279
|
-
for (const secret of secrets) {
|
|
280
|
-
const expected = buildWebhookSignatureHeader(input, secret);
|
|
281
|
-
matched = constantTimeEqual(expected, headerValue) || matched;
|
|
282
|
-
}
|
|
283
|
-
return matched;
|
|
284
|
-
}
|
|
285
|
-
function verifyAndCheckPayload(args) {
|
|
286
|
-
const tolerance = args.servedAtToleranceMs === void 0 ? 5 * 6e4 : args.servedAtToleranceMs;
|
|
287
|
-
if (tolerance !== null) {
|
|
288
|
-
const servedAtMs = Date.parse(args.input.served_at);
|
|
289
|
-
const nowMs = (args.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
290
|
-
if (Number.isNaN(servedAtMs) || Math.abs(nowMs - servedAtMs) > tolerance) return "stale_attempt";
|
|
291
|
-
}
|
|
292
|
-
const fullInput = { ...args.input, payload_sha256: sha256HexLower(args.rawBody) };
|
|
293
|
-
if (!verifyWebhookSignatureHeader(args.headerValue, fullInput, args.secrets)) return "invalid_signature";
|
|
294
|
-
return "ok";
|
|
295
|
-
}
|
|
296
|
-
function isProbeRequest(headers) {
|
|
297
|
-
for (const [name, value] of Object.entries(headers)) {
|
|
298
|
-
if (name.toLowerCase() !== "x-arp-probe") continue;
|
|
299
|
-
const v = Array.isArray(value) ? value[0] : value;
|
|
300
|
-
if (v === "1" || v === "true") return true;
|
|
301
|
-
}
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
function sha256HexLower(bytes) {
|
|
305
|
-
const digest = sha2.sha256(bytes);
|
|
306
|
-
let hex = "";
|
|
307
|
-
for (const b of digest) hex += b.toString(16).padStart(2, "0");
|
|
308
|
-
return hex;
|
|
309
|
-
}
|
|
310
|
-
function constantTimeEqual(a, b) {
|
|
311
|
-
if (a.length !== b.length) return false;
|
|
312
|
-
let diff = 0;
|
|
313
|
-
for (let i = 0; i < a.length; i++) {
|
|
314
|
-
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
315
|
-
}
|
|
316
|
-
return diff === 0;
|
|
317
|
-
}
|
|
318
268
|
|
|
319
269
|
// src/types/identity.ts
|
|
320
270
|
var SCRYPT_PARAMS = {
|
|
@@ -611,16 +561,15 @@ function u16LE(value) {
|
|
|
611
561
|
var SPL_TOKEN_PROGRAM_ID_BASE58 = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
612
562
|
var TOKEN_2022_PROGRAM_ID_BASE58 = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
|
|
613
563
|
function detectTokenProgramFromOwner(mintAccountOwnerBase58) {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
`detectTokenProgram: unsupported mint owner ${mintAccountOwnerBase58}; ARP escrow supports legacy SPL Token (${SPL_TOKEN_PROGRAM_ID_BASE58}) and Token-2022 (${TOKEN_2022_PROGRAM_ID_BASE58}) only`
|
|
622
|
-
);
|
|
564
|
+
if (mintAccountOwnerBase58 === SPL_TOKEN_PROGRAM_ID_BASE58) {
|
|
565
|
+
return { kind: "legacy", programIdBase58: SPL_TOKEN_PROGRAM_ID_BASE58 };
|
|
566
|
+
}
|
|
567
|
+
if (mintAccountOwnerBase58 === TOKEN_2022_PROGRAM_ID_BASE58) {
|
|
568
|
+
throw new Error(
|
|
569
|
+
`detectTokenProgram: mint owner is the Token-2022 program (${TOKEN_2022_PROGRAM_ID_BASE58}), which ARP escrow does not support; use a legacy SPL Token mint (${SPL_TOKEN_PROGRAM_ID_BASE58}) or native SOL`
|
|
570
|
+
);
|
|
623
571
|
}
|
|
572
|
+
throw new Error(`detectTokenProgram: unsupported mint owner ${mintAccountOwnerBase58}; ARP escrow supports legacy SPL Token (${SPL_TOKEN_PROGRAM_ID_BASE58}) only`);
|
|
624
573
|
}
|
|
625
574
|
function detectTokenProgramFromOwnerBytes(mintAccountOwnerBytes) {
|
|
626
575
|
if (mintAccountOwnerBytes.length !== 32) {
|
|
@@ -769,24 +718,22 @@ function resolveAsset(input) {
|
|
|
769
718
|
return null;
|
|
770
719
|
}
|
|
771
720
|
var WELL_KNOWN_ASSET_KEYS = Object.keys(WELL_KNOWN_ASSETS);
|
|
772
|
-
function
|
|
773
|
-
const required = ["
|
|
721
|
+
function deriveDelegationConditionHash(delegation) {
|
|
722
|
+
const required = ["delegationId", "scopeSummary", "pricingModel", "settlementModel"];
|
|
774
723
|
for (const field of required) {
|
|
775
|
-
if (
|
|
776
|
-
throw new Error(`
|
|
724
|
+
if (delegation[field] === void 0) {
|
|
725
|
+
throw new Error(`deriveDelegationConditionHash: required field '${String(field)}' is missing from the delegation input`);
|
|
777
726
|
}
|
|
778
727
|
}
|
|
779
728
|
const subset = {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
settlementModel: contract.settlementModel
|
|
729
|
+
delegationId: delegation.delegationId,
|
|
730
|
+
scopeSummary: delegation.scopeSummary,
|
|
731
|
+
pricingModel: delegation.pricingModel,
|
|
732
|
+
settlementModel: delegation.settlementModel
|
|
785
733
|
};
|
|
786
|
-
if (
|
|
787
|
-
if (
|
|
788
|
-
if (
|
|
789
|
-
if (contract.allowedDelegationTags !== void 0) subset.allowedDelegationTags = contract.allowedDelegationTags;
|
|
734
|
+
if (delegation.rateAmount !== void 0) subset.rateAmount = delegation.rateAmount;
|
|
735
|
+
if (delegation.rateUnit !== void 0) subset.rateUnit = delegation.rateUnit;
|
|
736
|
+
if (delegation.currency !== void 0) subset.currency = delegation.currency;
|
|
790
737
|
const bytes = canonicalBytes(subset);
|
|
791
738
|
return sha2.sha256(bytes);
|
|
792
739
|
}
|
|
@@ -866,7 +813,6 @@ exports.SETTLEMENT_PURPOSES = SETTLEMENT_PURPOSES;
|
|
|
866
813
|
exports.SLIP44_SOLANA = SLIP44_SOLANA;
|
|
867
814
|
exports.SOLANA_CLUSTER_IDS = SOLANA_CLUSTER_IDS;
|
|
868
815
|
exports.SPL_TOKEN_PROGRAM_ID_BASE58 = SPL_TOKEN_PROGRAM_ID_BASE58;
|
|
869
|
-
exports.TOKEN_2022_PROGRAM_ID_BASE58 = TOKEN_2022_PROGRAM_ID_BASE58;
|
|
870
816
|
exports.USDC_MINTS = USDC_MINTS;
|
|
871
817
|
exports.WELL_KNOWN_ASSETS = WELL_KNOWN_ASSETS;
|
|
872
818
|
exports.WELL_KNOWN_ASSET_KEYS = WELL_KNOWN_ASSET_KEYS;
|
|
@@ -876,14 +822,13 @@ exports.buildCreateLockIxData = buildCreateLockIxData;
|
|
|
876
822
|
exports.buildPartialReleaseDigest = buildPartialReleaseDigest;
|
|
877
823
|
exports.buildRefundDigest = buildRefundDigest;
|
|
878
824
|
exports.buildReleaseDigest = buildReleaseDigest;
|
|
879
|
-
exports.buildWebhookSignatureHeader = buildWebhookSignatureHeader;
|
|
880
825
|
exports.bytes16ToDelegationId = bytes16ToDelegationId;
|
|
881
826
|
exports.canonicalBytes = canonicalBytes;
|
|
882
827
|
exports.canonicalJson = canonicalJson;
|
|
883
828
|
exports.canonicalSha256Hex = canonicalSha256Hex;
|
|
884
829
|
exports.computeCreateLockDiscriminator = computeCreateLockDiscriminator;
|
|
885
830
|
exports.delegationIdToBytes16 = delegationIdToBytes16;
|
|
886
|
-
exports.
|
|
831
|
+
exports.deriveDelegationConditionHash = deriveDelegationConditionHash;
|
|
887
832
|
exports.deriveLockId = deriveLockId;
|
|
888
833
|
exports.deriveScryptKey = deriveScryptKey;
|
|
889
834
|
exports.detectTokenProgramFromOwner = detectTokenProgramFromOwner;
|
|
@@ -895,7 +840,6 @@ exports.generateKeyPair = generateKeyPair;
|
|
|
895
840
|
exports.getPublicKey = getPublicKey2;
|
|
896
841
|
exports.isAssetIdentifier = isAssetIdentifier;
|
|
897
842
|
exports.isDeclineReason = isDeclineReason;
|
|
898
|
-
exports.isProbeRequest = isProbeRequest;
|
|
899
843
|
exports.isValidDid = isValidDid;
|
|
900
844
|
exports.parseCaip19SolanaAssetId = parseCaip19SolanaAssetId;
|
|
901
845
|
exports.parseDid = parseDid;
|
|
@@ -906,7 +850,6 @@ exports.scryptPasswordProofSign = scryptPasswordProofSign;
|
|
|
906
850
|
exports.scryptPasswordProofVerify = scryptPasswordProofVerify;
|
|
907
851
|
exports.senderNonce = senderNonce;
|
|
908
852
|
exports.serverEventHash = serverEventHash;
|
|
909
|
-
exports.sha256HexLower = sha256HexLower;
|
|
910
853
|
exports.sign = sign2;
|
|
911
854
|
exports.signChallenge = signChallenge;
|
|
912
855
|
exports.signCosignature = signCosignature;
|
|
@@ -916,10 +859,8 @@ exports.signKeyRotationAttestation = signKeyRotationAttestation;
|
|
|
916
859
|
exports.signedMessageHash = signedMessageHash;
|
|
917
860
|
exports.uuidV4 = uuidV4;
|
|
918
861
|
exports.verify = verify2;
|
|
919
|
-
exports.verifyAndCheckPayload = verifyAndCheckPayload;
|
|
920
862
|
exports.verifyChallenge = verifyChallenge;
|
|
921
863
|
exports.verifyCosignature = verifyCosignature;
|
|
922
864
|
exports.verifyEnvelope = verifyEnvelope;
|
|
923
865
|
exports.verifyKeyLinkAttestation = verifyKeyLinkAttestation;
|
|
924
866
|
exports.verifyKeyRotationAttestation = verifyKeyRotationAttestation;
|
|
925
|
-
exports.verifyWebhookSignatureHeader = verifyWebhookSignatureHeader;
|
package/dist/index.mjs
CHANGED
|
@@ -135,8 +135,6 @@ var Purpose = {
|
|
|
135
135
|
RECEIPT: "ARP-RECEIPT-v1",
|
|
136
136
|
/** Dispute response co-signature payload. */
|
|
137
137
|
DISPUTE_RESPONSE: "ARP-DISPUTE-RESPONSE-v1",
|
|
138
|
-
/** Webhook delivery HMAC signature payload. */
|
|
139
|
-
WEBHOOK: "ARP-WEBHOOK-v1",
|
|
140
138
|
/** Identity ownership challenge proof. */
|
|
141
139
|
CHALLENGE: "ARP-CHALLENGE-v1",
|
|
142
140
|
/** Verifiable Credential issued by the platform. */
|
|
@@ -242,54 +240,6 @@ function verifyChallenge(challengeBytes, signature, identityPubkey) {
|
|
|
242
240
|
if (signature.length !== 64) return false;
|
|
243
241
|
return verify2(signature, buildSigningInput(challengeBytes), identityPubkey);
|
|
244
242
|
}
|
|
245
|
-
function buildWebhookSignatureHeader(input, sharedSecret) {
|
|
246
|
-
const digest = sha256(canonicalBytes(input));
|
|
247
|
-
const mac = hmac(sha256, sharedSecret, digest);
|
|
248
|
-
return `${Purpose.WEBHOOK}=${base64.encode(mac)}`;
|
|
249
|
-
}
|
|
250
|
-
function verifyWebhookSignatureHeader(headerValue, input, sharedSecret) {
|
|
251
|
-
const secrets = Array.isArray(sharedSecret) ? sharedSecret : [sharedSecret];
|
|
252
|
-
if (secrets.length === 0) return false;
|
|
253
|
-
let matched = false;
|
|
254
|
-
for (const secret of secrets) {
|
|
255
|
-
const expected = buildWebhookSignatureHeader(input, secret);
|
|
256
|
-
matched = constantTimeEqual(expected, headerValue) || matched;
|
|
257
|
-
}
|
|
258
|
-
return matched;
|
|
259
|
-
}
|
|
260
|
-
function verifyAndCheckPayload(args) {
|
|
261
|
-
const tolerance = args.servedAtToleranceMs === void 0 ? 5 * 6e4 : args.servedAtToleranceMs;
|
|
262
|
-
if (tolerance !== null) {
|
|
263
|
-
const servedAtMs = Date.parse(args.input.served_at);
|
|
264
|
-
const nowMs = (args.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
265
|
-
if (Number.isNaN(servedAtMs) || Math.abs(nowMs - servedAtMs) > tolerance) return "stale_attempt";
|
|
266
|
-
}
|
|
267
|
-
const fullInput = { ...args.input, payload_sha256: sha256HexLower(args.rawBody) };
|
|
268
|
-
if (!verifyWebhookSignatureHeader(args.headerValue, fullInput, args.secrets)) return "invalid_signature";
|
|
269
|
-
return "ok";
|
|
270
|
-
}
|
|
271
|
-
function isProbeRequest(headers) {
|
|
272
|
-
for (const [name, value] of Object.entries(headers)) {
|
|
273
|
-
if (name.toLowerCase() !== "x-arp-probe") continue;
|
|
274
|
-
const v = Array.isArray(value) ? value[0] : value;
|
|
275
|
-
if (v === "1" || v === "true") return true;
|
|
276
|
-
}
|
|
277
|
-
return false;
|
|
278
|
-
}
|
|
279
|
-
function sha256HexLower(bytes) {
|
|
280
|
-
const digest = sha256(bytes);
|
|
281
|
-
let hex = "";
|
|
282
|
-
for (const b of digest) hex += b.toString(16).padStart(2, "0");
|
|
283
|
-
return hex;
|
|
284
|
-
}
|
|
285
|
-
function constantTimeEqual(a, b) {
|
|
286
|
-
if (a.length !== b.length) return false;
|
|
287
|
-
let diff = 0;
|
|
288
|
-
for (let i = 0; i < a.length; i++) {
|
|
289
|
-
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
290
|
-
}
|
|
291
|
-
return diff === 0;
|
|
292
|
-
}
|
|
293
243
|
|
|
294
244
|
// src/types/identity.ts
|
|
295
245
|
var SCRYPT_PARAMS = {
|
|
@@ -586,16 +536,15 @@ function u16LE(value) {
|
|
|
586
536
|
var SPL_TOKEN_PROGRAM_ID_BASE58 = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
587
537
|
var TOKEN_2022_PROGRAM_ID_BASE58 = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
|
|
588
538
|
function detectTokenProgramFromOwner(mintAccountOwnerBase58) {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
`detectTokenProgram: unsupported mint owner ${mintAccountOwnerBase58}; ARP escrow supports legacy SPL Token (${SPL_TOKEN_PROGRAM_ID_BASE58}) and Token-2022 (${TOKEN_2022_PROGRAM_ID_BASE58}) only`
|
|
597
|
-
);
|
|
539
|
+
if (mintAccountOwnerBase58 === SPL_TOKEN_PROGRAM_ID_BASE58) {
|
|
540
|
+
return { kind: "legacy", programIdBase58: SPL_TOKEN_PROGRAM_ID_BASE58 };
|
|
541
|
+
}
|
|
542
|
+
if (mintAccountOwnerBase58 === TOKEN_2022_PROGRAM_ID_BASE58) {
|
|
543
|
+
throw new Error(
|
|
544
|
+
`detectTokenProgram: mint owner is the Token-2022 program (${TOKEN_2022_PROGRAM_ID_BASE58}), which ARP escrow does not support; use a legacy SPL Token mint (${SPL_TOKEN_PROGRAM_ID_BASE58}) or native SOL`
|
|
545
|
+
);
|
|
598
546
|
}
|
|
547
|
+
throw new Error(`detectTokenProgram: unsupported mint owner ${mintAccountOwnerBase58}; ARP escrow supports legacy SPL Token (${SPL_TOKEN_PROGRAM_ID_BASE58}) only`);
|
|
599
548
|
}
|
|
600
549
|
function detectTokenProgramFromOwnerBytes(mintAccountOwnerBytes) {
|
|
601
550
|
if (mintAccountOwnerBytes.length !== 32) {
|
|
@@ -744,24 +693,22 @@ function resolveAsset(input) {
|
|
|
744
693
|
return null;
|
|
745
694
|
}
|
|
746
695
|
var WELL_KNOWN_ASSET_KEYS = Object.keys(WELL_KNOWN_ASSETS);
|
|
747
|
-
function
|
|
748
|
-
const required = ["
|
|
696
|
+
function deriveDelegationConditionHash(delegation) {
|
|
697
|
+
const required = ["delegationId", "scopeSummary", "pricingModel", "settlementModel"];
|
|
749
698
|
for (const field of required) {
|
|
750
|
-
if (
|
|
751
|
-
throw new Error(`
|
|
699
|
+
if (delegation[field] === void 0) {
|
|
700
|
+
throw new Error(`deriveDelegationConditionHash: required field '${String(field)}' is missing from the delegation input`);
|
|
752
701
|
}
|
|
753
702
|
}
|
|
754
703
|
const subset = {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
settlementModel: contract.settlementModel
|
|
704
|
+
delegationId: delegation.delegationId,
|
|
705
|
+
scopeSummary: delegation.scopeSummary,
|
|
706
|
+
pricingModel: delegation.pricingModel,
|
|
707
|
+
settlementModel: delegation.settlementModel
|
|
760
708
|
};
|
|
761
|
-
if (
|
|
762
|
-
if (
|
|
763
|
-
if (
|
|
764
|
-
if (contract.allowedDelegationTags !== void 0) subset.allowedDelegationTags = contract.allowedDelegationTags;
|
|
709
|
+
if (delegation.rateAmount !== void 0) subset.rateAmount = delegation.rateAmount;
|
|
710
|
+
if (delegation.rateUnit !== void 0) subset.rateUnit = delegation.rateUnit;
|
|
711
|
+
if (delegation.currency !== void 0) subset.currency = delegation.currency;
|
|
765
712
|
const bytes = canonicalBytes(subset);
|
|
766
713
|
return sha256(bytes);
|
|
767
714
|
}
|
|
@@ -826,4 +773,4 @@ function computeCreateLockDiscriminator() {
|
|
|
826
773
|
return h.slice(0, 8);
|
|
827
774
|
}
|
|
828
775
|
|
|
829
|
-
export { CAIP19_REGEX, COSIGNATURE_PURPOSES, CREATE_LOCK_DISCRIMINATOR, DECLINE_REASONS, PROTECTED_PURPOSES, PURPOSE_PARTIAL_RELEASE_STRING, PURPOSE_REFUND_STRING, PURPOSE_RELEASE_STRING, Purpose, REFUND_REASON_BYTES, SCRYPT_PARAMS, SETTLEMENT_PURPOSES, SLIP44_SOLANA, SOLANA_CLUSTER_IDS, SPL_TOKEN_PROGRAM_ID_BASE58,
|
|
776
|
+
export { CAIP19_REGEX, COSIGNATURE_PURPOSES, CREATE_LOCK_DISCRIMINATOR, DECLINE_REASONS, PROTECTED_PURPOSES, PURPOSE_PARTIAL_RELEASE_STRING, PURPOSE_REFUND_STRING, PURPOSE_RELEASE_STRING, Purpose, REFUND_REASON_BYTES, SCRYPT_PARAMS, SETTLEMENT_PURPOSES, SLIP44_SOLANA, SOLANA_CLUSTER_IDS, SPL_TOKEN_PROGRAM_ID_BASE58, USDC_MINTS, WELL_KNOWN_ASSETS, WELL_KNOWN_ASSET_KEYS, base58btcDecode, base58btcEncode, buildCreateLockIxData, buildPartialReleaseDigest, buildRefundDigest, buildReleaseDigest, bytes16ToDelegationId, canonicalBytes, canonicalJson, canonicalSha256Hex, computeCreateLockDiscriminator, delegationIdToBytes16, deriveDelegationConditionHash, deriveLockId, deriveScryptKey, detectTokenProgramFromOwner, detectTokenProgramFromOwnerBytes, expiresAt, findFirstChainDivergence, formatDid, generateKeyPair, getPublicKey2 as getPublicKey, isAssetIdentifier, isDeclineReason, isValidDid, parseCaip19SolanaAssetId, parseDid, pollUntil, resolveAsset, rfc3339, scryptPasswordProofSign, scryptPasswordProofVerify, senderNonce, serverEventHash, sign2 as sign, signChallenge, signCosignature, signEnvelope, signKeyLinkAttestation, signKeyRotationAttestation, signedMessageHash, uuidV4, verify2 as verify, verifyChallenge, verifyCosignature, verifyEnvelope, verifyKeyLinkAttestation, verifyKeyRotationAttestation };
|
package/dist/purpose.d.ts
CHANGED
|
@@ -17,8 +17,6 @@ export declare const Purpose: {
|
|
|
17
17
|
readonly RECEIPT: "ARP-RECEIPT-v1";
|
|
18
18
|
/** Dispute response co-signature payload. */
|
|
19
19
|
readonly DISPUTE_RESPONSE: "ARP-DISPUTE-RESPONSE-v1";
|
|
20
|
-
/** Webhook delivery HMAC signature payload. */
|
|
21
|
-
readonly WEBHOOK: "ARP-WEBHOOK-v1";
|
|
22
20
|
/** Identity ownership challenge proof. */
|
|
23
21
|
readonly CHALLENGE: "ARP-CHALLENGE-v1";
|
|
24
22
|
/** Verifiable Credential issued by the platform. */
|
|
@@ -45,7 +43,7 @@ export declare const Purpose: {
|
|
|
45
43
|
export type PurposeValue = (typeof Purpose)[keyof typeof Purpose];
|
|
46
44
|
/**
|
|
47
45
|
* `protected.purpose` accepts a subset — the others are payload-level
|
|
48
|
-
* (co-signature / settlement-signature
|
|
46
|
+
* (co-signature / settlement-signature).
|
|
49
47
|
*/
|
|
50
48
|
export declare const PROTECTED_PURPOSES: readonly PurposeValue[];
|
|
51
49
|
export declare const COSIGNATURE_PURPOSES: readonly PurposeValue[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { buildReleaseDigest, buildPartialReleaseDigest, buildRefundDigest, REFUND_REASON_BYTES, PURPOSE_RELEASE_STRING, PURPOSE_PARTIAL_RELEASE_STRING, PURPOSE_REFUND_STRING, } from './settlement';
|
|
2
2
|
export type { ReleaseDigestInput, PartialReleaseDigestInput, RefundDigestInput, RefundReasonByte } from './settlement';
|
|
3
|
-
export { detectTokenProgramFromOwner, detectTokenProgramFromOwnerBytes, SPL_TOKEN_PROGRAM_ID_BASE58
|
|
3
|
+
export { detectTokenProgramFromOwner, detectTokenProgramFromOwnerBytes, SPL_TOKEN_PROGRAM_ID_BASE58 } from './token-program';
|
|
4
4
|
export type { TokenProgramKind, TokenProgramDetection } from './token-program';
|
|
@@ -1,47 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Token
|
|
2
|
+
* Token program detection helpers.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* ARP escrow settles in native SOL or a legacy SPL Token mint. The
|
|
5
|
+
* escrow contract dispatches transfer / close-account CPIs based on the
|
|
6
|
+
* mint's program kind, detected on-chain from the mint account's `owner`
|
|
7
|
+
* field; this helper mirrors that logic for off-chain consumers (tx
|
|
8
|
+
* builders, indexers, decoders).
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* account's owner pubkey (in the wire-level AccountInfoSerialised shape
|
|
13
|
-
* used by getAccountInfo RPC's "encoding=base64" + custom layout). For
|
|
14
|
-
* the more common `getAccountInfo` response, callers should pass the
|
|
15
|
-
* `owner` field directly via a 32-byte buffer.
|
|
10
|
+
* Token-2022 (the token-extensions program) is NOT supported — a mint
|
|
11
|
+
* owned by it is rejected here so it can never enter the escrow flow.
|
|
16
12
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* contexts.
|
|
13
|
+
* The function takes the mint account's `owner` pubkey — the program
|
|
14
|
+
* that minted it — NOT the mint's mint_authority. Confusingly, both are
|
|
15
|
+
* called "owner" in different contexts.
|
|
21
16
|
*/
|
|
22
17
|
/**
|
|
23
18
|
* Legacy SPL Token program ID (base58: `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`).
|
|
24
19
|
*/
|
|
25
20
|
export declare const SPL_TOKEN_PROGRAM_ID_BASE58 = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
26
21
|
/**
|
|
27
|
-
* Token
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* Token program kind. Maps to the new contract's `token_program_kind`
|
|
32
|
-
* u8 emitted on `LockCreated` events
|
|
33
|
-
* (`apps/arp-solana-contract/programs/arp-solana-contract/src/events.rs`):
|
|
34
|
-
* 'native' → 0 (mint == `Pubkey::default()`; the program
|
|
35
|
-
* ignores the token_program slot but Anchor
|
|
36
|
-
* still requires SPL Token or Token-2022 in it)
|
|
37
|
-
* 'legacy' → 1 (legacy SPL Token program)
|
|
38
|
-
* 'token-2022' → 2 (Token-2022 program)
|
|
22
|
+
* Token program kind. Maps to the contract's `token_program_kind` u8 on
|
|
23
|
+
* `LockCreated` events: `native` → 0, `legacy` → 1. (The contract also
|
|
24
|
+
* defines `2` for Token-2022, but ARP no longer supports it — such a
|
|
25
|
+
* lock is rejected before it is tracked, so this type never carries it.)
|
|
39
26
|
*
|
|
40
|
-
* `detectTokenProgramFromOwner`
|
|
41
|
-
*
|
|
42
|
-
*
|
|
27
|
+
* `detectTokenProgramFromOwner` resolves the kind for a NON-native mint
|
|
28
|
+
* by looking at its `.owner` field; native locks are detected from the
|
|
29
|
+
* mint slot value, not from this helper.
|
|
43
30
|
*/
|
|
44
|
-
export type TokenProgramKind = 'legacy' | '
|
|
31
|
+
export type TokenProgramKind = 'legacy' | 'native';
|
|
45
32
|
/**
|
|
46
33
|
* Result of `detectTokenProgram`: the program kind + a typed branding for
|
|
47
34
|
* downstream consumers.
|
|
@@ -55,9 +42,9 @@ export interface TokenProgramDetection {
|
|
|
55
42
|
* Detect a mint's token program kind from its OWNER pubkey (the program
|
|
56
43
|
* that owns the mint account in Solana account terms).
|
|
57
44
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
45
|
+
* Returns `legacy` for a legacy SPL Token mint. Throws for a Token-2022
|
|
46
|
+
* mint (unsupported) or any other owner — escrow would otherwise
|
|
47
|
+
* dispatch a CPI to a surface it does not handle.
|
|
61
48
|
*
|
|
62
49
|
* @param mintAccountOwnerBase58 — the mint account's `.owner` field as
|
|
63
50
|
* a base58 string. From `connection.getAccountInfo(mintPubkey)`, this is
|
package/dist/types/body.d.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { Body, Did, Sha256Hex } from './envelope';
|
|
11
11
|
/**
|
|
12
|
-
* Chain-qualified asset identifier carried by `
|
|
13
|
-
*
|
|
12
|
+
* Chain-qualified asset identifier carried by `delegation.currency`
|
|
13
|
+
* (the single asset for both rate + amount). Replaces the V1 string-enum `'USDC'`
|
|
14
14
|
* placeholder — that shape can't disambiguate `USDC on Solana mainnet`
|
|
15
15
|
* from `USDC on Polygon` or `USDC.e bridged on Avalanche` (all the same
|
|
16
16
|
* ticker, different on-chain assets). It also can't represent native
|
|
@@ -28,14 +28,10 @@ import type { Body, Did, Sha256Hex } from './envelope';
|
|
|
28
28
|
* - `symbol` — short human-readable hint for UI ("USDC", "SOL").
|
|
29
29
|
* Not used for any logic — purely display sugar. Optional.
|
|
30
30
|
*
|
|
31
|
-
* Validation invariants (server-enforced
|
|
32
|
-
* contract and `currency` on the delegation):
|
|
31
|
+
* Validation invariants (server-enforced on `delegation.currency`):
|
|
33
32
|
* • `asset_id` ∈ /^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32}\/[-a-z0-9]{3,8}:[-.%a-zA-Z0-9]{1,128}$/
|
|
34
33
|
* • `decimals` ∈ [0, 18]
|
|
35
34
|
* • `symbol` length ∈ [1, 16] if present
|
|
36
|
-
* • For `delegation.currency`: must match the contract's
|
|
37
|
-
* `rate_currency.asset_id` if the contract specifies one
|
|
38
|
-
* (a delegation under a USDC-priced contract can't quote in SOL).
|
|
39
35
|
*/
|
|
40
36
|
export interface AssetIdentifier {
|
|
41
37
|
/** CAIP-19 chain-qualified asset id — e.g. `solana:5eykt.../spl:EPjFWdd5...` */
|
|
@@ -47,9 +43,8 @@ export interface AssetIdentifier {
|
|
|
47
43
|
}
|
|
48
44
|
/**
|
|
49
45
|
* Closed enum of machine-readable decline reasons used across the
|
|
50
|
-
*
|
|
46
|
+
* two decline sites in V1:
|
|
51
47
|
* • `handshake_response.content.decision === 'decline'`
|
|
52
|
-
* • `contract.content.action === 'decline'`
|
|
53
48
|
* • `delegation.content.action === 'decline'`
|
|
54
49
|
*
|
|
55
50
|
* Required on every decline envelope so the counterparty's reactor
|
|
@@ -77,7 +72,7 @@ export declare const DECLINE_REASONS: readonly DeclineReason[];
|
|
|
77
72
|
export declare function isDeclineReason(v: unknown): v is DeclineReason;
|
|
78
73
|
/**
|
|
79
74
|
* `handshake` — first signed exchange between two agents. Establishes
|
|
80
|
-
* relationship;
|
|
75
|
+
* the relationship; carries no terms (those live on the delegation).
|
|
81
76
|
*/
|
|
82
77
|
export interface HandshakeBody extends Body<HandshakeContent> {
|
|
83
78
|
type: 'handshake';
|
|
@@ -110,32 +105,8 @@ export interface HandshakeResponseContent {
|
|
|
110
105
|
[extra: string]: unknown;
|
|
111
106
|
}
|
|
112
107
|
/**
|
|
113
|
-
* `
|
|
114
|
-
* discriminates lifecycle
|
|
115
|
-
*/
|
|
116
|
-
export interface ContractBody extends Body<ContractContent> {
|
|
117
|
-
type: 'contract';
|
|
118
|
-
}
|
|
119
|
-
export interface ContractContent {
|
|
120
|
-
action: 'proposal' | 'counter' | 'sign' | 'decline';
|
|
121
|
-
contract_id: string;
|
|
122
|
-
version: number;
|
|
123
|
-
scope_summary?: string;
|
|
124
|
-
pricing_model?: 'flat' | 'usage_based';
|
|
125
|
-
settlement_model?: 'prepaid' | 'escrow';
|
|
126
|
-
rate_amount?: string;
|
|
127
|
-
rate_currency?: AssetIdentifier;
|
|
128
|
-
rate_unit?: 'task' | 'thread' | 'handoff';
|
|
129
|
-
allowed_delegation_tags?: string[];
|
|
130
|
-
/** Machine-readable reason — REQUIRED when `action === 'decline'`. See `DeclineReason`. */
|
|
131
|
-
reason?: DeclineReason;
|
|
132
|
-
/** Optional free-text elaboration (e.g. "rate floor 0.20 USDC for current model pricing"). */
|
|
133
|
-
reason_detail?: string;
|
|
134
|
-
[extra: string]: unknown;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* `delegation` — concrete task offer or lifecycle action under an
|
|
138
|
-
* active contract. `action` discriminates the lifecycle event.
|
|
108
|
+
* `delegation` — concrete task offer or lifecycle action. `action`
|
|
109
|
+
* discriminates the lifecycle event.
|
|
139
110
|
*/
|
|
140
111
|
export interface DelegationBody extends Body<DelegationContent> {
|
|
141
112
|
type: 'delegation';
|
|
@@ -143,13 +114,17 @@ export interface DelegationBody extends Body<DelegationContent> {
|
|
|
143
114
|
export interface DelegationContent {
|
|
144
115
|
action: 'offer' | 'accept' | 'decline' | 'cancel';
|
|
145
116
|
delegation_id: string;
|
|
146
|
-
contract_id: string;
|
|
147
117
|
title?: string;
|
|
148
118
|
brief?: Record<string, unknown>;
|
|
149
119
|
acceptance_criteria?: string[];
|
|
150
120
|
deadline?: string;
|
|
151
121
|
amount?: string;
|
|
152
122
|
currency?: AssetIdentifier;
|
|
123
|
+
scope_summary?: string;
|
|
124
|
+
pricing_model?: 'flat' | 'usage_based';
|
|
125
|
+
settlement_model?: 'prepaid' | 'escrow';
|
|
126
|
+
rate_amount?: string;
|
|
127
|
+
rate_unit?: 'task' | 'thread' | 'handoff';
|
|
153
128
|
/** Machine-readable reason — REQUIRED when `action === 'decline'`. See `DeclineReason`. */
|
|
154
129
|
reason?: DeclineReason;
|
|
155
130
|
/** Optional free-text elaboration (e.g. "delegation offer missing required brief.goal field"). */
|
|
@@ -210,18 +185,6 @@ export interface ReceiptContent {
|
|
|
210
185
|
notes_hash?: Sha256Hex;
|
|
211
186
|
[extra: string]: unknown;
|
|
212
187
|
}
|
|
213
|
-
/**
|
|
214
|
-
* `memory_delta` — append-only update to a relationship's shared memory.
|
|
215
|
-
*/
|
|
216
|
-
export interface MemoryDeltaBody extends Body<MemoryDeltaContent> {
|
|
217
|
-
type: 'memory_delta';
|
|
218
|
-
}
|
|
219
|
-
export interface MemoryDeltaContent {
|
|
220
|
-
kind: 'intro' | 'handoff' | 'preference' | 'note' | 'decision' | 'continuity';
|
|
221
|
-
scope: 'thread_only' | 'thread_and_pilot';
|
|
222
|
-
content: string;
|
|
223
|
-
supersedes?: string;
|
|
224
|
-
}
|
|
225
188
|
/**
|
|
226
189
|
* `dispute` — challenge against a delegation outcome. `action`
|
|
227
190
|
* discriminates lifecycle events.
|
|
@@ -336,7 +299,7 @@ export interface SettlementSignatureContent {
|
|
|
336
299
|
* Base-unit decimal-integer string — required when `purpose ===
|
|
337
300
|
* 'ARP-SOLANA-PARTIAL-RELEASE-v1.5'`, omitted (or undefined) for
|
|
338
301
|
* full RELEASE. The server cross-checks it against
|
|
339
|
-
* `receipt.usage.computed_amount` for usage_based
|
|
302
|
+
* `receipt.usage.computed_amount` for usage_based delegations before
|
|
340
303
|
* verifying the digest (same invariant
|
|
341
304
|
* `ESC_USAGE_COMPUTED_AMOUNT_MISMATCH` already guards on the
|
|
342
305
|
* propose-time receipt body).
|
|
@@ -348,7 +311,7 @@ export interface SettlementSignatureContent {
|
|
|
348
311
|
* Union over every standard body type. Consumers can narrow on
|
|
349
312
|
* `body.type` via discriminated dispatch.
|
|
350
313
|
*/
|
|
351
|
-
export type AnyBody = HandshakeBody | HandshakeResponseBody |
|
|
314
|
+
export type AnyBody = HandshakeBody | HandshakeResponseBody | DelegationBody | WorkRequestBody | WorkResponseBody | ReceiptBody | DisputeBody | SettlementSignatureBody;
|
|
352
315
|
/** Receipt co-signature payload — what gets `payload_hash`'d in `attachments.co_signature`. */
|
|
353
316
|
export interface ReceiptCosignPayload {
|
|
354
317
|
purpose: 'ARP-RECEIPT-v1';
|
package/dist/types/envelope.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { PurposeValue } from '../purpose';
|
|
|
3
3
|
* Wire-level types per [00-core/protocol.md](../../../00-core/protocol.md)
|
|
4
4
|
* and [00-core/schemas.md](../../../00-core/schemas.md).
|
|
5
5
|
*
|
|
6
|
-
* Body-type-specific shapes (handshake /
|
|
6
|
+
* Body-type-specific shapes (handshake / delegation / receipt
|
|
7
7
|
* / etc.) live alongside their handlers in the consumer code — keeping
|
|
8
8
|
* them out of this file avoids the SDK becoming a kitchen-sink of every
|
|
9
9
|
* message type. A consumer who needs typed bodies can extend `Envelope<T>`
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { Sha256Hex, Ed25519Sig, Did, ProtectedBlock, Body, Attachments, CoSignature, SettlementSignatures, SettlementParty, EscrowLockAttachment, Envelope, PersistedEvent, } from './envelope';
|
|
2
|
-
export type { HandshakeBody, HandshakeContent, HandshakeResponseBody, HandshakeResponseContent,
|
|
2
|
+
export type { HandshakeBody, HandshakeContent, HandshakeResponseBody, HandshakeResponseContent, DelegationBody, DelegationContent, WorkRequestBody, WorkRequestContent, WorkResponseBody, WorkResponseContent, ReceiptBody, ReceiptContent, DisputeBody, DisputeContent, SettlementSignatureBody, SettlementSignatureContent, AnyBody, ReceiptCosignPayload, DisputeResponseCosignPayload, CosignPayload, DeclineReason, AssetIdentifier, } from './body';
|
|
3
3
|
export { DECLINE_REASONS, isDeclineReason } from './body';
|
|
4
4
|
export type { OwnerSigningMethod, KeyLinkPayload, KeyRotationPayload, ScryptPasswordAttestation } from './identity';
|
|
5
5
|
export { SCRYPT_PARAMS } from './identity';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heyanon-arp/sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "TypeScript SDK for the Agent Relationship Protocol — canonical JSON, Ed25519 envelope sign/verify, did:arp identity, receipt co-signatures, scrypt key attestation, chain-audit helpers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
package/dist/webhook/index.d.ts
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
export { buildWebhookSignatureHeader, isProbeRequest, sha256HexLower, verifyAndCheckPayload, verifyWebhookSignatureHeader } from './webhook';
|
|
2
|
-
export type { WebhookSignableInput, WebhookSignableSource } from './webhook';
|
|
3
|
-
export type { ChainEventRecoveryRow, ChainEventRecoveryRowCommon, ChainPayloadCommon, ChainWebhookPayload, DisputeResolvedWebhookPayload, EnvelopeWebhookPayload, LockCreatedWebhookPayload, LockPartiallyReleasedWebhookPayload, LockRefundedWebhookPayload, LockReleasedWebhookPayload, WebhookPayload, } from './payload-types';
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SDK consumer types for the on-the-wire body the OutboxDeliveryWorker
|
|
3
|
-
* POSTs to a recipient's `webhookUrl`. Mirrors the JSON shape the
|
|
4
|
-
* server's `OutboxDeliveryWorkerService.deliver` builds.
|
|
5
|
-
*
|
|
6
|
-
* The body is a discriminated union on `event_type`:
|
|
7
|
-
*
|
|
8
|
-
* - `envelope.<type>` → `payload` is the envelope `EventPublic`
|
|
9
|
-
* shape (eventId, messageId, sender/recipient DIDs, protected
|
|
10
|
-
* block, body, attachments, server-derived hashes).
|
|
11
|
-
* - `escrow.<event>` → `payload` is the per-event-type chain shape
|
|
12
|
-
* (delegation/lock IDs, amounts, verdict, etc.).
|
|
13
|
-
*
|
|
14
|
-
* Recipient TypeScript consumers `switch (payload.event_type)` to
|
|
15
|
-
* branch exhaustively without `default` — drift on either side
|
|
16
|
-
* surfaces as a missing-case compile error.
|
|
17
|
-
*/
|
|
18
|
-
/**
|
|
19
|
-
* What the server-side `events` collection row looks like on the wire
|
|
20
|
-
* after `buildEnvelopeDeliveryPayload` formatting. Recipients see the
|
|
21
|
-
* full canonical envelope so they can re-verify the signature locally
|
|
22
|
-
* with the SDK (same call they'd make on `/inbox?since=` rows).
|
|
23
|
-
*
|
|
24
|
-
* Body shape varies by `type` — keep `body` open (the SDK's
|
|
25
|
-
* type-narrowing for each `BodyType` lives in
|
|
26
|
-
* `packages/sdk/src/envelope/*` and is consumed when the recipient
|
|
27
|
-
* branches on body.type).
|
|
28
|
-
*/
|
|
29
|
-
export interface EnvelopeWebhookPayload {
|
|
30
|
-
event_type: `envelope.${string}`;
|
|
31
|
-
delivery_id: string;
|
|
32
|
-
attempt_n: number;
|
|
33
|
-
served_at: string;
|
|
34
|
-
payload: {
|
|
35
|
-
eventId: string;
|
|
36
|
-
messageId: string;
|
|
37
|
-
senderDid: string;
|
|
38
|
-
recipientDid: string;
|
|
39
|
-
relationshipId: string;
|
|
40
|
-
senderSequence: number;
|
|
41
|
-
protocolVersion: string;
|
|
42
|
-
purpose: string;
|
|
43
|
-
type: string;
|
|
44
|
-
protectedBlock: Record<string, unknown>;
|
|
45
|
-
body: Record<string, unknown>;
|
|
46
|
-
attachments?: Record<string, unknown>;
|
|
47
|
-
senderSignature: string;
|
|
48
|
-
relationshipEventIndex: number;
|
|
49
|
-
prevServerEventHash: string | null;
|
|
50
|
-
serverTimestamp: string;
|
|
51
|
-
signedMessageHash: string;
|
|
52
|
-
serverEventHash: string;
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Fields every chain-source payload carries. `chain_event_id` +
|
|
57
|
-
* `instruction_idx` let the recipient reconstruct
|
|
58
|
-
* `WebhookSignableInput.source` for HMAC verification from the body
|
|
59
|
-
* alone — no reverse-parse of `delivery_id` required.
|
|
60
|
-
*/
|
|
61
|
-
export interface ChainPayloadCommon {
|
|
62
|
-
chain_event_id: string;
|
|
63
|
-
instruction_idx: number;
|
|
64
|
-
delegation_id: string;
|
|
65
|
-
relationship_id: string;
|
|
66
|
-
payer_did: string;
|
|
67
|
-
payee_did: string;
|
|
68
|
-
tx_signature: string;
|
|
69
|
-
slot: number;
|
|
70
|
-
block_time_iso?: string;
|
|
71
|
-
lock_id: string;
|
|
72
|
-
}
|
|
73
|
-
export interface LockCreatedWebhookPayload {
|
|
74
|
-
event_type: 'escrow.lock_created';
|
|
75
|
-
delivery_id: string;
|
|
76
|
-
attempt_n: number;
|
|
77
|
-
served_at: string;
|
|
78
|
-
payload: ChainPayloadCommon & {
|
|
79
|
-
amount: string;
|
|
80
|
-
asset_id?: string;
|
|
81
|
-
expiry_unix?: number;
|
|
82
|
-
fee_bps?: number;
|
|
83
|
-
fee_recipient?: string;
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
export interface LockReleasedWebhookPayload {
|
|
87
|
-
event_type: 'escrow.lock_released';
|
|
88
|
-
delivery_id: string;
|
|
89
|
-
attempt_n: number;
|
|
90
|
-
served_at: string;
|
|
91
|
-
payload: ChainPayloadCommon & {
|
|
92
|
-
released_amount: string;
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
export interface LockPartiallyReleasedWebhookPayload {
|
|
96
|
-
event_type: 'escrow.lock_partially_released';
|
|
97
|
-
delivery_id: string;
|
|
98
|
-
attempt_n: number;
|
|
99
|
-
served_at: string;
|
|
100
|
-
payload: ChainPayloadCommon & {
|
|
101
|
-
released_amount: string;
|
|
102
|
-
refunded_amount: string;
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
export interface LockRefundedWebhookPayload {
|
|
106
|
-
event_type: 'escrow.lock_refunded';
|
|
107
|
-
delivery_id: string;
|
|
108
|
-
attempt_n: number;
|
|
109
|
-
served_at: string;
|
|
110
|
-
payload: ChainPayloadCommon & {
|
|
111
|
-
refunded_amount: string;
|
|
112
|
-
reason?: string;
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
export interface DisputeResolvedWebhookPayload {
|
|
116
|
-
event_type: 'escrow.dispute_resolved';
|
|
117
|
-
delivery_id: string;
|
|
118
|
-
attempt_n: number;
|
|
119
|
-
served_at: string;
|
|
120
|
-
payload: ChainPayloadCommon & {
|
|
121
|
-
verdict: string;
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Discriminated union of every webhook delivery the recipient might
|
|
126
|
-
* receive. Use exhaustive `switch` on `event_type`:
|
|
127
|
-
*
|
|
128
|
-
* ```ts
|
|
129
|
-
* function handle(p: WebhookPayload) {
|
|
130
|
-
* switch (p.event_type) {
|
|
131
|
-
* case 'escrow.lock_created': return onLockCreated(p);
|
|
132
|
-
* case 'escrow.lock_released': return onLockReleased(p);
|
|
133
|
-
* case 'escrow.lock_partially_released': return onPartial(p);
|
|
134
|
-
* case 'escrow.lock_refunded': return onRefund(p);
|
|
135
|
-
* case 'escrow.dispute_resolved': return onDispute(p);
|
|
136
|
-
* default:
|
|
137
|
-
* // p.event_type is `envelope.${string}` here
|
|
138
|
-
* return onEnvelope(p);
|
|
139
|
-
* }
|
|
140
|
-
* }
|
|
141
|
-
* ```
|
|
142
|
-
*
|
|
143
|
-
* The chain branches narrow exhaustively because each is a string-
|
|
144
|
-
* literal type; the envelope branch carries the catch-all
|
|
145
|
-
* `envelope.${string}` template-literal type so recipients can
|
|
146
|
-
* still narrow on `payload.type` if they care about specific body
|
|
147
|
-
* types.
|
|
148
|
-
*/
|
|
149
|
-
export type ChainWebhookPayload = LockCreatedWebhookPayload | LockReleasedWebhookPayload | LockPartiallyReleasedWebhookPayload | LockRefundedWebhookPayload | DisputeResolvedWebhookPayload;
|
|
150
|
-
export type WebhookPayload = EnvelopeWebhookPayload | ChainWebhookPayload;
|
|
151
|
-
/**
|
|
152
|
-
* Fields every recovery row carries irrespective of event type — the
|
|
153
|
-
* envelope around the per-event-type `payload`.
|
|
154
|
-
*/
|
|
155
|
-
export interface ChainEventRecoveryRowCommon {
|
|
156
|
-
chain_event_id: string;
|
|
157
|
-
delegation_id: string;
|
|
158
|
-
relationship_id: string;
|
|
159
|
-
lock_id: string;
|
|
160
|
-
tx_signature: string;
|
|
161
|
-
slot: number;
|
|
162
|
-
block_time_iso?: string;
|
|
163
|
-
created_at: string;
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Recovery channel for chain events. Returned by the
|
|
167
|
-
* `GET /v1/agents/me/chain-events?since=…` endpoint. Recipients call
|
|
168
|
-
* this on handler restart / reconnection to catch any chain events
|
|
169
|
-
* they missed while their handler was down.
|
|
170
|
-
*
|
|
171
|
-
* **Discriminated union**: both `event_type` and `payload` come from
|
|
172
|
-
* the SAME tagged union — `switch (row.event_type)` narrows
|
|
173
|
-
* `row.payload` to the right per-event-type shape so the advertised
|
|
174
|
-
* "share a single handler between push and pull" actually type-checks.
|
|
175
|
-
*
|
|
176
|
-
* switch (row.event_type) {
|
|
177
|
-
* case 'escrow.lock_released':
|
|
178
|
-
* // row.payload is LockReleasedWebhookPayload['payload'] here
|
|
179
|
-
* break;
|
|
180
|
-
* ...
|
|
181
|
-
* }
|
|
182
|
-
*/
|
|
183
|
-
export type ChainEventRecoveryRow = ChainEventRecoveryRowCommon & ({
|
|
184
|
-
event_type: 'escrow.lock_created';
|
|
185
|
-
payload: LockCreatedWebhookPayload['payload'];
|
|
186
|
-
} | {
|
|
187
|
-
event_type: 'escrow.lock_released';
|
|
188
|
-
payload: LockReleasedWebhookPayload['payload'];
|
|
189
|
-
} | {
|
|
190
|
-
event_type: 'escrow.lock_partially_released';
|
|
191
|
-
payload: LockPartiallyReleasedWebhookPayload['payload'];
|
|
192
|
-
} | {
|
|
193
|
-
event_type: 'escrow.lock_refunded';
|
|
194
|
-
payload: LockRefundedWebhookPayload['payload'];
|
|
195
|
-
} | {
|
|
196
|
-
event_type: 'escrow.dispute_resolved';
|
|
197
|
-
payload: DisputeResolvedWebhookPayload['payload'];
|
|
198
|
-
});
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `ARP-WEBHOOK-v1` HMAC over the canonical webhook envelope. Used by
|
|
3
|
-
* the OutboxDeliveryWorker for the `X-ARP-Signature` header.
|
|
4
|
-
*
|
|
5
|
-
* The signable input is a discriminated `source` union covering BOTH
|
|
6
|
-
* envelope deliveries (`source.kind === 'envelope'`) AND chain
|
|
7
|
-
* settlement events (`source.kind === 'chain'`). The single
|
|
8
|
-
* canonical-JSON output binds the HMAC to the source variant so a
|
|
9
|
-
* recipient receiving a chain webhook can't accidentally verify
|
|
10
|
-
* against an envelope-shaped signable, and vice versa.
|
|
11
|
-
*
|
|
12
|
-
* The body-tampering defence (`payload_sha256`) is mandatory on the
|
|
13
|
-
* new shape — recipients SHOULD verify the hash matches their
|
|
14
|
-
* received raw body BEFORE the HMAC check (cheap, and surfaces
|
|
15
|
-
* "wrong body" as a separate diagnostic from "wrong HMAC").
|
|
16
|
-
*/
|
|
17
|
-
export type WebhookSignableSource = {
|
|
18
|
-
kind: 'envelope';
|
|
19
|
-
envelope_message_id: string;
|
|
20
|
-
server_event_hash: string;
|
|
21
|
-
} | {
|
|
22
|
-
kind: 'chain';
|
|
23
|
-
chain_event_id: string;
|
|
24
|
-
tx_signature: string;
|
|
25
|
-
instruction_idx: number;
|
|
26
|
-
};
|
|
27
|
-
export interface WebhookSignableInput {
|
|
28
|
-
/** Outbox row id (deterministic per delivery attempt). */
|
|
29
|
-
delivery_id: string;
|
|
30
|
-
recipient_did: string;
|
|
31
|
-
/** Public event-type discriminator (`envelope.<type>` or `escrow.<event>`). */
|
|
32
|
-
event_type: string;
|
|
33
|
-
/** Source-variant discriminated union. Binds the HMAC to the kind. */
|
|
34
|
-
source: WebhookSignableSource;
|
|
35
|
-
/** SHA-256 (lowercase hex, no `sha256:` prefix) of the canonical raw body bytes the recipient receives. */
|
|
36
|
-
payload_sha256: string;
|
|
37
|
-
/** 1-based retry counter — participates in canonical input so replays of an old header on a fresh attempt fail. */
|
|
38
|
-
attempt_n: number;
|
|
39
|
-
/** RFC 3339 wall-clock time the server generated this attempt. Recipient policy MAY reject outside a tolerance window. */
|
|
40
|
-
served_at: string;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Compute `X-ARP-Signature` value:
|
|
44
|
-
* `<purpose>=<base64(HMAC-SHA256(secret, sha256(canonical_json(input))))>`.
|
|
45
|
-
*
|
|
46
|
-
* Recipients lookup their per-sender shared secret(s), recompute the
|
|
47
|
-
* HMAC over the same canonical input, and compare against the
|
|
48
|
-
* received header. See `verifyWebhookSignatureHeader` for the
|
|
49
|
-
* standard recipient flow with rotation grace.
|
|
50
|
-
*/
|
|
51
|
-
export declare function buildWebhookSignatureHeader(input: WebhookSignableInput, sharedSecret: Uint8Array): string;
|
|
52
|
-
/**
|
|
53
|
-
* Verify an inbound `X-ARP-Signature` header against the reconstructed
|
|
54
|
-
* signable input. The 3-argument form is the convenience for
|
|
55
|
-
* single-secret consumers (most use); the array form is the rotation-
|
|
56
|
-
* grace path (two-phase rotate: recipient verifies against
|
|
57
|
-
* `[current, pending, previous]` during the 1h grace window).
|
|
58
|
-
*
|
|
59
|
-
* Returns `false` on shape mismatch, missing purpose label, OR HMAC
|
|
60
|
-
* mismatch under EVERY supplied secret. Never throws.
|
|
61
|
-
*
|
|
62
|
-
* **Timing-uniform across rotation slots** — the array form runs ALL
|
|
63
|
-
* HMAC computations + constant-time compares before returning, so the
|
|
64
|
-
* total time is proportional to N (number of secrets) regardless of
|
|
65
|
-
* which slot matched. A short-circuiting implementation would leak
|
|
66
|
-
* the position of the matching key via total time. Practical impact
|
|
67
|
-
* is small (attacker would only learn "current vs pending vs previous
|
|
68
|
-
* matched", not key material), but timing-uniform is the right
|
|
69
|
-
* default for a verification primitive.
|
|
70
|
-
*
|
|
71
|
-
* Recipient flow:
|
|
72
|
-
* 1. Receive POST with `X-ARP-Signature` header + raw body bytes.
|
|
73
|
-
* 2. Compute `payload_sha256 = sha256-hex(rawBodyBytes)`.
|
|
74
|
-
* 3. Reconstruct `WebhookSignableInput` from headers + payload sha256.
|
|
75
|
-
* 4. Call `verifyWebhookSignatureHeader(header, input, [current, pending, previous].filter(Boolean))`.
|
|
76
|
-
* 5. On `true`, accept. On `false`, 401 the request.
|
|
77
|
-
*
|
|
78
|
-
* Helper `verifyAndCheckPayload` (below) bundles steps 2-4 into one
|
|
79
|
-
* call for the common path.
|
|
80
|
-
*/
|
|
81
|
-
export declare function verifyWebhookSignatureHeader(headerValue: string, input: WebhookSignableInput, sharedSecret: Uint8Array | Uint8Array[]): boolean;
|
|
82
|
-
/**
|
|
83
|
-
* Recipient convenience: combine body-hash binding + HMAC verify +
|
|
84
|
-
* optional `served_at` clock-skew tolerance into one call.
|
|
85
|
-
*
|
|
86
|
-
* 1. Compute `payload_sha256 = sha256-hex(rawBody)` and OVERLAY it
|
|
87
|
-
* onto `input.payload_sha256` (caller's value, if any, is
|
|
88
|
-
* replaced).
|
|
89
|
-
* 2. Verify `served_at` is within `toleranceMs` of `now()` (default
|
|
90
|
-
* ±5 minutes; pass `null` to skip).
|
|
91
|
-
* 3. Verify HMAC against each secret in `secrets`.
|
|
92
|
-
*
|
|
93
|
-
* Returns a tagged outcome the caller can branch on:
|
|
94
|
-
* - `'ok'` — accept the delivery
|
|
95
|
-
* - `'stale_attempt'` — served_at outside the tolerance window
|
|
96
|
-
* - `'invalid_signature'` — HMAC didn't verify under any secret
|
|
97
|
-
*
|
|
98
|
-
* **Why no `body_mismatch` outcome** — `payload_sha256` is a derived
|
|
99
|
-
* field bound to the raw body. The HMAC signs the input INCLUDING
|
|
100
|
-
* `payload_sha256`, so a tampered body causes the recipient's
|
|
101
|
-
* recomputed hash to differ from the server-signed one, which fails
|
|
102
|
-
* the HMAC check as `'invalid_signature'`. Body tampering is
|
|
103
|
-
* detected via the HMAC failure; if you need a dedicated diagnostic,
|
|
104
|
-
* compute `sha256HexLower(rawBody)` yourself and compare to an
|
|
105
|
-
* out-of-band trusted source (e.g. a signed mirror of the body).
|
|
106
|
-
*/
|
|
107
|
-
export declare function verifyAndCheckPayload(args: {
|
|
108
|
-
headerValue: string;
|
|
109
|
-
/** Reconstructed signable input MINUS `payload_sha256` — helper computes that field from `rawBody`. */
|
|
110
|
-
input: Omit<WebhookSignableInput, 'payload_sha256'>;
|
|
111
|
-
rawBody: Uint8Array;
|
|
112
|
-
secrets: Uint8Array | Uint8Array[];
|
|
113
|
-
servedAtToleranceMs?: number | null;
|
|
114
|
-
now?: Date;
|
|
115
|
-
}): 'ok' | 'stale_attempt' | 'invalid_signature';
|
|
116
|
-
/**
|
|
117
|
-
* Probe-skip helper for recipient handlers. The `POST /v1/agents/me/webhook-config`
|
|
118
|
-
* flow fires an UNSIGNED probe POST with the `X-ARP-Probe: 1` header.
|
|
119
|
-
* Recipient handlers MUST 2xx without HMAC verification on probe
|
|
120
|
-
* requests — otherwise the operator can't set their webhook URL
|
|
121
|
-
* (the probe gets rejected as unsigned, the URL persist step fails).
|
|
122
|
-
*
|
|
123
|
-
* Use this at the top of your handler:
|
|
124
|
-
*
|
|
125
|
-
* ```ts
|
|
126
|
-
* if (isProbeRequest(req.headers)) {
|
|
127
|
-
* res.statusCode = 200;
|
|
128
|
-
* res.end();
|
|
129
|
-
* return;
|
|
130
|
-
* }
|
|
131
|
-
* // Otherwise: verify signature normally.
|
|
132
|
-
* ```
|
|
133
|
-
*
|
|
134
|
-
* Header values are inspected case-insensitively per Node's
|
|
135
|
-
* IncomingMessage convention.
|
|
136
|
-
*
|
|
137
|
-
* **SECURITY (spoofability)** — the helper is a pure header check;
|
|
138
|
-
* it cannot itself defend against an attacker setting
|
|
139
|
-
* `X-ARP-Probe: 1` to bypass HMAC verification. The probe branch
|
|
140
|
-
* in your handler MUST be side-effect-free:
|
|
141
|
-
*
|
|
142
|
-
* - Respond 2xx with an empty body — no DB write, no downstream
|
|
143
|
-
* fan-out, no metric increment that an attacker could amplify.
|
|
144
|
-
* - Consider rate-limiting probe-skipped requests per source IP if
|
|
145
|
-
* the surrounding stack doesn't already.
|
|
146
|
-
* - Do NOT log full request bodies on the probe branch (an
|
|
147
|
-
* attacker can pump log noise).
|
|
148
|
-
* - If your handler genuinely has zero side-effects on a 200
|
|
149
|
-
* response, this is automatic; otherwise treat the probe branch
|
|
150
|
-
* as you would an unauthenticated health-check endpoint.
|
|
151
|
-
*/
|
|
152
|
-
export declare function isProbeRequest(headers: Record<string, string | string[] | undefined>): boolean;
|
|
153
|
-
/**
|
|
154
|
-
* SHA-256 hex digest of the input bytes, lowercase. Exported for unit
|
|
155
|
-
* tests + the rare consumer that needs to compute payload_sha256
|
|
156
|
-
* outside `verifyAndCheckPayload`.
|
|
157
|
-
*/
|
|
158
|
-
export declare function sha256HexLower(bytes: Uint8Array): string;
|