Menu
BIP-bip-174 Final

BIP-174: PSBT

Partially Signed Bitcoin Transactions. Standard format for multi-party transaction signing and hardware wallet integration.

Type Bitcoin Improvement Proposal
Number bip-174
Status Final
Authors Andrew Chow
Original https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
Requires

BIP-174: Partially Signed Bitcoin Transactions (PSBT)

PSBT is a standard format for passing around unsigned or partially signed transactions. It enables hardware wallets, multi-signature setups, and airgapped signing.

Motivation

Before PSBT:

  • No standard format for unsigned transactions
  • Hardware wallets needed custom protocols
  • Multi-party signing was fragmented
  • Airgapped signing was complex

PSBT provides:

  • Interoperability: Standard format across tools
  • Modularity: Each party adds their data
  • Security: Clear separation of concerns
  • Extensibility: New fields can be added

Use Cases

ScenarioHow PSBT Helps
Hardware walletCreate PSBT → sign on device → extract signed tx
Multi-sigCreate PSBT → each signer adds signature → combine
Airgapped computerCreate PSBT → transfer via QR/USB → sign offline
Watch-only walletCreate PSBT → send to hot wallet for signing

Structure

PSBT consists of:

  1. Global section: Unsigned transaction and global metadata
  2. Input sections: One per transaction input
  3. Output sections: One per transaction output

Binary Format

<magic: 0x70736274ff>  // "psbt" + 0xff
<global>
<input 0>
<input 1>
...
<output 0>
<output 1>
...

Each section is a series of key-value pairs:

<key length><key><value length><value>

Section separator: 0x00

Key Types

Global Keys:

KeyValueDescription
0x00TransactionUnsigned transaction
0x01xpubExtended public key
0xfbVersionPSBT version number

Input Keys:

KeyValueDescription
0x00Non-witness UTXOFull transaction for legacy inputs
0x01Witness UTXOOutput being spent (SegWit)
0x02Partial signatureSignature for specific pubkey
0x03Sighash typeSignature hash type
0x04Redeem scriptP2SH redeem script
0x05Witness scriptP2WSH witness script
0x06BIP-32 derivationHD key path
0x07Final scriptSigFinalized scriptSig
0x08Final witnessFinalized witness

Output Keys:

KeyValueDescription
0x00Redeem scriptP2SH redeem script
0x01Witness scriptP2WSH witness script
0x02BIP-32 derivationHD key path

Roles

PSBT workflow involves distinct roles:

Creator

Creates initial PSBT with unsigned transaction.

const bitcoin = require('bitcoinjs-lib');

// Create unsigned transaction
const psbt = new bitcoin.Psbt({ network: bitcoin.networks.bitcoin });

psbt.addInput({
  hash: 'abcd...', // txid
  index: 0,
  witnessUtxo: {
    script: Buffer.from('0014...', 'hex'),
    value: 100000
  }
});

psbt.addOutput({
  address: 'bc1q...',
  value: 50000
});

// Export as base64
const psbtBase64 = psbt.toBase64();

Updater

Adds information needed for signing:

  • UTXO data (witness or non-witness)
  • Redeem/witness scripts
  • BIP-32 derivation paths
  • Sighash types
// Add derivation info
psbt.updateInput(0, {
  bip32Derivation: [{
    masterFingerprint: Buffer.from('deadbeef', 'hex'),
    path: "m/84'/0'/0'/0/0",
    pubkey: publicKey
  }]
});

Signer

Adds partial signatures using available keys.

// Sign with private key
psbt.signInput(0, keyPair);

// Or sign all inputs the key can sign
psbt.signAllInputs(keyPair);

Combiner

Merges multiple PSBTs with partial signatures.

const psbt1 = bitcoin.Psbt.fromBase64(signer1Result);
const psbt2 = bitcoin.Psbt.fromBase64(signer2Result);

psbt1.combine(psbt2);

Finalizer

Creates final scriptSig/witness from partial signatures.

psbt.finalizeAllInputs();

Extractor

Extracts the final signed transaction.

const tx = psbt.extractTransaction();
const txHex = tx.toHex();

// Broadcast
await fetch('https://mempool.space/api/tx', {
  method: 'POST',
  body: txHex
});

Complete Workflow Example

Single Signer (Hardware Wallet)

const bitcoin = require('bitcoinjs-lib');

// 1. CREATOR: Watch-only wallet creates PSBT
const psbt = new bitcoin.Psbt();
psbt.addInput({
  hash: txid,
  index: vout,
  witnessUtxo: { script, value }
});
psbt.addOutput({ address: recipient, value: amount });
psbt.addOutput({ address: changeAddress, value: change });

// 2. UPDATER: Add derivation paths
psbt.updateInput(0, {
  bip32Derivation: [{
    masterFingerprint,
    path: "m/84'/0'/0'/0/5",
    pubkey
  }]
});

// 3. Export for hardware wallet
const psbtBase64 = psbt.toBase64();
// Send to hardware wallet via USB/QR

