From 222391a31361e42db713d1737d5a6e15cf8d5ed2 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 14 Aug 2026 20:12:03 -0700 Subject: A send covers itself from as many coins as it needs: one atomic package cosign spends them all, the recipient receives several vtxos, and "consolidate with refresh() first" retires from the vocabulary Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv --- src/ark/send.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'src/ark/send.js') diff --git a/src/ark/send.js b/src/ark/send.js index 0e63c7c..f0e471a 100644 --- a/src/ark/send.js +++ b/src/ark/send.js @@ -461,13 +461,25 @@ export function combineCosign({ build, nonces, serverResp, vtxoKeys, serverPubke // full MuSig2 ceremony over the ASP's RequestArkoorCosign export async function cosignWithServer(ark, build, { input, vtxoKeys, serverPubkey, preimage }) { - const nonces = genUserNonces(build, vtxoKeys); + const [sigs] = await cosignPackageWithServer(ark, [{ build, input, vtxoKeys, preimage }], serverPubkey); + return sigs; +} + +// Same ceremony for a PACKAGE of arkoor parts (one per input vtxo) in a single +// atomic request — the server cosigns all or none, so a multi-input send can't +// half-spend. Returns one finalSigs array per part, in order. +export async function cosignPackageWithServer(ark, parts, serverPubkey) { + const noncesList = parts.map((p) => genUserNonces(p.build, p.vtxoKeys)); const req = pbWriter(); - req.bytesField(1, cosignPartBytes({ build, input, vtxoKeys, nonces, preimage })); + parts.forEach((p, i) => req.bytesField(1, cosignPartBytes({ + build: p.build, input: p.input, vtxoKeys: p.vtxoKeys, nonces: noncesList[i], preimage: p.preimage, + }))); const respBytes = await grpcCall(ark, 'bark_server.ArkService/RequestArkoorCosign', req.finish()); - const [serverResp] = parsePackageCosignResponse(respBytes); - if (!serverResp) throw new Error('empty cosign response'); - return combineCosign({ build, nonces, serverResp, vtxoKeys, serverPubkey }); + const resps = parsePackageCosignResponse(respBytes); + if (resps.length !== parts.length) throw new Error('bad cosign response count'); + return parts.map((p, i) => combineCosign({ + build: p.build, nonces: noncesList[i], serverResp: resps[i], vtxoKeys: p.vtxoKeys, serverPubkey, + })); } // assemble the final signed Vtxo bytes for output `idx`, where idx -- cgit v1.2.3