From 15e0b236e7ff2cafcb439c891c790deea8ec16a7 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 5 Jul 2026 20:39:02 -0700 Subject: Unilateral exit: put vtxos on-chain without the server The trustless backstop behind the Ark card's promise. The vtxo's genesis chain reconstructs to fully-signed txs (keyspend sigs for cosigned/arkoor transitions, preimage script-path for hark leaves); they are zero-fee v3 with P2A anchors, so each hop broadcasts as a [parent, CPFP child] package via esplora POST /txs/package, one confirmed hop at a time (TRUC allows a single unconfirmed parent). After the vtxo tx confirms and the CSV exit delta passes, the claim spends the delayed_sign leaf with only the user key into the wallet's own receive address. Settings gains 'Exit on-chain without the server' with live per-exit status lines; the claim tx is labeled 'Ark exit' in History. Probing uses /tx/:txid (definitive 404), never /tx/:txid/status, which answers confirmed:false for unknown txids. Also: a fresh visitor opening an ark gift link now adopts the gift's network automatically instead of being sent to Settings. Co-Authored-By: Claude Fable 5 --- src/ark/send.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'src/ark/send.js') diff --git a/src/ark/send.js b/src/ark/send.js index 39cd1fb..a959b22 100644 --- a/src/ark/send.js +++ b/src/ark/send.js @@ -84,14 +84,19 @@ export function taprootOneLeaf(userPub, serverPub, leafScript) { // lift internal x-only, add tweak*G const internalPoint = P.fromHex(concatBytes(Uint8Array.of(0x02), internalXOnly)); const outputPoint = internalPoint.add(P.BASE.multiply(BigInt('0x' + hex.encode(tapTweak)))); - const outputXOnly = outputPoint.toRawBytes(true).slice(1); + const outputCompressed = outputPoint.toRawBytes(true); + const outputXOnly = outputCompressed.slice(1); const scriptPubKey = concatBytes(hex.decode('5120'), outputXOnly); - return { sortedKeys, internalXOnly, merkleRoot, tapTweak, outputXOnly, scriptPubKey }; + const outputParity = outputCompressed[0] === 0x03 ? 1 : 0; // for script-path control blocks + return { sortedKeys, internalXOnly, merkleRoot, tapTweak, outputXOnly, outputParity, scriptPubKey }; } -// PubkeyVtxoPolicy: keyspend musig(user,server); leaf = delayed_sign(exit_delta, user) -export const pubkeyPolicyTaproot = (userPub, serverPub, exitDelta) => - taprootOneLeaf(userPub, serverPub, delayedSignScript(exitDelta, xonly(userPub))); +// PubkeyVtxoPolicy: keyspend musig(user,server); leaf = delayed_sign(exit_delta, user). +// leafScript is returned so a unilateral exit can claim through it after the CSV. +export const pubkeyPolicyTaproot = (userPub, serverPub, exitDelta) => { + const leafScript = delayedSignScript(exitDelta, xonly(userPub)); + return { ...taprootOneLeaf(userPub, serverPub, leafScript), leafScript }; +}; // CheckpointVtxoPolicy: keyspend musig(user,server); leaf = timelock_sign(expiry, server) export const checkpointPolicyTaproot = (userPub, serverPub, expiryHeight) => -- cgit v1.2.3