@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.
- package/LICENSE +21 -0
- package/README.md +147 -0
- package/dist/assets.d.ts +101 -0
- package/dist/attestation/attestation.d.ts +29 -0
- package/dist/attestation/index.d.ts +2 -0
- package/dist/attestation/scrypt-proof.d.ts +28 -0
- package/dist/canonical/canonicalize.d.ts +33 -0
- package/dist/canonical/index.d.ts +1 -0
- package/dist/challenge/challenge.d.ts +11 -0
- package/dist/challenge/index.d.ts +1 -0
- package/dist/cosignature/cosign.d.ts +35 -0
- package/dist/cosignature/index.d.ts +2 -0
- package/dist/did/document.d.ts +30 -0
- package/dist/did/format.d.ts +23 -0
- package/dist/did/index.d.ts +2 -0
- package/dist/envelope/index.d.ts +4 -0
- package/dist/envelope/sign.d.ts +28 -0
- package/dist/envelope/verify.d.ts +37 -0
- package/dist/escrow/caip19.d.ts +41 -0
- package/dist/escrow/condition-hash.d.ts +79 -0
- package/dist/escrow/create-lock.d.ts +39 -0
- package/dist/escrow/index.d.ts +4 -0
- package/dist/escrow/lock-id.d.ts +67 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +853 -0
- package/dist/index.mjs +761 -0
- package/dist/keys/base58btc.d.ts +10 -0
- package/dist/keys/ed25519.d.ts +33 -0
- package/dist/keys/index.d.ts +3 -0
- package/dist/purpose.d.ts +52 -0
- package/dist/server-chain/chain.d.ts +52 -0
- package/dist/server-chain/index.d.ts +2 -0
- package/dist/settlement/index.d.ts +4 -0
- package/dist/settlement/settlement.d.ts +111 -0
- package/dist/settlement/token-program.d.ts +72 -0
- package/dist/types/body.d.ts +263 -0
- package/dist/types/envelope.d.ts +121 -0
- package/dist/types/identity.d.ts +62 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/nonce.d.ts +9 -0
- package/dist/utils/timestamp.d.ts +17 -0
- package/dist/utils/uuid.d.ts +10 -0
- package/dist/webhook/index.d.ts +2 -0
- package/dist/webhook/webhook.d.ts +38 -0
- 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
|
package/dist/assets.d.ts
ADDED
|
@@ -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,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,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,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;
|