| … | … | @@ -0,0 +1 @@ |
| 1 | node_modules/ |
| … | … | @@ -0,0 +1,72 @@ |
| 1 | // Minimal CAR v1 codec — bare-safe, dependency-light replacement for @ipld/car. | |
| 2 | // | |
| 3 | // CAR v1 is a trivial container: varint(headerLen) ++ dag-cbor({version:1, roots:[…]}) | |
| 4 | // then, per block, varint(len) ++ CIDbytes ++ blockBytes. We only need whole-buffer | |
| 5 | // read/write (no streaming, no file I/O), so this is ~40 lines of pure JS over | |
| 6 | // multiformats' CID (byte-exact with @ipld/car — verified by the repo tests + the | |
| 7 | // @atproto/repo conformance reader). Drops @ipld/car (which imports `fs`/streams and | |
| 8 | // won't load under Bare). | |
| 9 | // | |
| 10 | // ⚠️ LIMITATIONS vs @ipld/car (tracked in memory: bare-car-limitations). Fine for our | |
| 11 | // current use (whole-buffer, CARv1, OUR-OWN trusted output); add the feature before the | |
| 12 | // gate it guards: | |
| 13 | // - No streaming — whole CAR is buffered in memory. GATE: large repos (getRepo of a | |
| 14 | // big repo could be GBs). Add an async-iterator read/write path. | |
| 15 | // - No untrusted-input hardening — readCar trusts varints/lengths/CIDs. GATE: reading | |
| 16 | // UNTRUSTED CARs (com.atproto.repo.importRepo, ingress bridge). Add bounds/overflow/ | |
| 17 | // truncation checks + duplicate-CID handling before then. | |
| 18 | // - CARv1 only (no CARv2/index). atproto is CARv1, so no gate today. | |
| 19 | // Fully-featured path: grow this file at each gate, OR extract a standalone bare-native | |
| 20 | // `bare-car` package (the holepunch ecosystem lacks one — a real contribution). | |
| 21 | ||
| 22 | import { CID } from 'multiformats/cid' | |
| 23 | import * as dagCbor from '@ipld/dag-cbor' | |
| 24 | ||
| 25 | function varintEncode (n) { | |
| 26 | const out = [] | |
| 27 | while (n >= 0x80) { out.push((n & 0x7f) | 0x80); n >>>= 7 } | |
| 28 | out.push(n) | |
| 29 | return Uint8Array.from(out) | |
| 30 | } | |
| 31 | ||
| 32 | function varintDecode (bytes, offset) { | |
| 33 | let n = 0; let shift = 0; let i = offset | |
| 34 | for (;;) { | |
| 35 | const b = bytes[i++] | |
| 36 | n |= (b & 0x7f) << shift | |
| 37 | if ((b & 0x80) === 0) break | |
| 38 | shift += 7 | |
| 39 | } | |
| 40 | return [n >>> 0, i] | |
| 41 | } | |
| 42 | ||
| 43 | // Serialise { root, blocks } to CAR bytes. `blocks` is an iterable of { cid, bytes }. | |
| 44 | export async function writeCar (rootCid, blocks) { | |
| 45 | const header = dagCbor.encode({ version: 1, roots: [rootCid] }) | |
| 46 | const parts = [Buffer.from(varintEncode(header.length)), Buffer.from(header)] | |
| 47 | for (const { cid, bytes } of blocks) { | |
| 48 | const cidBytes = cid.bytes | |
| 49 | const body = new Uint8Array(cidBytes.length + bytes.length) | |
| 50 | body.set(cidBytes, 0) | |
| 51 | body.set(bytes, cidBytes.length) | |
| 52 | parts.push(Buffer.from(varintEncode(body.length)), Buffer.from(body)) | |
| 53 | } | |
| 54 | return Buffer.concat(parts) | |
| 55 | } | |
| 56 | ||
| 57 | // Read a CAR into { roots, blocks } where blocks maps cid-string -> { cid, bytes }. | |
| 58 | export async function readCar (carBytes) { | |
| 59 | const bytes = carBytes instanceof Uint8Array ? carBytes : new Uint8Array(carBytes) | |
| 60 | let [hlen, off] = varintDecode(bytes, 0) | |
| 61 | const header = dagCbor.decode(bytes.subarray(off, off + hlen)) | |
| 62 | off += hlen | |
| 63 | const blocks = new Map() | |
| 64 | while (off < bytes.length) { | |
| 65 | const [blen, bodyStart] = varintDecode(bytes, off) | |
| 66 | const end = bodyStart + blen | |
| 67 | const [cid, rest] = CID.decodeFirst(bytes.subarray(bodyStart, end)) | |
| 68 | blocks.set(cid.toString(), { cid, bytes: rest }) | |
| 69 | off = end | |
| 70 | } | |
| 71 | return { roots: header.roots, blocks } | |
| 72 | } |
| … | … | @@ -0,0 +1,41 @@ |
| 1 | // atproto block CIDs — CIDv1, dag-cbor codec (0x71), sha2-256 multihash (0x12). | |
| 2 | // Every block in a repo (commit, MST node, record) is content-addressed this way, | |
| 3 | // so computeCid(dag-cbor(block)) IS the block's identity. This is the anchor for | |
| 4 | // byte-exact fidelity: if our re-encoded bytes hash to the same CID the reference | |
| 5 | // produced, our encoding is byte-identical. | |
| 6 | ||
| 7 | import { CID } from 'multiformats/cid' | |
| 8 | import * as Digest from 'multiformats/hashes/digest' | |
| 9 | import { sha256 as nobleSha256 } from '@noble/hashes/sha2' | |
| 10 | import * as dagCbor from '@ipld/dag-cbor' | |
| 11 | import * as raw from 'multiformats/codecs/raw' | |
| 12 | ||
| 13 | // raw block bytes -> CID. Uses noble's pure-JS sha256 (same as the sync path) rather | |
| 14 | // than multiformats' `crypto.subtle` path — identical CID, and no WebCrypto dependency | |
| 15 | // (so it loads + runs under Bare, which has no crypto.subtle). | |
| 16 | export async function cidForBytes (bytes) { | |
| 17 | return cidForBytesSync(bytes) | |
| 18 | } | |
| 19 | ||
| 20 | // Synchronous variant — needed by the recursive MST build. Same result as | |
| 21 | // cidForBytes (CIDv1 / dag-cbor 0x71 / sha2-256 0x12), just without the async | |
| 22 | // WebCrypto digest. | |
| 23 | export function cidForBytesSync (bytes) { | |
| 24 | const digest = Digest.create(0x12, nobleSha256(bytes)) // 0x12 = sha2-256 multihash code | |
| 25 | return CID.createV1(dagCbor.code, digest) | |
| 26 | } | |
| 27 | ||
| 28 | // BLOB CID — CIDv1, RAW codec (0x55), sha2-256. atproto blobs (images/avatars) are | |
| 29 | // content-addressed with the raw codec (opaque bytes), NOT dag-cbor. This is the CID that | |
| 30 | // goes in a blob ref ({$type:'blob', ref:{$link:<this>}, mimeType, size}) and the key | |
| 31 | // com.atproto.sync.getBlob serves by. | |
| 32 | export function cidForBlobSync (bytes) { | |
| 33 | const digest = Digest.create(0x12, nobleSha256(bytes)) | |
| 34 | return CID.createV1(raw.code, digest) | |
| 35 | } | |
| 36 | ||
| 37 | // a JS value -> its canonical dag-cbor bytes + CID | |
| 38 | export async function cidForValue (value) { | |
| 39 | const bytes = dagCbor.encode(value) | |
| 40 | return { bytes, cid: await cidForBytes(bytes) } | |
| 41 | } |
| … | … | @@ -0,0 +1,36 @@ |
| 1 | // atproto repo commit — sign + verify. | |
| 2 | // | |
| 3 | // A commit is { did, version: 3, data: <MST root CID>, rev, prev, sig }. The | |
| 4 | // signature is ECDSA-secp256k1 (low-S) over sha256(dag-cbor(unsignedCommit)), stored | |
| 5 | // as a raw 64-byte CBOR byte string (NOT base64url — that's the did:plc genesis). | |
| 6 | // The relay validates this sig against the DID doc's verificationMethod. | |
| 7 | ||
| 8 | import * as dagCbor from '@ipld/dag-cbor' | |
| 9 | import { sha256 as nobleSha256 } from '@noble/hashes/sha2' | |
| 10 | import { secp256k1 } from '@noble/curves/secp256k1' | |
| 11 | import { cidForBytesSync } from './cid.js' | |
| 12 | ||
| 13 | export const REPO_VERSION = 3 | |
| 14 | ||
| 15 | // The unsigned commit (everything the signature covers). | |
| 16 | export function unsignedCommit ({ did, rev, data, prev = null }) { | |
| 17 | return { did, version: REPO_VERSION, data, rev, prev } | |
| 18 | } | |
| 19 | ||
| 20 | // Sign an unsigned commit -> signed commit with `sig` (64 raw bytes). | |
| 21 | export function signCommit (unsigned, privateKey) { | |
| 22 | const hash = nobleSha256(dagCbor.encode(unsigned)) | |
| 23 | const sig = secp256k1.sign(hash, privateKey, { lowS: true }).toCompactRawBytes() | |
| 24 | return { ...unsigned, sig } | |
| 25 | } | |
| 26 | ||
| 27 | export function commitCid (signedCommit) { | |
| 28 | return cidForBytesSync(dagCbor.encode(signedCommit)) | |
| 29 | } | |
| 30 | ||
| 31 | // Verify a signed commit against a compressed secp256k1 pubkey (33 bytes). | |
| 32 | export function verifyCommit (signedCommit, publicKey) { | |
| 33 | const { sig, ...unsigned } = signedCommit | |
| 34 | const hash = nobleSha256(dagCbor.encode(unsigned)) | |
| 35 | return secp256k1.verify(sig, hash, publicKey, { lowS: true }) | |
| 36 | } |
| … | … | @@ -0,0 +1,337 @@ |
| 1 | // atproto MST — WRITE path (slice 2). | |
| 2 | // | |
| 3 | // A faithful port of millipds's `atmst` (mst/node.py + node_wrangler.py) — the exact | |
| 4 | // code that produced our golden CAR. Byte-exactness is verified two ways: rebuilding | |
| 5 | // the golden repo's MST, and matching atmst's own root CIDs for a multi-height tree | |
| 6 | // (build + delete oracles). See test/mst-write.test.js. | |
| 7 | // | |
| 8 | // Node shape (interleaved): subtrees[0], (keys[0],vals[0]), subtrees[1], … ,subtrees[n]. | |
| 9 | // key layer = leading-zero BITS of sha256(key), // 2 (fanout 4). | |
| 10 | ||
| 11 | import * as dagCbor from '@ipld/dag-cbor' | |
| 12 | import { sha256 as nobleSha256 } from '@noble/hashes/sha2' | |
| 13 | import { cidForBytesSync } from './cid.js' | |
| 14 | ||
| 15 | const cidEq = (a, b) => (a === null || b === null) ? a === b : a.equals(b) | |
| 16 | const replaceAt = (arr, i, v) => { const c = arr.slice(); c[i] = v; return c } | |
| 17 | const insertAt = (arr, i, v) => arr.slice(0, i).concat([v], arr.slice(i)) | |
| 18 | const removeAt = (arr, i) => arr.slice(0, i).concat(arr.slice(i + 1)) | |
| 19 | ||
| 20 | export function keyHeight (key) { | |
| 21 | const h = nobleSha256(Buffer.from(key, 'utf8')) // 32 bytes | |
| 22 | let zeros = 0 | |
| 23 | for (const byte of h) { | |
| 24 | if (byte === 0) { zeros += 8; continue } | |
| 25 | zeros += Math.clz32(byte) - 24 // leading zero bits within this byte | |
| 26 | break | |
| 27 | } | |
| 28 | return zeros >> 1 // // 2 | |
| 29 | } | |
| 30 | ||
| 31 | export class MSTNode { | |
| 32 | constructor (keys, vals, subtrees) { | |
| 33 | this.keys = keys // [string] | |
| 34 | this.vals = vals // [CID] | |
| 35 | this.subtrees = subtrees // [CID|null], length keys.length + 1 | |
| 36 | } | |
| 37 | ||
| 38 | static empty () { return new MSTNode([], [], [null]) } | |
| 39 | ||
| 40 | isEmpty () { return this.keys.length === 0 && this.subtrees.length === 1 && this.subtrees[0] === null } | |
| 41 | ||
| 42 | serialise () { | |
| 43 | const e = [] | |
| 44 | let prevKey = Buffer.alloc(0) | |
| 45 | for (let i = 0; i < this.keys.length; i++) { | |
| 46 | const keyBytes = Buffer.from(this.keys[i], 'utf8') | |
| 47 | const max = Math.min(prevKey.length, keyBytes.length) | |
| 48 | let p = 0 | |
| 49 | while (p < max && prevKey[p] === keyBytes[p]) p++ | |
| 50 | e.push({ k: keyBytes.subarray(p), p, t: this.subtrees[i + 1], v: this.vals[i] }) | |
| 51 | prevKey = keyBytes | |
| 52 | } | |
| 53 | return dagCbor.encode({ e, l: this.subtrees[0] }) // dag-cbor canonicalises map key order | |
| 54 | } | |
| 55 | ||
| 56 | cid () { return cidForBytesSync(this.serialise()) } | |
| 57 | ||
| 58 | toOptional () { return this.isEmpty() ? null : this.cid() } | |
| 59 | ||
| 60 | gteIndex (key) { let i = 0; while (i < this.keys.length && key > this.keys[i]) i++; return i } | |
| 61 | ||
| 62 | height () { | |
| 63 | if (this.keys.length) return keyHeight(this.keys[0]) | |
| 64 | if (this.subtrees[0] === null) return 0 | |
| 65 | return null // indeterminate (empty intermediate node) | |
| 66 | } | |
| 67 | ||
| 68 | static deserialise (bytes) { | |
| 69 | const cbor = dagCbor.decode(bytes) | |
| 70 | const subtrees = [cbor.l] | |
| 71 | const keys = [] | |
| 72 | const vals = [] | |
| 73 | let prevKey = Buffer.alloc(0) | |
| 74 | for (const e of cbor.e) { | |
| 75 | const suffix = Buffer.from(e.k) | |
| 76 | const thisKey = Buffer.concat([prevKey.subarray(0, e.p), suffix]) | |
| 77 | keys.push(thisKey.toString('utf8')) | |
| 78 | vals.push(e.v) | |
| 79 | subtrees.push(e.t) | |
| 80 | prevKey = thisKey | |
| 81 | } | |
| 82 | return new MSTNode(keys, vals, subtrees) | |
| 83 | } | |
| 84 | } | |
| 85 | ||
| 86 | // In-memory node store — maps CID string -> { cid, bytes }. get(null) returns the | |
| 87 | // (stored) empty node, matching atmst NodeStore.get_node(None). | |
| 88 | export class NodeStore { | |
| 89 | constructor () { this.blocks = new Map() } | |
| 90 | ||
| 91 | storedNode (node) { | |
| 92 | const bytes = node.serialise() | |
| 93 | const cid = cidForBytesSync(bytes) | |
| 94 | this.blocks.set(cid.toString(), { cid, bytes }) | |
| 95 | return node | |
| 96 | } | |
| 97 | ||
| 98 | getNode (cid) { | |
| 99 | if (cid === null || cid === undefined) return this.storedNode(MSTNode.empty()) | |
| 100 | const b = this.blocks.get(cid.toString()) | |
| 101 | if (!b) throw new Error(`missing MST node ${cid}`) | |
| 102 | return MSTNode.deserialise(b.bytes) | |
| 103 | } | |
| 104 | } | |
| 105 | ||
| 106 | export class NodeWrangler { | |
| 107 | constructor (ns) { this.ns = ns } | |
| 108 | ||
| 109 | putRecord (rootCid, key, val) { | |
| 110 | const root = this.ns.getNode(rootCid) | |
| 111 | if (root.isEmpty()) return this._putHere(root, key, val).cid() | |
| 112 | return this._putRecursive(root, key, val, keyHeight(key), root.height()).cid() | |
| 113 | } | |
| 114 | ||
| 115 | delRecord (rootCid, key) { | |
| 116 | const root = this.ns.getNode(rootCid) | |
| 117 | const afterDel = this._deleteRecursive(root, key, keyHeight(key), root.height()) | |
| 118 | return this.ns.getNode(this._squashTop(afterDel)).cid() | |
| 119 | } | |
| 120 | ||
| 121 | _putHere (node, key, val) { | |
| 122 | const i = node.gteIndex(key) | |
| 123 | if (i < node.keys.length && node.keys[i] === key) { | |
| 124 | if (cidEq(node.vals[i], val)) return node | |
| 125 | return this.ns.storedNode(new MSTNode(node.keys.slice(), replaceAt(node.vals, i, val), node.subtrees.slice())) | |
| 126 | } | |
| 127 | const [lsub, rsub] = this._splitOnKey(node.subtrees[i], key) | |
| 128 | return this.ns.storedNode(new MSTNode( | |
| 129 | insertAt(node.keys, i, key), | |
| 130 | insertAt(node.vals, i, val), | |
| 131 | node.subtrees.slice(0, i).concat([lsub, rsub], node.subtrees.slice(i + 1)) | |
| 132 | )) | |
| 133 | } | |
| 134 | ||
| 135 | _putRecursive (node, key, val, keyH, treeH) { | |
| 136 | if (keyH > treeH) { // grow the tree upward | |
| 137 | return this.ns.storedNode(this._putRecursive( | |
| 138 | this.ns.storedNode(new MSTNode([], [], [node.cid()])), | |
| 139 | key, val, keyH, treeH + 1 | |
| 140 | )) | |
| 141 | } | |
| 142 | if (keyH < treeH) { // descend | |
| 143 | const i = node.gteIndex(key) | |
| 144 | return this.ns.storedNode(new MSTNode( | |
| 145 | node.keys.slice(), node.vals.slice(), | |
| 146 | replaceAt(node.subtrees, i, this._putRecursive(this.ns.getNode(node.subtrees[i]), key, val, keyH, treeH - 1).cid()) | |
| 147 | )) | |
| 148 | } | |
| 149 | return this._putHere(node, key, val) // keyH === treeH | |
| 150 | } | |
| 151 | ||
| 152 | _splitOnKey (nodeCid, key) { | |
| 153 | if (nodeCid === null || nodeCid === undefined) return [null, null] | |
| 154 | const node = this.ns.getNode(nodeCid) | |
| 155 | const i = node.gteIndex(key) | |
| 156 | const [lsub, rsub] = this._splitOnKey(node.subtrees[i], key) | |
| 157 | const left = this.ns.storedNode(new MSTNode(node.keys.slice(0, i), node.vals.slice(0, i), node.subtrees.slice(0, i).concat([lsub]))).toOptional() | |
| 158 | const right = this.ns.storedNode(new MSTNode(node.keys.slice(i), node.vals.slice(i), [rsub].concat(node.subtrees.slice(i + 1)))).toOptional() | |
| 159 | return [left, right] | |
| 160 | } | |
| 161 | ||
| 162 | _squashTop (nodeCid) { | |
| 163 | const node = this.ns.getNode(nodeCid) | |
| 164 | if (node.keys.length) return nodeCid | |
| 165 | if (node.subtrees[0] === null) return nodeCid | |
| 166 | return this._squashTop(node.subtrees[0]) | |
| 167 | } | |
| 168 | ||
| 169 | _deleteRecursive (node, key, keyH, treeH) { | |
| 170 | if (keyH > treeH) return node.toOptional() | |
| 171 | let i = node.gteIndex(key) | |
| 172 | if (keyH < treeH) { | |
| 173 | if (node.subtrees[i] === null) return node.toOptional() | |
| 174 | return this.ns.storedNode(new MSTNode( | |
| 175 | node.keys.slice(), node.vals.slice(), | |
| 176 | replaceAt(node.subtrees, i, this._deleteRecursive(this.ns.getNode(node.subtrees[i]), key, keyH, treeH - 1)) | |
| 177 | )).toOptional() | |
| 178 | } | |
| 179 | i = node.gteIndex(key) | |
| 180 | if (i === node.keys.length || node.keys[i] !== key) return node.toOptional() | |
| 181 | return this.ns.storedNode(new MSTNode( | |
| 182 | removeAt(node.keys, i), removeAt(node.vals, i), | |
| 183 | node.subtrees.slice(0, i).concat([this._merge(node.subtrees[i], node.subtrees[i + 1])], node.subtrees.slice(i + 2)) | |
| 184 | )).toOptional() | |
| 185 | } | |
| 186 | ||
| 187 | _merge (leftCid, rightCid) { | |
| 188 | if (leftCid === null) return rightCid | |
| 189 | if (rightCid === null) return leftCid | |
| 190 | const left = this.ns.getNode(leftCid) | |
| 191 | const right = this.ns.getNode(rightCid) | |
| 192 | return this.ns.storedNode(new MSTNode( | |
| 193 | left.keys.concat(right.keys), left.vals.concat(right.vals), | |
| 194 | left.subtrees.slice(0, -1).concat([this._merge(left.subtrees[left.subtrees.length - 1], right.subtrees[0])], right.subtrees.slice(1)) | |
| 195 | )).toOptional() | |
| 196 | } | |
| 197 | } | |
| 198 | ||
| 199 | // Build an MST from records [{ key, value: CID }] (any order). Returns | |
| 200 | // { root: CID, ns: NodeStore }. ns.blocks holds every node created; use | |
| 201 | // reachableBlocks(ns, root) for just the live set (CAR output). | |
| 202 | export function buildMst (records) { | |
| 203 | const ns = new NodeStore() | |
| 204 | const w = new NodeWrangler(ns) | |
| 205 | let root = ns.storedNode(MSTNode.empty()).cid() | |
| 206 | for (const { key, value } of records) root = w.putRecord(root, key, value) | |
| 207 | return { root, ns, wrangler: w } | |
| 208 | } | |
| 209 | ||
| 210 | // BFS the live MST nodes reachable from root (for CAR block sets). | |
| 211 | export function reachableBlocks (ns, rootCid) { | |
| 212 | const out = new Map() | |
| 213 | const stack = [rootCid] | |
| 214 | while (stack.length) { | |
| 215 | const cid = stack.pop() | |
| 216 | if (cid === null || cid === undefined) continue | |
| 217 | const key = cid.toString() | |
| 218 | if (out.has(key)) continue | |
| 219 | const b = ns.blocks.get(key) | |
| 220 | if (!b) throw new Error(`missing node ${key}`) | |
| 221 | out.set(key, b) | |
| 222 | const node = MSTNode.deserialise(b.bytes) | |
| 223 | for (const t of node.subtrees) if (t) stack.push(t) | |
| 224 | } | |
| 225 | return out | |
| 226 | } | |
| 227 | ||
| 228 | // ---- Firehose MST-inversion proof (slice 3b) ---- | |
| 229 | // | |
| 230 | // A #commit's `blocks` CAR must be a MINIMAL, self-contained proof of the | |
| 231 | // old→new transition — not the whole tree (that would trip a relay's `tooBig`). | |
| 232 | // This ports millipds `repo_ops.py:apply_writes` step 2b + `atmst` proof.py: | |
| 233 | // blocks = created | deletion_proof_cids | inductive_proof_nodes (+ new records) | |
| 234 | // where `inductive_proof_nodes` = every MST node READ while replaying the ops | |
| 235 | // backward from the new root, which must reproduce the old (prevData) root. | |
| 236 | // The prior record CIDs travel in each op's `prev` (like millipds), so a cold | |
| 237 | // relay can invert with no old-tree state of its own. | |
| 238 | ||
| 239 | // A NodeStore overlay: writes land in an in-memory upper layer (NOT logged); | |
| 240 | // reads that fall through to `lower` are recorded in `.gets`. Mirrors millipds's | |
| 241 | // OverlayBlockStore(MemoryBlockStore, LoggingBlockStoreWrapper(bs)). | |
| 242 | export class OverlayNodeStore { | |
| 243 | constructor (lower) { this.lower = lower; this.upper = new Map(); this.gets = new Set() } | |
| 244 | ||
| 245 | storedNode (node) { | |
| 246 | const bytes = node.serialise() | |
| 247 | const cid = cidForBytesSync(bytes) | |
| 248 | this.upper.set(cid.toString(), { cid, bytes }) | |
| 249 | return node | |
| 250 | } | |
| 251 | ||
| 252 | getNode (cid) { | |
| 253 | if (cid === null || cid === undefined) return MSTNode.empty() | |
| 254 | const k = cid.toString() | |
| 255 | const u = this.upper.get(k) | |
| 256 | if (u) return MSTNode.deserialise(u.bytes) | |
| 257 | this.gets.add(k) // a read of a pre-existing node — part of the inductive proof | |
| 258 | return this.lower.getNode(cid) | |
| 259 | } | |
| 260 | } | |
| 261 | ||
| 262 | // Follow the search path for `key` from `rootCid`, collecting every node visited. | |
| 263 | // Returns { value: CID|null, proof: Set<cidStr> }. value===null ⇒ exclusion proof; | |
| 264 | // value!==null ⇒ inclusion proof. Port of atmst find_rpath_and_build_proof. | |
| 265 | export function findRpathProof (ns, rootCid, key) { | |
| 266 | const proof = new Set() | |
| 267 | let cid = rootCid | |
| 268 | while (cid !== null && cid !== undefined) { | |
| 269 | const node = ns.getNode(cid) | |
| 270 | proof.add(cid.toString()) | |
| 271 | const i = node.gteIndex(key) | |
| 272 | if (i < node.keys.length && node.keys[i] === key) return { value: node.vals[i], proof } | |
| 273 | cid = node.subtrees[i] // descend the subtree left of the first key >= target | |
| 274 | } | |
| 275 | return { value: null, proof } | |
| 276 | } | |
| 277 | ||
| 278 | // Build a NodeStore holding exactly the given blocks (cid-string -> { cid, bytes }). | |
| 279 | export function nodeStoreFromBlocks (blocks) { | |
| 280 | const ns = new NodeStore() | |
| 281 | for (const [k, v] of blocks) ns.blocks.set(k, v) | |
| 282 | return ns | |
| 283 | } | |
| 284 | ||
| 285 | // Compute the minimal firehose block set for a single commit's ops. | |
| 286 | // oldRecords / newRecords : [{ key, value: CID }] — the record sets BEFORE/AFTER. | |
| 287 | // ops : [{ action:'create'|'update'|'delete', path, cid: CID|null, prev: CID|null }] | |
| 288 | // Returns { blocks: Map<cidStr,{cid,bytes}>, dataCid, oldRoot } — MST nodes only | |
| 289 | // (record + commit blocks are added by the caller). Throws if the backward replay | |
| 290 | // does not reproduce the old root (i.e. the proof would be invalid). | |
| 291 | export function firehoseProofBlocks ({ oldRecords, newRecords, ops }) { | |
| 292 | const oldB = buildMst(oldRecords) | |
| 293 | const newB = buildMst(newRecords) | |
| 294 | const oldRoot = oldB.root | |
| 295 | const newRoot = newB.root | |
| 296 | ||
| 297 | const oldSet = reachableBlocks(oldB.ns, oldRoot) | |
| 298 | const newSet = reachableBlocks(newB.ns, newRoot) | |
| 299 | ||
| 300 | // combined store holds every node of BOTH trees (proof reads span both) | |
| 301 | const combined = new NodeStore() | |
| 302 | for (const [k, v] of oldSet) combined.blocks.set(k, v) | |
| 303 | for (const [k, v] of newSet) combined.blocks.set(k, v) | |
| 304 | ||
| 305 | const proofCids = new Set() | |
| 306 | ||
| 307 | // created: new-tree nodes not present in the old tree | |
| 308 | for (const k of newSet.keys()) if (!oldSet.has(k)) proofCids.add(k) | |
| 309 | ||
| 310 | // deletion proofs: for each delete, the exclusion-proof path in the NEW tree | |
| 311 | for (const op of ops) { | |
| 312 | if (op.action !== 'delete') continue | |
| 313 | const { value, proof } = findRpathProof(combined, newRoot, op.path) | |
| 314 | if (value !== null) throw new Error(`delete proof: ${op.path} still present in new tree`) | |
| 315 | for (const c of proof) proofCids.add(c) | |
| 316 | } | |
| 317 | ||
| 318 | // inductive proof: replay ops backward from newRoot; every node read is required. | |
| 319 | const overlay = new OverlayNodeStore(combined) | |
| 320 | const w = new NodeWrangler(overlay) | |
| 321 | let inv = newRoot | |
| 322 | for (let i = ops.length - 1; i >= 0; i--) { | |
| 323 | const op = ops[i] | |
| 324 | if (op.action === 'create') { | |
| 325 | inv = w.delRecord(inv, op.path) | |
| 326 | } else { // update | delete → restore the prior value | |
| 327 | if (op.prev === null || op.prev === undefined) throw new Error(`inversion: ${op.action} ${op.path} missing prev`) | |
| 328 | inv = w.putRecord(inv, op.path, op.prev) | |
| 329 | } | |
| 330 | } | |
| 331 | if (!inv.equals(oldRoot)) throw new Error('inversion proof failed: backward replay did not reproduce prevData root') | |
| 332 | for (const k of overlay.gets) proofCids.add(k) | |
| 333 | ||
| 334 | const blocks = new Map() | |
| 335 | for (const k of proofCids) blocks.set(k, combined.blocks.get(k)) | |
| 336 | return { blocks, dataCid: newRoot, oldRoot } | |
| 337 | } |
| … | … | @@ -0,0 +1,109 @@ |
| 1 | // atproto repo — READ path (slice 1). | |
| 2 | // | |
| 3 | // A repo is a CAR whose root is a signed commit; the commit's `data` CID points at | |
| 4 | // the root of a Merkle Search Tree (MST) of records. This module reads a CAR, walks | |
| 5 | // the MST, and — most importantly — VERIFIES byte-exact fidelity by re-encoding every | |
| 6 | // block and checking its CID. Writing/mutating the MST is slice 2. | |
| 7 | ||
| 8 | import * as dagCbor from '@ipld/dag-cbor' | |
| 9 | import { cidForBytes, cidForBytesSync } from './cid.js' | |
| 10 | import { buildMst, reachableBlocks } from './mst.js' | |
| 11 | import { unsignedCommit, signCommit, commitCid } from './commit.js' | |
| 12 | import { readCar, writeCar } from './car.js' | |
| 13 | ||
| 14 | // CAR read/write moved to the bare-safe minimal codec in ./car.js (was @ipld/car). | |
| 15 | export { readCar, writeCar } | |
| 16 | ||
| 17 | export function getBlock (car, cid) { | |
| 18 | const b = car.blocks.get(cid.toString()) | |
| 19 | if (!b) throw new Error(`missing block ${cid}`) | |
| 20 | return b | |
| 21 | } | |
| 22 | ||
| 23 | export function decodeBlock (car, cid) { | |
| 24 | return dagCbor.decode(getBlock(car, cid).bytes) | |
| 25 | } | |
| 26 | ||
| 27 | // The signed commit at the CAR root. | |
| 28 | export function loadCommit (car) { | |
| 29 | if (car.roots.length !== 1) throw new Error(`expected 1 root, got ${car.roots.length}`) | |
| 30 | const commit = decodeBlock(car, car.roots[0]) | |
| 31 | if (!commit || !commit.did || commit.data === undefined) throw new Error('root is not a commit') | |
| 32 | return { cid: car.roots[0], value: commit } | |
| 33 | } | |
| 34 | ||
| 35 | // Walk the MST in key order, yielding { key, value: recordCid }. MST node shape: | |
| 36 | // { l: CID|null, e: [ { p: int, k: bytes, v: CID, t: CID|null } ] } | |
| 37 | // keys are prefix-compressed against the previous key in the SAME node. | |
| 38 | export function * walkMst (car, rootCid, _prevKey = '') { | |
| 39 | const node = decodeBlock(car, rootCid) | |
| 40 | if (!node || !Array.isArray(node.e)) throw new Error(`not an MST node: ${rootCid}`) | |
| 41 | if (node.l) yield * walkMst(car, node.l) | |
| 42 | let prev = '' | |
| 43 | for (const e of node.e) { | |
| 44 | const suffix = Buffer.from(e.k).toString('utf8') | |
| 45 | const key = prev.slice(0, e.p) + suffix | |
| 46 | prev = key | |
| 47 | yield { key, value: e.v } | |
| 48 | if (e.t) yield * walkMst(car, e.t) | |
| 49 | } | |
| 50 | } | |
| 51 | ||
| 52 | // All records as { key, cid, value } in key order. | |
| 53 | export function listRecords (car) { | |
| 54 | const commit = loadCommit(car) | |
| 55 | const out = [] | |
| 56 | for (const { key, value } of walkMst(car, commit.value.data)) { | |
| 57 | out.push({ key, cid: value, value: decodeBlock(car, value) }) | |
| 58 | } | |
| 59 | return out | |
| 60 | } | |
| 61 | ||
| 62 | // Byte-exact fidelity check: re-encode every block and confirm its CID is unchanged. | |
| 63 | // Also confirms the commit's data CID and every MST value CID resolve to a present | |
| 64 | // block. Returns { blocks, records, ok, mismatches }. | |
| 65 | export async function verifyRepo (car) { | |
| 66 | const mismatches = [] | |
| 67 | for (const [cidStr, { cid, bytes }] of car.blocks) { | |
| 68 | // re-encode: decode then canonical-encode; must reproduce the exact bytes + CID | |
| 69 | const reencoded = dagCbor.encode(dagCbor.decode(bytes)) | |
| 70 | const sameBytes = Buffer.from(reencoded).equals(Buffer.from(bytes)) | |
| 71 | const recomputed = await cidForBytes(reencoded) | |
| 72 | if (!sameBytes || !recomputed.equals(cid)) { | |
| 73 | mismatches.push({ cid: cidStr, sameBytes, recomputed: recomputed.toString() }) | |
| 74 | } | |
| 75 | } | |
| 76 | const commit = loadCommit(car) | |
| 77 | getBlock(car, commit.value.data) // MST root must be present | |
| 78 | const records = listRecords(car) | |
| 79 | for (const r of records) getBlock(car, r.cid) // each record block present | |
| 80 | return { blocks: car.blocks.size, records: records.length, ok: mismatches.length === 0, mismatches } | |
| 81 | } | |
| 82 | ||
| 83 | // ---- WRITE path (slice 3) ---- | |
| 84 | ||
| 85 | // Originate a full atproto repo from records. Each record is | |
| 86 | // { collection, rkey, record } (or { key, value }). Returns the CAR bytes plus the | |
| 87 | // commit/data CIDs. `privateKey` is the atproto secp256k1 signing key. | |
| 88 | export async function createRepo ({ records, did, rev, privateKey }) { | |
| 89 | const recordBlocks = new Map() | |
| 90 | const mstRecords = [] | |
| 91 | for (const r of records) { | |
| 92 | const key = r.key !== undefined ? r.key : `${r.collection}/${r.rkey}` | |
| 93 | const value = r.record !== undefined ? r.record : r.value | |
| 94 | const bytes = dagCbor.encode(value) | |
| 95 | const cid = cidForBytesSync(bytes) | |
| 96 | recordBlocks.set(cid.toString(), { cid, bytes }) | |
| 97 | mstRecords.push({ key, value: cid }) | |
| 98 | } | |
| 99 | const { root: dataCid, ns } = buildMst(mstRecords) | |
| 100 | const mstBlocks = reachableBlocks(ns, dataCid) | |
| 101 | ||
| 102 | const signed = signCommit(unsignedCommit({ did, rev, data: dataCid }), privateKey) | |
| 103 | const cCid = commitCid(signed) | |
| 104 | const commitBytes = dagCbor.encode(signed) | |
| 105 | ||
| 106 | const blocks = [{ cid: cCid, bytes: commitBytes }, ...mstBlocks.values(), ...recordBlocks.values()] | |
| 107 | const carBytes = await writeCar(cCid, blocks) | |
| 108 | return { carBytes, commitCid: cCid, dataCid, signedCommit: signed } | |
| 109 | } |
| … | … | @@ -0,0 +1,1114 @@ |
| 1 | { | |
| 2 | "name": "atproto-repo", | |
| 3 | "version": "0.0.1", | |
| 4 | "lockfileVersion": 3, | |
| 5 | "requires": true, | |
| 6 | "packages": { | |
| 7 | "": { | |
| 8 | "name": "atproto-repo", | |
| 9 | "version": "0.0.1", | |
| 10 | "hasInstallScript": true, | |
| 11 | "dependencies": { | |
| 12 | "@ipld/car": "^5.4.0", | |
| 13 | "@ipld/dag-cbor": "^9.2.1", | |
| 14 | "@noble/curves": "^1.9.7", | |
| 15 | "@noble/hashes": "^1.5.0", | |
| 16 | "multiformats": "^13.3.0" | |
| 17 | }, | |
| 18 | "devDependencies": { | |
| 19 | "atproto-identity": "file:../atproto-identity", | |
| 20 | "brittle": "^4.0.2" | |
| 21 | } | |
| 22 | }, | |
| 23 | "../atproto-identity": { | |
| 24 | "version": "0.0.1", | |
| 25 | "dev": true, | |
| 26 | "hasInstallScript": true, | |
| 27 | "dependencies": { | |
| 28 | "@ipld/dag-cbor": "^9.2.1", | |
| 29 | "@noble/curves": "^1.6.0", | |
| 30 | "@noble/hashes": "^1.5.0", | |
| 31 | "@scure/bip32": "^1.5.0", | |
| 32 | "@scure/bip39": "^1.4.0", | |
| 33 | "keet-identity-key": "^3.2.0", | |
| 34 | "multiformats": "^13.3.0" | |
| 35 | }, | |
| 36 | "devDependencies": { | |
| 37 | "brittle": "^4.0.2" | |
| 38 | } | |
| 39 | }, | |
| 40 | "node_modules/@ipld/car": { | |
| 41 | "version": "5.4.6", | |
| 42 | "resolved": "https://registry.npmjs.org/@ipld/car/-/car-5.4.6.tgz", | |
| 43 | "integrity": "sha512-f1Iyb9ci8B/TGCyjAuSQ6BOLiy2u8en7rB+SumbyweqXDu3gxYJwYbGzoposKjOzc4CeGlkEbwEw8REoekYAKQ==", | |
| 44 | "license": "Apache-2.0 OR MIT", | |
| 45 | "dependencies": { | |
| 46 | "@ipld/dag-cbor": "^10.0.1", | |
| 47 | "cborg": "^5.0.0", | |
| 48 | "multiformats": "^14.0.0", | |
| 49 | "varint": "^6.0.0" | |
| 50 | }, | |
| 51 | "engines": { | |
| 52 | "node": ">=16.0.0", | |
| 53 | "npm": ">=7.0.0" | |
| 54 | } | |
| 55 | }, | |
| 56 | "node_modules/@ipld/car/node_modules/@ipld/dag-cbor": { | |
| 57 | "version": "10.0.1", | |
| 58 | "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-10.0.1.tgz", | |
| 59 | "integrity": "sha512-nF07iiZPqduSXyMxc0jGANArRHFa9hjMQpQlgLOV2O/3xI1CNb5sXhvbmigbMiz5owSGi0Oq10VtauupMojuiw==", | |
| 60 | "license": "Apache-2.0 OR MIT", | |
| 61 | "dependencies": { | |
| 62 | "cborg": "^5.0.1", | |
| 63 | "multiformats": "^14.0.0" | |
| 64 | } | |
| 65 | }, | |
| 66 | "node_modules/@ipld/car/node_modules/multiformats": { | |
| 67 | "version": "14.0.4", | |
| 68 | "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.4.tgz", | |
| 69 | "integrity": "sha512-+SXItmOUWzT0kjlIfkZ4NawJaC9jW/mPlyn902V9Qgpc7YDwdEiwqbiZxTyvC0XWh1jZguXca3A/WQ+UOPRLUA==", | |
| 70 | "license": "Apache-2.0 OR MIT" | |
| 71 | }, | |
| 72 | "node_modules/@ipld/dag-cbor": { | |
| 73 | "version": "9.2.7", | |
| 74 | "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.7.tgz", | |
| 75 | "integrity": "sha512-ZmfXmElRWATr+hoUTSAOr6HUcjVhOcNHDqgczc76qte2DHHFEK0ZhNzUcdTDQhF/VSIvf2ioaRTRLWwLc83sNw==", | |
| 76 | "license": "Apache-2.0 OR MIT", | |
| 77 | "dependencies": { | |
| 78 | "cborg": "^5.0.1", | |
| 79 | "multiformats": "^13.1.0" | |
| 80 | } | |
| 81 | }, | |
| 82 | "node_modules/@jridgewell/resolve-uri": { | |
| 83 | "version": "3.1.2", | |
| 84 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 85 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 86 | "dev": true, | |
| 87 | "license": "MIT", | |
| 88 | "engines": { | |
| 89 | "node": ">=6.0.0" | |
| 90 | } | |
| 91 | }, | |
| 92 | "node_modules/@jridgewell/sourcemap-codec": { | |
| 93 | "version": "1.5.5", | |
| 94 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | |
| 95 | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | |
| 96 | "dev": true, | |
| 97 | "license": "MIT" | |
| 98 | }, | |
| 99 | "node_modules/@jridgewell/trace-mapping": { | |
| 100 | "version": "0.3.31", | |
| 101 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 102 | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 103 | "dev": true, | |
| 104 | "license": "MIT", | |
| 105 | "dependencies": { | |
| 106 | "@jridgewell/resolve-uri": "^3.1.0", | |
| 107 | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 108 | } | |
| 109 | }, | |
| 110 | "node_modules/@noble/curves": { | |
| 111 | "version": "1.9.7", | |
| 112 | "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", | |
| 113 | "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", | |
| 114 | "license": "MIT", | |
| 115 | "dependencies": { | |
| 116 | "@noble/hashes": "1.8.0" | |
| 117 | }, | |
| 118 | "engines": { | |
| 119 | "node": "^14.21.3 || >=16" | |
| 120 | }, | |
| 121 | "funding": { | |
| 122 | "url": "https://paulmillr.com/funding/" | |
| 123 | } | |
| 124 | }, | |
| 125 | "node_modules/@noble/hashes": { | |
| 126 | "version": "1.8.0", | |
| 127 | "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", | |
| 128 | "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", | |
| 129 | "license": "MIT", | |
| 130 | "engines": { | |
| 131 | "node": "^14.21.3 || >=16" | |
| 132 | }, | |
| 133 | "funding": { | |
| 134 | "url": "https://paulmillr.com/funding/" | |
| 135 | } | |
| 136 | }, | |
| 137 | "node_modules/@types/istanbul-lib-coverage": { | |
| 138 | "version": "2.0.6", | |
| 139 | "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", | |
| 140 | "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", | |
| 141 | "dev": true, | |
| 142 | "license": "MIT" | |
| 143 | }, | |
| 144 | "node_modules/atproto-identity": { | |
| 145 | "resolved": "../atproto-identity", | |
| 146 | "link": true | |
| 147 | }, | |
| 148 | "node_modules/b4a": { | |
| 149 | "version": "1.8.1", | |
| 150 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", | |
| 151 | "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", | |
| 152 | "dev": true, | |
| 153 | "license": "Apache-2.0", | |
| 154 | "peerDependencies": { | |
| 155 | "react-native-b4a": "*" | |
| 156 | }, | |
| 157 | "peerDependenciesMeta": { | |
| 158 | "react-native-b4a": { | |
| 159 | "optional": true | |
| 160 | } | |
| 161 | } | |
| 162 | }, | |
| 163 | "node_modules/bare-abort": { | |
| 164 | "version": "2.0.13", | |
| 165 | "resolved": "https://registry.npmjs.org/bare-abort/-/bare-abort-2.0.13.tgz", | |
| 166 | "integrity": "sha512-zdc8l88eB11Jsz5rDd6sCAgv2kUFXgdrZWoMlgU6JMkfAi1/uuGFC3IEHswKbIRQTk5H3T5CMuechsXYxiaHlQ==", | |
| 167 | "dev": true, | |
| 168 | "license": "Apache-2.0" | |
| 169 | }, | |
| 170 | "node_modules/bare-addon-resolve": { | |
| 171 | "version": "1.10.0", | |
| 172 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", | |
| 173 | "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", | |
| 174 | "dev": true, | |
| 175 | "license": "Apache-2.0", | |
| 176 | "dependencies": { | |
| 177 | "bare-module-resolve": "^1.10.0", | |
| 178 | "bare-semver": "^1.0.0" | |
| 179 | }, | |
| 180 | "peerDependencies": { | |
| 181 | "bare-url": "*" | |
| 182 | }, | |
| 183 | "peerDependenciesMeta": { | |
| 184 | "bare-url": { | |
| 185 | "optional": true | |
| 186 | } | |
| 187 | } | |
| 188 | }, | |
| 189 | "node_modules/bare-ansi-escapes": { | |
| 190 | "version": "2.2.3", | |
| 191 | "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz", | |
| 192 | "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==", | |
| 193 | "dev": true, | |
| 194 | "license": "Apache-2.0", | |
| 195 | "dependencies": { | |
| 196 | "bare-stream": "^2.6.5" | |
| 197 | }, | |
| 198 | "peerDependencies": { | |
| 199 | "bare-buffer": "*" | |
| 200 | }, | |
| 201 | "peerDependenciesMeta": { | |
| 202 | "bare-buffer": { | |
| 203 | "optional": true | |
| 204 | } | |
| 205 | } | |
| 206 | }, | |
| 207 | "node_modules/bare-assert": { | |
| 208 | "version": "1.2.0", | |
| 209 | "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz", | |
| 210 | "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==", | |
| 211 | "dev": true, | |
| 212 | "license": "Apache-2.0", | |
| 213 | "dependencies": { | |
| 214 | "bare-inspect": "^3.1.2" | |
| 215 | } | |
| 216 | }, | |
| 217 | "node_modules/bare-buffer": { | |
| 218 | "version": "3.6.1", | |
| 219 | "resolved": "https://registry.npmjs.org/bare-buffer/-/bare-buffer-3.6.1.tgz", | |
| 220 | "integrity": "sha512-8mzp60t6jdSD3cEGmyxAV1YrAN2+mnxiUvBPQHJJ4WNk0hSVLJGSIEolRj09ZP8yt/+oSj2N2f5kDVW8297n4A==", | |
| 221 | "dev": true, | |
| 222 | "license": "Apache-2.0", | |
| 223 | "engines": { | |
| 224 | "bare": ">=1.20.0" | |
| 225 | } | |
| 226 | }, | |
| 227 | "node_modules/bare-bundle": { | |
| 228 | "version": "1.10.0", | |
| 229 | "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz", | |
| 230 | "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==", | |
| 231 | "dev": true, | |
| 232 | "license": "Apache-2.0", | |
| 233 | "peerDependencies": { | |
| 234 | "bare-buffer": "*", | |
| 235 | "bare-url": "*" | |
| 236 | }, | |
| 237 | "peerDependenciesMeta": { | |
| 238 | "bare-buffer": { | |
| 239 | "optional": true | |
| 240 | }, | |
| 241 | "bare-url": { | |
| 242 | "optional": true | |
| 243 | } | |
| 244 | } | |
| 245 | }, | |
| 246 | "node_modules/bare-channel": { | |
| 247 | "version": "5.2.4", | |
| 248 | "resolved": "https://registry.npmjs.org/bare-channel/-/bare-channel-5.2.4.tgz", | |
| 249 | "integrity": "sha512-enkaOtvXUiZsLBSbataxV/QBjehCOK+SUTd2JQWUYW2iIOufpPu9jyZ9bqJqkEquktrK/rpEMVEmKsPzoci/sA==", | |
| 250 | "dev": true, | |
| 251 | "license": "Apache-2.0", | |
| 252 | "dependencies": { | |
| 253 | "bare-events": "^2.0.0", | |
| 254 | "bare-stream": "^2.7.0", | |
| 255 | "bare-structured-clone": "^1.4.0" | |
| 256 | }, | |
| 257 | "engines": { | |
| 258 | "bare": ">=1.7.0" | |
| 259 | } | |
| 260 | }, | |
| 261 | "node_modules/bare-cov": { | |
| 262 | "version": "1.2.2", | |
| 263 | "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.2.tgz", | |
| 264 | "integrity": "sha512-d/b4HdL5ohZDwBvDegJeSkmo1X2MGlm22jXzuY2D7wl2W1+7nk/b7+HMogwgUa+zi8rVVRJQKHCHPHYBJpvlvw==", | |
| 265 | "dev": true, | |
| 266 | "license": "Apache-2.0", | |
| 267 | "dependencies": { | |
| 268 | "bare-fs": "^4.1.2", | |
| 269 | "bare-inspector": "^6.0.1", | |
| 270 | "bare-path": "^3.0.0", | |
| 271 | "bare-process": "^4.2.1", | |
| 272 | "bare-url": "^2.1.5", | |
| 273 | "bare-v8-to-istanbul": "^1.0.2", | |
| 274 | "picomatch": "^4.0.2", | |
| 275 | "which-runtime": "^1.2.1" | |
| 276 | } | |
| 277 | }, | |
| 278 | "node_modules/bare-crypto": { | |
| 279 | "version": "1.15.3", | |
| 280 | "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.15.3.tgz", | |
| 281 | "integrity": "sha512-macV9lbyJTsLPRXJkBtz8ivTGEo3LCyJInLT9IB/PWJ7pRXwvHs/FP4bx/fWw+HZkiepIYCAV2cuU5CR92XWCw==", | |
| 282 | "dev": true, | |
| 283 | "license": "Apache-2.0", | |
| 284 | "dependencies": { | |
| 285 | "bare-assert": "^1.2.0", | |
| 286 | "bare-stream": "^2.6.3" | |
| 287 | }, | |
| 288 | "peerDependencies": { | |
| 289 | "bare-buffer": "*" | |
| 290 | }, | |
| 291 | "peerDependenciesMeta": { | |
| 292 | "bare-buffer": { | |
| 293 | "optional": true | |
| 294 | } | |
| 295 | } | |
| 296 | }, | |
| 297 | "node_modules/bare-debug-log": { | |
| 298 | "version": "2.0.0", | |
| 299 | "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", | |
| 300 | "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", | |
| 301 | "dev": true, | |
| 302 | "license": "Apache-2.0", | |
| 303 | "dependencies": { | |
| 304 | "bare-os": "^3.0.1" | |
| 305 | } | |
| 306 | }, | |
| 307 | "node_modules/bare-dns": { | |
| 308 | "version": "2.1.4", | |
| 309 | "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", | |
| 310 | "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==", | |
| 311 | "dev": true, | |
| 312 | "license": "Apache-2.0", | |
| 313 | "engines": { | |
| 314 | "bare": ">=1.7.0" | |
| 315 | } | |
| 316 | }, | |
| 317 | "node_modules/bare-encoding": { | |
| 318 | "version": "1.0.3", | |
| 319 | "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz", | |
| 320 | "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==", | |
| 321 | "dev": true, | |
| 322 | "license": "Apache-2.0", | |
| 323 | "peerDependencies": { | |
| 324 | "bare-buffer": "*" | |
| 325 | }, | |
| 326 | "peerDependenciesMeta": { | |
| 327 | "bare-buffer": { | |
| 328 | "optional": true | |
| 329 | } | |
| 330 | } | |
| 331 | }, | |
| 332 | "node_modules/bare-env": { | |
| 333 | "version": "3.0.0", | |
| 334 | "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", | |
| 335 | "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", | |
| 336 | "dev": true, | |
| 337 | "license": "Apache-2.0", | |
| 338 | "dependencies": { | |
| 339 | "bare-os": "^3.0.1" | |
| 340 | } | |
| 341 | }, | |
| 342 | "node_modules/bare-events": { | |
| 343 | "version": "2.9.1", | |
| 344 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", | |
| 345 | "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", | |
| 346 | "dev": true, | |
| 347 | "license": "Apache-2.0", | |
| 348 | "peerDependencies": { | |
| 349 | "bare-abort-controller": "*" | |
| 350 | }, | |
| 351 | "peerDependenciesMeta": { | |
| 352 | "bare-abort-controller": { | |
| 353 | "optional": true | |
| 354 | } | |
| 355 | } | |
| 356 | }, | |
| 357 | "node_modules/bare-format": { | |
| 358 | "version": "1.0.2", | |
| 359 | "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.2.tgz", | |
| 360 | "integrity": "sha512-GswdhnOnP9QtwRbrf4wLApw5widkaLMsLe2XOs35fQD2YfEN1ApoGka+cZ7PfvzxMgfYXmMhj/2OGlVn5/Dxgw==", | |
| 361 | "dev": true, | |
| 362 | "license": "Apache-2.0", | |
| 363 | "dependencies": { | |
| 364 | "bare-inspect": "^3.0.0" | |
| 365 | } | |
| 366 | }, | |
| 367 | "node_modules/bare-fs": { | |
| 368 | "version": "4.7.3", | |
| 369 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.3.tgz", | |
| 370 | "integrity": "sha512-xRgplks8SvcKkdlv2M6Z2LZmRsmqd+x0nXXGXeMEjwdibj1HSDrlnqBRLeYdMvsgCox7Bq0e+DHwfczOfsn6IA==", | |
| 371 | "dev": true, | |
| 372 | "license": "Apache-2.0", | |
| 373 | "dependencies": { | |
| 374 | "bare-events": "^2.5.4", | |
| 375 | "bare-path": "^3.0.0", | |
| 376 | "bare-stream": "^2.6.4", | |
| 377 | "bare-url": "^2.2.2", | |
| 378 | "fast-fifo": "^1.3.2" | |
| 379 | }, | |
| 380 | "engines": { | |
| 381 | "bare": ">=1.16.0" | |
| 382 | }, | |
| 383 | "peerDependencies": { | |
| 384 | "bare-buffer": "*" | |
| 385 | }, | |
| 386 | "peerDependenciesMeta": { | |
| 387 | "bare-buffer": { | |
| 388 | "optional": true | |
| 389 | } | |
| 390 | } | |
| 391 | }, | |
| 392 | "node_modules/bare-hrtime": { | |
| 393 | "version": "2.1.1", | |
| 394 | "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", | |
| 395 | "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", | |
| 396 | "dev": true, | |
| 397 | "license": "Apache-2.0" | |
| 398 | }, | |
| 399 | "node_modules/bare-http-parser": { | |
| 400 | "version": "1.1.5", | |
| 401 | "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.1.5.tgz", | |
| 402 | "integrity": "sha512-sPaDetLbWRmth04JmuTCvw/hoNdWJvYUJN8n1tYdGW3HM0mMnCvK2f0oIxE6HK7iOq+WlsRzEMg1LT/b0wGbLQ==", | |
| 403 | "dev": true, | |
| 404 | "license": "Apache-2.0" | |
| 405 | }, | |
| 406 | "node_modules/bare-http1": { | |
| 407 | "version": "4.5.7", | |
| 408 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.7.tgz", | |
| 409 | "integrity": "sha512-PRuzs9ywt4vUvrC3mnHhQyaQfLYzdFy8XKOH0oKeKmYfyYYUqXBkdbJE2csESLBxJEbKDBjxPGLU+Qa0l1ds4A==", | |
| 410 | "dev": true, | |
| 411 | "license": "Apache-2.0", | |
| 412 | "dependencies": { | |
| 413 | "bare-events": "^2.6.0", | |
| 414 | "bare-http-parser": "^1.1.1", | |
| 415 | "bare-stream": "^2.10.0", | |
| 416 | "bare-tcp": "^2.2.0" | |
| 417 | }, | |
| 418 | "peerDependencies": { | |
| 419 | "bare-buffer": "*", | |
| 420 | "bare-url": "*" | |
| 421 | }, | |
| 422 | "peerDependenciesMeta": { | |
| 423 | "bare-buffer": { | |
| 424 | "optional": true | |
| 425 | }, | |
| 426 | "bare-url": { | |
| 427 | "optional": true | |
| 428 | } | |
| 429 | } | |
| 430 | }, | |
| 431 | "node_modules/bare-https": { | |
| 432 | "version": "3.0.0", | |
| 433 | "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-3.0.0.tgz", | |
| 434 | "integrity": "sha512-W1GRSCzn+xXKf5bMcPs/hg6Ga1bxPqb7owGfS+tvlBQfPe5Q2STcanRuKZrgU60v5uKrhXH5cgWwM+DLqvXZgQ==", | |
| 435 | "dev": true, | |
| 436 | "license": "Apache-2.0", | |
| 437 | "dependencies": { | |
| 438 | "bare-http1": "^4.4.0", | |
| 439 | "bare-tcp": "^2.2.0", | |
| 440 | "bare-tls": "^3.0.0" | |
| 441 | } | |
| 442 | }, | |
| 443 | "node_modules/bare-inspect": { | |
| 444 | "version": "3.1.4", | |
| 445 | "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz", | |
| 446 | "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==", | |
| 447 | "dev": true, | |
| 448 | "license": "Apache-2.0", | |
| 449 | "dependencies": { | |
| 450 | "bare-ansi-escapes": "^2.1.0", | |
| 451 | "bare-type": "^1.0.0" | |
| 452 | }, | |
| 453 | "engines": { | |
| 454 | "bare": ">=1.18.0" | |
| 455 | } | |
| 456 | }, | |
| 457 | "node_modules/bare-inspector": { | |
| 458 | "version": "6.1.0", | |
| 459 | "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-6.1.0.tgz", | |
| 460 | "integrity": "sha512-PRxmZ4gF+K3TLzGubgRFvzdECybTCSKackgNsAdd4e7SdvICxMkGj+p5iX7bSKYSmgDnxgCR+2Z6UXmWykKhvw==", | |
| 461 | "dev": true, | |
| 462 | "license": "Apache-2.0", | |
| 463 | "dependencies": { | |
| 464 | "bare-events": "^2.1.0", | |
| 465 | "bare-http1": "^4.0.0", | |
| 466 | "bare-stream": "^2.0.0", | |
| 467 | "bare-url": "^2.0.0", | |
| 468 | "bare-ws": "^3.0.0" | |
| 469 | }, | |
| 470 | "engines": { | |
| 471 | "bare": ">=1.29.0" | |
| 472 | }, | |
| 473 | "peerDependencies": { | |
| 474 | "bare-tcp": "*" | |
| 475 | }, | |
| 476 | "peerDependenciesMeta": { | |
| 477 | "bare-tcp": { | |
| 478 | "optional": true | |
| 479 | } | |
| 480 | } | |
| 481 | }, | |
| 482 | "node_modules/bare-module": { | |
| 483 | "version": "6.4.0", | |
| 484 | "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.4.0.tgz", | |
| 485 | "integrity": "sha512-Yn4V5g5EqGQL4LYUOmt7fjKzj2JPWyJOqE3lPoeZwfUH5rk4CKUfZj6JhDwbzhBYCqqmUgjgQ5aY8cihAPILLA==", | |
| 486 | "dev": true, | |
| 487 | "license": "Apache-2.0", | |
| 488 | "dependencies": { | |
| 489 | "bare-bundle": "^1.3.0", | |
| 490 | "bare-module-lexer": "^1.0.0", | |
| 491 | "bare-module-resolve": "^1.8.0", | |
| 492 | "bare-path": "^3.0.0", | |
| 493 | "bare-type-stripper": "^0.1.2", | |
| 494 | "bare-url": "^2.0.1" | |
| 495 | }, | |
| 496 | "engines": { | |
| 497 | "bare": ">=1.29.4" | |
| 498 | }, | |
| 499 | "peerDependencies": { | |
| 500 | "bare-buffer": "*" | |
| 501 | }, | |
| 502 | "peerDependenciesMeta": { | |
| 503 | "bare-buffer": { | |
| 504 | "optional": true | |
| 505 | } | |
| 506 | } | |
| 507 | }, | |
| 508 | "node_modules/bare-module-lexer": { | |
| 509 | "version": "1.6.0", | |
| 510 | "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.6.0.tgz", | |
| 511 | "integrity": "sha512-uVDdbh6EnypLg5K4Gz5GkFloXiJu0QN5S1JNiKdKbiLoyTjUqIXKnHR/8fb2QejDT/+5BNlnqlbkAAg1iEZSFA==", | |
| 512 | "dev": true, | |
| 513 | "license": "Apache-2.0", | |
| 514 | "dependencies": { | |
| 515 | "require-addon": "^1.0.2" | |
| 516 | }, | |
| 517 | "peerDependencies": { | |
| 518 | "bare-buffer": "*" | |
| 519 | }, | |
| 520 | "peerDependenciesMeta": { | |
| 521 | "bare-buffer": { | |
| 522 | "optional": true | |
| 523 | } | |
| 524 | } | |
| 525 | }, | |
| 526 | "node_modules/bare-module-resolve": { | |
| 527 | "version": "1.12.2", | |
| 528 | "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.2.tgz", | |
| 529 | "integrity": "sha512-j+hiD5k99qec4KjJvYsI67q5AOBifmy9JG3oeMVxTmvrhn2sIdp8StrUvZu4YNgwTpO+NhniQG16N1ETDe1k5w==", | |
| 530 | "dev": true, | |
| 531 | "license": "Apache-2.0", | |
| 532 | "dependencies": { | |
| 533 | "bare-semver": "^1.0.0" | |
| 534 | }, | |
| 535 | "peerDependencies": { | |
| 536 | "bare-url": "*" | |
| 537 | }, | |
| 538 | "peerDependenciesMeta": { | |
| 539 | "bare-url": { | |
| 540 | "optional": true | |
| 541 | } | |
| 542 | } | |
| 543 | }, | |
| 544 | "node_modules/bare-net": { | |
| 545 | "version": "2.3.2", | |
| 546 | "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.3.2.tgz", | |
| 547 | "integrity": "sha512-I+yz+pqbYsBkxDsnu5vkKvy7RSNY9CcAvu2jZT6PsmdXJQG1i3dmD5V7xc3334OVp2absgtUEYLmmuNFlphBzg==", | |
| 548 | "dev": true, | |
| 549 | "license": "Apache-2.0", | |
| 550 | "dependencies": { | |
| 551 | "bare-events": "^2.2.2", | |
| 552 | "bare-pipe": "^4.0.0", | |
| 553 | "bare-stream": "^2.0.0", | |
| 554 | "bare-tcp": "^2.0.0" | |
| 555 | } | |
| 556 | }, | |
| 557 | "node_modules/bare-os": { | |
| 558 | "version": "3.9.3", | |
| 559 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.3.tgz", | |
| 560 | "integrity": "sha512-fF4Q7QsyKVF5Rj0qvI8BgUNjqzC2JvQlpTaPLjVJVxYVUX5Zr9un+y3w1HmA4nNKdFmRBT8z/WmrjvXzXVerKQ==", | |
| 561 | "dev": true, | |
| 562 | "license": "Apache-2.0", | |
| 563 | "engines": { | |
| 564 | "bare": ">=1.14.0" | |
| 565 | } | |
| 566 | }, | |
| 567 | "node_modules/bare-path": { | |
| 568 | "version": "3.0.1", | |
| 569 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.1.tgz", | |
| 570 | "integrity": "sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==", | |
| 571 | "dev": true, | |
| 572 | "license": "Apache-2.0", | |
| 573 | "dependencies": { | |
| 574 | "bare-os": "^3.0.1" | |
| 575 | } | |
| 576 | }, | |
| 577 | "node_modules/bare-pipe": { | |
| 578 | "version": "4.2.2", | |
| 579 | "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.2.2.tgz", | |
| 580 | "integrity": "sha512-Wkf6//CmKS8inHrkgi3X/xNMeaGfs2I0ayLM0B8cbPB4DxYHcoes6Ng+muCBfXNIJ9qmEjwDDJjAUz7nRxxjgQ==", | |
| 581 | "dev": true, | |
| 582 | "license": "Apache-2.0", | |
| 583 | "peer": true, | |
| 584 | "dependencies": { | |
| 585 | "bare-events": "^2.0.0", | |
| 586 | "bare-stream": "^2.0.0" | |
| 587 | }, | |
| 588 | "engines": { | |
| 589 | "bare": ">=1.16.0" | |
| 590 | } | |
| 591 | }, | |
| 592 | "node_modules/bare-posix": { | |
| 593 | "version": "1.0.1", | |
| 594 | "resolved": "https://registry.npmjs.org/bare-posix/-/bare-posix-1.0.1.tgz", | |
| 595 | "integrity": "sha512-b4quA8dyRVvX0aFIAIiev5zFATHwIUGr42Gmt+CqiBNg/bdsNkrj4bbkeotMOMiljJS4DCME5kywJO/TFwKo1A==", | |
| 596 | "dev": true, | |
| 597 | "license": "Apache-2.0" | |
| 598 | }, | |
| 599 | "node_modules/bare-process": { | |
| 600 | "version": "4.5.0", | |
| 601 | "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.5.0.tgz", | |
| 602 | "integrity": "sha512-NTtYDiwleJjWWNg4+yzS6B/RovL/LLPHVvoOM+18W+dYAXCmky0zFfN66hqqBbrlGAlb52/QgKrdzPR3x1QyHA==", | |
| 603 | "dev": true, | |
| 604 | "license": "Apache-2.0", | |
| 605 | "dependencies": { | |
| 606 | "bare-abort": "^2.0.13", | |
| 607 | "bare-env": "^3.0.0", | |
| 608 | "bare-events": "^2.3.1", | |
| 609 | "bare-hrtime": "^2.0.0", | |
| 610 | "bare-os": "^3.7.1", | |
| 611 | "bare-posix": "^1.0.1", | |
| 612 | "bare-signals": "^4.0.0", | |
| 613 | "bare-stdio": "^1.0.1" | |
| 614 | } | |
| 615 | }, | |
| 616 | "node_modules/bare-semver": { | |
| 617 | "version": "1.1.0", | |
| 618 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.1.0.tgz", | |
| 619 | "integrity": "sha512-1Hw5qJ7hXdVt3uPUqjeFTuxyvBUJauvz5A1I2jk8gzjZMHp04n//6nV9MDbG9CMw78JHY2lGV0w6s//LrASm2w==", | |
| 620 | "dev": true, | |
| 621 | "license": "Apache-2.0" | |
| 622 | }, | |
| 623 | "node_modules/bare-signals": { | |
| 624 | "version": "4.2.0", | |
| 625 | "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", | |
| 626 | "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", | |
| 627 | "dev": true, | |
| 628 | "license": "Apache-2.0", | |
| 629 | "dependencies": { | |
| 630 | "bare-events": "^2.5.3", | |
| 631 | "bare-os": "^3.3.1" | |
| 632 | }, | |
| 633 | "engines": { | |
| 634 | "bare": ">=1.7.0" | |
| 635 | } | |
| 636 | }, | |
| 637 | "node_modules/bare-stdio": { | |
| 638 | "version": "1.0.2", | |
| 639 | "resolved": "https://registry.npmjs.org/bare-stdio/-/bare-stdio-1.0.2.tgz", | |
| 640 | "integrity": "sha512-3WJDqtvVGP4f+j68kyEC05umOYNwKJ1xG+YAXL8yZ605WgNqiRhVaFq+mVIhBt2eKNp7pa5vCQdhOt1pNh79SA==", | |
| 641 | "dev": true, | |
| 642 | "license": "Apache-2.0", | |
| 643 | "dependencies": { | |
| 644 | "bare-fs": "^4.5.2", | |
| 645 | "bare-pipe": "^4.1.5", | |
| 646 | "bare-tty": "^5.0.3" | |
| 647 | } | |
| 648 | }, | |
| 649 | "node_modules/bare-stream": { | |
| 650 | "version": "2.13.3", | |
| 651 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", | |
| 652 | "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==", | |
| 653 | "dev": true, | |
| 654 | "license": "Apache-2.0", | |
| 655 | "dependencies": { | |
| 656 | "b4a": "^1.8.1", | |
| 657 | "streamx": "^2.25.0", | |
| 658 | "teex": "^1.0.1" | |
| 659 | }, | |
| 660 | "peerDependencies": { | |
| 661 | "bare-abort-controller": "*", | |
| 662 | "bare-buffer": "*", | |
| 663 | "bare-events": "*" | |
| 664 | }, | |
| 665 | "peerDependenciesMeta": { | |
| 666 | "bare-abort-controller": { | |
| 667 | "optional": true | |
| 668 | }, | |
| 669 | "bare-buffer": { | |
| 670 | "optional": true | |
| 671 | }, | |
| 672 | "bare-events": { | |
| 673 | "optional": true | |
| 674 | } | |
| 675 | } | |
| 676 | }, | |
| 677 | "node_modules/bare-structured-clone": { | |
| 678 | "version": "1.6.0", | |
| 679 | "resolved": "https://registry.npmjs.org/bare-structured-clone/-/bare-structured-clone-1.6.0.tgz", | |
| 680 | "integrity": "sha512-AZjEERyqF7kAuudlmgrweT2KwwWM0LsGG4Gx/bWyFwJrKU7E3wNG8c09z2DIrdw93p3vDtfOX8fyHOcXfy5m+g==", | |
| 681 | "dev": true, | |
| 682 | "license": "Apache-2.0", | |
| 683 | "dependencies": { | |
| 684 | "bare-buffer": "^3.6.0", | |
| 685 | "bare-type": "^1.1.0", | |
| 686 | "bare-url": "^2.4.0", | |
| 687 | "compact-encoding": "^3.0.1" | |
| 688 | }, | |
| 689 | "engines": { | |
| 690 | "bare": ">=1.2.0" | |
| 691 | } | |
| 692 | }, | |
| 693 | "node_modules/bare-stylize": { | |
| 694 | "version": "0.0.1", | |
| 695 | "resolved": "https://registry.npmjs.org/bare-stylize/-/bare-stylize-0.0.1.tgz", | |
| 696 | "integrity": "sha512-l3MjmIl476bWijYWf3RbE+osl4iuXSOMudzp0vAqzIK7gPgn/+G3oAxp8Oin9CFF911KBP0LO9kts8Ci8mGZaQ==", | |
| 697 | "dev": true, | |
| 698 | "license": "Apache-2.0", | |
| 699 | "dependencies": { | |
| 700 | "bare-ansi-escapes": "^2.2.3", | |
| 701 | "bare-process": "^4.2.1" | |
| 702 | } | |
| 703 | }, | |
| 704 | "node_modules/bare-subprocess": { | |
| 705 | "version": "5.2.3", | |
| 706 | "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.3.tgz", | |
| 707 | "integrity": "sha512-07wwswlV7M3sC9IykbZRZ/jHAkrXFWVLqdBWGv1y0ojCimtRD9hGwxdHmR5FUFmDUZLNsBmTYJNQqgio5+A85Q==", | |
| 708 | "dev": true, | |
| 709 | "license": "Apache-2.0", | |
| 710 | "dependencies": { | |
| 711 | "bare-env": "^3.0.0", | |
| 712 | "bare-events": "^2.5.4", | |
| 713 | "bare-os": "^3.0.1", | |
| 714 | "bare-pipe": "^4.0.0", | |
| 715 | "bare-url": "^2.2.2" | |
| 716 | }, | |
| 717 | "engines": { | |
| 718 | "bare": ">=1.7.0" | |
| 719 | }, | |
| 720 | "peerDependencies": { | |
| 721 | "bare-buffer": "*" | |
| 722 | }, | |
| 723 | "peerDependenciesMeta": { | |
| 724 | "bare-buffer": { | |
| 725 | "optional": true | |
| 726 | } | |
| 727 | } | |
| 728 | }, | |
| 729 | "node_modules/bare-tcp": { | |
| 730 | "version": "2.5.1", | |
| 731 | "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.5.1.tgz", | |
| 732 | "integrity": "sha512-N32OosAegmGzksr/huxIYLU/uaUARte6utO+vAu/EPQztMM7iAC55VeQSfzr2/XBJoz2OIETMvmU7/sdfiOGjw==", | |
| 733 | "dev": true, | |
| 734 | "license": "Apache-2.0", | |
| 735 | "dependencies": { | |
| 736 | "bare-dns": "^2.0.4", | |
| 737 | "bare-events": "^2.5.4", | |
| 738 | "bare-stream": "^2.6.4" | |
| 739 | }, | |
| 740 | "engines": { | |
| 741 | "bare": ">=1.16.0" | |
| 742 | }, | |
| 743 | "peerDependencies": { | |
| 744 | "bare-pipe": "*" | |
| 745 | }, | |
| 746 | "peerDependenciesMeta": { | |
| 747 | "bare-pipe": { | |
| 748 | "optional": true | |
| 749 | } | |
| 750 | } | |
| 751 | }, | |
| 752 | "node_modules/bare-tls": { | |
| 753 | "version": "3.1.7", | |
| 754 | "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-3.1.7.tgz", | |
| 755 | "integrity": "sha512-AMw8tJlb3LhzAmhgXRcjDrTlNxR3gXXyj6G8eU9iwvCFtiUBD8MxAW7bwunA1gXDukgo40A970jX0APc2jMU7A==", | |
| 756 | "dev": true, | |
| 757 | "license": "Apache-2.0", | |
| 758 | "dependencies": { | |
| 759 | "bare-net": "^2.0.1", | |
| 760 | "bare-stream": "^2.6.4" | |
| 761 | }, | |
| 762 | "engines": { | |
| 763 | "bare": ">=1.7.0" | |
| 764 | } | |
| 765 | }, | |
| 766 | "node_modules/bare-tty": { | |
| 767 | "version": "5.1.1", | |
| 768 | "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.1.1.tgz", | |
| 769 | "integrity": "sha512-cXCMAlcLCFTCWwCtXBIhDuXa4hutTEbrHHIK5pvSSb4BLaSGJ9tvxVN3mvGOvXXqONS7l04mTiP+eIUlZKI92g==", | |
| 770 | "dev": true, | |
| 771 | "license": "Apache-2.0", | |
| 772 | "dependencies": { | |
| 773 | "bare-events": "^2.2.0", | |
| 774 | "bare-signals": "^4.0.0", | |
| 775 | "bare-stream": "^2.0.0" | |
| 776 | }, | |
| 777 | "engines": { | |
| 778 | "bare": ">=1.16.0" | |
| 779 | } | |
| 780 | }, | |
| 781 | "node_modules/bare-type": { | |
| 782 | "version": "1.1.0", | |
| 783 | "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", | |
| 784 | "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==", | |
| 785 | "dev": true, | |
| 786 | "license": "Apache-2.0", | |
| 787 | "engines": { | |
| 788 | "bare": ">=1.2.0" | |
| 789 | } | |
| 790 | }, | |
| 791 | "node_modules/bare-type-stripper": { | |
| 792 | "version": "0.1.4", | |
| 793 | "resolved": "https://registry.npmjs.org/bare-type-stripper/-/bare-type-stripper-0.1.4.tgz", | |
| 794 | "integrity": "sha512-FdZhp9XEnQpj8AWFmIft/sVUyKS9XSmB6PhcxBHhuEDxxZM5Kkt8+kFS7eEpLXR7TkaRkNpSENoGH/8lpAmtkA==", | |
| 795 | "dev": true, | |
| 796 | "license": "Apache-2.0", | |
| 797 | "dependencies": { | |
| 798 | "require-addon": "^1.0.2" | |
| 799 | }, | |
| 800 | "peerDependencies": { | |
| 801 | "bare-buffer": "*" | |
| 802 | }, | |
| 803 | "peerDependenciesMeta": { | |
| 804 | "bare-buffer": { | |
| 805 | "optional": true | |
| 806 | } | |
| 807 | } | |
| 808 | }, | |
| 809 | "node_modules/bare-url": { | |
| 810 | "version": "2.4.5", | |
| 811 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz", | |
| 812 | "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==", | |
| 813 | "dev": true, | |
| 814 | "license": "Apache-2.0", | |
| 815 | "dependencies": { | |
| 816 | "bare-path": "^3.0.0" | |
| 817 | } | |
| 818 | }, | |
| 819 | "node_modules/bare-utils": { | |
| 820 | "version": "1.6.0", | |
| 821 | "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.6.0.tgz", | |
| 822 | "integrity": "sha512-WhQEIkkAxkSnW7u1QgrI0AfNm5JpMruETXeYsb5qnkBJ0TTfNKygZmsh6rkoHBANaV+C/7Jed7bJP9OmEHG7rQ==", | |
| 823 | "dev": true, | |
| 824 | "license": "Apache-2.0", | |
| 825 | "dependencies": { | |
| 826 | "bare-debug-log": "^2.0.0", | |
| 827 | "bare-encoding": "^1.0.0", | |
| 828 | "bare-format": "^1.0.0", | |
| 829 | "bare-inspect": "^3.0.0", | |
| 830 | "bare-stylize": "^0.0.1", | |
| 831 | "bare-type": "^1.0.6" | |
| 832 | } | |
| 833 | }, | |
| 834 | "node_modules/bare-v8-to-istanbul": { | |
| 835 | "version": "1.0.2", | |
| 836 | "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz", | |
| 837 | "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==", | |
| 838 | "dev": true, | |
| 839 | "license": "Apache-2.0", | |
| 840 | "dependencies": { | |
| 841 | "bare-assert": "^1.0.2", | |
| 842 | "bare-fs": "^4.1.2", | |
| 843 | "bare-module": "^6.1.2", | |
| 844 | "bare-path": "^3.0.0", | |
| 845 | "bare-process": "^4.2.0", | |
| 846 | "bare-url": "^2.1.5", | |
| 847 | "bare-utils": "^1.2.0", | |
| 848 | "v8-to-istanbul": "^9.3.0", | |
| 849 | "which-runtime": "^1.2.1" | |
| 850 | } | |
| 851 | }, | |
| 852 | "node_modules/bare-ws": { | |
| 853 | "version": "3.0.0", | |
| 854 | "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-3.0.0.tgz", | |
| 855 | "integrity": "sha512-q2v0UIQ0cFQBXQMp+0FRaoSk1EoYgOhzO7yio0TqBV6Rkfot97mbBYxb1ssw5faVCisVaFxQYY8113SMLYQBow==", | |
| 856 | "dev": true, | |
| 857 | "license": "Apache-2.0", | |
| 858 | "dependencies": { | |
| 859 | "bare-crypto": "^1.2.0", | |
| 860 | "bare-events": "^2.3.1", | |
| 861 | "bare-http1": "^4.0.0", | |
| 862 | "bare-https": "^3.0.0", | |
| 863 | "bare-stream": "^2.1.2" | |
| 864 | }, | |
| 865 | "peerDependencies": { | |
| 866 | "bare-buffer": "*", | |
| 867 | "bare-url": "*" | |
| 868 | }, | |
| 869 | "peerDependenciesMeta": { | |
| 870 | "bare-buffer": { | |
| 871 | "optional": true | |
| 872 | }, | |
| 873 | "bare-url": { | |
| 874 | "optional": true | |
| 875 | } | |
| 876 | } | |
| 877 | }, | |
| 878 | "node_modules/brittle": { | |
| 879 | "version": "4.0.2", | |
| 880 | "resolved": "https://registry.npmjs.org/brittle/-/brittle-4.0.2.tgz", | |
| 881 | "integrity": "sha512-frnh7gB76uiSmFAf4+uIsaKjygMEvzUbN/GhxS+30WKz0pd/COBVuj+O9TmW9t0kIzJTafv8xquV6DriW316Vw==", | |
| 882 | "dev": true, | |
| 883 | "license": "Apache-2.0", | |
| 884 | "dependencies": { | |
| 885 | "b4a": "^1.8.0", | |
| 886 | "bare-assert": "^1.2.0", | |
| 887 | "bare-buffer": "^3.6.0", | |
| 888 | "bare-channel": "^5.2.3", | |
| 889 | "bare-cov": "^1.2.1", | |
| 890 | "bare-fs": "^4.5.5", | |
| 891 | "bare-os": "^3.7.1", | |
| 892 | "bare-path": "^3.0.0", | |
| 893 | "bare-process": "^4.3.0", | |
| 894 | "bare-subprocess": "^5.2.2", | |
| 895 | "bare-url": "^2.3.2", | |
| 896 | "error-stack-parser": "^2.1.4", | |
| 897 | "globbie": "^1.0.3", | |
| 898 | "paparam": "^1.10.0", | |
| 899 | "same-object": "^1.0.2", | |
| 900 | "streamx": "^2.25.0", | |
| 901 | "test-tmp": "^1.4.0", | |
| 902 | "tmatch": "^5.0.0", | |
| 903 | "which-runtime": "^1.3.2" | |
| 904 | }, | |
| 905 | "bin": { | |
| 906 | "brittle": "advise.js", | |
| 907 | "brittle-bare": "brittle-bare.js", | |
| 908 | "brittle-make-test": "make-test.js", | |
| 909 | "brittle-node": "brittle-node.js" | |
| 910 | } | |
| 911 | }, | |
| 912 | "node_modules/cborg": { | |
| 913 | "version": "5.1.6", | |
| 914 | "resolved": "https://registry.npmjs.org/cborg/-/cborg-5.1.6.tgz", | |
| 915 | "integrity": "sha512-apu/mOSODJ98JXBz3cbiv/gAFX4oaCZQQ53+8b4IPjc1wylYxd8eJis87QP7ic4AOE4O02j02jjfUgVZUJIIvA==", | |
| 916 | "license": "Apache-2.0", | |
| 917 | "bin": { | |
| 918 | "cborg": "lib/bin.js" | |
| 919 | } | |
| 920 | }, | |
| 921 | "node_modules/compact-encoding": { | |
| 922 | "version": "3.3.0", | |
| 923 | "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-3.3.0.tgz", | |
| 924 | "integrity": "sha512-e64XyzlBvTRJ3iScuU/U2w25Dglkm7fhrRbrGaHIwuTlNnzjRKMaQBCVl7mJRvZg7wI5a9wX1IVTXCuaAANNkw==", | |
| 925 | "dev": true, | |
| 926 | "license": "Apache-2.0", | |
| 927 | "dependencies": { | |
| 928 | "b4a": "^1.3.0" | |
| 929 | } | |
| 930 | }, | |
| 931 | "node_modules/convert-source-map": { | |
| 932 | "version": "2.0.0", | |
| 933 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", | |
| 934 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | |
| 935 | "dev": true, | |
| 936 | "license": "MIT" | |
| 937 | }, | |
| 938 | "node_modules/error-stack-parser": { | |
| 939 | "version": "2.1.4", | |
| 940 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", | |
| 941 | "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", | |
| 942 | "dev": true, | |
| 943 | "license": "MIT", | |
| 944 | "dependencies": { | |
| 945 | "stackframe": "^1.3.4" | |
| 946 | } | |
| 947 | }, | |
| 948 | "node_modules/events-universal": { | |
| 949 | "version": "1.0.1", | |
| 950 | "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", | |
| 951 | "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", | |
| 952 | "dev": true, | |
| 953 | "license": "Apache-2.0", | |
| 954 | "dependencies": { | |
| 955 | "bare-events": "^2.7.0" | |
| 956 | } | |
| 957 | }, | |
| 958 | "node_modules/fast-fifo": { | |
| 959 | "version": "1.3.2", | |
| 960 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | |
| 961 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | |
| 962 | "dev": true, | |
| 963 | "license": "MIT" | |
| 964 | }, | |
| 965 | "node_modules/globbie": { | |
| 966 | "version": "1.0.3", | |
| 967 | "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz", | |
| 968 | "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==", | |
| 969 | "dev": true, | |
| 970 | "license": "Apache-2.0", | |
| 971 | "dependencies": { | |
| 972 | "bare-fs": "^4.1.2", | |
| 973 | "bare-path": "^3.0.0", | |
| 974 | "bare-process": "^4.2.1", | |
| 975 | "picomatch": "^4.0.2" | |
| 976 | } | |
| 977 | }, | |
| 978 | "node_modules/multiformats": { | |
| 979 | "version": "13.4.2", | |
| 980 | "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", | |
| 981 | "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", | |
| 982 | "license": "Apache-2.0 OR MIT" | |
| 983 | }, | |
| 984 | "node_modules/paparam": { | |
| 985 | "version": "1.10.1", | |
| 986 | "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.1.tgz", | |
| 987 | "integrity": "sha512-viyQI64VIja0Za3njIzhoEP8ZVkgPowhZPuG0E96NwBfYJ6ZIyrrlhWGtFPkdN7eYLl2L8CTWL+wla0evm1KKQ==", | |
| 988 | "dev": true, | |
| 989 | "license": "Apache-2.0" | |
| 990 | }, | |
| 991 | "node_modules/picomatch": { | |
| 992 | "version": "4.0.5", | |
| 993 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", | |
| 994 | "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", | |
| 995 | "dev": true, | |
| 996 | "license": "MIT", | |
| 997 | "engines": { | |
| 998 | "node": ">=12" | |
| 999 | }, | |
| 1000 | "funding": { | |
| 1001 | "url": "https://github.com/sponsors/jonschlinkert" | |
| 1002 | } | |
| 1003 | }, | |
| 1004 | "node_modules/require-addon": { | |
| 1005 | "version": "1.2.0", | |
| 1006 | "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", | |
| 1007 | "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", | |
| 1008 | "dev": true, | |
| 1009 | "license": "Apache-2.0", | |
| 1010 | "dependencies": { | |
| 1011 | "bare-addon-resolve": "^1.3.0" | |
| 1012 | }, | |
| 1013 | "engines": { | |
| 1014 | "bare": ">=1.10.0" | |
| 1015 | } | |
| 1016 | }, | |
| 1017 | "node_modules/same-object": { | |
| 1018 | "version": "1.0.2", | |
| 1019 | "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz", | |
| 1020 | "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==", | |
| 1021 | "dev": true, | |
| 1022 | "license": "MIT" | |
| 1023 | }, | |
| 1024 | "node_modules/stackframe": { | |
| 1025 | "version": "1.3.4", | |
| 1026 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", | |
| 1027 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", | |
| 1028 | "dev": true, | |
| 1029 | "license": "MIT" | |
| 1030 | }, | |
| 1031 | "node_modules/streamx": { | |
| 1032 | "version": "2.28.0", | |
| 1033 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz", | |
| 1034 | "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==", | |
| 1035 | "dev": true, | |
| 1036 | "license": "MIT", | |
| 1037 | "dependencies": { | |
| 1038 | "events-universal": "^1.0.0", | |
| 1039 | "fast-fifo": "^1.3.2", | |
| 1040 | "text-decoder": "^1.1.0" | |
| 1041 | } | |
| 1042 | }, | |
| 1043 | "node_modules/teex": { | |
| 1044 | "version": "1.0.1", | |
| 1045 | "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", | |
| 1046 | "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", | |
| 1047 | "dev": true, | |
| 1048 | "license": "MIT", | |
| 1049 | "dependencies": { | |
| 1050 | "streamx": "^2.12.5" | |
| 1051 | } | |
| 1052 | }, | |
| 1053 | "node_modules/test-tmp": { | |
| 1054 | "version": "1.4.0", | |
| 1055 | "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz", | |
| 1056 | "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==", | |
| 1057 | "dev": true, | |
| 1058 | "license": "MIT", | |
| 1059 | "dependencies": { | |
| 1060 | "bare-fs": "^4.0.1", | |
| 1061 | "bare-os": "^3.3.0", | |
| 1062 | "bare-path": "^3.0.0" | |
| 1063 | } | |
| 1064 | }, | |
| 1065 | "node_modules/text-decoder": { | |
| 1066 | "version": "1.2.7", | |
| 1067 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", | |
| 1068 | "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", | |
| 1069 | "dev": true, | |
| 1070 | "license": "Apache-2.0", | |
| 1071 | "dependencies": { | |
| 1072 | "b4a": "^1.6.4" | |
| 1073 | } | |
| 1074 | }, | |
| 1075 | "node_modules/tmatch": { | |
| 1076 | "version": "5.0.0", | |
| 1077 | "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz", | |
| 1078 | "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==", | |
| 1079 | "dev": true, | |
| 1080 | "license": "ISC", | |
| 1081 | "engines": { | |
| 1082 | "node": ">=8" | |
| 1083 | } | |
| 1084 | }, | |
| 1085 | "node_modules/v8-to-istanbul": { | |
| 1086 | "version": "9.3.0", | |
| 1087 | "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", | |
| 1088 | "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", | |
| 1089 | "dev": true, | |
| 1090 | "license": "ISC", | |
| 1091 | "dependencies": { | |
| 1092 | "@jridgewell/trace-mapping": "^0.3.12", | |
| 1093 | "@types/istanbul-lib-coverage": "^2.0.1", | |
| 1094 | "convert-source-map": "^2.0.0" | |
| 1095 | }, | |
| 1096 | "engines": { | |
| 1097 | "node": ">=10.12.0" | |
| 1098 | } | |
| 1099 | }, | |
| 1100 | "node_modules/varint": { | |
| 1101 | "version": "6.0.0", | |
| 1102 | "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", | |
| 1103 | "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", | |
| 1104 | "license": "MIT" | |
| 1105 | }, | |
| 1106 | "node_modules/which-runtime": { | |
| 1107 | "version": "1.4.0", | |
| 1108 | "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.4.0.tgz", | |
| 1109 | "integrity": "sha512-0ugbP4CJW4e2D20jvEcC4973dCgIaHI4Rw1PT+26U9zEve7FyYdWAIwUnoeOYvoCfn+wXHoHTKb1KhkYlb60Pw==", | |
| 1110 | "dev": true, | |
| 1111 | "license": "Apache-2.0" | |
| 1112 | } | |
| 1113 | } | |
| 1114 | } |
| … | … | @@ -0,0 +1,22 @@ |
| 1 | { | |
| 2 | "name": "atproto-repo", | |
| 3 | "version": "0.0.1", | |
| 4 | "description": "Byte-exact atproto repo layer — CAR + DAG-CBOR + CID + MST — over Hypercore-friendly primitives. Verified against real relay-accepted repos.", | |
| 5 | "type": "module", | |
| 6 | "main": "lib/repo.js", | |
| 7 | "scripts": { | |
| 8 | "test": "brittle-node test/*.test.js", | |
| 9 | "postinstall": "node scripts/bare-deps.mjs" | |
| 10 | }, | |
| 11 | "dependencies": { | |
| 12 | "@ipld/car": "^5.4.0", | |
| 13 | "@ipld/dag-cbor": "^9.2.1", | |
| 14 | "@noble/curves": "^1.9.7", | |
| 15 | "@noble/hashes": "^1.5.0", | |
| 16 | "multiformats": "^13.3.0" | |
| 17 | }, | |
| 18 | "devDependencies": { | |
| 19 | "atproto-identity": "file:../atproto-identity", | |
| 20 | "brittle": "^4.0.2" | |
| 21 | } | |
| 22 | } |
| … | … | @@ -0,0 +1,53 @@ |
| 1 | // Bare dep compat: add a "bare" export condition to @noble/hashes' ./crypto so it resolves | |
| 2 | // to the bare-safe crypto.js (globalThis.crypto ?? undefined) instead of cryptoNode.js | |
| 3 | // (`import 'node:crypto'`, which Bare can't load). @noble/curves + @scure/* import | |
| 4 | // @noble/hashes, so this one condition unblocks the whole crypto tree. We sign | |
| 5 | // deterministically (RFC6979), so the shim degrading to undefined is never called. | |
| 6 | // | |
| 7 | // node_modules is reinstall-wiped, so this runs on `postinstall` (see the package.json of | |
| 8 | // each package that depends on @noble). Idempotent. Run manually with explicit roots | |
| 9 | // (`node scripts/bare-deps.mjs identity repo pds`) to patch the whole dev tree at once; | |
| 10 | // with no args it patches the CURRENT package (process.cwd()) — the postinstall case. | |
| 11 | ||
| 12 | import { readdirSync, readFileSync, writeFileSync, statSync } from 'node:fs' | |
| 13 | import { join } from 'node:path' | |
| 14 | ||
| 15 | function patch (pjPath) { | |
| 16 | let j | |
| 17 | try { j = JSON.parse(readFileSync(pjPath, 'utf8')) } catch { return false } | |
| 18 | const exp = j.exports && j.exports['./crypto'] | |
| 19 | if (exp && typeof exp === 'object' && !exp.bare) { | |
| 20 | j.exports['./crypto'] = { bare: './esm/crypto.js', ...exp } // "bare" first → matched before "node" | |
| 21 | writeFileSync(pjPath, JSON.stringify(j, null, 2) + '\n') | |
| 22 | return true | |
| 23 | } | |
| 24 | return false | |
| 25 | } | |
| 26 | ||
| 27 | const isDir = (p) => { try { return statSync(p).isDirectory() } catch { return false } } | |
| 28 | ||
| 29 | // Walk a node_modules dir: patch any @noble/hashes found, recurse into nested node_modules. | |
| 30 | function walk (nm, found) { | |
| 31 | let entries | |
| 32 | try { entries = readdirSync(nm, { withFileTypes: true }) } catch { return } | |
| 33 | for (const e of entries) { | |
| 34 | if (!e.isDirectory()) continue | |
| 35 | const dir = join(nm, e.name) | |
| 36 | if (e.name === '@noble') { | |
| 37 | const pj = join(dir, 'hashes', 'package.json') | |
| 38 | if (patch(pj)) found.push(pj) | |
| 39 | // scoped pkgs may carry their own nested node_modules | |
| 40 | for (const p of readdirSync(dir)) { const nn = join(dir, p, 'node_modules'); if (isDir(nn)) walk(nn, found) } | |
| 41 | } else if (e.name.startsWith('@')) { | |
| 42 | for (const p of readdirSync(dir)) { const nn = join(dir, p, 'node_modules'); if (isDir(nn)) walk(nn, found) } | |
| 43 | } else { | |
| 44 | const nn = join(dir, 'node_modules'); if (isDir(nn)) walk(nn, found) | |
| 45 | } | |
| 46 | } | |
| 47 | } | |
| 48 | ||
| 49 | const roots = process.argv.slice(2) | |
| 50 | if (roots.length === 0) roots.push(process.cwd()) | |
| 51 | const found = [] | |
| 52 | for (const r of roots) { const nm = join(r, 'node_modules'); if (isDir(nm)) walk(nm, found) } | |
| 53 | console.log(found.length ? `bare-deps: patched ${found.length} @noble/hashes` : 'bare-deps: nothing to patch') |
| … | … | @@ -0,0 +1,51 @@ |
| 1 | // Slice 1: prove our CAR + DAG-CBOR + CID + MST-read stack is byte-exact against a | |
| 2 | // REAL, relay-accepted repo (getRepo output from our live PDS). The killer check: | |
| 3 | // every block re-encodes to identical bytes and the same CID. | |
| 4 | ||
| 5 | import test from 'brittle' | |
| 6 | import { readFileSync } from 'node:fs' | |
| 7 | import { fileURLToPath } from 'node:url' | |
| 8 | import { dirname, join } from 'node:path' | |
| 9 | import { readCar, loadCommit, listRecords, verifyRepo } from '../lib/repo.js' | |
| 10 | import { cidForValue } from '../lib/cid.js' | |
| 11 | ||
| 12 | const here = dirname(fileURLToPath(import.meta.url)) | |
| 13 | const carBytes = readFileSync(join(here, 'vectors', 'bridgetest-repo.car')) | |
| 14 | ||
| 15 | const DID = 'did:plc:bh2hiuoeb34g4lif62zj2vx7' | |
| 16 | const ROOT = 'bafyreigiu7fhjqrqo3cxvg5ymdjazlat6n3go4h6akm4yh47iwodnebr2a' | |
| 17 | const POST_KEY = 'app.bsky.feed.post/3mpmksfzaqm7k' | |
| 18 | ||
| 19 | const car = await readCar(carBytes) | |
| 20 | ||
| 21 | test('CAR structure decodes to the signed commit', (t) => { | |
| 22 | t.ok(car.roots.length === 1 && car.roots[0].toString() === ROOT, | |
| 23 | 'CAR has exactly one root (the commit)') | |
| 24 | ||
| 25 | const c = loadCommit(car) | |
| 26 | t.ok(c.value.did === DID && c.value.rev === '3mpmksfzaqm7k' && c.value.sig && c.value.data, | |
| 27 | 'root decodes to our signed commit') | |
| 28 | }) | |
| 29 | ||
| 30 | // THE fidelity proof: every block re-encodes to identical bytes + same CID. | |
| 31 | test('every block is byte-exact (re-encode -> same CID)', async (t) => { | |
| 32 | const r = await verifyRepo(car) | |
| 33 | if (!r.ok) t.comment('mismatches: ' + JSON.stringify(r.mismatches)) | |
| 34 | t.comment(`verified ${r.blocks} blocks, ${r.records} record(s)`) | |
| 35 | t.ok(r.ok, 'every block is byte-exact (re-encode -> same CID)') | |
| 36 | }) | |
| 37 | ||
| 38 | test('MST traversal and record fidelity', async (t) => { | |
| 39 | const recs = listRecords(car) | |
| 40 | t.ok(recs.length === 1 && recs[0].key === POST_KEY, 'MST traversal yields the post key') | |
| 41 | ||
| 42 | const rec = listRecords(car)[0].value | |
| 43 | t.ok(rec.$type === 'app.bsky.feed.post' && typeof rec.text === 'string' && rec.text.includes('pds.7rnx.net'), | |
| 44 | 'the record is the expected app.bsky.feed.post') | |
| 45 | ||
| 46 | // Independent CID check: recompute the record's CID from its value, compare to the | |
| 47 | // CID the MST points at (proves cidForValue matches the on-wire addressing). | |
| 48 | const rec2 = listRecords(car)[0] | |
| 49 | const { cid } = await cidForValue(rec2.value) | |
| 50 | t.ok(cid.equals(rec2.cid), 'recomputed record CID matches the MST pointer') | |
| 51 | }) |
| … | … | @@ -0,0 +1,66 @@ |
| 1 | // Slice 3: commit signing + full CAR origination, verified offline. | |
| 2 | // - sign/verify round-trip + tamper detection (secp256k1) | |
| 3 | // - createRepo -> re-read via slice-1 reader -> byte-exact, records intact | |
| 4 | // - whole-stack compose: mnemonic (identity) -> did:plc -> sign a repo commit -> | |
| 5 | // verify the sig against the DID's verificationMethod. No PLC/relay needed. | |
| 6 | ||
| 7 | import test from 'brittle' | |
| 8 | import * as dagCbor from '@ipld/dag-cbor' | |
| 9 | import { secp256k1 } from '@noble/curves/secp256k1' | |
| 10 | import { readCar, loadCommit, listRecords, verifyRepo, createRepo } from '../lib/repo.js' | |
| 11 | import { unsignedCommit, signCommit, verifyCommit } from '../lib/commit.js' | |
| 12 | import { cidForBytesSync } from '../lib/cid.js' | |
| 13 | // cross-package (dev dep; offline; deriveAtprotoKey needs no native deps): | |
| 14 | import { deriveAtprotoKey } from 'atproto-identity' | |
| 15 | import { decodeDidKey } from 'atproto-identity/lib/did-key.js' | |
| 16 | ||
| 17 | const MNEMONIC = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' | |
| 18 | const DID = 'did:plc:yu4kgcktm7qtrwo5ofyiaf4c' | |
| 19 | const RECORDS = [ | |
| 20 | { collection: 'app.bsky.feed.post', rkey: '3aaa', record: { $type: 'app.bsky.feed.post', text: 'first', createdAt: '2026-07-01T00:00:00.000Z' } }, | |
| 21 | { collection: 'app.bsky.feed.post', rkey: '3bbb', record: { $type: 'app.bsky.feed.post', text: 'second', createdAt: '2026-07-01T00:00:01.000Z' } }, | |
| 22 | { collection: 'app.bsky.actor.profile', rkey: 'self', record: { $type: 'app.bsky.actor.profile', displayName: 'test' } } | |
| 23 | ] | |
| 24 | ||
| 25 | // 1. sign / verify / tamper | |
| 26 | const key = deriveAtprotoKey(MNEMONIC) | |
| 27 | const dataCid = cidForBytesSync(dagCbor.encode('placeholder-mst-root')) // any valid CID for the sign/verify shape | |
| 28 | ||
| 29 | test('sign / verify / tamper (secp256k1)', (t) => { | |
| 30 | const signed = signCommit(unsignedCommit({ did: DID, rev: '3zzz', data: dataCid }), key.privateKey) | |
| 31 | t.ok(signed.sig instanceof Uint8Array && signed.sig.length === 64 && verifyCommit(signed, key.publicKey), | |
| 32 | 'sign -> verify round-trips (secp256k1, 64-byte sig)') | |
| 33 | ||
| 34 | const signed2 = signCommit(unsignedCommit({ did: DID, rev: '3zzz', data: dataCid }), key.privateKey) | |
| 35 | const tampered = { ...signed2, rev: '3zzy' } | |
| 36 | t.ok(!verifyCommit(tampered, key.publicKey), 'tampered commit fails verification') | |
| 37 | }) | |
| 38 | ||
| 39 | // 2. full CAR origination -> re-read with the slice-1 reader | |
| 40 | let repo | |
| 41 | test('createRepo -> re-read via slice-1 reader (byte-exact, records intact)', async (t) => { | |
| 42 | repo = await createRepo({ records: RECORDS, did: DID, rev: '3ccc', privateKey: key.privateKey }) | |
| 43 | const car = await readCar(repo.carBytes) | |
| 44 | const v = await verifyRepo(car) | |
| 45 | const commit = loadCommit(car) | |
| 46 | t.comment(`originated CAR: ${repo.carBytes.length}b, ${v.blocks} blocks, ${v.records} records`) | |
| 47 | t.ok(v.ok && commit.cid.equals(repo.commitCid) && commit.value.did === DID && commit.value.data.equals(repo.dataCid), | |
| 48 | 'createRepo produces a byte-exact, re-readable CAR') | |
| 49 | ||
| 50 | const car2 = await readCar(repo.carBytes) | |
| 51 | const recs = listRecords(car2) | |
| 52 | const keys = recs.map(r => r.key).sort() | |
| 53 | const want = ['app.bsky.actor.profile/self', 'app.bsky.feed.post/3aaa', 'app.bsky.feed.post/3bbb'] | |
| 54 | const post = recs.find(r => r.key === 'app.bsky.feed.post/3aaa') | |
| 55 | t.ok(JSON.stringify(keys) === JSON.stringify(want) && post.value.text === 'first', | |
| 56 | 're-read records match what we put in (keys + text)') | |
| 57 | }) | |
| 58 | ||
| 59 | // 3. whole-stack compose: identity did -> our commit -> verify sig vs the DID key | |
| 60 | test('commit signed with mnemonic key verifies against the did:key', async (t) => { | |
| 61 | const { pubkey, curve } = decodeDidKey(key.didKey) | |
| 62 | const car = await readCar(repo.carBytes) | |
| 63 | const signed = loadCommit(car).value | |
| 64 | t.ok(curve === 'secp256k1' && verifyCommit(signed, pubkey), | |
| 65 | 'commit signed with mnemonic key verifies against the did:key') | |
| 66 | }) |
| … | … | @@ -0,0 +1,58 @@ |
| 1 | // Slice 2: prove the MST WRITE path is byte-exact, against two oracles: | |
| 2 | // A. rebuild the golden repo's MST from its records -> match commit.data + node bytes | |
| 3 | // B. atmst's own root CIDs for a 50-key, 3-height tree (build + delete) | |
| 4 | // The atmst roots were generated by running millipds's reference on stortron with | |
| 5 | // val(key) = cid(dag-cbor(key)); we reproduce that mapping here. | |
| 6 | ||
| 7 | import test from 'brittle' | |
| 8 | import { readFileSync } from 'node:fs' | |
| 9 | import { fileURLToPath } from 'node:url' | |
| 10 | import { dirname, join } from 'node:path' | |
| 11 | import { readCar, loadCommit, listRecords } from '../lib/repo.js' | |
| 12 | import { buildMst, keyHeight } from '../lib/mst.js' | |
| 13 | import { cidForBytesSync } from '../lib/cid.js' | |
| 14 | import * as dagCbor from '@ipld/dag-cbor' | |
| 15 | ||
| 16 | const here = dirname(fileURLToPath(import.meta.url)) | |
| 17 | const carBytes = readFileSync(join(here, 'vectors', 'bridgetest-repo.car')) | |
| 18 | ||
| 19 | // atmst oracle (reference roots), val(k) = cid(dag-cbor(k)): | |
| 20 | const ATMST_KEYS = Array.from({ length: 50 }, (_, i) => `app.test.record/${String(i).padStart(4, '0')}`) | |
| 21 | const ATMST_ROOT = 'bafyreiaxxeqrlo6flqjewuug3locbq7mmf5cmswecbic3xj6h4lyajymta' | |
| 22 | const ATMST_ROOT_AFTER_DELETES = 'bafyreigwzj7pnllj3ggewdj4buwcaxqjhctasgcuwknjwjl7kbeiwlaot4' | |
| 23 | const DELETE_KEYS = ['app.test.record/0000', 'app.test.record/0025', 'app.test.record/0049'] | |
| 24 | const valFor = (k) => cidForBytesSync(dagCbor.encode(k)) | |
| 25 | ||
| 26 | test('A: rebuilt golden MST matches commit.data + byte-identical node', async (t) => { | |
| 27 | const car = await readCar(carBytes) | |
| 28 | const commit = loadCommit(car) | |
| 29 | const records = listRecords(car).map(r => ({ key: r.key, value: r.cid })) | |
| 30 | const builtGolden = buildMst(records) | |
| 31 | t.ok(builtGolden.root.equals(commit.value.data), 'rebuilt golden MST root == commit.data') | |
| 32 | const goldenNode = car.blocks.get(commit.value.data.toString()) | |
| 33 | const ourNode = builtGolden.ns.blocks.get(builtGolden.root.toString()) | |
| 34 | t.ok(Buffer.from(ourNode.bytes).equals(Buffer.from(goldenNode.bytes)), | |
| 35 | 'rebuilt MST node bytes are byte-identical to the golden block') | |
| 36 | }) | |
| 37 | ||
| 38 | test('B: 50-key multi-height build matches atmst oracle (+ order independence)', (t) => { | |
| 39 | const built = buildMst(ATMST_KEYS.map(k => ({ key: k, value: valFor(k) }))) | |
| 40 | const hist = {} | |
| 41 | for (const k of ATMST_KEYS) { const h = keyHeight(k); hist[h] = (hist[h] || 0) + 1 } | |
| 42 | t.comment('height histogram: ' + JSON.stringify(hist)) | |
| 43 | t.is(built.root.toString(), ATMST_ROOT, '50-key build root matches atmst oracle') | |
| 44 | ||
| 45 | const shuffled = buildMst(ATMST_KEYS.slice().reverse().map(k => ({ key: k, value: valFor(k) }))) | |
| 46 | t.is(shuffled.root.toString(), ATMST_ROOT, 'root is insertion-order independent') | |
| 47 | }) | |
| 48 | ||
| 49 | test('D: delete path matches atmst oracle', (t) => { | |
| 50 | const { root, wrangler } = buildMst(ATMST_KEYS.map(k => ({ key: k, value: valFor(k) }))) | |
| 51 | let r = root | |
| 52 | for (const k of DELETE_KEYS) r = wrangler.delRecord(r, k) | |
| 53 | t.is(r.toString(), ATMST_ROOT_AFTER_DELETES, 'root after 3 deletes matches atmst oracle') | |
| 54 | }) | |
| 55 | ||
| 56 | test('E: key heights span >1 level (splits exercised)', (t) => { | |
| 57 | t.ok(new Set(ATMST_KEYS.map(keyHeight)).size >= 2, 'height histogram spans >1 level') | |
| 58 | }) |