A new lineage/v0 op binds a node's keys in one inscription whose parent is the node itself: its Taproot and SegWit addresses, its npub and its Ethereum address. Every key signs the same statement the way its own wallets sign a message: BIP-322 simple signatures for the Bitcoin addresses, a NIP-01 note (kind 1, created_at 0) for the npub, EIP-191 for Ethereum. The reducer checks every proof, binds a standing keyring's keys to its node, lets the newest keyring replace the older, and retires keyrings at a graduation (the keys change with the new root) and at a close. Every account gets an Ethereum key at m/44'/60'/account'/0/0; an org's, at account 0, is the key tree page's legal-control key and matches its published vector. Keyring is the ninth verb; Inspect shows each node's keyring and the statement its keys signed, and Reveal its Ethereum private key. The sample publishes three keyrings, and lineage's retires when it graduates. Tests pin BIP-322's published vectors and values cross-checked with bip322-js, ethers and nostr-tools. The regtest rehearsal, rerun with five keyrings, confirmed 89 transactions on Bitcoin Core; ord's 26 events fold to the same trees and keyrings, every proof checked from ord's copy, and the fixture is re-recorded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H4upF5LVwmPDDqrhdUfnXh
85 lines
6.5 KiB
JavaScript
85 lines
6.5 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import { Script, Tap, Tx } from '@cmdcode/tapscript';
|
|
import { LocalChain, tapscriptValid } from '../src/adapters/chain.js';
|
|
import { TestWallet } from '../src/adapters/wallet.js';
|
|
import { InscriptionAdapter, POSTAGE, FEE, envelope } from '../src/adapters/inscription.js';
|
|
import { MetadataIndex } from '../src/adapters/metadata-index.js';
|
|
import { getVin, getVout } from '../src/upstream/primitives.js';
|
|
import { census, rows } from '../src/orgchain/lineage.js';
|
|
import { ordClient, ordEvents, ordState } from '../src/indexer/ord.js';
|
|
import { compareWithLab } from '../src/indexer/compare.js';
|
|
|
|
// What ord answered after tools/regtest/rehearse.mjs, and the lab's public chain (tools/regtest/record.mjs).
|
|
const fixture = JSON.parse(readFileSync(new URL('./fixtures/regtest-rehearsal.json', import.meta.url), 'utf8'));
|
|
const replay = async url => { const hit = fixture.ord[new URL(url).pathname]; return new Response(hit?.body ?? '', { status: hit?.status ?? 404 }); };
|
|
const draw = (s, root) => rows(s, root).map(r => (r.type === 'group' ? `${r.guide}${r.label}` : `${r.guide}${r.path} ${r.node.name}${r.away ? ' · graduated' : ''}`));
|
|
const NUMS = ['OP_1', '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0'];
|
|
const hex = script => Script.encode(script, false).hex;
|
|
|
|
test('real ord indexed the regtest rehearsal, and the same reducer folds it into the trees the lab drew', async () => {
|
|
const events = await ordEvents(ordClient('http://ord.test', replay), { from: fixture.from });
|
|
assert.equal(events.length, 26);
|
|
assert.ok(events.every(e => e.protocol === 'lineage/v0' && e.parents <= 1 && /^[0-9a-f]{64}:\d+$/.test(e.location)));
|
|
const s = ordState(events), id = name => [...s.nodes.values()].find(n => n.name === name).id;
|
|
assert.ok(s.log.every(e => e.ok));
|
|
assert.deepEqual(s.roots.map(r => r.name), ['yolopreneur', 'lineage', 'acme']);
|
|
assert.deepEqual(draw(s, id('yolopreneur')), ['● yolopreneur', '└─ orgs', " ├─ 0' well-known", ' │ └─ orgs', " │ ├─ 0' mailr",
|
|
" │ └─ 1' lineage · graduated", " └─ 1' time", ' ├─ agents', " │ ├─ 0' luna", " │ ├─ 1' sol", " │ └─ 2' astra",
|
|
' └─ orgs', " ├─ 0' docscope", " ├─ 1' dollhouse", " ├─ 2' doodleloop", " └─ 3' hearthframe"]);
|
|
assert.deepEqual(census(s, id('lineage')), { orgs: 0, agents: 0, belts: 0, decisions: 1, closed: 0 });
|
|
assert.deepEqual(s.nodes.get(id('scout')).badges.map(b => [b.skill, b.belt, s.nodes.get(b.by).name]), [['listen', 'white', 'well-known']]);
|
|
assert.deepEqual([...s.items.values()].map(i => [i.name, s.nodes.get(i.maker).name]), [['launch-kit', 'sol']]);
|
|
// Five keyrings, their proofs checked from ord's metadata (two 520-byte pushes each): lineage's first retired when it
|
|
// graduated, and the one it published a block later stands. Four keyrings, four keys each, are bound.
|
|
assert.deepEqual(s.log.filter(e => e.op === 'keyring').map(e => s.nodes.get(e.node).name), ['yolopreneur', 'sol', 'lineage', 'lineage', 'scout']);
|
|
assert.deepEqual([...s.nodes.values()].filter(n => n.keyring).map(n => [n.name, n.keyring.retired]).sort(),
|
|
[['lineage', null], ['scout', null], ['sol', null], ['yolopreneur', null]]);
|
|
assert.equal(s.bound.size, 16);
|
|
// Byte for byte: the same events in the same beats, the same metadata and parents, every inscription in the same
|
|
// output (the launch kit where lineage's deal put it). Only the miner's order within blocks differs.
|
|
const { problems, reordered } = compareWithLab({ chain: new LocalChain(fixture.chain) }, events);
|
|
assert.deepEqual(problems, []);
|
|
assert.equal(reordered, 9);
|
|
});
|
|
|
|
test('a reveal commits to its exact script (BIP-341), and the lab still loads reveals made the old way', () => {
|
|
const chain = new LocalChain(), w = TestWallet.create();
|
|
chain.mine(w.script);
|
|
new InscriptionAdapter(chain, w).inscribe('found x', null, { op: 'found', name: 'x' }, { metaprotocol: 'lineage/v0' });
|
|
const prevout = raw => { const out = Tx.decode(raw).vout[0]; return { value: Number(out.value), scriptPubKey: out.scriptPubKey }; };
|
|
const reveal = Tx.decode(chain.mempool[1]);
|
|
reveal.vin[0].prevout = prevout(chain.mempool[0]);
|
|
assert.equal(tapscriptValid(reveal, 0), true);
|
|
// Before v1.9 the leaf came from tapscript's encodeScript, which re-encodes the hex script; real Bitcoin rejected that.
|
|
const old = new LocalChain();
|
|
old.mine(w.script);
|
|
const [coin] = old.coins(w.script), script = envelope(w.pubkey, 'found y', null, { op: 'found', name: 'y' }, { metaprotocol: 'lineage/v0' });
|
|
const leaf = Tap.encodeScript(script), [tapkey, control] = Tap.getPubKey(w.pubkey, { target: leaf });
|
|
const commit = Tx.create({ vin: [getVin(coin.txid, coin.vout, coin.value, coin.scriptPubKey)],
|
|
vout: [getVout(POSTAGE + FEE, ['OP_1', tapkey]), getVout(coin.value - POSTAGE - 2 * FEE, w.script)] });
|
|
commit.vin[0].witness = [w.sign(commit)];
|
|
const legacy = Tx.create({ vin: [getVin(Tx.util.getTxid(commit), 0, POSTAGE + FEE, ['OP_1', tapkey])], vout: [getVout(POSTAGE, w.script)] });
|
|
legacy.vin[0].witness = [w.sign(legacy, leaf, 0), Script.encode(script, false).hex, control];
|
|
const raws = [Tx.encode(commit).hex, Tx.encode(legacy).hex], check = Tx.decode(raws[1]);
|
|
check.vin[0].prevout = prevout(raws[0]);
|
|
assert.equal(tapscriptValid(check, 0), false);
|
|
old.submitPackage(raws); // still accepted, so labs saved before v1.9 load
|
|
assert.equal(new MetadataIndex(old).events.length, 1);
|
|
});
|
|
|
|
test('a chain can start from coins imported from a real chain, and keeps them through a rebuild', () => {
|
|
const chain = new LocalChain(), w = TestWallet.create(), coin = { txid: 'ab'.repeat(32), vout: 1, value: 2e8, script: hex(w.script) };
|
|
chain.importCoin(coin);
|
|
assert.throws(() => chain.importCoin(coin), /already in this chain/);
|
|
assert.throws(() => chain.importCoin({ ...coin, vout: 2, value: 51e8 }), /Invalid imported coin/);
|
|
const id = new InscriptionAdapter(chain, w).inscribe('found x', null, { op: 'found', name: 'x' }, { metaprotocol: 'lineage/v0' });
|
|
chain.mine(NUMS);
|
|
const copy = new LocalChain(chain.snapshot());
|
|
assert.deepEqual(copy.imports, [coin]);
|
|
assert.deepEqual(copy.imported(coin.txid, 1), { value: 2e8, scriptPubKey: coin.script });
|
|
assert.deepEqual(new MetadataIndex(copy).events.map(e => [e.id, e.status]), [[id, 'confirmed']]);
|
|
assert.equal(copy.coins(w.script).length, 2); // the reveal's postage and the commit's change
|
|
});
|