Skip to main content
The board (“boarding”) module enables users to onboard on-chain UTXOs into Ark by creating VTXOs backed by a co-signed exit transaction. This is the primary mechanism for bringing Bitcoin into the Ark.

Overview

Boarding allows users to:
  • Convert on-chain UTXOs into off-chain VTXOs
  • Join the Ark without waiting for a round
  • Create spendable VTXOs with cooperative server signatures
  • Exit anytime using the co-signed transaction
The process uses MuSig2 for compact co-signing between user and server.

BoardBuilder

A state-machine builder for creating board VTXOs.
lib/src/board.rs
pub struct BoardBuilder<S: BuilderState> {
    pub user_pubkey: PublicKey,
    pub expiry_height: BlockHeight,
    pub server_pubkey: PublicKey,
    pub exit_delta: BlockDelta,
    amount: Option<Amount>,
    fee: Option<Amount>,
    utxo: Option<OutPoint>,
    user_pub_nonce: Option<PublicNonce>,
    user_sec_nonce: Option<SecretNonce>,
    exit_data: Option<ExitData>,
}

Builder States

lib/src/board.rs
pub mod state {
    pub struct Preparing;           // Initial state
    pub struct CanGenerateNonces;   // Funding details set
    pub struct ServerCanCosign;     // Server can cosign
    pub struct CanFinish;           // User can finalize
}

State Transitions

User Path:
Preparing → set_funding_details() → CanGenerateNonces → generate_user_nonces() → CanFinish → build_vtxo()
Server Path:
(skip Preparing) → new_for_cosign() → ServerCanCosign → server_cosign()

Board Flow

  1. User creates builder: BoardBuilder::new()
  2. User creates funding tx: Pays to funding_script_pubkey()
  3. User sets funding details: set_funding_details()
  4. User generates nonces: generate_user_nonces()
  5. User sends board info to server
  6. Server creates builder: BoardBuilder::new_for_cosign()
  7. Server cosigns: server_cosign() → sends response to user
  8. User validates response: verify_cosign_response()
  9. User builds VTXO: build_vtxo()

Construction (Preparing State)

Creating a Builder

pub fn new(
    user_pubkey: PublicKey,
    expiry_height: BlockHeight,
    server_pubkey: PublicKey,
    exit_delta: BlockDelta,
) -> BoardBuilder<state::Preparing>
Create a new board builder. Sets the basic parameters for the VTXO.

Funding Script

pub fn funding_script_pubkey(&self) -> ScriptBuf
Get the scriptPubkey to send board funds to. This is a MuSig2 aggregated key with an expiry clause. The funding script is:
  • Keyspend: MuSig2(user_pubkey, server_pubkey) - co-signed exit
  • Script path: Server can sweep after expiry_height

Setting Funding Details (CanGenerateNonces State)

pub fn set_funding_details(
    self,
    amount: Amount,
    fee: Amount,
    utxo: OutPoint,
) -> Result<BoardBuilder<state::CanGenerateNonces>, BoardFundingError>
Set the UTXO, amount, and fee. Computes exit transaction and transitions to CanGenerateNonces. Validation:
  • Amount must be > 0
  • Fee must be ≤ amount
  • Amount after fee must be > 0

Funding Errors

lib/src/board.rs
pub enum BoardFundingError {
    FeeHigherThanAmount { amount: Amount, fee: Amount },
    ZeroAmount,
    ZeroAmountAfterFee { amount: Amount, fee: Amount },
}

Generating Nonces (CanFinish State)

pub fn generate_user_nonces(self) -> BoardBuilder<state::CanFinish>
Generate MuSig2 nonces for signing the exit transaction. Transitions to CanFinish state.
// Available in CanSign trait (ServerCanCosign + CanFinish)
pub fn user_pub_nonce(&self) -> &PublicNonce
Access the generated public nonce (needed for server cosigning).

Reconstructing from VTXO (CanGenerateNonces State)

