@dcasegr/skein 0.0.0-stage → 0.1.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/README.md +525 -2
- package/bin/skein-host.js +22 -0
- package/bin/skein.js +8 -0
- package/dist/client/admin-cli.d.ts +10 -0
- package/dist/client/admin-cli.js +370 -0
- package/dist/client/admin.d.ts +139 -0
- package/dist/client/admin.js +256 -0
- package/dist/client/bundle.d.ts +9 -0
- package/dist/client/bundle.js +34 -0
- package/dist/client/cli.d.ts +44 -0
- package/dist/client/cli.js +306 -0
- package/dist/client/client.d.ts +161 -0
- package/dist/client/client.js +237 -0
- package/dist/client/config.d.ts +18 -0
- package/dist/client/config.js +31 -0
- package/dist/client/conversation.d.ts +62 -0
- package/dist/client/conversation.js +113 -0
- package/dist/client/raw.d.ts +94 -0
- package/dist/client/raw.js +154 -0
- package/dist/client/target.d.ts +34 -0
- package/dist/client/target.js +158 -0
- package/dist/dev/cli.d.ts +8 -0
- package/dist/dev/cli.js +248 -0
- package/dist/dev/scan.d.ts +15 -0
- package/dist/dev/scan.js +173 -0
- package/dist/envelope-cbor.d.ts +110 -0
- package/dist/envelope-cbor.js +160 -0
- package/dist/envelope.d.ts +38 -0
- package/dist/envelope.js +101 -0
- package/dist/host/arc.d.ts +131 -0
- package/dist/host/arc.js +425 -0
- package/dist/host/billing.d.ts +102 -0
- package/dist/host/billing.js +167 -0
- package/dist/host/boot.d.ts +246 -0
- package/dist/host/boot.js +643 -0
- package/dist/host/cli.d.ts +57 -0
- package/dist/host/cli.js +1253 -0
- package/dist/host/clock.d.ts +3 -0
- package/dist/host/clock.js +8 -0
- package/dist/host/control.d.ts +45 -0
- package/dist/host/control.js +170 -0
- package/dist/host/cron.d.ts +99 -0
- package/dist/host/cron.js +196 -0
- package/dist/host/deliver.d.ts +53 -0
- package/dist/host/deliver.js +251 -0
- package/dist/host/deploy.d.ts +29 -0
- package/dist/host/deploy.js +55 -0
- package/dist/host/door-answer.d.ts +24 -0
- package/dist/host/door-answer.js +21 -0
- package/dist/host/events.d.ts +89 -0
- package/dist/host/events.js +222 -0
- package/dist/host/fake-arcade.d.ts +40 -0
- package/dist/host/fake-arcade.js +149 -0
- package/dist/host/fake-discovery.d.ts +4 -0
- package/dist/host/fake-discovery.js +58 -0
- package/dist/host/feeds.d.ts +127 -0
- package/dist/host/feeds.js +422 -0
- package/dist/host/frontdoor.d.ts +92 -0
- package/dist/host/frontdoor.js +172 -0
- package/dist/host/genesis.d.ts +259 -0
- package/dist/host/genesis.js +353 -0
- package/dist/host/handles.d.ts +40 -0
- package/dist/host/handles.js +88 -0
- package/dist/host/hostenv.d.ts +30 -0
- package/dist/host/hostenv.js +83 -0
- package/dist/host/image-chain.d.ts +127 -0
- package/dist/host/image-chain.js +440 -0
- package/dist/host/install.d.ts +22 -0
- package/dist/host/install.js +139 -0
- package/dist/host/instances.d.ts +227 -0
- package/dist/host/instances.js +430 -0
- package/dist/host/kernel.d.ts +246 -0
- package/dist/host/kernel.js +603 -0
- package/dist/host/liveness.d.ts +65 -0
- package/dist/host/liveness.js +155 -0
- package/dist/host/manifest.d.ts +160 -0
- package/dist/host/manifest.js +597 -0
- package/dist/host/p2p.d.ts +337 -0
- package/dist/host/p2p.js +1002 -0
- package/dist/host/packet.d.ts +121 -0
- package/dist/host/packet.js +562 -0
- package/dist/host/plan.d.ts +154 -0
- package/dist/host/plan.js +418 -0
- package/dist/host/providers.d.ts +238 -0
- package/dist/host/providers.js +565 -0
- package/dist/host/roster.d.ts +72 -0
- package/dist/host/roster.js +172 -0
- package/dist/host/router.d.ts +782 -0
- package/dist/host/router.js +2411 -0
- package/dist/host/signer.d.ts +49 -0
- package/dist/host/signer.js +167 -0
- package/dist/host/store-sqlite.d.ts +39 -0
- package/dist/host/store-sqlite.js +170 -0
- package/dist/host/stores.d.ts +43 -0
- package/dist/host/stores.js +342 -0
- package/dist/host/sync.d.ts +121 -0
- package/dist/host/sync.js +467 -0
- package/dist/host/testbeef.d.ts +21 -0
- package/dist/host/testbeef.js +37 -0
- package/dist/host/testhost.d.ts +149 -0
- package/dist/host/testhost.js +211 -0
- package/dist/host/vcdiff.d.ts +9 -0
- package/dist/host/vcdiff.js +210 -0
- package/dist/host/wasm-kernel.d.ts +87 -0
- package/dist/host/wasm-kernel.js +296 -0
- package/dist/host/wasm-worker.d.ts +1 -0
- package/dist/host/wasm-worker.js +186 -0
- package/dist/peers/cron.d.ts +33 -0
- package/dist/peers/cron.js +75 -0
- package/dist/peers/infer-main.d.ts +2 -0
- package/dist/peers/infer-main.js +74 -0
- package/dist/peers/infer.d.ts +163 -0
- package/dist/peers/infer.js +367 -0
- package/dist/runtime/beef.d.ts +28 -0
- package/dist/runtime/beef.js +107 -0
- package/dist/runtime/bitcoin.d.ts +36 -0
- package/dist/runtime/bitcoin.js +141 -0
- package/dist/runtime/cid.d.ts +14 -0
- package/dist/runtime/cid.js +51 -0
- package/dist/runtime/dispatch.d.ts +92 -0
- package/dist/runtime/dispatch.js +142 -0
- package/dist/runtime/envelope.d.ts +60 -0
- package/dist/runtime/envelope.js +101 -0
- package/dist/runtime/heads.d.ts +25 -0
- package/dist/runtime/heads.js +37 -0
- package/dist/runtime/identity.d.ts +28 -0
- package/dist/runtime/identity.js +67 -0
- package/dist/runtime/index-store.d.ts +152 -0
- package/dist/runtime/index-store.js +715 -0
- package/dist/runtime/log.d.ts +62 -0
- package/dist/runtime/log.js +104 -0
- package/dist/runtime/programs.d.ts +30 -0
- package/dist/runtime/programs.js +61 -0
- package/dist/runtime/records.d.ts +135 -0
- package/dist/runtime/records.js +149 -0
- package/dist/runtime/sqlite.d.ts +33 -0
- package/dist/runtime/sqlite.js +604 -0
- package/dist/runtime/store.d.ts +141 -0
- package/dist/runtime/store.js +24 -0
- package/dist/runtime/syscalls.d.ts +9 -0
- package/dist/runtime/syscalls.js +41 -0
- package/dist/runtime/tree.d.ts +60 -0
- package/dist/runtime/tree.js +196 -0
- package/dist/runtime/types.d.ts +100 -0
- package/dist/runtime/types.js +7 -0
- package/dist/testapps.d.ts +101 -0
- package/dist/testapps.js +275 -0
- package/dist/testkit.d.ts +25 -0
- package/dist/testkit.js +48 -0
- package/dist/wallet.d.ts +35 -0
- package/dist/wallet.js +87 -0
- package/images/README.md +68 -0
- package/images/default/README.md +38 -0
- package/images/default/bin/frontdoor.cid +1 -0
- package/images/default/bin/frontdoor.json +5 -0
- package/images/default/bin/messagebox.cid +1 -0
- package/images/default/bin/messagebox.json +8 -0
- package/images/default/bin/wallet.cid +1 -0
- package/images/default/bin/wallet.json +14 -0
- package/images/default/etc/config.json +1 -0
- package/images/default/etc/dispatch.json +82 -0
- package/images/host/etc/apps.json +8 -0
- package/kernel-zig/README.md +1247 -0
- package/kernel-zig/web/client.js +65 -0
- package/kernel-zig/web/kernel.d.ts +59 -0
- package/kernel-zig/web/kernel.js +329 -0
- package/kernel-zig/web/store.js +67 -0
- package/kernel-zig/web/worker.js +79 -0
- package/kernel-zig/zig-out/web/skein-kernel.wasm +0 -0
- package/package.json +77 -4
- package/wasm/README.md +35 -0
- package/wasm/frontdoor.wasm +0 -0
- package/wasm/messagebox.wasm +0 -0
- package/wasm/resolve.wasm +0 -0
- package/wasm/wallet.wasm +0 -0
- package/wasm/wire-probe.wasm +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { CID } from "multiformats/cid";
|
|
2
|
+
export declare const BITCOIN_BLOCK = 176, BITCOIN_TX = 177, DBL_SHA2_256 = 86;
|
|
3
|
+
export declare const hashCid: (code: number, h: Uint8Array) => CID<unknown, number, 86, 1>;
|
|
4
|
+
export declare const displayHash: (h: Uint8Array) => string;
|
|
5
|
+
export declare const isBitcoin: (c: CID) => boolean;
|
|
6
|
+
export interface Input {
|
|
7
|
+
prev: Uint8Array;
|
|
8
|
+
vout: number;
|
|
9
|
+
script: Uint8Array;
|
|
10
|
+
sequence: number;
|
|
11
|
+
}
|
|
12
|
+
export interface Output {
|
|
13
|
+
value: bigint;
|
|
14
|
+
script: Uint8Array;
|
|
15
|
+
}
|
|
16
|
+
export interface Tx {
|
|
17
|
+
version: number;
|
|
18
|
+
inputs: Input[];
|
|
19
|
+
outputs: Output[];
|
|
20
|
+
locktime: number;
|
|
21
|
+
}
|
|
22
|
+
/** A transaction's standard serialization (no segwit: BSV); throws when malformed. */
|
|
23
|
+
export declare function parseTx(b: Uint8Array): Tx;
|
|
24
|
+
/** The typed node for a bitcoin block's bytes; undefined when it is not one (or does not decode). */
|
|
25
|
+
export declare function decodeBitcoin(cid: CID, b: Uint8Array): unknown;
|
|
26
|
+
export interface BitcoinLink {
|
|
27
|
+
to: CID;
|
|
28
|
+
rel: string;
|
|
29
|
+
locator: number | null;
|
|
30
|
+
}
|
|
31
|
+
/** The links a bitcoin block holds, in order (kernel-zig/src/bitcoin.zig `links`); none when it is not one or does not decode. */
|
|
32
|
+
export declare function bitcoinLinks(cid: CID, b: Uint8Array): BitcoinLink[];
|
|
33
|
+
/** The edges a kept bitcoin block contributes (kernel-zig/src/bitcoin.zig `edgesOf`): a transaction's `spends` links; none for a header or a merkle node. */
|
|
34
|
+
export declare function bitcoinEdges(cid: CID, b: Uint8Array): BitcoinLink[];
|
|
35
|
+
/** What a bitcoin block is, for display. */
|
|
36
|
+
export declare function bitcoinKind(cid: CID, b: Uint8Array): "header" | "merkle node" | "transaction" | undefined;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Bitcoin blocks decoded the IPLD way (issue #42) — the TypeScript twin of
|
|
2
|
+
// kernel-zig/src/bitcoin.zig, for the readers (the explorer, packets, the
|
|
3
|
+
// index derivation in index-store.ts). All dbl-sha2-256, the hash in internal
|
|
4
|
+
// byte order:
|
|
5
|
+
//
|
|
6
|
+
// bitcoin-block (0xb0), an 80-byte header:
|
|
7
|
+
// {version, previousblockhash → link(bitcoin-block) | null (genesis),
|
|
8
|
+
// merkleroot → link(bitcoin-tx), time, bits, nonce}
|
|
9
|
+
// bitcoin-tx (0xb1), a transaction:
|
|
10
|
+
// {version, vin: [{txid → link(bitcoin-tx), vout, script, sequence} | {coinbase, sequence}],
|
|
11
|
+
// vout: [{value, script}], locktime}
|
|
12
|
+
// bitcoin-tx (0xb1), exactly 64 bytes: a merkle tree node [left → link, right → link]
|
|
13
|
+
// (IPLD's convention: a 64-byte transaction is malformed by convention, so
|
|
14
|
+
// 64 bytes always reads as a node)
|
|
15
|
+
//
|
|
16
|
+
// Forward links (`bitcoinLinks`, what packets and the explorer follow):
|
|
17
|
+
// inputs → the spent txid, rel `spends`, locator = vout (a coinbase input
|
|
18
|
+
// links nothing); header → `prev`, `merkleroot`; merkle node → `child`,
|
|
19
|
+
// locator 0 / 1. Edges (`bitcoinEdges`, what a kept block contributes to the
|
|
20
|
+
// index, index.zig; #42 decided 2026-09-30): a transaction's inputs only —
|
|
21
|
+
// headers and merkle nodes contribute none.
|
|
22
|
+
import { CID } from "multiformats/cid";
|
|
23
|
+
import * as Digest from "multiformats/hashes/digest";
|
|
24
|
+
export const BITCOIN_BLOCK = 0xb0, BITCOIN_TX = 0xb1, DBL_SHA2_256 = 0x56;
|
|
25
|
+
export const hashCid = (code, h) => CID.createV1(code, Digest.create(DBL_SHA2_256, Uint8Array.from(h)));
|
|
26
|
+
export const displayHash = (h) => Buffer.from(h).reverse().toString("hex");
|
|
27
|
+
export const isBitcoin = (c) => (c.code === BITCOIN_BLOCK || c.code === BITCOIN_TX) && c.multihash.code === DBL_SHA2_256;
|
|
28
|
+
const isNull = (h) => h.every((b) => b === 0);
|
|
29
|
+
/** A transaction's standard serialization (no segwit: BSV); throws when malformed. */
|
|
30
|
+
export function parseTx(b) {
|
|
31
|
+
const dv = new DataView(b.buffer, b.byteOffset, b.byteLength);
|
|
32
|
+
let i = 0;
|
|
33
|
+
const need = (n) => { if (n > b.length - i)
|
|
34
|
+
throw new Error("malformed transaction"); };
|
|
35
|
+
const u32 = () => { need(4); const v = dv.getUint32(i, true); i += 4; return v; };
|
|
36
|
+
const u64 = () => { need(8); const v = dv.getBigUint64(i, true); i += 8; return v; };
|
|
37
|
+
const varint = () => {
|
|
38
|
+
need(1);
|
|
39
|
+
const f = b[i++];
|
|
40
|
+
if (f === 0xfd) {
|
|
41
|
+
need(2);
|
|
42
|
+
const v = dv.getUint16(i, true);
|
|
43
|
+
i += 2;
|
|
44
|
+
return v;
|
|
45
|
+
}
|
|
46
|
+
if (f === 0xfe)
|
|
47
|
+
return u32();
|
|
48
|
+
if (f === 0xff)
|
|
49
|
+
return Number(u64());
|
|
50
|
+
return f;
|
|
51
|
+
};
|
|
52
|
+
const bytes = () => { const n = varint(); need(n); const v = b.subarray(i, i + n); i += n; return v; };
|
|
53
|
+
const take = (n) => { need(n); const v = b.subarray(i, i + n); i += n; return v; };
|
|
54
|
+
const version = u32() | 0;
|
|
55
|
+
const nin = varint();
|
|
56
|
+
if (nin > (b.length - i) / 41)
|
|
57
|
+
throw new Error("malformed transaction");
|
|
58
|
+
const inputs = [];
|
|
59
|
+
for (let k = 0; k < nin; k++)
|
|
60
|
+
inputs.push({ prev: take(32), vout: u32(), script: bytes(), sequence: u32() });
|
|
61
|
+
const nout = varint();
|
|
62
|
+
if (nout > (b.length - i) / 9)
|
|
63
|
+
throw new Error("malformed transaction");
|
|
64
|
+
const outputs = [];
|
|
65
|
+
for (let k = 0; k < nout; k++)
|
|
66
|
+
outputs.push({ value: u64(), script: bytes() });
|
|
67
|
+
const locktime = u32();
|
|
68
|
+
if (i !== b.length)
|
|
69
|
+
throw new Error("malformed transaction");
|
|
70
|
+
return { version, inputs, outputs, locktime };
|
|
71
|
+
}
|
|
72
|
+
const isCoinbase = (x) => x.vout === 0xffffffff && isNull(x.prev);
|
|
73
|
+
/** The typed node for a bitcoin block's bytes; undefined when it is not one (or does not decode). */
|
|
74
|
+
export function decodeBitcoin(cid, b) {
|
|
75
|
+
if (!isBitcoin(cid))
|
|
76
|
+
return undefined;
|
|
77
|
+
if (cid.code === BITCOIN_BLOCK) {
|
|
78
|
+
if (b.length !== 80)
|
|
79
|
+
return undefined;
|
|
80
|
+
const dv = new DataView(b.buffer, b.byteOffset, 80);
|
|
81
|
+
return {
|
|
82
|
+
version: dv.getInt32(0, true),
|
|
83
|
+
previousblockhash: isNull(b.subarray(4, 36)) ? null : hashCid(BITCOIN_BLOCK, b.subarray(4, 36)),
|
|
84
|
+
merkleroot: hashCid(BITCOIN_TX, b.subarray(36, 68)),
|
|
85
|
+
time: dv.getUint32(68, true),
|
|
86
|
+
bits: dv.getUint32(72, true),
|
|
87
|
+
nonce: dv.getUint32(76, true),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (b.length === 64)
|
|
91
|
+
return [hashCid(BITCOIN_TX, b.subarray(0, 32)), hashCid(BITCOIN_TX, b.subarray(32, 64))];
|
|
92
|
+
let tx;
|
|
93
|
+
try {
|
|
94
|
+
tx = parseTx(b);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return undefined;
|
|
98
|
+
} // not a transaction: no fields (the explorer shows the raw block)
|
|
99
|
+
return {
|
|
100
|
+
version: tx.version,
|
|
101
|
+
vin: tx.inputs.map((x) => isCoinbase(x) ? { coinbase: x.script, sequence: x.sequence } : { txid: hashCid(BITCOIN_TX, x.prev), vout: x.vout, script: x.script, sequence: x.sequence }),
|
|
102
|
+
vout: tx.outputs.map((o) => ({ value: Number(o.value), script: o.script })),
|
|
103
|
+
locktime: tx.locktime,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** The links a bitcoin block holds, in order (kernel-zig/src/bitcoin.zig `links`); none when it is not one or does not decode. */
|
|
107
|
+
export function bitcoinLinks(cid, b) {
|
|
108
|
+
if (!isBitcoin(cid))
|
|
109
|
+
return [];
|
|
110
|
+
if (cid.code === BITCOIN_BLOCK) {
|
|
111
|
+
if (b.length !== 80)
|
|
112
|
+
return [];
|
|
113
|
+
const out = [];
|
|
114
|
+
if (!isNull(b.subarray(4, 36)))
|
|
115
|
+
out.push({ to: hashCid(BITCOIN_BLOCK, b.subarray(4, 36)), rel: "prev", locator: null });
|
|
116
|
+
out.push({ to: hashCid(BITCOIN_TX, b.subarray(36, 68)), rel: "merkleroot", locator: null });
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
if (b.length === 64)
|
|
120
|
+
return [0, 1].map((k) => ({ to: hashCid(BITCOIN_TX, b.subarray(32 * k, 32 * k + 32)), rel: "child", locator: k }));
|
|
121
|
+
try {
|
|
122
|
+
return parseTx(b).inputs.filter((x) => !isCoinbase(x)).map((x) => ({ to: hashCid(BITCOIN_TX, x.prev), rel: "spends", locator: x.vout }));
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return [];
|
|
126
|
+
} // does not decode: no links, as the doc says
|
|
127
|
+
}
|
|
128
|
+
/** The edges a kept bitcoin block contributes (kernel-zig/src/bitcoin.zig `edgesOf`): a transaction's `spends` links; none for a header or a merkle node. */
|
|
129
|
+
export function bitcoinEdges(cid, b) {
|
|
130
|
+
if (!isBitcoin(cid) || cid.code !== BITCOIN_TX || b.length === 64)
|
|
131
|
+
return [];
|
|
132
|
+
return bitcoinLinks(cid, b);
|
|
133
|
+
}
|
|
134
|
+
/** What a bitcoin block is, for display. */
|
|
135
|
+
export function bitcoinKind(cid, b) {
|
|
136
|
+
if (!isBitcoin(cid))
|
|
137
|
+
return undefined;
|
|
138
|
+
if (cid.code === BITCOIN_BLOCK)
|
|
139
|
+
return b.length === 80 ? "header" : undefined;
|
|
140
|
+
return b.length === 64 ? "merkle node" : "transaction";
|
|
141
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CID } from "multiformats/cid";
|
|
2
|
+
export { CID };
|
|
3
|
+
/** Encode a value as a dag-cbor block. Undefined properties are dropped (not an IPLD type). */
|
|
4
|
+
export declare function encode(value: unknown): {
|
|
5
|
+
cid: CID;
|
|
6
|
+
bytes: Uint8Array;
|
|
7
|
+
};
|
|
8
|
+
export declare function decode<T = unknown>(bytes: Uint8Array): T;
|
|
9
|
+
/** Accepts any multibase the CID parser knows; base32 (`bafy…`) is what we emit. */
|
|
10
|
+
export declare function parse(s: string): CID;
|
|
11
|
+
export declare function fmt(cid: CID): string;
|
|
12
|
+
/** CID from its binary form (how the index stores them). */
|
|
13
|
+
export declare function fromBytes(bytes: Uint8Array): CID;
|
|
14
|
+
export declare function isCID(x: unknown): x is CID;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// CID helpers. Blocks are dag-cbor, hashed sha2-256, identified by CIDv1.
|
|
2
|
+
// Everything here is synchronous on purpose: the store encodes inside its
|
|
3
|
+
// write transactions, and an await there would let appends interleave.
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import * as dagCbor from "@ipld/dag-cbor";
|
|
6
|
+
import { CID } from "multiformats/cid";
|
|
7
|
+
import * as Digest from "multiformats/hashes/digest";
|
|
8
|
+
export { CID };
|
|
9
|
+
const SHA2_256 = 0x12;
|
|
10
|
+
/** Encode a value as a dag-cbor block. Undefined properties are dropped (not an IPLD type). */
|
|
11
|
+
export function encode(value) {
|
|
12
|
+
const bytes = dagCbor.encode(strip(value));
|
|
13
|
+
const digest = Digest.create(SHA2_256, createHash("sha256").update(bytes).digest());
|
|
14
|
+
return { cid: CID.createV1(dagCbor.code, digest), bytes };
|
|
15
|
+
}
|
|
16
|
+
export function decode(bytes) {
|
|
17
|
+
return dagCbor.decode(bytes);
|
|
18
|
+
}
|
|
19
|
+
/** Accepts any multibase the CID parser knows; base32 (`bafy…`) is what we emit. */
|
|
20
|
+
export function parse(s) {
|
|
21
|
+
return CID.parse(s);
|
|
22
|
+
}
|
|
23
|
+
export function fmt(cid) {
|
|
24
|
+
return cid.toString();
|
|
25
|
+
}
|
|
26
|
+
/** CID from its binary form (how the index stores them). */
|
|
27
|
+
export function fromBytes(bytes) {
|
|
28
|
+
return CID.decode(bytes);
|
|
29
|
+
}
|
|
30
|
+
export function isCID(x) {
|
|
31
|
+
return CID.asCID(x) !== null;
|
|
32
|
+
}
|
|
33
|
+
// Callers build update bodies with optional fields left undefined; dag-cbor
|
|
34
|
+
// rejects undefined outright. Only plain objects are rewritten — CIDs, bytes
|
|
35
|
+
// and arrays pass through (undefined inside an array is still an error).
|
|
36
|
+
function strip(v) {
|
|
37
|
+
if (v === null || typeof v !== "object")
|
|
38
|
+
return v;
|
|
39
|
+
if (Array.isArray(v))
|
|
40
|
+
return v.map(strip);
|
|
41
|
+
if (CID.asCID(v) || ArrayBuffer.isView(v))
|
|
42
|
+
return v;
|
|
43
|
+
const proto = Object.getPrototypeOf(v);
|
|
44
|
+
if (proto !== Object.prototype && proto !== null)
|
|
45
|
+
return v;
|
|
46
|
+
const out = {};
|
|
47
|
+
for (const [k, x] of Object.entries(v))
|
|
48
|
+
if (x !== undefined)
|
|
49
|
+
out[k] = strip(x);
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { CID } from "multiformats/cid";
|
|
2
|
+
import { type Store } from "./store.ts";
|
|
3
|
+
import type { Ms } from "./types.ts";
|
|
4
|
+
export type DispatchOrigin = {
|
|
5
|
+
kind: "dispatch";
|
|
6
|
+
};
|
|
7
|
+
export type Op = "add" | "remove";
|
|
8
|
+
export type Transport = "mailbox" | "event" | "http" | "libp2p" | "local";
|
|
9
|
+
/** A route as the kernel holds it; `program` a program record's CID, "kernel" (an admin operation named by `fn`), or absent (a read route). */
|
|
10
|
+
export type DispatchRow = {
|
|
11
|
+
transport: Transport;
|
|
12
|
+
address: string;
|
|
13
|
+
prefix?: boolean;
|
|
14
|
+
filters?: string[];
|
|
15
|
+
program?: CID | "kernel";
|
|
16
|
+
fn?: string;
|
|
17
|
+
app?: string;
|
|
18
|
+
[setting: string]: unknown;
|
|
19
|
+
};
|
|
20
|
+
export type DispatchUpdate = {
|
|
21
|
+
op: Op;
|
|
22
|
+
row: DispatchRow;
|
|
23
|
+
origin: CID;
|
|
24
|
+
prev: CID;
|
|
25
|
+
seq: number;
|
|
26
|
+
thread?: CID;
|
|
27
|
+
input: CID;
|
|
28
|
+
at: Ms;
|
|
29
|
+
};
|
|
30
|
+
/** The kernel's own filters (kernel-zig door.zig). */
|
|
31
|
+
export declare const KERNEL_FILTERS: readonly ["kernel.brc104", "kernel.beef"];
|
|
32
|
+
/** The chain's origin: the same CID in every store. */
|
|
33
|
+
export declare const dispatchOrigin: () => CID;
|
|
34
|
+
/**
|
|
35
|
+
* A route's key as text: (transport, address, prefix), space-separated with the word `prefix` for a
|
|
36
|
+
* prefix route. No address has a space, so the texts of two keys are equal only when the keys are
|
|
37
|
+
* (dispatch.zig `sameKey`).
|
|
38
|
+
*/
|
|
39
|
+
export declare const rowKey: (r: {
|
|
40
|
+
transport: string;
|
|
41
|
+
address: string;
|
|
42
|
+
prefix?: boolean;
|
|
43
|
+
}) => string;
|
|
44
|
+
/** Whether `s` names a filter (dispatch.zig isFilterRef): one of the kernel's, or `<app>.<filter>`. */
|
|
45
|
+
export declare function isFilterRef(s: unknown): s is string;
|
|
46
|
+
/**
|
|
47
|
+
* Whether the kernel's fold keeps `r` (dispatch.zig `rowOf`): a map with a text transport and
|
|
48
|
+
* address, no sender, and a program that is text, a CID, or absent. What the kernel's `problem`
|
|
49
|
+
* refuses never reaches a chain; this is what the fold itself drops.
|
|
50
|
+
*/
|
|
51
|
+
export declare function isFoldable(r: unknown): r is DispatchRow;
|
|
52
|
+
/** Whether a route is a read route (#143): filters only, no handler. */
|
|
53
|
+
export declare const isReadRoute: (r: DispatchRow) => boolean;
|
|
54
|
+
/** The routes after `updates`, in order (dispatch.zig `fold`: an update with no op or a route the kernel does not read is skipped). */
|
|
55
|
+
export declare function fold(updates: Iterable<{
|
|
56
|
+
op: Op;
|
|
57
|
+
row: DispatchRow;
|
|
58
|
+
}>): DispatchRow[];
|
|
59
|
+
/** The chain's updates, oldest first; undefined if the chain was never opened (no genesis processed). */
|
|
60
|
+
export declare function dispatchUpdates(store: Store): Promise<Array<{
|
|
61
|
+
cid: CID;
|
|
62
|
+
u: DispatchUpdate;
|
|
63
|
+
}> | undefined>;
|
|
64
|
+
/** The routes now; undefined if the chain was never opened. */
|
|
65
|
+
export declare function currentDispatch(store: Store): Promise<DispatchRow[] | undefined>;
|
|
66
|
+
/** Whether a mailbox route takes a message in `box` (dispatch.zig forMail). */
|
|
67
|
+
export declare const takesMail: (r: DispatchRow, box: string) => boolean;
|
|
68
|
+
/** Whether an event route takes an event in `box` (dispatch.zig forEvent). */
|
|
69
|
+
export declare const takesEvent: (r: DispatchRow, box: string) => boolean;
|
|
70
|
+
/** The first route a message in `box` routes to, with its index, or undefined (dispatch.zig forMail). */
|
|
71
|
+
export declare const forMail: (rows: DispatchRow[], box: string) => {
|
|
72
|
+
i: number;
|
|
73
|
+
row: DispatchRow;
|
|
74
|
+
} | undefined;
|
|
75
|
+
/** The first route an event in `box` routes to (dispatch.zig forEvent). */
|
|
76
|
+
export declare const forEvent: (rows: DispatchRow[], box: string) => {
|
|
77
|
+
i: number;
|
|
78
|
+
row: DispatchRow;
|
|
79
|
+
} | undefined;
|
|
80
|
+
/** A request's http route (dispatch.zig forHttp): the exact path first, then the longest prefix the path starts with. */
|
|
81
|
+
export declare function forHttp(rows: DispatchRow[], path: string): {
|
|
82
|
+
i: number;
|
|
83
|
+
row: DispatchRow;
|
|
84
|
+
} | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* A libp2p package's route (dispatch.zig forLibp2p): the `libp2p` route at its topic or
|
|
87
|
+
* `/<protocol>` exactly. A topic no route is at may be delivered by an app's subscription (#119, the kernel's).
|
|
88
|
+
*/
|
|
89
|
+
export declare const forLibp2p: (rows: DispatchRow[], name: string) => {
|
|
90
|
+
i: number;
|
|
91
|
+
row: DispatchRow;
|
|
92
|
+
} | undefined;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// The route table (#77, #143; docs/MESSAGES.md "The dispatch table"), read
|
|
2
|
+
// from a store: one of the kernel's tables, the one that routes. Routing only
|
|
3
|
+
// (#143: no sender — who may run a function is the grants', kernel-zig
|
|
4
|
+
// grants.zig). One chain per instance, origin
|
|
5
|
+
//
|
|
6
|
+
// { kind: "dispatch" }
|
|
7
|
+
//
|
|
8
|
+
// and one update per change, { op: "add" | "remove", row, thread?, input, at }
|
|
9
|
+
// (the store adds origin, prev, seq). The routes are the updates folded in
|
|
10
|
+
// order: a route's key is (transport, address, prefix); `add` replaces the
|
|
11
|
+
// route with that key in place, else appends; `remove` deletes it.
|
|
12
|
+
//
|
|
13
|
+
// route {transport: "mailbox" | "event" | "http" | "libp2p" | "local", address, prefix?: true (http only),
|
|
14
|
+
// filters?: ["kernel.brc104" | "kernel.beef" | "<app>.<filter>"], program?: <cid> | "kernel", fn?, app?,
|
|
15
|
+
// …settings}
|
|
16
|
+
//
|
|
17
|
+
// No program: a read route (http only) — its filters answer, nothing is logged. `event` (#143): events
|
|
18
|
+
// are their own transport (the host's wiring into a box), never a message.
|
|
19
|
+
//
|
|
20
|
+
// The kernel writes the chain and matches by it (kernel-zig/src/dispatch.zig):
|
|
21
|
+
// the genesis's `dispatch` as its first updates (no `thread`), then a change
|
|
22
|
+
// whenever its `dispatch` operation takes an admin message. Here it is read —
|
|
23
|
+
// by the host (the libp2p node's topics, whether an instance takes a box, the
|
|
24
|
+
// install client), the explorer, and tests — and matched with the kernel's
|
|
25
|
+
// rules: kernel-zig/test/dispatch-cases.json holds the cases both this file
|
|
26
|
+
// (dispatch.test.ts) and dispatch.zig are checked against.
|
|
27
|
+
import { CID } from "multiformats/cid";
|
|
28
|
+
import { encode } from "./cid.js";
|
|
29
|
+
import { NotFound } from "./store.js";
|
|
30
|
+
const ORIGIN = { kind: "dispatch" };
|
|
31
|
+
/** The kernel's own filters (kernel-zig door.zig). */
|
|
32
|
+
export const KERNEL_FILTERS = ["kernel.brc104", "kernel.beef"];
|
|
33
|
+
/** The chain's origin: the same CID in every store. */
|
|
34
|
+
export const dispatchOrigin = () => encode(ORIGIN).cid;
|
|
35
|
+
/**
|
|
36
|
+
* A route's key as text: (transport, address, prefix), space-separated with the word `prefix` for a
|
|
37
|
+
* prefix route. No address has a space, so the texts of two keys are equal only when the keys are
|
|
38
|
+
* (dispatch.zig `sameKey`).
|
|
39
|
+
*/
|
|
40
|
+
export const rowKey = (r) => `${r.transport} ${r.address}${r.prefix === true ? " prefix" : ""}`;
|
|
41
|
+
const isCid = (x) => CID.asCID(x) !== null;
|
|
42
|
+
/** Whether `s` names a filter (dispatch.zig isFilterRef): one of the kernel's, or `<app>.<filter>`. */
|
|
43
|
+
export function isFilterRef(s) {
|
|
44
|
+
if (typeof s !== "string")
|
|
45
|
+
return false;
|
|
46
|
+
const dot = s.lastIndexOf(".");
|
|
47
|
+
if (dot <= 0)
|
|
48
|
+
return false;
|
|
49
|
+
const app = s.slice(0, dot), name = s.slice(dot + 1);
|
|
50
|
+
if (!/^[A-Za-z0-9_-]+$/.test(name) || /[\s\0/]/.test(app))
|
|
51
|
+
return false;
|
|
52
|
+
return app === "kernel" ? KERNEL_FILTERS.includes(s) : true;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Whether the kernel's fold keeps `r` (dispatch.zig `rowOf`): a map with a text transport and
|
|
56
|
+
* address, no sender, and a program that is text, a CID, or absent. What the kernel's `problem`
|
|
57
|
+
* refuses never reaches a chain; this is what the fold itself drops.
|
|
58
|
+
*/
|
|
59
|
+
export function isFoldable(r) {
|
|
60
|
+
if (typeof r !== "object" || r === null || Array.isArray(r) || r instanceof Uint8Array)
|
|
61
|
+
return false;
|
|
62
|
+
const x = r;
|
|
63
|
+
if (typeof x.transport !== "string" || typeof x.address !== "string")
|
|
64
|
+
return false;
|
|
65
|
+
if (x.sender !== undefined)
|
|
66
|
+
return false;
|
|
67
|
+
if (x.program === undefined)
|
|
68
|
+
return true;
|
|
69
|
+
return x.program === "kernel" || isCid(x.program);
|
|
70
|
+
}
|
|
71
|
+
/** Whether a route is a read route (#143): filters only, no handler. */
|
|
72
|
+
export const isReadRoute = (r) => r.program === undefined;
|
|
73
|
+
/** The routes after `updates`, in order (dispatch.zig `fold`: an update with no op or a route the kernel does not read is skipped). */
|
|
74
|
+
export function fold(updates) {
|
|
75
|
+
const out = [];
|
|
76
|
+
for (const u of updates) {
|
|
77
|
+
if (typeof u.op !== "string" || !isFoldable(u.row))
|
|
78
|
+
continue;
|
|
79
|
+
const i = out.findIndex((r) => rowKey(r) === rowKey(u.row));
|
|
80
|
+
if (u.op === "add") {
|
|
81
|
+
if (i < 0)
|
|
82
|
+
out.push(u.row);
|
|
83
|
+
else
|
|
84
|
+
out[i] = u.row;
|
|
85
|
+
}
|
|
86
|
+
if (u.op === "remove" && i >= 0)
|
|
87
|
+
out.splice(i, 1);
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
/** The chain's updates, oldest first; undefined if the chain was never opened (no genesis processed). */
|
|
92
|
+
export async function dispatchUpdates(store) {
|
|
93
|
+
const origin = dispatchOrigin();
|
|
94
|
+
const out = [];
|
|
95
|
+
try {
|
|
96
|
+
await store.chains.tip(origin);
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
if (e instanceof NotFound)
|
|
100
|
+
return undefined;
|
|
101
|
+
throw e;
|
|
102
|
+
}
|
|
103
|
+
for await (const c of store.chains.history(origin))
|
|
104
|
+
if (!c.equals(origin))
|
|
105
|
+
out.push({ cid: c, u: await store.get(c) });
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
/** The routes now; undefined if the chain was never opened. */
|
|
109
|
+
export async function currentDispatch(store) {
|
|
110
|
+
const ups = await dispatchUpdates(store);
|
|
111
|
+
return ups && fold(ups.map((x) => x.u));
|
|
112
|
+
}
|
|
113
|
+
// ---------------------------------------------------------------- the match (dispatch.zig, the same rules)
|
|
114
|
+
const found = (rows, i) => i < 0 ? undefined : { i, row: rows[i] };
|
|
115
|
+
const atBox = (r, box) => r.address === "*" || r.address === box;
|
|
116
|
+
/** Whether a mailbox route takes a message in `box` (dispatch.zig forMail). */
|
|
117
|
+
export const takesMail = (r, box) => r.transport === "mailbox" && atBox(r, box);
|
|
118
|
+
/** Whether an event route takes an event in `box` (dispatch.zig forEvent). */
|
|
119
|
+
export const takesEvent = (r, box) => r.transport === "event" && atBox(r, box);
|
|
120
|
+
/** The first route a message in `box` routes to, with its index, or undefined (dispatch.zig forMail). */
|
|
121
|
+
export const forMail = (rows, box) => found(rows, rows.findIndex((r) => takesMail(r, box)));
|
|
122
|
+
/** The first route an event in `box` routes to (dispatch.zig forEvent). */
|
|
123
|
+
export const forEvent = (rows, box) => found(rows, rows.findIndex((r) => takesEvent(r, box)));
|
|
124
|
+
/** A request's http route (dispatch.zig forHttp): the exact path first, then the longest prefix the path starts with. */
|
|
125
|
+
export function forHttp(rows, path) {
|
|
126
|
+
const exact = rows.findIndex((r) => r.transport === "http" && r.prefix !== true && r.address === path);
|
|
127
|
+
if (exact >= 0)
|
|
128
|
+
return found(rows, exact);
|
|
129
|
+
let best = -1;
|
|
130
|
+
rows.forEach((r, i) => {
|
|
131
|
+
if (r.transport !== "http" || r.prefix !== true || !path.startsWith(r.address))
|
|
132
|
+
return;
|
|
133
|
+
if (best < 0 || r.address.length > rows[best].address.length)
|
|
134
|
+
best = i;
|
|
135
|
+
});
|
|
136
|
+
return found(rows, best);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* A libp2p package's route (dispatch.zig forLibp2p): the `libp2p` route at its topic or
|
|
140
|
+
* `/<protocol>` exactly. A topic no route is at may be delivered by an app's subscription (#119, the kernel's).
|
|
141
|
+
*/
|
|
142
|
+
export const forLibp2p = (rows, name) => found(rows, rows.findIndex((r) => r.transport === "libp2p" && r.prefix !== true && r.address === name));
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { WalletProtocol } from "@bsv/sdk";
|
|
2
|
+
export declare const ENVELOPE_PROTOCOL: [2, "metanet handles envelope"];
|
|
3
|
+
export declare const ENVELOPE_KEY_ID = "send";
|
|
4
|
+
export declare const MESSAGE_ENCRYPTION: WalletProtocol;
|
|
5
|
+
export declare const BRC78_VERSION: Uint8Array<ArrayBuffer>;
|
|
6
|
+
export interface Envelope {
|
|
7
|
+
metanetHandles: "1.0";
|
|
8
|
+
recipient: {
|
|
9
|
+
handle: string;
|
|
10
|
+
tag?: string;
|
|
11
|
+
domain: string;
|
|
12
|
+
};
|
|
13
|
+
sender: {
|
|
14
|
+
identityKey: string;
|
|
15
|
+
handle?: string;
|
|
16
|
+
domain?: string;
|
|
17
|
+
};
|
|
18
|
+
created: string;
|
|
19
|
+
quoteId?: string;
|
|
20
|
+
payment?: unknown;
|
|
21
|
+
contentHash: string;
|
|
22
|
+
content: string;
|
|
23
|
+
signature: string;
|
|
24
|
+
}
|
|
25
|
+
/** The sender-signed part: the envelope without its wire encryption. What an instance keeps. */
|
|
26
|
+
export type Signed = Omit<Envelope, "content">;
|
|
27
|
+
export declare function signedPart(env: Envelope | Signed): Signed;
|
|
28
|
+
/** hex SHA-256 of the plaintext content. */
|
|
29
|
+
export declare const contentHash: (body: Uint8Array) => string;
|
|
30
|
+
/**
|
|
31
|
+
* RFC 8785 (JCS) canonical JSON of the envelope without `content` and
|
|
32
|
+
* `signature`: keys sorted by UTF-16 code units, no whitespace, strings and
|
|
33
|
+
* numbers as ECMAScript JSON.stringify writes them (which is what JCS
|
|
34
|
+
* specifies). Undefined members are omitted.
|
|
35
|
+
*/
|
|
36
|
+
export declare function canonical(env: Partial<Envelope>): string;
|
|
37
|
+
export declare function jcs(v: unknown): string;
|
|
38
|
+
export interface Brc78 {
|
|
39
|
+
version: Uint8Array;
|
|
40
|
+
sender: string;
|
|
41
|
+
recipient: string;
|
|
42
|
+
keyId: Uint8Array;
|
|
43
|
+
ciphertext: Uint8Array;
|
|
44
|
+
}
|
|
45
|
+
export declare function brc78Encode(m: {
|
|
46
|
+
sender: string;
|
|
47
|
+
recipient: string;
|
|
48
|
+
keyId: Uint8Array;
|
|
49
|
+
ciphertext: Uint8Array;
|
|
50
|
+
}): Uint8Array;
|
|
51
|
+
export declare function brc78Decode(bytes: Uint8Array): Brc78;
|
|
52
|
+
/**
|
|
53
|
+
* The signature against sender.identityKey, with no wallet: the signing key is
|
|
54
|
+
* derived per BRC-42 (invoice "2-metanet handles envelope-1", counterparty
|
|
55
|
+
* anyone). Also checks the shape, and, when `content` is present, that the
|
|
56
|
+
* BRC-78 content names the same sender. Works on the signed part alone.
|
|
57
|
+
*/
|
|
58
|
+
export declare function verify(env: Envelope | Signed): boolean;
|
|
59
|
+
export declare function isSigned(x: unknown): x is Signed;
|
|
60
|
+
export declare function isEnvelope(x: unknown): x is Envelope;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// BRC-169 §7.2 envelopes: the shape and the checks that need no wallet —
|
|
2
|
+
// the signed part, contentHash, RFC 8785 canonical form, BRC-78 framing, the
|
|
3
|
+
// signature against sender.identityKey. src/envelope.ts adds the wallet
|
|
4
|
+
// operations (sign, encrypt, open) for the client and the peers. See
|
|
5
|
+
// src/envelope.ts for the wire format.
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { verifyAnyone } from "./identity.js";
|
|
8
|
+
export const ENVELOPE_PROTOCOL = [2, "metanet handles envelope"];
|
|
9
|
+
export const ENVELOPE_KEY_ID = "send";
|
|
10
|
+
export const MESSAGE_ENCRYPTION = [2, "message encryption"];
|
|
11
|
+
export const BRC78_VERSION = Uint8Array.of(0x42, 0x42, 0x10, 0x33);
|
|
12
|
+
export function signedPart(env) {
|
|
13
|
+
const { content: _c, ...rest } = env;
|
|
14
|
+
return rest;
|
|
15
|
+
}
|
|
16
|
+
/** hex SHA-256 of the plaintext content. */
|
|
17
|
+
export const contentHash = (body) => createHash("sha256").update(body).digest("hex");
|
|
18
|
+
// ---------------------------------------------------------------- RFC 8785
|
|
19
|
+
/**
|
|
20
|
+
* RFC 8785 (JCS) canonical JSON of the envelope without `content` and
|
|
21
|
+
* `signature`: keys sorted by UTF-16 code units, no whitespace, strings and
|
|
22
|
+
* numbers as ECMAScript JSON.stringify writes them (which is what JCS
|
|
23
|
+
* specifies). Undefined members are omitted.
|
|
24
|
+
*/
|
|
25
|
+
export function canonical(env) {
|
|
26
|
+
const { content: _c, signature: _s, ...rest } = env;
|
|
27
|
+
return jcs(rest);
|
|
28
|
+
}
|
|
29
|
+
export function jcs(v) {
|
|
30
|
+
if (v === null || typeof v === "boolean" || typeof v === "string")
|
|
31
|
+
return JSON.stringify(v);
|
|
32
|
+
if (typeof v === "number") {
|
|
33
|
+
if (!Number.isFinite(v))
|
|
34
|
+
throw new TypeError("JCS: non-finite number");
|
|
35
|
+
return JSON.stringify(v); // ES Number::toString, -0 → "0": exactly JCS
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(v))
|
|
38
|
+
return `[${v.map((x) => (x === undefined ? "null" : jcs(x))).join(",")}]`;
|
|
39
|
+
if (typeof v === "object") {
|
|
40
|
+
const o = v;
|
|
41
|
+
const keys = Object.keys(o).filter((k) => o[k] !== undefined).sort(); // default sort compares UTF-16 code units
|
|
42
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${jcs(o[k])}`).join(",")}}`;
|
|
43
|
+
}
|
|
44
|
+
throw new TypeError(`JCS: cannot canonicalize ${typeof v}`);
|
|
45
|
+
}
|
|
46
|
+
export function brc78Encode(m) {
|
|
47
|
+
if (m.keyId.length !== 32)
|
|
48
|
+
throw new TypeError("BRC-78: key id must be 32 bytes");
|
|
49
|
+
const s = Buffer.from(m.sender, "hex"), r = Buffer.from(m.recipient, "hex");
|
|
50
|
+
if (s.length !== 33 || r.length !== 33)
|
|
51
|
+
throw new TypeError("BRC-78: identities must be 33-byte compressed keys");
|
|
52
|
+
return Buffer.concat([BRC78_VERSION, s, r, m.keyId, m.ciphertext]);
|
|
53
|
+
}
|
|
54
|
+
export function brc78Decode(bytes) {
|
|
55
|
+
if (bytes.length < 4 + 33 + 33 + 32 + 32)
|
|
56
|
+
throw new TypeError("BRC-78: too short");
|
|
57
|
+
const version = bytes.subarray(0, 4);
|
|
58
|
+
if (!Buffer.from(version).equals(BRC78_VERSION))
|
|
59
|
+
throw new TypeError(`BRC-78: version ${Buffer.from(version).toString("hex")}, want 42421033`);
|
|
60
|
+
return {
|
|
61
|
+
version,
|
|
62
|
+
sender: Buffer.from(bytes.subarray(4, 37)).toString("hex"),
|
|
63
|
+
recipient: Buffer.from(bytes.subarray(37, 70)).toString("hex"),
|
|
64
|
+
keyId: bytes.subarray(70, 102),
|
|
65
|
+
ciphertext: bytes.subarray(102),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// ---------------------------------------------------------------- verify
|
|
69
|
+
/**
|
|
70
|
+
* The signature against sender.identityKey, with no wallet: the signing key is
|
|
71
|
+
* derived per BRC-42 (invoice "2-metanet handles envelope-1", counterparty
|
|
72
|
+
* anyone). Also checks the shape, and, when `content` is present, that the
|
|
73
|
+
* BRC-78 content names the same sender. Works on the signed part alone.
|
|
74
|
+
*/
|
|
75
|
+
export function verify(env) {
|
|
76
|
+
if (!isSigned(env))
|
|
77
|
+
return false;
|
|
78
|
+
if ("content" in env && env.content !== undefined) {
|
|
79
|
+
try {
|
|
80
|
+
if (typeof env.content !== "string" || brc78Decode(Buffer.from(env.content, "base64")).sender !== env.sender.identityKey)
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return verifyAnyone(env.sender.identityKey, ENVELOPE_PROTOCOL, ENVELOPE_KEY_ID, Buffer.from(canonical(env), "utf8"), Buffer.from(env.signature, "hex"));
|
|
88
|
+
}
|
|
89
|
+
const KEY = /^0[23][0-9a-f]{64}$/;
|
|
90
|
+
export function isSigned(x) {
|
|
91
|
+
const e = x;
|
|
92
|
+
return !!e && typeof e === "object" && e.metanetHandles === "1.0"
|
|
93
|
+
&& !!e.recipient && typeof e.recipient.handle === "string" && typeof e.recipient.domain === "string"
|
|
94
|
+
&& (e.recipient.tag === undefined || typeof e.recipient.tag === "string")
|
|
95
|
+
&& !!e.sender && typeof e.sender.identityKey === "string" && KEY.test(e.sender.identityKey)
|
|
96
|
+
&& typeof e.created === "string" && typeof e.contentHash === "string" && /^[0-9a-f]{64}$/.test(e.contentHash)
|
|
97
|
+
&& typeof e.signature === "string" && /^[0-9a-f]+$/i.test(e.signature);
|
|
98
|
+
}
|
|
99
|
+
export function isEnvelope(x) {
|
|
100
|
+
return isSigned(x) && typeof x.content === "string";
|
|
101
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CID } from "multiformats/cid";
|
|
2
|
+
import { type Store } from "./store.ts";
|
|
3
|
+
import type { Ms } from "./types.ts";
|
|
4
|
+
export type HeadOrigin = {
|
|
5
|
+
kind: "head";
|
|
6
|
+
name: string;
|
|
7
|
+
};
|
|
8
|
+
export type HeadUpdate = {
|
|
9
|
+
origin: CID;
|
|
10
|
+
prev: CID;
|
|
11
|
+
seq: number;
|
|
12
|
+
tree: CID;
|
|
13
|
+
/** The app the head's name is under (#77): `<app>/…` → `<app>`; a bare name is its own (heads.zig ownerOf). */
|
|
14
|
+
owner: string;
|
|
15
|
+
/** Null: moved by the genesis (a system tree, #4) or a kernel operation (#77), not a thread. */
|
|
16
|
+
thread?: CID | null;
|
|
17
|
+
input: CID;
|
|
18
|
+
at: Ms;
|
|
19
|
+
};
|
|
20
|
+
export declare const MAIN = "main";
|
|
21
|
+
export declare const isHeadName: (x: unknown) => x is string;
|
|
22
|
+
/** The head's chain origin: the same CID in every store. */
|
|
23
|
+
export declare function headOrigin(name: string): CID;
|
|
24
|
+
/** The tree a head names now, or undefined if it has never moved. */
|
|
25
|
+
export declare function headTree(store: Store, name: string): Promise<CID | undefined>;
|