Skip to content
Writing
16 August 2025 · 10 min read schnorr taproot cross-chain

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) where R = k · G for a secret nonce k, the challenge is e = H(R_x ‖ P_x ‖ m), and the signature scalar is s = k + e · x (mod n). Verification checks s · G = R + e · P.

  • MuSig2: A two-round aggregatable multi-signature scheme. Two public keys P_A and P_B are aggregated into P = μ(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 under P.

  • Adaptor signature: Given an “adaptor point” T = t · G for some secret scalar t, the signer computes a pre-signature s* = k + e · x + t (mod n). Observe that s* · G = R + e · P + T. The pre-signature is not a valid Schnorr signature by itself, but once t becomes known, the completed signature s = s* - t verifies normally. Crucially, anyone who observes both s* (the pre-signature) and s (the completed signature published on-chain) can extract t = s* - s.

  • Taproot (BIP-341): A SpendInfo consists of an internal key P and optional script paths (tapleaves). The output key is Q = 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)

  1. Key exchange: A and B exchange public keys. They compute aggregated keys P_btc = μ(P_A_btc, P_B_btc) and P_ton = μ(P_A_ton, P_B_ton) for each chain.

  2. Adaptor point generation: A generates a random scalar t and computes T = t · G. A may optionally provide a proof-of-knowledge of t (a Schnorr signature under T) to assure B that T was not chosen adversarially.

  3. Nonce exchange: Both parties engage in the MuSig2 nonce commitment-reveal protocol for both chains, obtaining aggregated nonces R_btc and R_ton.

  4. Pre-signature exchange:

    • A computes the challenge e_btc for the BTC transaction and produces the pre-signature s*_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_ton for the DAG-chain transaction and produces the pre-signature s*_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 adds t, since t need 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.

2.4 Execution

  1. 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.

  2. B extracts t: B observes the completed signature on the DAG chain. Since B knows s*_ton (the pre-signature), B computes t = s*_ton - s_ton. This is the crucial extraction step.

  3. B claims on BTC: B uses t to complete the BTC-side pre-signature: s_btc = s*_btc - t. B co-signs and broadcasts the BTC transaction.

  4. 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

KeyTypeDescription
PSTT_GLOBAL_VERSIONu32Container version, set to 2
PSTT_GLOBAL_TX_VERSIONu32Transaction version per consensus
PSTT_GLOBAL_LOCKu64Optional absolute lock value
PSTT_GLOBAL_NETWORK_IDu8Mainnet or testnet
PSTT_GLOBAL_SWAP_CONTEXTstructSession identifier, party role, domain separation tag

3.2 Input Fields

KeyTypeDescription
PSTT_IN_PREVOUT32 bytesPrevious transaction hash
PSTT_IN_SEQUENCEu32Sequence number (for CSV)
PSTT_IN_TAP_KEY_ADAPTORstructAdaptor pre-signature data (optional)
PSTT_IN_TAP_KEY_SIG64 bytesCompleted 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

FunctionPurpose
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 Zeroize crate 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 generating T also provides a Schnorr proof-of-knowledge of the discrete logarithm t. Without this, a malicious party could construct T as 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.