@dcasegr/skein 0.0.0-stage → 0.1.1

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 (176) hide show
  1. package/README.md +522 -2
  2. package/bin/skein-host.js +22 -0
  3. package/bin/skein.js +8 -0
  4. package/dist/client/admin-cli.d.ts +10 -0
  5. package/dist/client/admin-cli.js +370 -0
  6. package/dist/client/admin.d.ts +139 -0
  7. package/dist/client/admin.js +256 -0
  8. package/dist/client/bundle.d.ts +9 -0
  9. package/dist/client/bundle.js +34 -0
  10. package/dist/client/cli.d.ts +44 -0
  11. package/dist/client/cli.js +306 -0
  12. package/dist/client/client.d.ts +161 -0
  13. package/dist/client/client.js +237 -0
  14. package/dist/client/config.d.ts +18 -0
  15. package/dist/client/config.js +31 -0
  16. package/dist/client/conversation.d.ts +62 -0
  17. package/dist/client/conversation.js +113 -0
  18. package/dist/client/raw.d.ts +94 -0
  19. package/dist/client/raw.js +154 -0
  20. package/dist/client/target.d.ts +34 -0
  21. package/dist/client/target.js +158 -0
  22. package/dist/dev/cli.d.ts +8 -0
  23. package/dist/dev/cli.js +248 -0
  24. package/dist/dev/scan.d.ts +15 -0
  25. package/dist/dev/scan.js +173 -0
  26. package/dist/envelope-cbor.d.ts +110 -0
  27. package/dist/envelope-cbor.js +160 -0
  28. package/dist/envelope.d.ts +38 -0
  29. package/dist/envelope.js +101 -0
  30. package/dist/host/arc.d.ts +131 -0
  31. package/dist/host/arc.js +425 -0
  32. package/dist/host/billing.d.ts +102 -0
  33. package/dist/host/billing.js +167 -0
  34. package/dist/host/boot.d.ts +246 -0
  35. package/dist/host/boot.js +643 -0
  36. package/dist/host/cli.d.ts +57 -0
  37. package/dist/host/cli.js +1253 -0
  38. package/dist/host/clock.d.ts +3 -0
  39. package/dist/host/clock.js +8 -0
  40. package/dist/host/control.d.ts +45 -0
  41. package/dist/host/control.js +170 -0
  42. package/dist/host/cron.d.ts +99 -0
  43. package/dist/host/cron.js +196 -0
  44. package/dist/host/deliver.d.ts +53 -0
  45. package/dist/host/deliver.js +251 -0
  46. package/dist/host/deploy.d.ts +29 -0
  47. package/dist/host/deploy.js +55 -0
  48. package/dist/host/door-answer.d.ts +24 -0
  49. package/dist/host/door-answer.js +21 -0
  50. package/dist/host/events.d.ts +89 -0
  51. package/dist/host/events.js +222 -0
  52. package/dist/host/fake-arcade.d.ts +40 -0
  53. package/dist/host/fake-arcade.js +149 -0
  54. package/dist/host/fake-discovery.d.ts +4 -0
  55. package/dist/host/fake-discovery.js +58 -0
  56. package/dist/host/feeds.d.ts +127 -0
  57. package/dist/host/feeds.js +422 -0
  58. package/dist/host/frontdoor.d.ts +92 -0
  59. package/dist/host/frontdoor.js +172 -0
  60. package/dist/host/genesis.d.ts +259 -0
  61. package/dist/host/genesis.js +353 -0
  62. package/dist/host/handles.d.ts +40 -0
  63. package/dist/host/handles.js +88 -0
  64. package/dist/host/hostenv.d.ts +30 -0
  65. package/dist/host/hostenv.js +83 -0
  66. package/dist/host/image-chain.d.ts +127 -0
  67. package/dist/host/image-chain.js +440 -0
  68. package/dist/host/install.d.ts +22 -0
  69. package/dist/host/install.js +139 -0
  70. package/dist/host/instances.d.ts +227 -0
  71. package/dist/host/instances.js +430 -0
  72. package/dist/host/kernel.d.ts +246 -0
  73. package/dist/host/kernel.js +603 -0
  74. package/dist/host/liveness.d.ts +65 -0
  75. package/dist/host/liveness.js +155 -0
  76. package/dist/host/manifest.d.ts +160 -0
  77. package/dist/host/manifest.js +597 -0
  78. package/dist/host/p2p.d.ts +337 -0
  79. package/dist/host/p2p.js +1002 -0
  80. package/dist/host/packet.d.ts +121 -0
  81. package/dist/host/packet.js +562 -0
  82. package/dist/host/plan.d.ts +154 -0
  83. package/dist/host/plan.js +418 -0
  84. package/dist/host/providers.d.ts +238 -0
  85. package/dist/host/providers.js +565 -0
  86. package/dist/host/roster.d.ts +72 -0
  87. package/dist/host/roster.js +172 -0
  88. package/dist/host/router.d.ts +773 -0
  89. package/dist/host/router.js +2389 -0
  90. package/dist/host/signer.d.ts +49 -0
  91. package/dist/host/signer.js +167 -0
  92. package/dist/host/store-sqlite.d.ts +39 -0
  93. package/dist/host/store-sqlite.js +170 -0
  94. package/dist/host/stores.d.ts +43 -0
  95. package/dist/host/stores.js +342 -0
  96. package/dist/host/sync.d.ts +121 -0
  97. package/dist/host/sync.js +467 -0
  98. package/dist/host/testbeef.d.ts +21 -0
  99. package/dist/host/testbeef.js +37 -0
  100. package/dist/host/testhost.d.ts +149 -0
  101. package/dist/host/testhost.js +211 -0
  102. package/dist/host/vcdiff.d.ts +9 -0
  103. package/dist/host/vcdiff.js +210 -0
  104. package/dist/host/wasm-kernel.d.ts +87 -0
  105. package/dist/host/wasm-kernel.js +296 -0
  106. package/dist/host/wasm-worker.d.ts +1 -0
  107. package/dist/host/wasm-worker.js +186 -0
  108. package/dist/peers/cron.d.ts +33 -0
  109. package/dist/peers/cron.js +75 -0
  110. package/dist/peers/infer-main.d.ts +2 -0
  111. package/dist/peers/infer-main.js +74 -0
  112. package/dist/peers/infer.d.ts +163 -0
  113. package/dist/peers/infer.js +367 -0
  114. package/dist/runtime/beef.d.ts +28 -0
  115. package/dist/runtime/beef.js +107 -0
  116. package/dist/runtime/bitcoin.d.ts +36 -0
  117. package/dist/runtime/bitcoin.js +141 -0
  118. package/dist/runtime/cid.d.ts +14 -0
  119. package/dist/runtime/cid.js +51 -0
  120. package/dist/runtime/dispatch.d.ts +92 -0
  121. package/dist/runtime/dispatch.js +142 -0
  122. package/dist/runtime/envelope.d.ts +60 -0
  123. package/dist/runtime/envelope.js +101 -0
  124. package/dist/runtime/heads.d.ts +25 -0
  125. package/dist/runtime/heads.js +37 -0
  126. package/dist/runtime/identity.d.ts +28 -0
  127. package/dist/runtime/identity.js +67 -0
  128. package/dist/runtime/index-store.d.ts +152 -0
  129. package/dist/runtime/index-store.js +715 -0
  130. package/dist/runtime/log.d.ts +62 -0
  131. package/dist/runtime/log.js +104 -0
  132. package/dist/runtime/programs.d.ts +30 -0
  133. package/dist/runtime/programs.js +61 -0
  134. package/dist/runtime/records.d.ts +135 -0
  135. package/dist/runtime/records.js +149 -0
  136. package/dist/runtime/sqlite.d.ts +33 -0
  137. package/dist/runtime/sqlite.js +604 -0
  138. package/dist/runtime/store.d.ts +141 -0
  139. package/dist/runtime/store.js +24 -0
  140. package/dist/runtime/syscalls.d.ts +9 -0
  141. package/dist/runtime/syscalls.js +41 -0
  142. package/dist/runtime/tree.d.ts +60 -0
  143. package/dist/runtime/tree.js +196 -0
  144. package/dist/runtime/types.d.ts +100 -0
  145. package/dist/runtime/types.js +7 -0
  146. package/dist/testapps.d.ts +101 -0
  147. package/dist/testapps.js +275 -0
  148. package/dist/testkit.d.ts +25 -0
  149. package/dist/testkit.js +48 -0
  150. package/dist/wallet.d.ts +35 -0
  151. package/dist/wallet.js +87 -0
  152. package/images/README.md +68 -0
  153. package/images/default/README.md +38 -0
  154. package/images/default/bin/frontdoor.cid +1 -0
  155. package/images/default/bin/frontdoor.json +5 -0
  156. package/images/default/bin/messagebox.cid +1 -0
  157. package/images/default/bin/messagebox.json +8 -0
  158. package/images/default/bin/wallet.cid +1 -0
  159. package/images/default/bin/wallet.json +14 -0
  160. package/images/default/etc/config.json +1 -0
  161. package/images/default/etc/dispatch.json +82 -0
  162. package/images/host/etc/apps.json +8 -0
  163. package/kernel-zig/README.md +1247 -0
  164. package/kernel-zig/web/client.js +65 -0
  165. package/kernel-zig/web/kernel.d.ts +59 -0
  166. package/kernel-zig/web/kernel.js +329 -0
  167. package/kernel-zig/web/store.js +67 -0
  168. package/kernel-zig/web/worker.js +79 -0
  169. package/kernel-zig/zig-out/web/skein-kernel.wasm +0 -0
  170. package/package.json +77 -4
  171. package/wasm/README.md +35 -0
  172. package/wasm/frontdoor.wasm +0 -0
  173. package/wasm/messagebox.wasm +0 -0
  174. package/wasm/resolve.wasm +0 -0
  175. package/wasm/wallet.wasm +0 -0
  176. package/wasm/wire-probe.wasm +0 -0
