aboutsummaryrefslogtreecommitdiffstats
path: root/tools/ark/send-test.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-07-03 14:46:36 -0700
committerAdam Soltys <[email protected]>2026-07-03 14:46:36 -0700
commit5b7f8ba40cbf5c8189147b6a37aadc1b230ac4e9 (patch)
treec2de49506f15aad217e5df02e5ec03b19ce1491c /tools/ark/send-test.js
parent8029e4bd43cc425d31f50286d7ae4e94b4206ace (diff)
downloadcoinosv3-5b7f8ba40cbf5c8189147b6a37aadc1b230ac4e9.tar.gz
coinosv3-5b7f8ba40cbf5c8189147b6a37aadc1b230ac4e9.zip
Ark: native-JS protocol client + persistent ArkManager
Speaks Second's bark/captaind Ark protocol directly from the browser — gRPC-web over fetch with hand-rolled protobuf, no wasm, no new deps (crypto via the existing @scure/@noble stack incl. btc-signer's BIP-327 musig2 module). src/ark: - proto.js transport, ark address codec, mailbox blinding/auth, VTXO ProtocolEncoding v2 decode - send.js arkoor send: checkpoint+arkoor txs, BIP-341 sighashes, MuSig2 cosign ceremony, signed-VTXO assembly - board.js onchain -> ark: funding taproot, exit-tx cosign - refresh.js delegated round (hArk): participation, tx-chain reconstruction, hash-locked leaf cosign, forfeit swap - validate.js receive-side verification: chain must land on the vtxo point, anchor checked onchain, every transition sig verified - manager.js persistent wallet layer: storage-checkpointed step-machine actions (send/board/refresh) that survive crashes, mailbox sync with validation, multi-input consolidation refresh tools/ark: end-to-end regtest tests (receive, send, board, refresh, manager incl. crash-mid-send + resume), all passing against captaind with the bark CLI as counterparty. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QFAcD4V284DvYZZHP7Rxxy
Diffstat (limited to 'tools/ark/send-test.js')
-rw-r--r--tools/ark/send-test.js94
1 files changed, 94 insertions, 0 deletions
diff --git a/tools/ark/send-test.js b/tools/ark/send-test.js
new file mode 100644
index 0000000..181f877
--- /dev/null
+++ b/tools/ark/send-test.js
@@ -0,0 +1,94 @@
+// Regtest end-to-end test for the native-JS arkoor SEND path (MuSig2 cosign).
+//
+// Spends the VTXO received in receive-test.js: pays alice (bark CLI) 4000 sat
+// with change back to a fresh key of ours, entirely from JS.
+//
+// Usage: bun tools/ark/send-test.js (run receive-test.js first to get funded)
+
+import { execSync } from 'node:child_process';
+import { mnemonicToSeedSync } from '@scure/bip39';
+import { HDKey } from '@scure/bip32';
+import { hex } from '@scure/base';
+import { secp256k1 } from '@noble/curves/secp256k1';
+
+import { getArkInfo, decodeAddress, readMailbox, decodeVtxo } from '../../src/ark/proto.js';
+import {
+ buildArkoorSend, cosignWithServer, buildSignedVtxoBytes,
+ registerVtxoTransactions, postArkoorMessage,
+} from '../../src/ark/send.js';
+
+const ARK = process.env.ARK_URL || 'http://127.0.0.1:3535';
+const BARK = `${process.env.HOME}/bark/target/debug/bark`;
+const ALICE = `${BARK} --datadir ${process.env.HOME}/ark-regtest/alice`;
+const SEND_SAT = 4000;
+
+const MNEMONIC = 'rug rebuild abstract kiwi rifle vast food robust rifle spirit dilemma stumble';
+const seed = mnemonicToSeedSync(MNEMONIC);
+const master = HDKey.fromMasterSeed(seed);
+const kp = (path) => {
+ const k = master.derive(path);
+ return { privkey: k.privateKey, pubkey: secp256k1.getPublicKey(k.privateKey, true) };
+};
+const keys = { vtxo: kp("m/350'/0'/0'"), mailbox: kp("m/350'/1'/0'"), change: kp("m/350'/0'/1'") };
+
+const info = await getArkInfo(ARK);
+const serverPubkey = hex.decode(info.serverPubkey);
+
+// --- find our latest vtxo from the mailbox ---
+const { messages } = await readMailbox(ARK, keys.mailbox);
+const vtxos = messages.filter((m) => m.kind === 'arkoor').flatMap((m) => m.vtxos)
+ .filter((v) => v.policy.userPubkey === hex.encode(keys.vtxo.pubkey));
+if (!vtxos.length) { console.error('no vtxos in mailbox — run receive-test.js first'); process.exit(1); }
+const input = vtxos[vtxos.length - 1];
+console.log(`input vtxo: ${input.id} (${input.amountSat} sat, genesis depth ${input.genesis.length})`);
+
+// --- alice's address -> destination pubkey + her blinded mailbox id ---
+const aliceAddr = execSync(`${ALICE} -q address`).toString().trim();
+const alice = decodeAddress(aliceAddr);
+console.log(`alice pubkey: ${alice.userPubkey}`);
+const aliceBlindedId = hex.decode(alice.delivery.find((d) => d.type === 1).data);
+
+const outputs = [
+ { amountSat: SEND_SAT, userPubkey: hex.decode(alice.userPubkey) },
+ { amountSat: input.amountSat - SEND_SAT, userPubkey: keys.change.pubkey },
+];
+
+const aliceBalanceBefore = JSON.parse(execSync(`${ALICE} -q balance`).toString()).spendable_sat;
+
+// --- 1. pre-register our input chain ---
+await registerVtxoTransactions(ARK, [input._raw.bytes]);
+console.log('input chain registered with server');
+
+// --- 2. build txs + sighashes ---
+const build = buildArkoorSend({ input, outputs, serverPubkey, vtxoKeys: keys.vtxo });
+console.log(`checkpoint txid: ${hex.encode(build.checkpointTxid.slice().reverse())}`);
+console.log(`sighashes: ${build.sighashes.length} (1 checkpoint + ${outputs.length} arkoor)`);
+
+// --- 3. MuSig2 cosign ceremony with the ASP ---
+const finalSigs = await cosignWithServer(ARK, build, { input, outputs, vtxoKeys: keys.vtxo, serverPubkey });
+console.log(`✓ ${finalSigs.length} MuSig2 signatures combined and verified against taproot output keys`);
+
+// --- 4. assemble signed vtxos, round-trip check, register, deliver ---
+const destBytes = buildSignedVtxoBytes({ input, outputs, build, finalSigs, serverPubkey, idx: 0 });
+const changeBytes = buildSignedVtxoBytes({ input, outputs, build, finalSigs, serverPubkey, idx: 1 });
+const dest = decodeVtxo(destBytes); // local round-trip sanity check
+console.log(`destination vtxo: ${dest.id} (${dest.amountSat} sat, depth ${dest.genesis.length})`);
+const change = decodeVtxo(changeBytes);
+console.log(`change vtxo: ${change.id} (${change.amountSat} sat) -> our change key`);
+
+await registerVtxoTransactions(ARK, [destBytes, changeBytes]);
+console.log('signed vtxos registered with server');
+
+await postArkoorMessage(ARK, aliceBlindedId, [destBytes]);
+console.log('posted to alice\'s mailbox');
+
+// --- 5. verify alice received it (balance triggers a wallet sync) ---
+const aliceBalanceAfter = JSON.parse(execSync(`${ALICE} -q balance`).toString()).spendable_sat;
+console.log(`\nalice balance: ${aliceBalanceBefore} -> ${aliceBalanceAfter} sat`);
+
+if (aliceBalanceAfter === aliceBalanceBefore + SEND_SAT) {
+ console.log(`\n✅ SUCCESS: native-JS wallet sent ${SEND_SAT} sat over Ark via MuSig2 cosign — bark CLI accepted the vtxo`);
+} else {
+ console.log('\n❌ alice balance did not increase as expected');
+ process.exit(1);
+}