@heyanon-arp/sdk 0.0.2

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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/dist/assets.d.ts +101 -0
  4. package/dist/attestation/attestation.d.ts +29 -0
  5. package/dist/attestation/index.d.ts +2 -0
  6. package/dist/attestation/scrypt-proof.d.ts +28 -0
  7. package/dist/canonical/canonicalize.d.ts +33 -0
  8. package/dist/canonical/index.d.ts +1 -0
  9. package/dist/challenge/challenge.d.ts +11 -0
  10. package/dist/challenge/index.d.ts +1 -0
  11. package/dist/cosignature/cosign.d.ts +35 -0
  12. package/dist/cosignature/index.d.ts +2 -0
  13. package/dist/did/document.d.ts +30 -0
  14. package/dist/did/format.d.ts +23 -0
  15. package/dist/did/index.d.ts +2 -0
  16. package/dist/envelope/index.d.ts +4 -0
  17. package/dist/envelope/sign.d.ts +28 -0
  18. package/dist/envelope/verify.d.ts +37 -0
  19. package/dist/escrow/caip19.d.ts +41 -0
  20. package/dist/escrow/condition-hash.d.ts +79 -0
  21. package/dist/escrow/create-lock.d.ts +39 -0
  22. package/dist/escrow/index.d.ts +4 -0
  23. package/dist/escrow/lock-id.d.ts +67 -0
  24. package/dist/index.d.ts +38 -0
  25. package/dist/index.js +853 -0
  26. package/dist/index.mjs +761 -0
  27. package/dist/keys/base58btc.d.ts +10 -0
  28. package/dist/keys/ed25519.d.ts +33 -0
  29. package/dist/keys/index.d.ts +3 -0
  30. package/dist/purpose.d.ts +52 -0
  31. package/dist/server-chain/chain.d.ts +52 -0
  32. package/dist/server-chain/index.d.ts +2 -0
  33. package/dist/settlement/index.d.ts +4 -0
  34. package/dist/settlement/settlement.d.ts +111 -0
  35. package/dist/settlement/token-program.d.ts +72 -0
  36. package/dist/types/body.d.ts +263 -0
  37. package/dist/types/envelope.d.ts +121 -0
  38. package/dist/types/identity.d.ts +62 -0
  39. package/dist/types/index.d.ts +5 -0
  40. package/dist/utils/index.d.ts +3 -0
  41. package/dist/utils/nonce.d.ts +9 -0
  42. package/dist/utils/timestamp.d.ts +17 -0
  43. package/dist/utils/uuid.d.ts +10 -0
  44. package/dist/webhook/index.d.ts +2 -0
  45. package/dist/webhook/webhook.d.ts +38 -0
  46. package/package.json +58 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ARP contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,147 @@