pub fn new_from_vtxo(
    vtxo: &Vtxo,
    funding_tx: &Transaction,
    server_pubkey: PublicKey,
) -> Result<Self, BoardFromVtxoError>
Reconstruct a board builder from an existing board VTXO. Used to validate that a VTXO is a legitimate board from the server. Caller must call vtxo.validate() before using this constructor.

Board VTXO Errors

lib/src/board.rs
pub enum BoardFromVtxoError {
    FundingTxMismatch { expected: Txid, got: Txid },
    ServerPubkeyMismatch { expected: PublicKey, got: PublicKey },
    VtxoIdMismatch { expected: OutPoint, got: OutPoint },
    IncorrectGenesisItemCount { genesis_count: usize },
}

Exit Transaction (CanGenerateNonces State)

pub fn exit_tx(&self) -> &Transaction
Get the unsigned exit transaction. This spends the funding UTXO and creates the VTXO output.
pub fn exit_txid(&self) -> Txid
Get the txid of the exit transaction.

Building Internal VTXOs (CanGenerateNonces State)

pub fn build_internal_unsigned_vtxos(&self) -> Vec<ServerVtxo>
Build the internal unsigned VTXOs for server tracking. Returns two VTXOs:
  1. Expiry VTXO (empty genesis): Used by server to track the funding UTXO for sweeping on expiry
  2. Pubkey VTXO (arkoor genesis): The actual board VTXO with an arkoor genesis transition
Both share the same outpoint (the funding UTXO) but have different policies.

Spend Info (CanGenerateNonces State)

pub fn spend_info(&self) -> Vec<(VtxoId, Txid)>
Returns the VTXO ID and spending transaction ID. For boards, this is:
  • (funding_utxo, exit_txid)

Server Cosigning (ServerCanCosign State)

Creating Server Builder

pub fn new_for_cosign(
    user_pubkey: PublicKey,
    expiry_height: BlockHeight,
    server_pubkey: PublicKey,
    exit_delta: BlockDelta,
    amount: Amount,
    fee: Amount,
    utxo: OutPoint,
    user_pub_nonce: PublicNonce,
) -> BoardBuilder<state::ServerCanCosign>
Server constructor using information from the user. Computes exit transaction with user’s nonce.

Cosigning

pub fn server_cosign(&self, key: &Keypair) -> BoardCosignResponse
Server cosigns the exit transaction. Returns nonce and partial signature for the user.

BoardCosignResponse

lib/src/board.rs
pub struct BoardCosignResponse {
    pub pub_nonce: PublicNonce,
    pub partial_signature: PartialSignature,
}
Server’s response containing the MuSig2 nonce and partial signature.

User Finalization (CanFinish State)

Validating Server Response

pub fn verify_cosign_response(&self, server_cosign: &BoardCosignResponse) -> bool
Validate the server’s partial signature. Returns true if valid. Always verify before calling build_vtxo()!

Building the VTXO

pub fn build_vtxo(
    self,
    server_cosign: &BoardCosignResponse,
    user_key: &Keypair,
) -> Result<Vtxo, IncorrectSigningKeyError>
Finalize the board and create the VTXO. Combines user and server partial signatures. The resulting VTXO:
  • Amount: amount - fee
  • Policy: VtxoPolicy::Pubkey(user_pubkey)
  • Genesis: Single Cosigned transition with final signature
  • Point: exit_tx output 0

Constants

lib/src/board.rs
pub const BOARD_FUNDING_TX_VTXO_VOUT: u32 = 0;
The output index of the board VTXO in the exit transaction (always 0).

Usage Examples

User: Creating a Board VTXO

let user_key = Keypair::new(SECP256K1_CONTEXT, &mut rng);
let server_pubkey: PublicKey = // ... from server
let expiry_height = 850_000;
let exit_delta = 2016;  // ~2 weeks

// 1. Create builder
let builder = BoardBuilder::new(
    user_key.public_key(),
    expiry_height,
    server_pubkey,
    exit_delta,
);

// 2. Get funding script
let funding_spk = builder.funding_script_pubkey();

