Skip to main content

Overview

The transaction module provides comprehensive Bitcoin transaction creation, signing, and verification capabilities. It supports multiple address versions, submarine swaps, incoming swaps, and both partial and full signing workflows.

PartiallySignedTransaction

Represents a transaction with inputs that need user signatures.
type PartiallySignedTransaction struct {
    tx     *wire.MsgTx
    inputs []Input
    nonces *MusigNonces
}

NewPartiallySignedTransaction

Creates a new partially signed transaction from inputs and raw transaction bytes.
func NewPartiallySignedTransaction(
    inputs *InputList,
    rawTx []byte,
    userNonces *MusigNonces,
) (*PartiallySignedTransaction, error)
inputs
*InputList
required
List of transaction inputs with signing data
rawTx
[]byte
required
Serialized unsigned transaction
userNonces
*MusigNonces
required
MuSig2 nonces for taproot (V5/V6) inputs
Returns: *PartiallySignedTransaction
Errors: Transaction deserialization errors

Example

// Create input list
inputs := &InputList{}
inputs.Add(myInput)

// Parse transaction
pst, err := NewPartiallySignedTransaction(inputs, rawTxBytes, nonces)
if err != nil {
    log.Fatal(err)
}

Sign

Signs the transaction with the user’s private key. Muun’s signature must already be present in inputs.
func (p *PartiallySignedTransaction) Sign(
    userKey *HDPrivateKey,
    muunKey *HDPublicKey,
) (*Transaction, error)
userKey
*HDPrivateKey
required
User’s HD private key for signing
muunKey
*HDPublicKey
required
Muun’s HD public key for multisig validation
Returns: *Transaction - Fully signed transaction
Errors: Signing errors, missing Muun signature

Example

// Sign transaction (Muun signature already in inputs)
tx, err := pst.Sign(userKey, muunPublicKey)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Transaction hash: %s\n", tx.Hash)
fmt.Printf("Raw bytes: %x\n", tx.Bytes)

FullySign

Signs the transaction with both user and Muun private keys. Used for emergency recovery.
func (p *PartiallySignedTransaction) FullySign(
    userKey, muunKey *HDPrivateKey,
) (*Transaction, error)
userKey
*HDPrivateKey
required
User’s HD private key
muunKey
*HDPrivateKey
required
Muun’s HD private key (emergency recovery only)
Returns: *Transaction - Fully signed transaction
Errors: Signing errors
This method requires Muun’s private key and should only be used for emergency recovery scenarios.

Example

// Emergency recovery: sign with both keys
tx, err := pst.FullySign(userKey, muunKey)
if err != nil {
    log.Fatal(err)
}

Verify

Verifies the transaction matches expected parameters before signing.
func (p *PartiallySignedTransaction) Verify(
    expectations *SigningExpectations,
    userPublicKey *HDPublicKey,
    muunPublicKey *HDPublicKey,
) error
expectations
*SigningExpectations
required
Expected transaction parameters (destination, amount, fee, change)
userPublicKey
*HDPublicKey
required
User’s public key for change address validation
muunPublicKey
*HDPublicKey
required
Muun’s public key for change address validation
Returns: error if verification fails
Validates:
  • Output count (destination + optional change)
  • Destination address and amount
  • Change address ownership and derivation
  • Fee calculation
  • No dust outputs being burned as fees

Example

// Create expectations
expectations := NewSigningExpectations(
    "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq", // destination
    100000,      // amount in sats
    changeAddr,  // change address
    5000,        // fee in sats
    false,       // not alternative
)

// Verify before signing
err := pst.Verify(expectations, userPub, muunPub)
if err != nil {
    log.Fatalf("Transaction verification failed: %v", err)
}

SigningExpectations

Defines the expected parameters for transaction verification.
type SigningExpectations struct {
    destination string
    amount      int64
    change      MuunAddress
    fee         int64
    alternative bool
}

NewSigningExpectations

func NewSigningExpectations(
    destination string,
    amount int64,
    change MuunAddress,
    fee int64,
    alternative bool,
) *SigningExpectations
destination
string
required
Expected destination address
amount
int64
required
Expected amount in satoshis
change
MuunAddress
Expected change address (nil if no change)
fee
int64
required
Expected fee in satoshis
alternative
bool
Whether this is an alternative transaction (fee bump)

Input Interfaces

The Input interface provides access to input data for signing.
type Input interface {
    OutPoint() Outpoint
    Address() MuunAddress
    UserSignature() []byte
    MuunSignature() []byte
    SubmarineSwapV1() InputSubmarineSwapV1
    SubmarineSwapV2() InputSubmarineSwapV2
    IncomingSwap() InputIncomingSwap
    MuunPublicNonce() []byte
}

Outpoint Interface

type Outpoint interface {
    TxId() []byte
    Index() int
    Amount() int64
}

InputList

Container for managing transaction inputs.
type InputList struct {
    inputs []Input
}

Add

func (l *InputList) Add(input Input)
input
Input
required
Input to add to the list

Example

inputs := &InputList{}
inputs.Add(input1)
inputs.Add(input2)
inputs.Add(input3)

pst, err := NewPartiallySignedTransaction(inputs, rawTx, nonces)

Transaction Structure

The signed transaction result.
type Transaction struct {
    Hash  string  // Transaction ID (hex)
    Bytes []byte  // Serialized transaction
}

Supported Address Versions

The library supports signing for multiple address types:
// P2SH multisig (deprecated)
case addresses.V1:
    return &coinV1{...}

Security Validations

The Verify method performs critical security checks:

Output Validation

  • Ensures exactly 1 or 2 outputs (destination + optional change)
  • Verifies destination address matches expectations
  • Confirms destination amount matches expectations

Change Address Security

  • Validates change address is owned by the wallet
  • Verifies proper key derivation from user and Muun keys
  • Checks change amount calculation (inputs - amount - fee)

Fee Protection

  • Prevents excessive fees
  • Ensures dust outputs aren’t burned as fees (dust threshold: 546 sats)
  • Validates fee matches expected value

Alternative Transaction Handling

For fee bumps (RBF), allows:
  • Reduced destination amount
  • Missing destination output if change present
  • Adjusted fee expectations
Segwit inputs (V3+) include the input amount in the signature data, preventing amount manipulation attacks.

Constants

const dustThreshold = 546 // Minimum output value in satoshis

Error Scenarios

Common errors during transaction operations:
  • Signature verification failed: Muun signature invalid
  • Output mismatch: Unexpected number of outputs
  • Amount mismatch: Destination or change amount incorrect
  • Address mismatch: Change address not owned by wallet
  • Fee mismatch: Calculated fee doesn’t match expectations
  • Dust as fee: Change output below dust threshold

Build docs developers (and LLMs) love