1
+ # `@heyanon-arp/sdk`
2
+
3
+ TypeScript SDK for the **Agent Relationship Protocol** (ARP) — the
4
+ crypto + canonical-JSON building blocks for autonomous agents that
5
+ exchange signed messages, co-sign receipts, and settle on chain.
6
+
7
+ If you're building a custom client (a non-Node runtime, a browser
8
+ extension, a Python bridge, an internal agent framework) and you want
9
+ your envelopes / receipts / attestations to be byte-identical to
10
+ what the reference ARP server expects, this is the library to wire in.
11
+
12
+ If you just want to talk to a running ARP server from the command line,
13
+ use [`@heyanon-arp/cli`](https://www.npmjs.com/package/@heyanon-arp/cli)
14
+ instead — it depends on this SDK internally.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ npm install @heyanon-arp/sdk
20
+ # or: pnpm add @heyanon-arp/sdk
21
+ ```
22
+
23
+ Requires **Node ≥ 22** (uses Ed25519 / WebCrypto from `node:crypto`).
24
+ ESM + CJS builds + TypeScript declarations all ship in the same package.
25
+
26
+ ## Quick start
27
+
28
+ ### Generate a DID + keypair
29
+
30
+ ```ts
31
+ import { formatDid, generateKeyPair } from '@heyanon-arp/sdk';
32
+
33
+ const kp = generateKeyPair();
34
+ const did = formatDid(kp.publicKey); // "did:arp:7c3GhJ8L…"
35
+ ```
36
+
37
+ ### Sign + verify an envelope
38
+
39
+ ```ts
40
+ import {
41
+ Purpose, expiresAt, formatDid, generateKeyPair, rfc3339,
42
+ senderNonce, signEnvelope, uuidV4, verifyEnvelope,
43
+ } from '@heyanon-arp/sdk';
44
+
45
+ const sender = generateKeyPair();
46
+ const senderDid = formatDid(sender.publicKey);
47
+
48
+ const envelope = signEnvelope({
49
+ protected: {
50
+ protocol_version: 'arp/0.1',
51
+ purpose: Purpose.ENVELOPE,
52
+ message_id: uuidV4(),
53
+ sender_did: senderDid,
54
+ recipient_did: 'did:arp:9bDfQp2M…',
55
+ relationship_id: null, // first handshake
56
+ sender_sequence: 1,
57
+ sender_nonce: senderNonce(),
58
+ timestamp: rfc3339(),
59
+ expires_at: expiresAt(3600), // 1 hour from now
60
+ delivery_id: null,
61
+ },
62
+ body: { type: 'handshake', content: { greeting: 'hello' } },
63
+ identitySecretKey: sender.secretKey,
64
+ });
65
+
66
+ // On the receiver:
67
+ const result = verifyEnvelope(envelope, sender.publicKey);
68
+ if (!result.ok) throw new Error(`envelope rejected: ${result.reason}`);
69
+ ```
70
+
71
+ ### Co-sign a receipt
72
+
73
+ ```ts
74
+ import { signCosignature, verifyCosignature } from '@heyanon-arp/sdk';
75
+
76
+ const cs = signCosignature({
77
+ payload: {
78
+ purpose: 'ARP-RECEIPT-v1',
79
+ delegation_id: 'del_…',
80
+ receipt_event_hash: 'sha256:…',
81
+ verdict: 'accepted',
82
+ notes_hash: null,
83
+ },
84
+ signerDid: senderDid,
85
+ identitySecretKey: sender.secretKey,
86
+ });
87
+
88
+ verifyCosignature({ cosignature: cs, payload: cs.payload, signerIdentityPubkey: sender.publicKey });
89
+ ```
90
+
91
+ ## What's in the box
92
+
93
+ | Module | What it does |
94
+ |-----------------|-------------------------------------------------------------------------------|
95
+ | `canonical` | RFC 8785 JCS canonical JSON, sha256 helpers, signing-input bytes |
96
+ | `keys` | Ed25519 generate/sign/verify, base58btc + base64 encoding |
97
+ | `did` | `did:arp:<base58btc>` parse/format, DID document types |
98
+ | `envelope` | `signEnvelope` / `verifyEnvelope` — the wire-level message signing |
99
+ | `cosignature` | `ARP-RECEIPT-v1` + `ARP-DISPUTE-RESPONSE-v1` co-signatures |
100
+ | `challenge` | `ARP-CHALLENGE-v1` ownership proof for identity registration |
101
+ | `webhook` | `ARP-WEBHOOK-v1` HMAC for the outbox `X-ARP-Signature` header |
102
+ | `attestation` | `ARP-KEY-LINK-v1` / `ARP-KEY-ROTATION-v1` (scrypt password proof) |
103
+ | `server-chain` | `signed_message_hash` + `server_event_hash` builders for chain audit |
104
+ | `settlement` | `ARP-SOLANA-RELEASE-v1.5` / `…-PARTIAL-RELEASE-v1.5` / `…-REFUND-v1` digests |
105
+ | `escrow` | Solana `create_lock` ix data builder, condition-hash derivation |
106
+ | `purpose` | Domain separator constants |
107
+ | `utils` | UUID v4, sender_nonce, RFC 3339 timestamp helpers |
108
+ | `types` | Wire-level TypeScript types (envelope, body shapes, attestations) |
109
+
110
+ ## What's NOT in here
111
+
112
+ The SDK is deliberately **client-side / shared primitives** only. Server
113
+ concerns live in the backend implementation, not the SDK:
114
+
115
+ - Replay defence (Redis dedup of `message_id`, `sender_sequence` enforcement)
116
+ - Time-window check (±5 min, `expires_at` future)
117
+ - JSON schema validation
118
+ - Server-side chain assignment (`relationship_event_index`,
119
+ `prev_server_event_hash`, `server_event_hash` write)
120
+ - Inbox / pricing policy evaluation
121
+ - Rate limits
122
+
123
+ ## Conformance
124
+
125
+ Any other-language SDK (Python, Go, Rust…) **must** reproduce identical
126
+ canonical bytes for the golden vectors in this package's `test/canonical-vectors.json`
127
+ to claim ARP conformance. Same envelope → same `signed_message_hash` →
128
+ same Ed25519 signature.
129
+
130
+ ## Versioning
131
+
132
+ Semver:
133
+ - **Major** — protocol version bump (`arp/0.1` → `arp/0.2`), envelope
134
+ structure change, signature-input shape change, public function
135
+ return-type change.
136
+ - **Minor** — new exported helpers, new optional fields, new `Purpose`
137
+ constants.
138
+ - **Patch** — bug fixes that don't change observable behaviour.
139
+
140
+ ## See also
141
+
142
+ - [`@heyanon-arp/cli`](https://www.npmjs.com/package/@heyanon-arp/cli) —
143
+ command-line client built on top of this SDK.
144
+
145
+ ## License
146
+
147
+ MIT
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Well-known asset shorthand table + CAIP-19 helpers.
3
+ *
4
+ * The protocol carries assets as `AssetIdentifier` ({ asset_id, decimals,
5
+ * symbol? }) with `asset_id` always a CAIP-19 string. Operators almost
6
+ * always work with two assets — USDC and native SOL on either mainnet
7
+ * or devnet — and typing 90-char CAIP-19 strings on every CLI call is
8
+ * hostile. This module:
9
+ *
10
+ * 1. Exports `WELL_KNOWN_ASSETS` — the four mainstream entries
11
+ * mapped from shorthand keys to canonical `AssetIdentifier`.
12
+ * 2. Exports `resolveAsset(input)` — accepts either a shorthand key
13
+ * OR a raw CAIP-19 string. Shorthand resolves from the table;
14
+ * raw CAIP-19 is returned unchanged with `decimals: null`
15
+ * sentinel (caller must supply via separate flag/field).
16
+ * 3. Exports `CAIP19_REGEX` — the validation regex (server-side and
17
+ * client-side both lean on the same source of truth).
18
+ *
19
+ * V1 launch ships only the four shorthand entries — extending the
20
+ * table is a SemVer-minor patch. Adding a Solana cluster (testnet?)
21
+ * is one entry. Adding a different chain (Ethereum, Polygon) is two
22
+ * entries (the native + USDC equivalent on that chain).
23
+ */
24
+ import type { AssetIdentifier } from './types/body';
25
+ /**
26
+ * Solana cluster genesis-hash prefixes per CAIP-2 (`solana:<cluster_id>`).
27
+ * The full genesis hash is 32 bytes; CAIP-2 uses the first 32 chars
28
+ * of the base58 representation (= 24 bytes ≈ 256 bits).
29
+ *
30
+ * Sources:
31
+ * - mainnet-beta genesis: `5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpgnAv6oW4HXJK`
32
+ * → prefix `5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`
33
+ * - devnet genesis: `EtWTRABZaYq6iMfeYKouRu166VU2xqaq2gP7e2pAjFhX`
34
+ * → prefix `EtWTRABZaYq6iMfeYKouRu166VU2xqa1` (note: chainagnostic.org canonical form
35
+ * truncates at 32 chars; the actual genesis prefix lookup table is fixed)
36
+ */
37
+ export declare const SOLANA_CLUSTER_IDS: {
38
+ readonly 'solana-mainnet': "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
39
+ readonly 'solana-devnet': "EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
40
+ };
41
+ /**
42
+ * SLIP-44 coin types — `slip44:<coin_type>` for native chain assets.
43
+ * Solana = 501 (https://github.com/satoshilabs/slips/blob/master/slip-0044.md).
44
+ */
45
+ export declare const SLIP44_SOLANA = 501;
46
+ /**
47
+ * Canonical SPL mints for USDC across the two Solana clusters we
48
+ * support at V1 launch. Source: Circle's official USDC documentation.
49
+ */
50
+ export declare const USDC_MINTS: {
51
+ readonly 'solana-mainnet': "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
52
+ readonly 'solana-devnet': "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU";
53
+ };
54
+ /**
55
+ * Shorthand → `AssetIdentifier`. Keys follow the pattern
56
+ * `<TICKER>:<CLUSTER>` so a glance tells you both the asset and the
57
+ * cluster.
58
+ */
59
+ export declare const WELL_KNOWN_ASSETS: Readonly<Record<string, AssetIdentifier>>;
60
+ /**
61
+ * CAIP-19 strict regex. Reused by:
62
+ * - Server-side envelope validation (rejects malformed `asset_id`)
63
+ * - SDK-side `resolveAsset` (distinguishes raw CAIP-19 from shorthand)
64
+ * - CLI flag validation
65
+ *
66
+ * Per CAIP-19 spec:
67
+ * asset_id = chain_id "/" asset_namespace ":" asset_reference
68
+ * chain_id = chain_namespace ":" chain_reference
69
+ * chain_namespace = [-a-z0-9]{3,8}
70
+ * chain_reference = [-_a-zA-Z0-9]{1,32}
71
+ * asset_namespace = [-a-z0-9]{3,8}
72
+ * asset_reference = [-.%a-zA-Z0-9]{1,128}
73
+ */
74
+ export declare const CAIP19_REGEX: RegExp;
75
+ /**
76
+ * Type guard for `AssetIdentifier`. Validates structural shape +
77
+ * CAIP-19 conformance of `asset_id` + decimals range. Symbol is
78
+ * optional but if present must be 1-16 chars.
79
+ */
80
+ export declare function isAssetIdentifier(value: unknown): value is AssetIdentifier;
81
+ /**
82
+ * Resolve a shorthand key OR raw CAIP-19 string to an `AssetIdentifier`.
83
+ *
84
+ * Returns:
85
+ * - shorthand match → full `AssetIdentifier` from the table
86
+ * - raw CAIP-19 match → `{ asset_id: input, decimals: NaN, symbol: undefined }`
87
+ * (caller MUST supply decimals separately — NaN is a loud signal,
88
+ * not a useful default)
89
+ * - neither → `null`
90
+ *
91
+ * The NaN-on-raw-input behaviour exists because decimals can't be
92
+ * inferred from CAIP-19 alone — `slip44:501` is SOL (9 decimals) but
93
+ * `slip44:60` is ETH (18 decimals) and we don't ship a coin-type
94
+ * registry in V1. Callers using raw CAIP-19 are expected to pass
95
+ * `--rate-decimals <N>` alongside.
96
+ */
97
+ export declare function resolveAsset(input: string): AssetIdentifier | null;
98
+ /**
99
+ * Lookup table keys exported for `--help` choices in the CLI.
100
+ */
101
+ export declare const WELL_KNOWN_ASSET_KEYS: readonly string[];
@@ -0,0 +1,29 @@
1
+ import type { KeyLinkPayload, KeyRotationPayload, ScryptPasswordAttestation } from '../types/identity';
2
+ /**
3
+ * Build an `ARP-KEY-LINK-v1` attestation record signed via
4
+ * `scrypt_password_proof` (V1 default). Caller assembles the payload
5
+ * (DID, both pubkeys, owner_id, nonce, etc.); SDK produces the MAC
6
+ * and wraps the record in the standard shape.
7
+ *
8
+ * Use [signKeyRotationAttestation](#signkeyrotationattestation) for
9
+ * rotation events — they use a different `purpose` and break the
10
+ * `agent_did = base58btc(identity_public_key)` invariant.
11
+ */
12
+ export declare function signKeyLinkAttestation(input: {
13
+ payload: KeyLinkPayload;
14
+ scryptKey: Uint8Array;
15
+ scryptSaltId: string;
16
+ }): ScryptPasswordAttestation<KeyLinkPayload>;
17
+ export declare function verifyKeyLinkAttestation(attestation: ScryptPasswordAttestation<KeyLinkPayload>, scryptKey: Uint8Array): boolean;
18
+ /**
19
+ * Build an `ARP-KEY-ROTATION-v1` attestation. Same scrypt+HMAC
20
+ * mechanics as KEY-LINK but with rotation-specific payload fields
21
+ * (`current_identity_public_key` + `new_identity_public_key` +
22
+ * `supersedes_attestation_id`). The DID stays frozen.
23
+ */
24
+ export declare function signKeyRotationAttestation(input: {
25
+ payload: KeyRotationPayload;
26
+ scryptKey: Uint8Array;
27
+ scryptSaltId: string;
28
+ }): ScryptPasswordAttestation<KeyRotationPayload>;
29
+ export declare function verifyKeyRotationAttestation(attestation: ScryptPasswordAttestation<KeyRotationPayload>, scryptKey: Uint8Array): boolean;
@@ -0,0 +1,2 @@
1
+ export { deriveScryptKey, scryptPasswordProofSign, scryptPasswordProofVerify } from './scrypt-proof';
2
+ export { signKeyLinkAttestation, verifyKeyLinkAttestation, signKeyRotationAttestation, verifyKeyRotationAttestation } from './attestation';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Derive the scrypt key from an owner's password + salt. Uses V1
3
+ * standard parameters: N=32768, r=8, p=1, dkLen=32. Designed to be
4
+ * computed once at owner registration time and stored encrypted-at-rest;
5
+ * subsequent attestation verification recomputes the HMAC, not scrypt.
6
+ *
7
+ * Synchronous variant is intentional — the SDK targets V1 deployments
8
+ * where keys derive at low frequency (once per owner registration);
9
+ * the worst case is a few hundred ms which is acceptable. Async
10
+ * variants can be layered on top by the consumer if needed.
11
+ */
12
+ export declare function deriveScryptKey(password: string, salt: Uint8Array): Uint8Array;
13
+ /**
14
+ * Compute the scrypt-password-proof signature over the canonical
15
+ * payload bytes, returning the base64-encoded MAC.
16
+ *
17
+ * Steps per [00-core/identity.md](../../../00-core/identity.md):
18
+ * 1. payload_digest = sha256(canonical_json(payload))
19
+ * 2. mac = HMAC-SHA256(scrypt_key, payload_digest)
20
+ * 3. sig = base64(mac)
21
+ */
22
+ export declare function scryptPasswordProofSign(payload: unknown, scryptKey: Uint8Array): string;
23
+ /**
24
+ * Verify a scrypt-password-proof signature. Constant-time compare on
25
+ * MAC bytes — never compares strings directly because base64 decoding
26
+ * normalises some inputs which would mask differences.
27
+ */
28
+ export declare function scryptPasswordProofVerify(payload: unknown, sigBase64: string, scryptKey: Uint8Array): boolean;
@@ -0,0 +1,33 @@
1
+ import type { Sha256Hex } from '../types/envelope';
2
+ /**
3
+ * RFC 8785 JCS canonical JSON serialization.
4
+ *
5
+ * Re-export of the reference `canonicalize` package by Anders Rundgren
6
+ * (the RFC author). Wrapped here so the rest of the SDK doesn't depend
7
+ * on the third-party module name directly — if we ever swap the
8
+ * implementation, only this file changes.
9
+ *
10
+ * Properties guaranteed by the spec, relevant to ARP:
11
+ * - object keys sorted lexicographically by UTF-16 code units
12
+ * - numbers serialized per ECMAScript JSON.stringify (no trailing zeros, no `+E`)
13
+ * - strings escape only the mandatory characters (`"`, `\`, control bytes)
14
+ * - no whitespace, no trailing commas, no UTF-8 BOM
15
+ *
16
+ * The protocol forbids `undefined` and non-finite numbers; this function
17
+ * inherits the underlying library behaviour: `undefined` values inside
18
+ * objects are dropped, top-level `undefined` returns `undefined`, and
19
+ * non-finite numbers throw. ARP-side validators should reject such
20
+ * inputs before reaching this layer.
21
+ */
22
+ export declare function canonicalJson(value: unknown): string;
23
+ /**
24
+ * UTF-8 encode the canonical JSON of `value`. The byte form is what
25
+ * gets fed to hashing / signing.
26
+ */
27
+ export declare function canonicalBytes(value: unknown): Uint8Array;
28
+ /**
29
+ * sha256 of `canonical_json(value)`, formatted as the protocol's
30
+ * `sha256:<hex>` string. Used for `body_hash`, `attachments_hash`,
31
+ * `payload_hash` in co-signatures, etc.
32
+ */
33
+ export declare function canonicalSha256Hex(value: unknown): Sha256Hex;
@@ -0,0 +1 @@
1
+ export { canonicalJson, canonicalBytes, canonicalSha256Hex } from './canonicalize';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Produce the Ed25519 signature that proves identity-key ownership
3
+ * of `challengeBytes`.
4
+ */
5
+ export declare function signChallenge(challengeBytes: Uint8Array, identitySecretKey: Uint8Array): Uint8Array;
6
+ /**
7
+ * Verify a challenge signature. Returns boolean; never throws on a
8
+ * malformed signature so consumers can branch cleanly on `false` and
9
+ * map to `AUTH_CHALLENGE_NOT_FOUND` / `ENV_INVALID_SIGNATURE`.
10
+ */
11
+ export declare function verifyChallenge(challengeBytes: Uint8Array, signature: Uint8Array, identityPubkey: Uint8Array): boolean;
@@ -0,0 +1 @@
1
+ export { signChallenge, verifyChallenge } from './challenge';
@@ -0,0 +1,35 @@
1
+ import type { CoSignature, CosignPayload, Did } from '../types';
2
+ /**
3
+ * Build the `attachments.co_signature` block over `payload`.
4
+ *
5
+ * The signing input is `sha256(canonical_json(payload))` — same
6
+ * 32-byte digest the verifier reproduces — and the result is wrapped
7
+ * in the protocol's `co_signature` shape. `payload.purpose` is the
8
+ * domain separator; passing a payload whose purpose is not in the
9
+ * allowed cosignature set throws (defence-in-depth against accidental
10
+ * cross-purpose reuse).
11
+ */
12
+ export declare function signCosignature(input: {
13
+ payload: CosignPayload;
14
+ signerDid: Did;
15
+ identitySecretKey: Uint8Array;
16
+ }): CoSignature;
17
+ export type CosignVerifyResult = {
18
+ ok: true;
19
+ } | {
20
+ ok: false;
21
+ reason: 'payload_hash_mismatch' | 'signature_invalid' | 'signature_malformed' | 'purpose_mismatch';
22
+ detail?: string;
23
+ };
24
+ /**
25
+ * Verify that `cosignature` was produced by `signerIdentityPubkey`
26
+ * over `payload`. Three checks:
27
+ * 1. `cosignature.purpose === payload.purpose` (domain consistency).
28
+ * 2. recomputed `sha256(canonical_json(payload))` matches `payload_hash`.
29
+ * 3. Ed25519 verifies signature over the recomputed digest.
30
+ */
31
+ export declare function verifyCosignature(input: {
32
+ cosignature: CoSignature;
33
+ payload: CosignPayload;
34
+ signerIdentityPubkey: Uint8Array;
35
+ }): CosignVerifyResult;
@@ -0,0 +1,2 @@
1
+ export { signCosignature, verifyCosignature } from './cosign';
2
+ export type { CosignVerifyResult } from './cosign';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * DID document shape per [00-core/identity.md](../../../00-core/identity.md).
3
+ *
4
+ * Returned by the platform's DID resolver. Verification uses
5
+ * `identity_pubkey` from the document (not `decoded(did)`) because
6
+ * `identity_pubkey` may have rotated since registration while the DID
7
+ * itself stays stable.
8
+ */
9
+ export interface DidDocument {
10
+ id: string;
11
+ verificationMethod: VerificationMethod[];
12
+ service: ServiceEndpoint[];
13
+ metadata: {
14
+ key_mode: KeyMode;
15
+ owner_attestation_id: string;
16
+ registered_at: string;
17
+ };
18
+ }
19
+ export type KeyMode = 'single_key' | 'separated_soft' | 'separated_hard' | 'policy_controlled';
20
+ export interface VerificationMethod {
21
+ id: string;
22
+ type: 'Ed25519VerificationKey2020';
23
+ controller: string;
24
+ publicKeyMultibase: string;
25
+ }
26
+ export interface ServiceEndpoint {
27
+ id: string;
28
+ type: 'ARPEndpoint';
29
+ serviceEndpoint: string;
30
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Format an Ed25519 identity public key as a `did:arp:<base58btc>` DID.
3
+ *
4
+ * Per [00-core/identity.md](../../../00-core/identity.md), the
5
+ * method-specific identifier is base58btc(identity_public_key). 32-byte
6
+ * pubkey → 43-44 character base58btc string → 51-52 character DID.
7
+ */
8
+ export declare function formatDid(identityPublicKey: Uint8Array): string;
9
+ /**
10
+ * Decode a `did:arp:<base58btc>` DID back to its identity public key.
11
+ *
12
+ * Returns `null` for malformed inputs rather than throwing — DIDs come
13
+ * over the wire and untrusted callers should not crash the SDK on a
14
+ * bad string. Server-side validators should reject `null` results
15
+ * with `ENV_INVALID_DID_FORMAT`.
16
+ */
17
+ export declare function parseDid(did: string): Uint8Array | null;
18
+ /**
19
+ * Validate a string as a syntactically well-formed `did:arp:` DID.
20
+ * Does not check the underlying pubkey is registered or rotated — that
21
+ * is a server-side concern.
22
+ */
23
+ export declare function isValidDid(did: string): boolean;
@@ -0,0 +1,2 @@
1
+ export { formatDid, parseDid, isValidDid } from './format';
2
+ export type { DidDocument, KeyMode, VerificationMethod, ServiceEndpoint } from './document';
@@ -0,0 +1,4 @@
1
+ export { signEnvelope } from './sign';
2
+ export type { SignEnvelopeInput, SignableProtected } from './sign';
3
+ export { verifyEnvelope } from './verify';
4
+ export type { VerifyResult, VerifyFailureReason } from './verify';
@@ -0,0 +1,28 @@
1
+ import type { Attachments, Body, Envelope, ProtectedBlock } from '../types/envelope';
2
+ /**
3
+ * Inputs to `signEnvelope` — the caller fills `protected` MINUS the
4
+ * two hash fields, since those are derived from `body` / `attachments`
5
+ * and injected by the SDK.
6
+ */
7
+ export type SignableProtected = Omit<ProtectedBlock, 'body_hash' | 'attachments_hash'>;
8
+ export interface SignEnvelopeInput<TBody extends Body = Body> {
9
+ protected: SignableProtected;
10
+ body: TBody;
11
+ attachments?: Attachments;
12
+ identitySecretKey: Uint8Array;
13
+ }
14
+ /**
15
+ * Build a signed envelope.
16
+ *
17
+ * Sequence (matches [00-core/protocol.md#signing-rule](../../../00-core/protocol.md)):
18
+ * 1. Compute `body_hash = sha256(canonical_json(body))`.
19
+ * 2. Compute `attachments_hash` (or null when absent).
20
+ * 3. Inject both into `protected` so the wire-protected block is complete.
21
+ * 4. Compute signing input = `canonical_json({ protected, body, attachments })`.
22
+ * 5. Sign with Ed25519(identity_priv, signing_input_bytes).
23
+ *
24
+ * The protocol forbids the client from setting `body_hash` /
25
+ * `attachments_hash` to anything other than the recomputed value, so
26
+ * we deliberately drop them from the typed input.
27
+ */
28
+ export declare function signEnvelope<TBody extends Body>(input: SignEnvelopeInput<TBody>): Envelope<TBody>;
@@ -0,0 +1,37 @@
1
+ import type { Body, Envelope } from '../types/envelope';
2
+ /**
3
+ * Reasons `verifyEnvelope` may report. Map these to protocol error
4
+ * codes ([00-core/error-catalog.md](../../../00-core/error-catalog.md))
5
+ * at the consumer layer:
6
+ * - `body_hash_mismatch` → `ENV_BODY_HASH_MISMATCH`
7
+ * - `attachments_hash_mismatch` → `ENV_ATTACHMENTS_HASH_MISMATCH`
8
+ * - `signature_invalid` → `ENV_INVALID_SIGNATURE`
9
+ * - `signature_malformed` → `ENV_INVALID_SIGNATURE` (with details)
10
+ */
11
+ export type VerifyFailureReason = 'body_hash_mismatch' | 'attachments_hash_mismatch' | 'signature_invalid' | 'signature_malformed';
12
+ export type VerifyResult = {
13
+ ok: true;
14
+ } | {
15
+ ok: false;
16
+ reason: VerifyFailureReason;
17
+ detail?: string;
18
+ };
19
+ /**
20
+ * Verify the cryptographic + structural integrity of an envelope.
21
+ *
22
+ * Steps 4-6 of the [00-core/protocol.md](../../../00-core/protocol.md)
23
+ * verification rule:
24
+ * - Recompute `body_hash` and compare to `protected.body_hash`.
25
+ * - Recompute `attachments_hash` and compare to `protected.attachments_hash`.
26
+ * - Recompute canonical signing input and verify Ed25519 signature.
27
+ *
28
+ * Replay defence (steps 7), time defence (step 8), purpose / DID
29
+ * format checks (steps 1-3), and chain assignment (step 11) are
30
+ * application-layer concerns; this function focuses on the bytes.
31
+ *
32
+ * `senderIdentityPubkey` MUST be 32 bytes. Callers resolve it via the
33
+ * DID document — NOT via `decoded(sender_did)`, because the identity
34
+ * key may have been rotated since the DID was issued
35
+ * ([00-core/identity.md](../../../00-core/identity.md)).
36
+ */
37
+ export declare function verifyEnvelope<TBody extends Body>(envelope: Envelope<TBody>, senderIdentityPubkey: Uint8Array): VerifyResult;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * CAIP-19 ↔ Solana resolution helpers.
3
+ *
4
+ * Pure-SDK pieces:
5
+ * - `parseCaip19SolanaAssetId` — pure parse, no cluster cross-check
6
+ * (because the SDK has no notion of "deploy cluster" — that's
7
+ * server state).
8
+ *
9
+ * The server-side cross-check (`mintFromCaip19`) lives in the
10
+ * arp-server's escrow service because it depends on
11
+ * `program_state.clusterTag` which is on-chain state.
12
+ *
13
+ * `SOLANA_CLUSTER_IDS` is re-exported from `assets.ts` for callers that
14
+ * already import from `@heyanon-arp/sdk`.
15
+ */
16
+ /**
17
+ * Parsed CAIP-19 asset identifier — split into its three structural
18
+ * fields plus a derived `isNative` flag for the SOL fast-path.
19
+ */
20
+ export interface ParsedSolanaAssetId {
21
+ /** CAIP-2 chain reference, e.g. `5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` for mainnet. */
22
+ cluster: string;
23
+ /** Asset namespace: `spl` (SPL mint) or `slip44` (chain-native token). */
24
+ namespace: 'spl' | 'slip44';
25
+ /** Asset reference: SPL mint pubkey OR slip44 coin index (`501` for SOL). */
26
+ reference: string;
27
+ /** Convenience: `true` iff this is native SOL (namespace=slip44, ref=501). */
28
+ isNative: boolean;
29
+ }
30
+ /**
31
+ * Parse a CAIP-19 asset id without any cluster cross-check.
32
+ *
33
+ * Throws `Error('CAIP-19 asset_id is malformed')` on regex miss.
34
+ * Throws `Error('Unsupported slip44 coin index')` on non-501 slip44.
35
+ * Throws `Error('Unsupported CAIP-19 namespace')` on namespace != spl|slip44.
36
+ *
37
+ * (The regex already restricts namespace to one of those two, but the
38
+ * explicit check makes the failure mode surface as a typed error
39
+ * rather than a regex no-match if the regex is ever loosened.)
40
+ */
41
+ export declare function parseCaip19SolanaAssetId(assetId: string): ParsedSolanaAssetId;