Components
- Built on Distributed Lab foundations: The bridge architecture is adapted from Distributed Lab’s design (https://x.com/distributedlab).
- Gateway: Orchestrates both directions, derives deposit/exit addresses via FROST DKG, and constructs Spark/BTC transactions.
- Verifiers: Hold key shares, validate deposits, and co-sign mint/burn using threshold Schnorr signatures.
- BTC indexer: Monitors a deposit
txiduntil it reachesN_confirm_BTC = 1confirmation. - Spark balance checker: Confirms that a Spark deposit address holds the expected wRunes.
- Spark entity: Executes Spark-side transactions once signed.
- User wallet (BTC/Spark): Sends deposits and receives payouts.
Conventions
- wRunes: BTKN (LRC20-like) wrapped runes on Spark.
- Signers:
M = 3verifiers; threshold t-of-M enforced by FROST. - Confirmations:
N_confirm_BTC = 1for BTC deposits before minting. - Amounts: Always strings (u64) to avoid precision loss.
Address derivation & signing
- Gateway runs FROST DKG to derive a multisig pubkey for each
(user_pubkey, rune_id)pair. - Each deposit address is tweaked with a random nonce, producing a unique address per request; prevents address reuse and binding attacks.
- An issuer multisig is dedicated to minting/burning each wRune; verifiers co-sign via FROST.
- Key shares never combine; optional periodic DKG refresh rotates shares without exposing the master key.
Flow: Runes -> Spark (mint wRunes)
- Issue deposit address: Gateway derives/tweaks
(user_pubkey, rune_id)multisig and returnsdeposit_btc_address. - User deposits runes: Broadcasts to
deposit_btc_address, then submits{ txid, vout, bridgeAddress, btcAddress }viabridgeRunes. - Wait for confirmations: Verifiers subscribe the
txidwith the BTC indexer; once 1 conf, they validate the rune-bearing output. - Mint wRunes: Gateway uses the issuer multisig + verifier shares to sign a Spark mint; Spark entity executes it to the Spark address associated with the user.
- Track status: Progression typically goes
address_issued -> waiting_for_confirmations -> ready_for_mint -> minted(orfailed).
Flow: Spark -> Runes (burn wRunes, pay BTC)
- Issue Spark deposit address: Gateway derives/tweaks the same multisig, returning a unique
exit_spark_address. - User deposits wRunes: Sends the specified amount to
exit_spark_address, then notifies the gateway. - Verify Spark balance: Verifiers call the Spark balance checker to confirm funding.
- Burn & build payout: Gateway signs a burn with the issuer multisig; simultaneously selects rune UTXOs to build the BTC payout to the user. Any change is sent to a fresh deposit address derived/tweaked from the same key.
- Track status: Progression typically goes
address_issued -> waiting_for_confirmations -> ready_for_mint -> spent(orfailed).
Security model
| Threat | Mitigation |
|---|---|
Signer corruption < t | FROST threshold: no subset below t can move funds. |
| BTC re-org | Wait for 1 confirmation before minting. |
| Verifier liveness | Any t-of-M online can sign; FROST hides which signers participated. |
| Front-running / address reuse | Per-deposit nonce produces unique addresses; replays are invalid. |
| Accounting drift | Vault invariant sum(deposits) - sum(withdrawals) enforced on every operation. |
| Key leakage | Shares never reconstructed; DKG refresh can rotate shares. |
Operational notes
- Use
getActivityto poll byuserPublicKey;mintedindicates Spark mint completion,spentindicates BTC payout for Spark exits. - Preserve the provided
bridgeAddress/exit_spark_addressper request; each is single-use and bound to the requested amount/rune/user.