// 4. SIGNER: Hardware wallet signs and returns signed PSBT
const signedPsbtBase64 = await hardwareWallet.sign(psbtBase64);

// 5. FINALIZER: Finalize
const signedPsbt = bitcoin.Psbt.fromBase64(signedPsbtBase64);
signedPsbt.finalizeAllInputs();

// 6. EXTRACTOR: Get raw transaction
const tx = signedPsbt.extractTransaction();
console.log(tx.toHex());

Multi-Signature (2-of-3)

// 1. Create PSBT with multisig input
const psbt = new bitcoin.Psbt();
psbt.addInput({
  hash: txid,
  index: 0,
  witnessUtxo: { script: p2wshScript, value: 100000 },
  witnessScript: multisigScript
});
psbt.addOutput({ address: recipient, value: 90000 });

// 2. Signer 1 signs
const psbt1 = bitcoin.Psbt.fromBase64(psbtBase64);
psbt1.signInput(0, signer1KeyPair);
const signed1 = psbt1.toBase64();

// 3. Signer 2 signs (can start from original or signed1)
const psbt2 = bitcoin.Psbt.fromBase64(psbtBase64);
psbt2.signInput(0, signer2KeyPair);
const signed2 = psbt2.toBase64();

// 4. Combine
const combined = bitcoin.Psbt.fromBase64(signed1);
combined.combine(bitcoin.Psbt.fromBase64(signed2));

// 5. Finalize (now has 2 of 3 signatures)
combined.finalizeAllInputs();

// 6. Extract and broadcast
const tx = combined.extractTransaction();

Encoding Formats

FormatUse Case
BinaryFile storage, efficient transfer
Base64Text transmission, APIs
HexDebugging, logging
// Convert between formats
const psbt = new bitcoin.Psbt();

const base64 = psbt.toBase64();
const hex = psbt.toHex();
const buffer = psbt.toBuffer();

// Parse from any format
const fromBase64 = bitcoin.Psbt.fromBase64(base64);
const fromHex = bitcoin.Psbt.fromHex(hex);
const fromBuffer = bitcoin.Psbt.fromBuffer(buffer);

Validation

Before signing, validate PSBT contents:

function validatePsbt(psbt) {
  // Check all inputs have UTXO data
  for (let i = 0; i < psbt.inputCount; i++) {
    const input = psbt.data.inputs[i];
    if (!input.witnessUtxo && !input.nonWitnessUtxo) {
      throw new Error(`Input ${i} missing UTXO data`);
    }
  }

  // Verify output amounts are reasonable
  const inputSum = psbt.data.inputs.reduce((sum, input) => {
    return sum + (input.witnessUtxo?.value || 0);
  }, 0);

  const outputSum = psbt.txOutputs.reduce((sum, output) => {
    return sum + output.value;
  }, 0);

  const fee = inputSum - outputSum;
  if (fee < 0) {
    throw new Error('Invalid: outputs exceed inputs');
  }
  if (fee > inputSum * 0.1) {
    throw new Error('Warning: fee exceeds 10% of inputs');
  }

  return { inputSum, outputSum, fee };
}

CLI Tools

bitcoin-cli

# Create PSBT
bitcoin-cli createpsbt '[{"txid":"...","vout":0}]' '[{"bc1q...":0.001}]'

# Process (add UTXO info)
bitcoin-cli walletprocesspsbt "cHNidP8B..."

# Sign
bitcoin-cli walletprocesspsbt "cHNidP8B..." true

# Finalize
bitcoin-cli finalizepsbt "cHNidP8B..."

# Decode for inspection
bitcoin-cli decodepsbt "cHNidP8B..."

HWI (Hardware Wallet Interface)

# Enumerate devices
hwi enumerate

# Sign PSBT with hardware wallet
hwi -d <device_id> signtx "cHNidP8B..."

Security Considerations

  1. Verify UTXO data: Malicious PSBT could claim wrong amounts
  2. Check change addresses: Ensure change goes to your wallet
  3. Validate fee: Prevent excessive fees
  4. Inspect all outputs: Know where funds are going
  5. Use deterministic signing: Consistent signatures

PSBT Version 2 (BIP-370)

PSBT v2 adds:

  • Transaction modifiability flags
  • Output modifiers
  • Better extensibility

Check version: psbt.data.globalMap.get(0xfb)


Machine-Readable Summary

{
  "bip": 174,
  "title": "Partially Signed Bitcoin Transactions",
  "status": "final",
  "magic_bytes": "0x70736274ff",
  "roles": ["creator", "updater", "signer", "combiner", "finalizer", "extractor"],
  "formats": ["binary", "base64", "hex"],
  "use_cases": ["hardware_wallets", "multisig", "airgapped_signing"],
  "libraries": {
    "javascript": ["bitcoinjs-lib"],
    "python": ["python-bitcoinlib", "bdk"],
    "rust": ["rust-bitcoin", "bdk"]
  }
}