diff options
| author | Adam Soltys <[email protected]> | 2026-07-05 20:39:02 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-07-05 20:39:02 -0700 |
| commit | 15e0b236e7ff2cafcb439c891c790deea8ec16a7 (patch) | |
| tree | 960057559e60b4b2e624a7de9e4ac35b1ecb81ad /src/ark/send.js | |
| parent | 0408609fc68fa71ff705ef7a241880efefddcc3f (diff) | |
| download | coinosv3-15e0b236e7ff2cafcb439c891c790deea8ec16a7.tar.gz coinosv3-15e0b236e7ff2cafcb439c891c790deea8ec16a7.zip | |
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 <[email protected]>
Diffstat (limited to 'src/ark/send.js')
| -rw-r--r-- | src/ark/send.js | 15 |
1 files changed, 10 insertions, 5 deletions
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) => |