// 3. Create funding transaction
let funding_tx = Transaction {
    // ... transaction paying to funding_spk
};
let funding_amount = Amount::from_sat(100_000);
let fee = Amount::from_sat(500);
let utxo = OutPoint::new(funding_tx.compute_txid(), 0);

// 4. Set funding details
let builder = builder.set_funding_details(
    funding_amount,
    fee,
    utxo,
)?;

// 5. Generate nonces
let builder = builder.generate_user_nonces();

// 6. Send to server:
// - user_pubkey
// - expiry_height
// - exit_delta
// - amount, fee, utxo
// - user_pub_nonce

// ... receive cosign_response from server ...

// 7. Verify and build
if !builder.verify_cosign_response(&cosign_response) {
    return Err("Invalid server signature");
}

let vtxo = builder.build_vtxo(&cosign_response, &user_key)?;

// 8. Validate the VTXO
vtxo.validate(&funding_tx)?;

Server: Cosigning a Board Request

let server_key: Keypair = // ...

// Receive from user:
let user_pubkey: PublicKey = // ...
let expiry_height: BlockHeight = // ...
let exit_delta: BlockDelta = // ...
let amount: Amount = // ...
let fee: Amount = // ...
let utxo: OutPoint = // ...
let user_pub_nonce: PublicNonce = // ...

// 1. Create server builder
let builder = BoardBuilder::new_for_cosign(
    user_pubkey,
    expiry_height,
    server_key.public_key(),
    exit_delta,
    amount,
    fee,
    utxo,
    user_pub_nonce,
);

// 2. Cosign
let response = builder.server_cosign(&server_key);

// 3. Store internal VTXOs for tracking
let internal_vtxos = builder.build_internal_unsigned_vtxos();
let spend_info = builder.spend_info();

// Store in database:
// - internal_vtxos (for expiry tracking and arkoor origin)
// - spend_info (VTXO spending relationships)

// 4. Send response to user
// response.pub_nonce
// response.partial_signature

Validating an Existing Board VTXO

let vtxo: Vtxo = // ... received board VTXO
let funding_tx: Transaction = // ... from chain
let server_pubkey: PublicKey = // ...

// First validate the VTXO
vtxo.validate(&funding_tx)?;

// Reconstruct builder to verify it's a legitimate board
let builder = BoardBuilder::new_from_vtxo(
    &vtxo,
    &funding_tx,
    server_pubkey,
)?;

// If successful, this is a valid board VTXO from this server
let internal_vtxos = builder.build_internal_unsigned_vtxos();

// internal_vtxos[0] = Expiry VTXO (for sweep tracking)
// internal_vtxos[1] = Pubkey VTXO (arkoor-compatible)

Transaction Structure

Funding UTXO (on-chain)

[Exit Transaction]  ← Co-signed by user + server

  VTXO
Funding UTXO Policy:
  • Keyspend: MuSig2(user, server) - requires both signatures
  • Script path: Server after expiry_height
Exit Transaction:
  • Input: Funding UTXO (keyspend path)
  • Output 0: VTXO output (user’s final policy)
  • Output 1: Fee anchor (P2A)
Resulting VTXO:
  • Genesis: Single Cosigned transition
  • Policy: Pubkey(user_pubkey) (can be spent in rounds or arkoor)
  • Amount: funding_amount - fee

Important Notes

Always validate the server’s cosign response with verify_cosign_response() before calling build_vtxo(). Invalid signatures will cause the VTXO to be unusable.
Board VTXOs have a single genesis item with a Cosigned transition. The signature is the final MuSig2 aggregate signature over the exit transaction.
The board funding UTXO can be swept by the server after expiry_height if not spent. This ensures servers can reclaim stuck funds.
Use build_internal_unsigned_vtxos() to create tracking VTXOs:
  • Expiry VTXO tracks the funding UTXO for server sweeping
  • Arkoor VTXO enables the board to be spent out-of-round

Build docs developers (and LLMs) love