The Beef class in the BSV TypeScript SDK implements BRC-62, the Background Evaluation Extended Format for Bitcoin transaction packages. BEEF provides a standardized way to bundle transactions with their dependencies and merkle proofs, enabling efficient SPV validation without requiring access to the full blockchain. It creates atomic transaction envelopes that include all necessary proof data for independent verification.
Purpose
Bundle transactions with their input dependencies into atomic packages
Include merkle proofs for SPV validation of transaction ancestry
Enable offline transaction verification without blockchain access
Reduce data transmission overhead for transaction chains
Standardize transaction package format for interoperability
Support both Version 1 (simple) and Version 2 (optimized) BEEF formats
Basic Usage
Creating a BEEF Package
import{Beef,Transaction,MerklePath}from'@bsv/sdk';// Create a BEEF packageconstbeef=newBeef();// Add transactions to the packageconstparentTx=Transaction.fromHex('0100000001...');constchildTx=Transaction.fromHex('0100000001...');beef.addTransaction(parentTx);beef.addTransaction(childTx);// Add merkle proof for SPV validationconstmerklePath=MerklePath.fromHex('fed7c509000a02fddd01...');beef.addMerklePath(merklePath);// Serialize to hex for transmissionconstbeefHex=beef.toHex();console.log('BEEF package:',beefHex);// Parse BEEF packageconstparsedBeef=Beef.fromHex(beefHex);console.log('Transactions:',parsedBeef.getTransactions());
Verifying a BEEF Package
Key Features
1. Transaction Bundle Creation
BEEF packages bundle transactions with their dependencies for atomic transmission:
2. Merkle Proof Integration
BEEF includes merkle proofs for SPV validation of transaction ancestry:
3. Version 1 and Version 2 Formats
BEEF supports two formats for different optimization levels:
4. Atomic Transaction Validation
BEEF enables atomic validation of entire transaction chains:
API Reference
Constructor
Creates a new BEEF package.
Parameters:
version?: number - BEEF format version (1 or 2, default: 2)
Static Methods
Beef.fromBinary(binary: number[]): Beef
Parses a BEEF package from binary format.
Parameters:
binary: number[] - The binary BEEF data
Returns:Beef - The parsed BEEF package
Example:
Beef.fromHex(hex: string): Beef
Parses a BEEF package from hexadecimal string.
Parameters:
hex: string - The hexadecimal BEEF string
Returns:Beef - The parsed BEEF package
Example:
Instance Methods
addTransaction(tx: Transaction): void
Adds a transaction to the BEEF package.
Parameters:
tx: Transaction - The transaction to add
Example:
addMerklePath(path: MerklePath): void
Adds a merkle proof to the BEEF package.
Parameters:
path: MerklePath - The merkle path for SPV validation
import { Beef, Transaction, PrivateKey, P2PKH } from '@bsv/sdk';
// Create a chain of transactions
const privKey = PrivateKey.fromWif('L5EY1SbTvvPNSdCYQe1EJHfXCBBT4PmnF6CDbzCm9iifZptUvDGB');
// Parent transaction (already confirmed)
const parentTx = Transaction.fromHex('0100000001...');
// Create child transaction spending parent
const childTx = new Transaction();
childTx.addInput({
sourceTransaction: parentTx,
sourceOutputIndex: 0,
unlockingScriptTemplate: new P2PKH().unlock(privKey)
});
childTx.addOutput({
lockingScript: new P2PKH().lock('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'),
satoshis: 1000
});
await childTx.fee();
await childTx.sign();
// Create BEEF package with both transactions
const beef = new Beef();
// Add transactions in dependency order
beef.addTransaction(parentTx);
beef.addTransaction(childTx);
// BEEF automatically handles:
// - Transaction ordering
// - Dependency tracking
// - Binary serialization
console.log('BEEF size:', beef.toBinary().length, 'bytes');
console.log('Transactions included:', beef.getTransactions().length);
import { Beef, Transaction, MerklePath } from '@bsv/sdk';
// Create BEEF with merkle proof
const beef = new Beef();
// Add confirmed transaction
const confirmedTx = Transaction.fromHex('0100000001...');
beef.addTransaction(confirmedTx);
// Add merkle proof for the confirmed transaction
const merklePath = MerklePath.fromHex('fed7c509000a02fddd01...');
beef.addMerklePath(merklePath);
// The merkle path contains:
// - Block height
// - Transaction index in block
// - Merkle tree path to root
// - All sibling hashes needed for verification
// Get merkle root from path
const merkleRoot = merklePath.computeRoot(confirmedTx.id('hex'));
console.log('Merkle root:', merkleRoot);
console.log('Block height:', merklePath.blockHeight);
// Add unconfirmed transaction spending confirmed UTXO
const unconfirmedTx = new Transaction();
unconfirmedTx.addInput({
sourceTransaction: confirmedTx,
sourceOutputIndex: 0,
unlockingScriptTemplate: new P2PKH().unlock(privKey)
});
unconfirmedTx.addOutput({
lockingScript: new P2PKH().lock(recipientAddress),
satoshis: 800
});
await unconfirmedTx.fee();
await unconfirmedTx.sign();
beef.addTransaction(unconfirmedTx);
// BEEF now contains full proof chain:
// - Confirmed parent with merkle proof
// - Unconfirmed child with parent reference
console.log('Complete BEEF package ready for SPV validation');
import { Beef, Transaction } from '@bsv/sdk';
// Version 1 BEEF (simple format)
const beefV1 = new Beef();
beefV1.version = 1;
// Version 1 format:
// - Simple transaction list
// - Straightforward merkle proof attachment
// - Easier to parse and validate
// - Slightly larger size
beefV1.addTransaction(tx1);
beefV1.addTransaction(tx2);
const v1Binary = beefV1.toBinary();
console.log('Version 1 size:', v1Binary.length);
// Version 2 BEEF (optimized format)
const beefV2 = new Beef();
beefV2.version = 2;
// Version 2 format:
// - Optimized transaction encoding
// - Deduplication of common data
// - Compressed merkle proof storage
// - Smaller size for large packages
beefV2.addTransaction(tx1);
beefV2.addTransaction(tx2);
const v2Binary = beefV2.toBinary();
console.log('Version 2 size:', v2Binary.length);
console.log('Size reduction:', ((1 - v2Binary.length / v1Binary.length) * 100).toFixed(2) + '%');
// Both versions are fully compatible
const parsedV1 = Beef.fromBinary(v1Binary);
const parsedV2 = Beef.fromBinary(v2Binary);
console.log('V1 valid:', await parsedV1.verify(chainTracker));
console.log('V2 valid:', await parsedV2.verify(chainTracker));
import { Beef, Transaction, ChainTracker } from '@bsv/sdk';
class BeefValidator {
/**
* Validate entire BEEF package atomically
*/
static async validateBeef(
beef: Beef,
chainTracker: ChainTracker
): Promise<{ valid: boolean; errors: string[] }> {
const errors: string[] = [];
try {
// 1. Verify merkle proofs
const isValid = await beef.verify(chainTracker);
if (!isValid) {
errors.push('Merkle proof verification failed');
return { valid: false, errors };
}
// 2. Verify transaction chain integrity
const transactions = beef.getTransactions();
const txMap = new Map<string, Transaction>();
for (const tx of transactions) {
txMap.set(tx.id('hex'), tx);
}
// 3. Verify each transaction spends valid inputs
for (const tx of transactions) {
for (let i = 0; i < tx.inputs.length; i++) {
const input = tx.inputs[i];
// Check if input references transaction in package
if (input.sourceTXID) {
const sourceTx = txMap.get(input.sourceTXID);
if (!sourceTx) {
errors.push(
`Transaction ${tx.id('hex')} input ${i} ` +
`references missing transaction ${input.sourceTXID}`
);
} else {
// Verify output exists
if (!sourceTx.outputs[input.sourceOutputIndex]) {
errors.push(
`Transaction ${tx.id('hex')} input ${i} ` +
`references non-existent output ${input.sourceOutputIndex}`
);
}
}
}
}
}
// 4. Verify transaction scripts
for (const tx of transactions) {
try {
// Verify all inputs have valid unlocking scripts
for (let i = 0; i < tx.inputs.length; i++) {
if (!tx.inputs[i].unlockingScript) {
errors.push(
`Transaction ${tx.id('hex')} input ${i} missing unlocking script`
);
}
}
} catch (e) {
errors.push(`Transaction ${tx.id('hex')} validation failed: ${e.message}`);
}
}
return {
valid: errors.length === 0,
errors
};
} catch (e) {
errors.push(`BEEF validation error: ${e.message}`);
return { valid: false, errors };
}
}
}
// Usage
const beef = Beef.fromHex(beefHex);
const chainTracker = new WhatsOnChainTracker();
const result = await BeefValidator.validateBeef(beef, chainTracker);
if (result.valid) {
console.log('BEEF package is valid and complete');
} else {
console.error('BEEF validation errors:');
result.errors.forEach(err => console.error(' -', err));
}
constructor(version?: number)
const beef = Beef.fromBinary(binaryData);
const beef = Beef.fromHex('0100beef01...');
beef.addTransaction(transaction);
beef.addMerklePath(merklePath);
beef.version // number - BEEF format version (1 or 2)
beef.transactions // Transaction[] - Transactions in the package
beef.merklePaths // MerklePath[] - Merkle proofs for validation
import { Beef, Transaction, PrivateKey, P2PKH, MerklePath } from '@bsv/sdk';
class PaymentChannelBeef {
/**
* Create BEEF package for payment channel update
*/
static async createChannelUpdate(
fundingTx: Transaction,
fundingMerklePath: MerklePath,
channelState: number,
alicePrivKey: PrivateKey,
bobPrivKey: PrivateKey,
aliceAmount: number,
bobAmount: number
): Promise<Beef> {
const beef = new Beef();
// Add funding transaction with proof
beef.addTransaction(fundingTx);
beef.addMerklePath(fundingMerklePath);
// Create channel update transaction
const updateTx = new Transaction();
// Spend from 2-of-2 multisig funding output
updateTx.addInput({
sourceTransaction: fundingTx,
sourceOutputIndex: 0,
sequence: channelState // Use sequence for state tracking
});
// Output to Alice
updateTx.addOutput({
lockingScript: new P2PKH().lock(alicePrivKey.toPublicKey().toAddress()),
satoshis: aliceAmount
});
// Output to Bob
updateTx.addOutput({
lockingScript: new P2PKH().lock(bobPrivKey.toPublicKey().toAddress()),
satoshis: bobAmount
});
await updateTx.fee();
// Both parties sign
await updateTx.sign();
// Add update transaction
beef.addTransaction(updateTx);
return beef;
}
/**
* Verify and extract channel state
*/
static async verifyChannelUpdate(
beefHex: string,
chainTracker: ChainTracker,
expectedFundingTxid: string
): Promise<{ valid: boolean; state: number; aliceAmount: number; bobAmount: number }> {
const beef = Beef.fromHex(beefHex);
// Verify BEEF package
const isValid = await beef.verify(chainTracker);
if (!isValid) {
throw new Error('Invalid BEEF package');
}
const transactions = beef.getTransactions();
// Find funding and update transactions
const fundingTx = transactions.find(tx => tx.id('hex') === expectedFundingTxid);
if (!fundingTx) {
throw new Error('Funding transaction not found');
}
const updateTx = transactions.find(tx =>
tx.inputs.some(input => input.sourceTXID === expectedFundingTxid)
);
if (!updateTx) {
throw new Error('Update transaction not found');
}
// Extract state and amounts
const state = updateTx.inputs[0].sequence;
const aliceAmount = updateTx.outputs[0].satoshis || 0;
const bobAmount = updateTx.outputs[1].satoshis || 0;
return {
valid: true,
state,
aliceAmount,
bobAmount
};
}
}
// Usage
const fundingTx = Transaction.fromHex('...');
const fundingProof = MerklePath.fromHex('...');
const aliceKey = PrivateKey.fromRandom();
const bobKey = PrivateKey.fromRandom();
// Create channel update
const beef = await PaymentChannelBeef.createChannelUpdate(
fundingTx,
fundingProof,
42, // State number
aliceKey,
bobKey,
7000, // Alice gets 7000 sats
3000 // Bob gets 3000 sats
);
console.log('Channel update BEEF:', beef.toHex());
// Verify channel update
const chainTracker = new WhatsOnChainTracker();
const result = await PaymentChannelBeef.verifyChannelUpdate(
beef.toHex(),
chainTracker,
fundingTx.id('hex')
);
console.log('Channel state:', result.state);
console.log('Alice balance:', result.aliceAmount);
console.log('Bob balance:', result.bobAmount);