@@ -0,0 +1,121 @@
1
+ import { type ChainTracker } from "@bsv/sdk";
2
+ import { CID } from "multiformats/cid";
3
+ import { type EntryMode } from "../runtime/tree.ts";
4
+ import { MemBlocks, type Objects } from "./boot.ts";
5
+ export declare const PACKET_KIND = "skein-packet";
6
+ export declare const BITCOIN_TX = 177;
7
+ export declare const BITCOIN_BLOCK = 176;
8
+ export declare class PacketError extends Error {
9
+ readonly code: "malformed" | "scope-mismatch" | "incomplete" | "hash-mismatch" | "unproven";
10
+ constructor(code: PacketError["code"], message: string);
11
+ }
12
+ export interface IndexEntry {
13
+ tx: CID;
14
+ vout: number;
15
+ cid: CID;
16
+ }
17
+ export interface PacketRecord {
18
+ kind: typeof PACKET_KIND;
19
+ version: 1;
20
+ scope: CID;
21
+ beef: Uint8Array;
22
+ index?: IndexEntry[];
23
+ }
24
+ /** A transaction's CID: bitcoin-tx, dbl-sha2-256, the digest in internal byte order (the txid reversed). */
25
+ export declare function txCid(txidHex: string): CID;
26
+ export declare function txidOf(c: CID): string;
27
+ /** Do `bytes` hash to `cid` (git sha1, sha2-256, dbl-sha2-256)? */
28
+ export declare function hashMatches(cid: CID, bytes: Uint8Array): boolean;
29
+ /**
30
+ * The CIDs a block links to: dag-cbor links, git tree entries, and a bitcoin
31
+ * block's typed links (#42, bitcoin.ts): a header its previous header and
32
+ * its merkle root, a merkle node (64-byte bitcoin-tx) its children, a
33
+ * transaction what its inputs spend — all `optional`: the tree and the
34
+ * ancestry are sparse (only the paths to held transactions, only the
35
+ * ancestors held), so a target that is not held is not missing. None for
36
+ * blobs and raw.
37
+ */
38
+ export declare function linksOf(cid: CID, bytes: Uint8Array): Array<{
39
+ cid: CID;
40
+ tree?: boolean;
41
+ optional?: boolean;
42
+ }>;
43
+ /**
44
+ * The closure a scope requires: a system tree's objects plus the modules its
45
+ * bin/<name>.cid name; a state record's every link. `get` returns a block,
46
+ * checked against its CID, or undefined (then `missing` lists it).
47
+ */
48
+ export declare function closure(scope: CID, get: (cid: CID) => Promise<Uint8Array | undefined>): Promise<{
49
+ blocks: Array<{
50
+ cid: CID;
51
+ bytes: Uint8Array;
52
+ }>;
53
+ missing: string[];
54
+ }>;
55
+ /** Pushes of an `OP_FALSE OP_RETURN <type> <payload>` script, or undefined. */
56
+ export declare function carrierOf(script: Uint8Array): {
57
+ type: string;
58
+ payload: Uint8Array;
59
+ } | undefined;
60
+ export declare function carrierScript(type: string, payload: Uint8Array): Uint8Array;
61
+ export declare function encodeDir(entries: Array<{
62
+ name: string;
63
+ mode: EntryMode;
64
+ txid?: string;
65
+ vout: number;
66
+ }>): Uint8Array;
67
+ export interface ReadOptions {
68
+ /** The root the caller means to accept; the packet's scope must be it. */
69
+ scope?: CID;
70
+ /** Require chain inclusion: every carrier transaction's merkle path must verify against these headers. */
71
+ chainTracker?: ChainTracker;
72
+ }
73
+ export interface Verified {
74
+ scope: CID;
75
+ kind: "tree" | "checkpoint";
76
+ /** Every block the scope requires, each checked against its CID. */
77
+ blocks: MemBlocks;
78
+ list: Array<{
79
+ cid: CID;
80
+ bytes: Uint8Array;
81
+ }>;
82
+ /** Transactions objects came out of (and patch bases), by txid. */
83
+ carriers: string[];
84
+ }
85
+ export declare function decodePacket(bytes: Uint8Array): PacketRecord;
86
+ /** Read and verify a packet: the blocks its scope requires, every one checked; else a PacketError. */
87
+ export declare function readPacket(bytes: Uint8Array, o?: ReadOptions): Promise<Verified>;
88
+ export interface WriteOptions {
89
+ /** "ordfs" (default): trees as ordfs/dir manifests and files as bare content, as gib publishes; "git": every object as it is. */
90
+ form?: "ordfs" | "git";
91
+ /** Give every transaction a merkle path (a block at height 1, 2, …) and return the headers' roots. */
92
+ mined?: boolean;
93
+ /** Carrier outputs per transaction, and bytes per transaction, before a new one starts. */
94
+ maxOutputs?: number;
95
+ maxBytes?: number;
96
+ /** Leave the index out (readers then scan). */
97
+ noIndex?: boolean;
98
+ /**
99
+ * Blocks to carry beyond the scope's closure (a checkpoint's records that no
100
+ * link reaches: a run's args, an emitted envelope's signed part). Listed in
101
+ * the index, so a reader verifies and keeps them; the index is then required.
102
+ */
103
+ extras?: Array<{
104
+ cid: CID;
105
+ bytes: Uint8Array;
106
+ }>;
107
+ }
108
+ export interface Written {
109
+ bytes: Uint8Array;
110
+ packet: PacketRecord;
111
+ txids: string[];
112
+ roots: Record<number, string>;
113
+ objects: number;
114
+ }
115
+ /**
116
+ * A packet of everything `scope` requires, read out of `objects`, carried in
117
+ * synthetic transactions (never broadcast; see the header).
118
+ */
119
+ export declare function writePacket(scope: CID, objects: Objects, o?: WriteOptions): Promise<Written>;
120
+ /** A ChainTracker over a fixed set of header roots {height: root hex} (offline). */
121
+ export declare function rootsTracker(roots: Record<string | number, string>): ChainTracker;
@@ -0,0 +1,562 @@
1
+ // Bootstrap packets (issue #4, docs/BOOTSTRAP.md): on the pattern of BRC-176,
2
+ // a BEEF bag of the transactions that carry a system tree's objects (or a
3
+ // whole instance's, for a checkpoint) plus a **scope** — the root it is
4
+ // verified against. Reading one is an object lookup into the bag, not a
5
+ // chain reader; where the bag came from is nobody's concern.
6
+ //
7
+ // packet dag-cbor {kind: "skein-packet", version: 1, scope: CID, beef: bytes,
8
+ // index?: [{tx: CID(bitcoin-tx), vout: int, cid: CID}]}
9
+ // scope a git tree (a system tree: boot a new instance from it), or a
10
+ // skein-state record (#30: a checkpoint — restore the instance, its
11
+ // index read, never rebuilt)
12
+ // carrier an output `OP_FALSE OP_RETURN <content-type> <payload>`:
13
+ // ordfs/dir a gib directory manifest: resolves to the git tree of its entries
14
+ // ordfs/patch [0x00][36B base outpoint][vcdiff]: resolves to apply(delta, resolve(base))
15
+ // anything else the payload as it is
16
+ // A resolved payload is an object for a CID if it hashes to it —
17
+ // for a git blob either as the whole object or as the file's bare
18
+ // content (how ORDFS/gib carry files); a bitcoin-tx CID is also
19
+ // served by the bag's own transaction of that txid.
20
+ // index where each object is (optional: without it every carrier output
21
+ // is resolved and keyed by every CID its bytes hash to)
22
+ //
23
+ // Verification is offline. **Completeness**: every object reachable from the
24
+ // scope must come out of the bag — a system tree's trees and blobs (patched
25
+ // files walked to their bases), the modules its bin/<name>.cid files name; a
26
+ // checkpoint's every link (dag-cbor links, tree entries) from the state
27
+ // record down. **Content addressing**: every object is checked against its
28
+ // CID (git sha1, sha2-256, dbl-sha2-256). **Chain inclusion** only when asked
29
+ // (`chainTracker`): every carrier transaction must then have a merkle path
30
+ // in the bag that verifies against the tracker's headers.
31
+ //
32
+ // The writer (`writePacket`) makes the transactions itself: synthetic,
33
+ // unfunded (a made-up input), never broadcast — a test fixture and a file
34
+ // format, not a publisher. A real publisher (gib, 1sat inscriptions or B
35
+ // outputs, funded and broadcast) is out of scope; its outputs would be read
36
+ // the same way once their carrier shape is mapped onto (content-type, payload).
37
+ import { createHash } from "node:crypto";
38
+ import { Beef, MerklePath, Script, Transaction } from "@bsv/sdk";
39
+ import { CID } from "multiformats/cid";
40
+ import * as Digest from "multiformats/hashes/digest";
41
+ import { decode, encode } from "../runtime/cid.js";
42
+ import { GIT_RAW, hashTree, parseTree, sha1Cid } from "../runtime/tree.js";
43
+ import { BIN, DAG_CBOR, gitBody, MemBlocks, RAW } from "./boot.js";
44
+ import { vcdiffDecode } from "./vcdiff.js";
45
+ import { bitcoinLinks, isBitcoin } from "../runtime/bitcoin.js";
46
+ export const PACKET_KIND = "skein-packet";
47
+ export const BITCOIN_TX = 0xb1;
48
+ export const BITCOIN_BLOCK = 0xb0;
49
+ const SHA1 = 0x11, SHA2_256 = 0x12, DBL_SHA2_256 = 0x56;
50
+ const OP_FALSE = 0x00, OP_RETURN = 0x6a;
51
+ export class PacketError extends Error {
52
+ code;
53
+ constructor(code, message) { super(`packet ${code}: ${message}`); this.code = code; }
54
+ }
55
+ // ---------------------------------------------------------------- ids
56
+ const sha1 = (b) => createHash("sha1").update(b).digest();
57
+ const sha256 = (b) => createHash("sha256").update(b).digest();
58
+ const blobObject = (content) => Buffer.concat([Buffer.from(`blob ${content.length}\0`, "latin1"), content]);
59
+ /** A transaction's CID: bitcoin-tx, dbl-sha2-256, the digest in internal byte order (the txid reversed). */
60
+ export function txCid(txidHex) {
61
+ return CID.createV1(BITCOIN_TX, Digest.create(DBL_SHA2_256, Buffer.from(txidHex, "hex").reverse()));
62
+ }
63
+ export function txidOf(c) {
64
+ if (c.code !== BITCOIN_TX || c.multihash.code !== DBL_SHA2_256)
65
+ throw new PacketError("malformed", `${c} is not a bitcoin-tx CID`);
66
+ return Buffer.from(c.multihash.digest).reverse().toString("hex");
67
+ }
68
+ /** Do `bytes` hash to `cid` (git sha1, sha2-256, dbl-sha2-256)? */
69
+ export function hashMatches(cid, bytes) {
70
+ const d = Buffer.from(cid.multihash.digest);
71
+ switch (cid.multihash.code) {
72
+ case SHA1: return cid.code === GIT_RAW && d.equals(sha1(bytes));
73
+ case SHA2_256: return (cid.code === RAW || cid.code === DAG_CBOR) && d.equals(sha256(bytes));
74
+ case DBL_SHA2_256: return (cid.code === BITCOIN_TX || (cid.code === BITCOIN_BLOCK && bytes.length === 80)) && d.equals(sha256(sha256(bytes)));
75
+ default: return false;
76
+ }
77
+ }
78
+ /** The block `payload` is for `cid`, if it is one: as it is, or (a git blob) framed as a blob object. */
79
+ function asBlock(cid, payload) {
80
+ if (hashMatches(cid, payload))
81
+ return payload;
82
+ if (cid.code === GIT_RAW && cid.multihash.code === SHA1) {
83
+ const framed = blobObject(payload);
84
+ if (Buffer.from(cid.multihash.digest).equals(sha1(framed)))
85
+ return new Uint8Array(framed);
86
+ }
87
+ return undefined;
88
+ }
89
+ // ---------------------------------------------------------------- links (completeness)
90
+ /**
91
+ * The CIDs a block links to: dag-cbor links, git tree entries, and a bitcoin
92
+ * block's typed links (#42, bitcoin.ts): a header its previous header and
93
+ * its merkle root, a merkle node (64-byte bitcoin-tx) its children, a
94
+ * transaction what its inputs spend — all `optional`: the tree and the
95
+ * ancestry are sparse (only the paths to held transactions, only the
96
+ * ancestors held), so a target that is not held is not missing. None for
97
+ * blobs and raw.
98
+ */
99
+ export function linksOf(cid, bytes) {
100
+ if (isBitcoin(cid))
101
+ return bitcoinLinks(cid, bytes).map((l) => ({ cid: l.to, optional: true }));
102
+ if (cid.code === DAG_CBOR) {
103
+ const out = [];
104
+ const walk = (v) => {
105
+ const c = CID.asCID(v);
106
+ if (c) {
107
+ out.push({ cid: c });
108
+ return;
109
+ }
110
+ if (v instanceof Uint8Array || v === null || typeof v !== "object")
111
+ return;
112
+ for (const x of Array.isArray(v) ? v : Object.values(v))
113
+ walk(x);
114
+ };
115
+ walk(decode(bytes));
116
+ return out;
117
+ }
118
+ if (cid.code === GIT_RAW && gitBody(bytes, "tree"))
119
+ return parseTree(bytes, cid).map((e) => ({ cid: e.cid }));
120
+ return [];
121
+ }
122
+ /**
123
+ * The closure a scope requires: a system tree's objects plus the modules its
124
+ * bin/<name>.cid name; a state record's every link. `get` returns a block,
125
+ * checked against its CID, or undefined (then `missing` lists it).
126
+ */
127
+ export async function closure(scope, get) {
128
+ const blocks = [];
129
+ const missing = [];
130
+ const seen = new Set();
131
+ const visit = async (cid, path, optional = false) => {
132
+ const k = cid.toString();
133
+ if (seen.has(k))
134
+ return undefined;
135
+ seen.add(k);
136
+ const bytes = await get(cid);
137
+ if (!bytes) {
138
+ if (!optional)
139
+ missing.push(`${path || "/"} (${k})`);
140
+ return undefined;
141
+ }
142
+ blocks.push({ cid, bytes });
143
+ for (const l of linksOf(cid, bytes))
144
+ await visit(l.cid, cid.code === GIT_RAW ? `${path}/${nameIn(bytes, cid, l.cid)}` : `${path}→${short(l.cid)}`, l.optional);
145
+ return bytes;
146
+ };
147
+ const root = await visit(scope, "");
148
+ if (root && scope.code === GIT_RAW) {
149
+ // A system tree's bin/<name>.cid: the module it names is part of the tree's closure.
150
+ const bin = parseTree(root, scope).find((e) => e.name === BIN && e.mode === "40000");
151
+ const binBytes = bin && blocks.find((b) => b.cid.equals(bin.cid))?.bytes;
152
+ if (bin && binBytes) {
153
+ for (const e of parseTree(binBytes, bin.cid)) {
154
+ if (!e.name.endsWith(".cid"))
155
+ continue;
156
+ const f = blocks.find((b) => b.cid.equals(e.cid))?.bytes;
157
+ const text = f && gitBody(f, "blob");
158
+ if (!text)
159
+ continue;
160
+ let m;
161
+ try {
162
+ m = CID.parse(Buffer.from(text).toString("utf8").trim());
163
+ }
164
+ catch {
165
+ continue;
166
+ } // the loader refuses it with a reason
167
+ await visit(m, `/${BIN}/${e.name} → module`);
168
+ }
169
+ }
170
+ }
171
+ return { blocks, missing };
172
+ }
173
+ const short = (c) => c.toString().slice(-8);
174
+ function nameIn(tree, cid, child) {
175
+ return parseTree(tree, cid).find((e) => e.cid.equals(child))?.name ?? short(child);
176
+ }
177
+ // ---------------------------------------------------------------- carriers
178
+ /** Pushes of an `OP_FALSE OP_RETURN <type> <payload>` script, or undefined. */
179
+ export function carrierOf(script) {
180
+ if (script.length < 2 || script[0] !== OP_FALSE || script[1] !== OP_RETURN)
181
+ return undefined;
182
+ const pushes = [];
183
+ let i = 2;
184
+ while (i < script.length) {
185
+ const op = script[i++];
186
+ let n;
187
+ if (op >= 1 && op <= 75)
188
+ n = op;
189
+ else if (op === 0x4c)
190
+ n = script[i++];
191
+ else if (op === 0x4d) {
192
+ n = script[i] | (script[i + 1] << 8);
193
+ i += 2;
194
+ }
195
+ else if (op === 0x4e) {
196
+ n = (script[i] | (script[i + 1] << 8) | (script[i + 2] << 16) | (script[i + 3] << 24)) >>> 0;
197
+ i += 4;
198
+ }
199
+ else if (op === 0)
200
+ n = 0;
201
+ else
202
+ return undefined;
203
+ if (i + n > script.length)
204
+ return undefined;
205
+ pushes.push(script.subarray(i, i + n));
206
+ i += n;
207
+ }
208
+ if (pushes.length !== 2)
209
+ return undefined;
210
+ return { type: Buffer.from(pushes[0]).toString("utf8"), payload: pushes[1] };
211
+ }
212
+ function push(b) {
213
+ const n = b.length;
214
+ const head = n <= 75 ? [n] : n <= 0xff ? [0x4c, n] : n <= 0xffff ? [0x4d, n & 0xff, n >> 8] : [0x4e, n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >>> 24) & 0xff];
215
+ return Buffer.concat([Buffer.from(head), b]);
216
+ }
217
+ export function carrierScript(type, payload) {
218
+ return new Uint8Array(Buffer.concat([Buffer.from([OP_FALSE, OP_RETURN]), push(Buffer.from(type, "utf8")), push(payload)]));
219
+ }
220
+ /** An outpoint as ORDFS writes it: 32-byte txid (internal order) + 4-byte LE vout. */
221
+ const outpointBytes = (txid, vout) => { const b = Buffer.alloc(36); Buffer.from(txid, "hex").reverse().copy(b); b.writeUInt32LE(vout, 32); return b; };
222
+ const outpointOf = (b) => ({ txid: Buffer.from(b.subarray(0, 32)).reverse().toString("hex"), vout: Buffer.from(b).readUInt32LE(32) });
223
+ // ordfs/dir (ordfs-formats.html): [1B version=1][2B count][entries: flags, name len, name, target]
224
+ const KIND_DIR = 1, EXEC = 2, SYMLINK = 4, REFTYPE = 8;
225
+ export function encodeDir(entries) {
226
+ const sorted = [...entries].sort((a, b) => Buffer.compare(Buffer.from(a.name), Buffer.from(b.name)));
227
+ const parts = [Buffer.from([1, sorted.length >> 8, sorted.length & 0xff])];
228
+ for (const e of sorted) {
229
+ const flags = (e.mode === "40000" ? KIND_DIR : 0) | (e.mode === "100755" ? EXEC : 0) | (e.mode === "120000" ? SYMLINK : 0) | (e.txid ? REFTYPE : 0);
230
+ const name = Buffer.from(e.name, "utf8");
231
+ parts.push(Buffer.from([flags, name.length]), name, e.txid ? outpointBytes(e.txid, e.vout) : Buffer.from([e.vout]));
232
+ }
233
+ return new Uint8Array(Buffer.concat(parts));
234
+ }
235
+ function decodeDir(b) {
236
+ if (b[0] !== 1 || b.length < 3)
237
+ throw new PacketError("malformed", "ordfs/dir: bad version");
238
+ const n = (b[1] << 8) | b[2];
239
+ const out = [];
240
+ let i = 3, last;
241
+ for (let k = 0; k < n; k++) {
242
+ const flags = b[i++];
243
+ if (flags & 0xf0)
244
+ throw new PacketError("malformed", "ordfs/dir: reserved flag bits");
245
+ const len = b[i++];
246
+ const name = Buffer.from(b.subarray(i, i + len));
247
+ i += len;
248
+ if (!len || name.includes(0) || name.includes(0x2f) || (last && Buffer.compare(last, name) >= 0))
249
+ throw new PacketError("malformed", "ordfs/dir: bad or unsorted name");
250
+ last = name;
251
+ const mode = flags & KIND_DIR ? "40000" : flags & SYMLINK ? "120000" : flags & EXEC ? "100755" : "100644";
252
+ if (flags & REFTYPE) {
253
+ const op = outpointOf(b.subarray(i, i + 36));
254
+ i += 36;
255
+ out.push({ name: name.toString("utf8"), mode, ...op });
256
+ }
257
+ else
258
+ out.push({ name: name.toString("utf8"), mode, vout: b[i++] });
259
+ if (i > b.length)
260
+ throw new PacketError("malformed", "ordfs/dir: truncated");
261
+ }
262
+ if (i !== b.length)
263
+ throw new PacketError("malformed", "ordfs/dir: trailing bytes");
264
+ return out;
265
+ }
266
+ export function decodePacket(bytes) {
267
+ let p;
268
+ try {
269
+ p = decode(bytes);
270
+ }
271
+ catch {
272
+ throw new PacketError("malformed", "not dag-cbor");
273
+ }
274
+ if (!p || p.kind !== PACKET_KIND || p.version !== 1)
275
+ throw new PacketError("malformed", `not a ${PACKET_KIND} v1`);
276
+ if (!CID.asCID(p.scope))
277
+ throw new PacketError("malformed", "no scope");
278
+ if (!(p.beef instanceof Uint8Array))
279
+ throw new PacketError("malformed", "no beef");
280
+ if (p.index !== undefined && (!Array.isArray(p.index) || p.index.some((e) => !CID.asCID(e?.tx) || !Number.isInteger(e?.vout) || !CID.asCID(e?.cid))))
281
+ throw new PacketError("malformed", "bad index");
282
+ return p;
283
+ }
284
+ /** Read and verify a packet: the blocks its scope requires, every one checked; else a PacketError. */
285
+ export async function readPacket(bytes, o = {}) {
286
+ const p = decodePacket(bytes);
287
+ const scope = CID.asCID(p.scope);
288
+ if (o.scope && !o.scope.equals(scope))
289
+ throw new PacketError("scope-mismatch", `the packet's scope is ${scope}, not ${o.scope}`);
290
+ if (scope.code !== GIT_RAW && scope.code !== DAG_CBOR)
291
+ throw new PacketError("malformed", "the scope is neither a git tree nor a state record");
292
+ let beef;
293
+ try {
294
+ beef = Beef.fromBinary(Array.from(p.beef));
295
+ }
296
+ catch (e) {
297
+ throw new PacketError("malformed", `beef: ${e.message}`);
298
+ }
299
+ const txs = new Map();
300
+ for (const b of beef.txs) {
301
+ if (!b.tx)
302
+ continue; // txid-only (BRC-96): not a body
303
+ if (b.tx.id("hex") !== b.txid)
304
+ throw new PacketError("hash-mismatch", `beef: ${b.txid} does not match its body`);
305
+ txs.set(b.txid, b.tx);
306
+ }
307
+ const used = new Set();
308
+ const output = (txid, vout) => {
309
+ const tx = txs.get(txid);
310
+ if (!tx)
311
+ return undefined;
312
+ const out = tx.outputs[vout];
313
+ if (!out)
314
+ return undefined;
315
+ used.add(txid);
316
+ return carrierOf(Uint8Array.from(out.lockingScript.toBinary()));
317
+ };
318
+ // Resolution: an outpoint's bytes (patches applied), and the git objects a directory manifest makes.
319
+ const derived = new Map();
320
+ const resolved = new Map();
321
+ const resolving = new Set();
322
+ const resolve = (txid, vout, depth = 0) => {
323
+ const key = `${txid}_${vout}`;
324
+ if (resolved.has(key))
325
+ return resolved.get(key);
326
+ if (resolving.has(key) || depth > 1000)
327
+ throw new PacketError("malformed", `a patch or directory cycle at ${key}`);
328
+ resolving.add(key);
329
+ try {
330
+ const c = output(txid, vout);
331
+ if (!c)
332
+ return undefined;
333
+ let bytes;
334
+ if (c.type === "ordfs/patch") {
335
+ if (c.payload.length < 37 || c.payload[0] !== 0)
336
+ throw new PacketError("malformed", `ordfs/patch at ${key}`);
337
+ const base = outpointOf(c.payload.subarray(1, 37));
338
+ const b = resolve(base.txid, base.vout, depth + 1);
339
+ if (!b)
340
+ return undefined; // the base is missing: incomplete
341
+ bytes = vcdiffDecode(c.payload.subarray(37), b);
342
+ }
343
+ else if (c.type === "ordfs/dir") {
344
+ const entries = [];
345
+ for (const e of decodeDir(c.payload)) {
346
+ const at = e.txid ?? txid;
347
+ const child = resolve(at, e.vout, depth + 1);
348
+ if (!child)
349
+ return undefined;
350
+ if (e.mode === "40000")
351
+ entries.push({ mode: e.mode, name: e.name, cid: sha1Cid(sha1(child)) });
352
+ else {
353
+ // A file: ORDFS carries its bare content; the git blob is that content framed.
354
+ const obj = new Uint8Array(blobObject(child));
355
+ const cid = sha1Cid(sha1(obj));
356
+ derived.set(cid.toString(), obj);
357
+ entries.push({ mode: e.mode, name: e.name, cid });
358
+ }
359
+ }
360
+ const t = hashTree(entries);
361
+ derived.set(t.cid.toString(), t.object);
362
+ bytes = t.object;
363
+ }
364
+ else
365
+ bytes = c.payload;
366
+ resolved.set(key, bytes);
367
+ return bytes;
368
+ }
369
+ finally {
370
+ resolving.delete(key);
371
+ }
372
+ };
373
+ // Where each CID is: the index, else every carrier output keyed by what its bytes hash to.
374
+ const where = new Map();
375
+ const at = (cid, txid, vout) => { const k = cid.toString(); where.set(k, [...(where.get(k) ?? []), { txid, vout }]); };
376
+ if (p.index)
377
+ for (const e of p.index)
378
+ at(CID.asCID(e.cid), txidOf(CID.asCID(e.tx)), e.vout);
379
+ else {
380
+ for (const [txid, tx] of txs)
381
+ tx.outputs.forEach((_, vout) => {
382
+ const b = resolve(txid, vout);
383
+ if (!b)
384
+ return;
385
+ const h1 = sha1(b), h2 = sha256(b);
386
+ at(sha1Cid(h1), txid, vout);
387
+ at(sha1Cid(sha1(blobObject(b))), txid, vout);
388
+ at(CID.createV1(RAW, Digest.create(SHA2_256, h2)), txid, vout);
389
+ at(CID.createV1(DAG_CBOR, Digest.create(SHA2_256, h2)), txid, vout);
390
+ });
391
+ }
392
+ const get = async (cid) => {
393
+ const d = derived.get(cid.toString());
394
+ if (d)
395
+ return d;
396
+ if (cid.code === BITCOIN_TX) {
397
+ const tx = txs.get(txidOf(cid));
398
+ if (tx) {
399
+ used.add(txidOf(cid));
400
+ return Uint8Array.from(tx.toBinary());
401
+ }
402
+ }
403
+ const places = where.get(cid.toString());
404
+ if (!places)
405
+ return undefined;
406
+ for (const { txid, vout } of places) {
407
+ const b = resolve(txid, vout);
408
+ if (!b)
409
+ continue;
410
+ const block = asBlock(cid, b);
411
+ if (block)
412
+ return block;
413
+ if (p.index)
414
+ throw new PacketError("hash-mismatch", `${txid}:${vout} does not hash to ${cid}`);
415
+ }
416
+ return undefined;
417
+ };
418
+ const { blocks, missing } = await closure(scope, get);
419
+ if (missing.length)
420
+ throw new PacketError("incomplete", `${missing.length} object(s) the scope reaches are not in the bag: ${missing.slice(0, 5).join(", ")}${missing.length > 5 ? " …" : ""}`);
421
+ if (scope.code === DAG_CBOR) {
422
+ const s = decode(blocks[0].bytes);
423
+ if (s?.kind !== "skein-state")
424
+ throw new PacketError("malformed", "a dag-cbor scope must be a skein-state record (a checkpoint)");
425
+ }
426
+ // Extras: what the index lists beyond the closure (a checkpoint's records no link reaches) — each verified, then kept.
427
+ const inClosure = new Set(blocks.map((b) => b.cid.toString()));
428
+ for (const e of p.index ?? []) {
429
+ const c = CID.asCID(e.cid);
430
+ if (inClosure.has(c.toString()))
431
+ continue;
432
+ inClosure.add(c.toString());
433
+ const b = await get(c);
434
+ if (!b)
435
+ throw new PacketError("incomplete", `the index lists ${c}, which the bag does not carry`);
436
+ blocks.push({ cid: c, bytes: b });
437
+ }
438
+ if (o.chainTracker) {
439
+ for (const txid of used) {
440
+ const bt = beef.findTxid(txid);
441
+ const mp = bt?.bumpIndex !== undefined ? beef.bumps[bt.bumpIndex] : undefined;
442
+ if (!mp)
443
+ throw new PacketError("unproven", `${txid} has no merkle path in the bag`);
444
+ if (!(await mp.verify(txid, o.chainTracker)))
445
+ throw new PacketError("unproven", `${txid}'s merkle path does not verify against the headers`);
446
+ }
447
+ }
448
+ const mem = new MemBlocks();
449
+ for (const b of blocks)
450
+ await mem.putBlock(b.cid, b.bytes);
451
+ return { scope, kind: scope.code === GIT_RAW ? "tree" : "checkpoint", blocks: mem, list: blocks, carriers: [...used] };
452
+ }
453
+ /**
454
+ * A packet of everything `scope` requires, read out of `objects`, carried in
455
+ * synthetic transactions (never broadcast; see the header).
456
+ */
457
+ export async function writePacket(scope, objects, o = {}) {
458
+ const { blocks, missing } = await closure(scope, async (c) => {
459
+ const b = await objects.get(c);
460
+ if (b && !hashMatches(c, b))
461
+ throw new PacketError("hash-mismatch", `the source's ${c} does not hash to it`);
462
+ return b;
463
+ });
464
+ if (missing.length)
465
+ throw new PacketError("incomplete", `the source lacks ${missing.length} object(s): ${missing.slice(0, 5).join(", ")}`);
466
+ if (o.extras?.length && o.noIndex)
467
+ throw new PacketError("malformed", "extras need the index");
468
+ const have = new Set(blocks.map((b) => b.cid.toString()));
469
+ for (const x of o.extras ?? []) {
470
+ if (have.has(x.cid.toString()))
471
+ continue;
472
+ if (!hashMatches(x.cid, x.bytes))
473
+ throw new PacketError("hash-mismatch", `extra ${x.cid} does not hash to it`);
474
+ have.add(x.cid.toString());
475
+ blocks.push(x);
476
+ }
477
+ const form = o.form ?? "ordfs";
478
+ const maxOutputs = Math.min(o.maxOutputs ?? 200, 256), maxBytes = o.maxBytes ?? 1 << 20;
479
+ const byCid = new Map(blocks.map((b) => [b.cid.toString(), b]));
480
+ const done = [];
481
+ let cur, curBytes = 0;
482
+ const placed = new Map();
483
+ const index = [];
484
+ const close = () => { if (cur)
485
+ done.push(cur); cur = undefined; curBytes = 0; };
486
+ const open = () => {
487
+ const t = new Transaction();
488
+ // A made-up, unfunded input: the transaction only carries data (and is never broadcast).
489
+ t.addInput({ sourceTXID: sha256(Buffer.from(`skein-packet ${scope} ${done.length}`)).toString("hex"), sourceOutputIndex: 0, unlockingScript: new Script(), sequence: 0xffffffff });
490
+ cur = t;
491
+ return t;
492
+ };
493
+ const txidOfTx = (t) => t.id("hex");
494
+ const room = (n) => { if (cur && (cur.outputs.length >= maxOutputs || curBytes + n > maxBytes))
495
+ close(); return cur ?? open(); };
496
+ const add = (cid, type, payload) => {
497
+ const est = payload().length;
498
+ const t = room(est);
499
+ const data = payload(); // after `room`: a directory's same-tx references depend on which transaction it lands in
500
+ t.addOutput({ lockingScript: Script.fromBinary(Array.from(carrierScript(type, data))), satoshis: 0 });
501
+ curBytes += data.length;
502
+ placed.set(cid.toString(), { tx: t, vout: t.outputs.length - 1 });
503
+ index.push({ tx: t, vout: t.outputs.length - 1, cid });
504
+ };
505
+ // Post-order over trees (children first), then everything else.
506
+ const emitted = new Set();
507
+ const emit = (cid) => {
508
+ const k = cid.toString();
509
+ if (emitted.has(k))
510
+ return;
511
+ emitted.add(k);
512
+ const b = byCid.get(k);
513
+ if (!b)
514
+ return;
515
+ const tree = cid.code === GIT_RAW && gitBody(b.bytes, "tree") ? parseTree(b.bytes, cid) : undefined;
516
+ if (tree) {
517
+ for (const e of tree)
518
+ emit(e.cid);
519
+ if (form === "ordfs" && tree.every((e) => e.mode !== "160000" && placed.has(e.cid.toString()))) {
520
+ add(cid, "ordfs/dir", () => encodeDir(tree.map((e) => {
521
+ const at = placed.get(e.cid.toString());
522
+ return { name: e.name, mode: e.mode, vout: at.vout, ...(at.tx === cur ? {} : { txid: txidOfTx(at.tx) }) };
523
+ })));
524
+ return;
525
+ }
526
+ add(cid, "application/octet-stream", () => b.bytes);
527
+ return;
528
+ }
529
+ const blob = cid.code === GIT_RAW ? gitBody(b.bytes, "blob") : undefined;
530
+ if (blob && form === "ordfs")
531
+ add(cid, "application/octet-stream", () => blob);
532
+ else
533
+ add(cid, "application/octet-stream", () => b.bytes);
534
+ };
535
+ for (const b of blocks)
536
+ emit(b.cid);
537
+ close();
538
+ const roots = {};
539
+ const beef = new Beef();
540
+ done.forEach((t, i) => {
541
+ if (o.mined) {
542
+ const txid = txidOfTx(t);
543
+ // A two-transaction block: a made-up coinbase at offset 0 (offset 0 is a coinbase to verify()), this one at 1.
544
+ const coinbase = sha256(Buffer.from(`skein-packet coinbase ${i + 1}`)).toString("hex");
545
+ t.merklePath = new MerklePath(i + 1, [[{ offset: 0, hash: coinbase }, { offset: 1, hash: txid, txid: true }]]);
546
+ roots[i + 1] = t.merklePath.computeRoot(txid);
547
+ }
548
+ beef.mergeTransaction(t);
549
+ });
550
+ const packet = {
551
+ kind: PACKET_KIND, version: 1, scope, beef: Uint8Array.from(beef.toBinary()),
552
+ ...(o.noIndex ? {} : { index: index.map((x) => ({ tx: txCid(txidOfTx(x.tx)), vout: x.vout, cid: x.cid })) }),
553
+ };
554
+ return { bytes: encode(packet).bytes, packet, txids: done.map(txidOfTx), roots, objects: blocks.length };
555
+ }
556
+ /** A ChainTracker over a fixed set of header roots {height: root hex} (offline). */
557
+ export function rootsTracker(roots) {
558
+ return {
559
+ isValidRootForHeight: async (root, height) => roots[height] === root,
560
+ currentHeight: async () => Math.max(0, ...Object.keys(roots).map(Number)),
561
+ };
562
+ }