Adaptor Signatures for Atomic Cross-Chain Swaps: A Design Note on Taproot and Schnorr-based Scriptless Scripts
A technical note on using adaptor signatures — sometimes called scriptless scripts — to construct atomic cross-chain swaps between Taproot-based protocols. Nothing here is novel; the underlying cryptography has been well-understood since at least the work of Poelstra (2017) and the subsequent MuSig2 proposals. What we attempt is a reasonably complete engineering specification that others might find useful as a starting point.
0. Scope and Motivation
Atomic cross-chain swaps have historically relied on Hash-Time-Locked Contracts (HTLCs), which require both parties to publish on-chain scripts. This is functional but aesthetically displeasing: the scripts are visible on-chain, they consume block space, and the hash-lock mechanism creates a somewhat awkward timing asymmetry between the two chains.
Adaptor signatures offer an alternative. The idea, in essence, is to embed a secret scalar into a Schnorr signature such that publishing the completed signature on one chain inevitably reveals the secret, allowing the counterparty to complete the signature on the other chain. No scripts, no hash locks, no on-chain footprint beyond an ordinary Taproot key-path spend. It is, we think, rather elegant.
This note describes how to use adaptor signatures in conjunction with MuSig2 aggregated keys and Taproot to build atomic swaps between a Bitcoin-like chain (using BIP-340/341) and a DAG-based protocol that also mandates Taproot-only spending. We specify a partially-signed transaction container that extends the PSBT model, and a WASM SDK interface for client-side signature assembly.
We should be upfront about what this document is not: it is not a formal security proof, it is not a BIP proposal, and we make no claims of completeness. It is an engineering specification that reflects our current understanding. We would be grateful for corrections.
1. Notation and Primitives
We assume the reader is familiar with elliptic curve arithmetic on secp256k1 and the basics of Schnorr signatures as defined in BIP-340. For completeness, we state the relevant notation:
-
Schnorr signature: A pair
(R, s)whereR = k · Gfor a secret noncek, the challenge ise = H(R_x ‖ P_x ‖ m), and the signature scalar iss = k + e · x (mod n). Verification checkss · G = R + e · P. -
MuSig2: A two-round aggregatable multi-signature scheme. Two public keys
P_AandP_Bare aggregated intoP = μ(P_A, P_B)using a coefficient function that prevents key-cancellation attacks. Signers exchange nonce commitments before revealing nonces, then compute partial signatures that sum to a single valid Schnorr signature underP. -
Adaptor signature: Given an “adaptor point”
T = t · Gfor some secret scalart, the signer computes a pre-signatures* = k + e · x + t (mod n). Observe thats* · G = R + e · P + T. The pre-signature is not a valid Schnorr signature by itself, but oncetbecomes known, the completed signatures = s* - tverifies normally. Crucially, anyone who observes boths*(the pre-signature) ands(the completed signature published on-chain) can extractt = s* - s. -
Taproot (BIP-341): A SpendInfo consists of an internal key
Pand optional script paths (tapleaves). The output key isQ = P + H(P ‖ scripts) · G. Key-path spends appear as ordinary Schnorr signatures; script-path spends reveal the chosen tapleaf.
2. Atomic Swap Protocol
We describe the protocol from the perspective of two parties: A (who wishes to swap BTC for the DAG-chain’s native token) and B (who wishes to swap in the opposite direction).
2.1 Invariant
The entire protocol rests on a single invariant: the same adaptor point T = t · G is used in the pre-signatures on both chains. Party A generates t and keeps it secret. When A completes and broadcasts a signature on one chain, the scalar t is revealed (because t = s* - s), allowing B to complete the signature on the other chain. Atomicity follows from the algebra.
2.2 Locking Transactions (Setup)
Each party locks funds into a 2-of-2 MuSig2 Taproot output. The output key on each chain is the aggregated key of A and B, with a script-path refund as a safety net.
BTC side (A locks BTC):
P2TR(internal_key = P_btc,
tapleaf: <locktime> CLTV, DROP, <P_A_btc> CHECKSIG)
A can refund via the script path after the timeout expires.
DAG-chain side (B locks native tokens):
P2TR(internal_key = P_ton,
tapleaf: <Δ> CSV, DROP, <P_B_ton> CHECKSIG)
B can refund via the script path after the relative locktime expires.
We note that the timeout values must be chosen with some care. The BTC-side CLTV timeout should be substantially longer than the DAG-chain-side CSV timeout, to ensure B has time to extract t and claim the BTC before A’s refund becomes available. Getting this wrong would allow one party to claim both sides, which would rather defeat the purpose.
2.3 Session (Pre-signature Exchange)
-
Key exchange: A and B exchange public keys. They compute aggregated keys
P_btc = μ(P_A_btc, P_B_btc)andP_ton = μ(P_A_ton, P_B_ton)for each chain. -
Adaptor point generation: A generates a random scalar
tand computesT = t · G. A may optionally provide a proof-of-knowledge oft(a Schnorr signature underT) to assure B thatTwas not chosen adversarially. -
Nonce exchange: Both parties engage in the MuSig2 nonce commitment-reveal protocol for both chains, obtaining aggregated nonces
R_btcandR_ton. -
Pre-signature exchange:
- A computes the challenge
e_btcfor the BTC transaction and produces the pre-signatures*_btc = k_A_btc + e_btc · x_A_btc + t (mod n). A sends(T, R_btc, s*_btc)to B. - B computes the challenge
e_tonfor the DAG-chain transaction and produces the pre-signatures*_ton = k_B_ton + e_ton · x_B_ton + t (mod n). B sends(T, R_ton, s*_ton)to A.
Wait — this is not quite right. Since both chains use MuSig2, each pre-signature is a partial adaptor signature. The full pre-signature is the sum of both parties’ partial contributions. We apologise for the simplification; the precise partial signing mechanics follow the MuSig2 protocol, with each party adding their share of
t(in practice, only one party addst, sincetneed only be embedded once per chain). We elide the details here for clarity of exposition, but a correct implementation must follow the MuSig2 signing protocol faithfully. - A computes the challenge
2.4 Execution
-
A claims on the DAG chain: A computes
s_ton = s*_ton - t(completing the adaptor) and co-signs the DAG-chain transaction via MuSig2 key-path. A broadcasts. -
B extracts
t: B observes the completed signature on the DAG chain. Since B knowss*_ton(the pre-signature), B computest = s*_ton - s_ton. This is the crucial extraction step. -
B claims on BTC: B uses
tto complete the BTC-side pre-signature:s_btc = s*_btc - t. B co-signs and broadcasts the BTC transaction. -
Refund paths: If A fails to broadcast within the DAG-chain’s CSV window, B claims a refund. If B fails to broadcast on BTC before the CLTV expires, A claims a refund. The timeout asymmetry ensures that at least one party always has a recourse path.
3. Partially Signed Transaction Container
To coordinate the above protocol between wallet software, node backends, and potentially hardware signing devices, we require a structured container for partially-signed transactions. We extend the PSBTv2 format (BIP-370) to accommodate Taproot-only semantics and adaptor signature fields.
We call this extension PSTT. It follows the same key-value serialisation model as PSBT, with namespaces for global, per-input, and per-output fields.
3.1 Global Fields
| Key | Type | Description |
|---|---|---|
PSTT_GLOBAL_VERSION | u32 | Container version, set to 2 |
PSTT_GLOBAL_TX_VERSION | u32 | Transaction version per consensus |
PSTT_GLOBAL_LOCK | u64 | Optional absolute lock value |
PSTT_GLOBAL_NETWORK_ID | u8 | Mainnet or testnet |
PSTT_GLOBAL_SWAP_CONTEXT | struct | Session identifier, party role, domain separation tag |
3.2 Input Fields
| Key | Type | Description |
|---|---|---|
PSTT_IN_PREVOUT | 32 bytes | Previous transaction hash |
PSTT_IN_SEQUENCE | u32 | Sequence number (for CSV) |
PSTT_IN_TAP_KEY_ADAPTOR | struct | Adaptor pre-signature data (optional) |
PSTT_IN_TAP_KEY_SIG | 64 bytes | Completed signature |
The PSTT_IN_TAP_KEY_ADAPTOR field is the critical addition. When present, it contains:
{
"R_xonly": "9f1d...32bytes",
"s_star": "0b7a...32bytes",
"T_point": "027acb...33bytes",
"flags": "bitfield"
}
All adaptor-related fields are optional. A parser that does not understand them should simply ignore them, preserving backward compatibility with standard non-swap transactions.
4. WASM SDK Interface
We provide a WASM SDK for client-side cryptographic operations. The design principle is that the SDK handles only pure computation — signature generation, verification, witness assembly, and container encoding. It does not handle network communication, fee estimation, or transaction broadcasting. These responsibilities remain with the host environment (the wallet backend).
This separation ensures that private keys and nonces never leave the client’s memory space, and that the SDK’s attack surface is limited to its cryptographic interfaces.
4.1 Core Functions
| Function | Purpose |
|---|---|
musig2AggregateKeys(pubs) | Compute the MuSig2 aggregated public key |
nonceCommit(secret) | Produce a nonce commitment for the first round |
makeTapKeyAdaptor(sec, msg, T) | Generate an adaptor pre-signature |
verifyTapKeyAdaptor(R, s_star, P, T, msg) | Verify a pre-signature against the adaptor point |
completeAdaptor(s_star, t) | Extract the completed signature from the pre-signature |
extractSecret(s_star, s) | Recover the secret scalar t |
4.2 Security Considerations for the SDK
-
Nonce generation must use a cryptographically secure random number generator. On the web, this means
crypto.getRandomValues; in Node.js,node:crypto. We cannot stress this enough: nonce reuse in Schnorr signatures leads directly to private key recovery. This is not a theoretical concern. -
Zeroisation: All intermediate values (private keys, nonces, partial signatures) must be zeroised from memory after use. Rust’s
Zeroizecrate or equivalent should be used in the SDK’s native core. -
Domain separation: The challenge hash must include a domain tag specific to the protocol and chain. This prevents cross-protocol replay attacks, which are surprisingly easy to introduce if one is not careful.
-
Proof-of-knowledge of
t: We strongly recommend that the party generatingTalso provides a Schnorr proof-of-knowledge of the discrete logarithmt. Without this, a malicious party could constructTas a linear combination of known points in a way that biases the extraction. The cost of this check is negligible; the cost of omitting it could be severe.
5. Security Remarks
We collect here a number of security considerations that implementors would do well to keep in mind. This list is almost certainly incomplete.
Nonce reuse is catastrophic. If a signer ever uses the same nonce k for two different messages, the private key can be recovered algebraically. This is the same class of vulnerability that affected the PlayStation 3 and numerous Bitcoin wallets. The MuSig2 commitment-reveal protocol is specifically designed to prevent nonce reuse between co-signers, but each signer must still generate fresh nonces internally.
Timeout asymmetry matters. As noted above, the refund timeout on the BTC side must be strictly longer than the CSV timeout on the DAG-chain side. If they are equal, or if the DAG-chain timeout is longer, there exists a window in which one party has claimed both sides. A reasonable rule of thumb is to set the BTC CLTV timeout at 2-3 times the DAG-chain CSV timeout.
Mempole contention. If the swap involves large amounts, the completed transaction may attract front-running in the mempool. This is not specific to adaptor signatures, but the clean key-path spend makes the transaction indistinguishable from any other transfer, which is both a privacy benefit and a monitoring challenge.
Reorg risk. If the DAG chain reorganises after A’s claim transaction is included but before B has extracted t and broadcast on BTC, the protocol’s atomicity guarantee may be compromised. Implementations should wait for sufficient confirmations before considering a step irrevocable. The number of confirmations required depends on the chain’s security model.
6. Testing Strategy
We recommend the following test coverage, at minimum:
-
Unit tests: Adaptor signature generation, verification, completion, and secret extraction. MuSig2 key aggregation, nonce commitment, partial signing. Container serialisation and deserialisation round-trips.
-
Integration tests: End-to-end swap execution between regtest instances of both chains. Test the success path (A claims, B extracts and claims), both refund paths (A refunds, B refunds), and the timeout race conditions.
-
Adversarial tests: Attempt to claim both sides, attempt to reuse nonces, attempt to supply a malicious adaptor point without proof-of-knowledge. These tests should fail, obviously, but it is important to verify that they do.
Concluding Remarks
Adaptor signatures represent, in our view, one of the more underappreciated constructions in applied cryptography. The ability to atomically link the settlement of two independent transactions without any on-chain scripting — purely through the algebraic structure of Schnorr signatures — is quite remarkable.
What we have described here is a practical engineering specification for deploying this construction in a cross-chain context. We have tried to be honest about the sharp edges: nonce management, timeout calibration, reorg risk, and the subtleties of partial adaptor signatures under MuSig2. These are not insurmountable problems, but they are problems that reward careful thinking.
We share this in the hope that it may be useful to others working on similar problems, and we welcome feedback — particularly from anyone who has deployed adaptor-based swaps in production and lived to tell the tale.