LogoLogo
  • README
  • Contribute
    • Discuss on Github
  • Example
    • Banana-Powered Bitcoin Wallet Control Protocol
  • Apps
    • The deployment-info.json Specification
  • Wallet
    • Transaction Creation
    • Data Encryption and Decryption
    • Digital Signature Creation and Verification
    • Input Redemption
    • HTTP Wallet Communications Substrate
    • XDM Wallet Communications Substrate
    • Window Wallet Communication Substrate
    • Wallet Transaction Output Tracking (Output Baskets)
    • Submitting Received Payments to a Wallet
    • Certificate Creation and Revelation
    • Unified Abstract Wallet-to-Application Messaging Layer
    • Transaction Labels and List Actions
    • Output Basket Removal and Certificate Deletion
    • Group Permissions for App Access
    • Extensible Proof-Type Format for Specific Key Linkage Claims
    • P Protocols: Allowing future wallet protocol permission schemes
    • P Baskets: Allowing Future Wallet Basket and Digital Asset Permission Schemes
    • Unified, Vendor-Neutral, Unchanging, and Open BSV Blockchain Standard Wallet-to-Application Interface
  • Transactions
    • Everett-style Transaction Envelopes
    • Simplified Payment Verification
    • Merkle proof standardised format
    • TSC Proof Format with Heights
    • Raw Transaction Format
    • TXO Transaction Object Format
    • Transaction Extended Format (EF)
    • Merkle Path JSON format
    • Compound Merkle Path Format
    • Background Evaluation Extended Format (BEEF) Transactions
    • Simplified Payment Verification
    • Merkle Path Binary Format
    • BSV Unified Merkle Path (BUMP) Format
    • Graph Aware Sync Protocol
    • Scalable Transaction Processing in the BSV Network
    • Atomic BEEF Transactions
    • BEEF V2 Txid Only Extension
  • Scripts
    • Bitcoin Script Binary, Hex and ASM Formats
    • Bitcoin Script Assembly Language
    • Pay to Public Key Hash
    • Pay to R Puzzle Hash
    • Pay to False Return
    • Pay to True Return
    • Push TX
    • Bare Multi-Signature
    • Pay to Push Drop
  • Tokens
    • There is no BRC-20
    • Definition of UTXOs as Bitcoin Tokens
    • Token Exchange Protocol for UTXO-based Overlay Networks
    • Mandala Token Protocol
  • Overlays
    • Overlay Network Data Synchronization
    • Confederacy Host Interconnect Protocol (CHIP)
    • Overlay Network Lookup Services
    • Confederacy Lookup Availability Protocol (CLAP)
    • Universal Hash Resolution Protocol
    • Overlay Network Transaction History Tracking
    • Private Overlays with P2PKH Transactions
    • Standardized Naming Conventions for BRC-22 Topic Managers and BRC-24 Lookup Services
    • Overlay Services Synchronization Architecture
    • Diverse Facilitators and URL Protocols for SHIP and SLAP Overlay Advertisements
  • Payments
    • Direct Payment Protocol (DPP)
    • Paymail Payment Destinations
    • Simple Authenticated BSV P2PKH Payment Protocol
    • PacketPay HTTP Payment Mechanism
    • Hybrid Payment Mode for DPP
    • HTTPS Transport Mechanism for DPP
    • Paymail BEEF Transaction
    • HTTP Service Monetization Framework
  • Peer-to-Peer
    • Authrite Mutual Authentication
    • PeerServ Message Relay Interface
    • PeerServ Host Interconnect Protocol
    • Identity Certificates
    • Genealogical Identity Protocol
    • Publishing Trust Anchor Details at an Internet Domain
    • Message Signature Creation and Verification
    • Serialization Format for Portable Encrypted Messages
    • Defining a Scalable IPv6 Multicast Protocol for Blockchain Transaction Broadcast and Update Delivery
    • Proven Identity Key Exchange (PIKE)
    • Peer-to-Peer Mutual Authentication and Certificate Exchange Protocol
    • HTTP Transport for BRC-103 Mutual Authentication
  • Key Derivation
    • BIP32 Key Derivation Scheme
    • BSV Key Derivation Scheme (BKDS)
    • Security Levels, Protocol IDs, Key IDs and Counterparties
    • Admin-reserved and Prohibited Key Derivation Protocols
    • Revealing Key Linkages
    • Protecting BRC-69 Key Linkage Information in Transit
    • Mnemonic For Master Private Key
    • Linked Key Derivation Scheme
    • Bidirectionally Authenticated Derivation of Privacy Restricted Type 42 Keys
    • Limitations of BRC-69 Key Linkage Revelation
    • Verifiable Revelation of Shared Secrets Using Schnorr Protocol
  • Outpoints
    • Format for Bitcoin Outpoints
    • Spending Instructions Extension for UTXO Storage Format
  • Opinions
    • Users should never see an address
    • List of user experiences
    • Legitimate Uses for mAPI
    • Security and Scalability Benefits of UTXO-based Overlay Networks
    • Improving on MLD for BSV Multicast Services
    • Web 3.0 Standard (at a high level)
    • Thoughts on the Mandala Network
    • Outputs, Overlays, and Scripts in the Mandala Network
  • State Machines
    • Simplifying State Machine Event Chains in Bitcoin
Powered by GitBook
On this page
  • Abstract
  • Explainer Video
  • Copyright
  • Motivation
  • Specification
  • Example
  • Important Note
  • Hex
  • Bytewise Breakdown
  • Implementation
  • JSON Encoding of a Compound Merkle Path
  • Merkle Proof

Was this helpful?

Edit on GitHub
Export as PDF
  1. Transactions

Compound Merkle Path Format

PreviousMerkle Path JSON formatNextBackground Evaluation Extended Format (BEEF) Transactions

Last updated 1 year ago

Was this helpful?

Deggen (deggen@kschw.com) Damian Orzepowski (damian.orzepowski@4chain.studio)

Abstract

We propose a binary format for Compound Merkle Paths (CMP hereon) optimized for minimal data bandwidth during transmission.

Explainer Video

Copyright

This BRC is licensed under the Open BSV license.

Motivation

Current format standards do not cover merkle paths for multiple txids within the same block. This would help reduce the overall size of data needed to express any set of paths from the same block. The larger the set the bigger the space saving.

Specification

For each level of the merkle path the opposite hash from the one which can be calculated is provided.

Data Types

Field
Description
Size

height

The height of the tree up to a max 64

1 byte

nLeaves

VarInt number of leaves at this height

1-9 bytes

offset

VarInt offset from left hand side within tree

1-9 bytes

leaf

Each leaf is a 32 byte hash

32 bytes

Formatting Syntax

  1. offset and leaf are repeated for nLeaves at each height

  2. height does not need to be repeated, the inference is that height starts as the max height of the tree and is decremented by one each time we reach the end of the current set of leaves. Once height === -1 we stop parsing.

  3. nLeaves is repeated for each height, followed by the corresponding offset and leaf for each.

Example

Important Note

We must include the txid and offset within a block at height 0. This is the most efficient way to store the index data required to pull out individual paths when given only a txid. In the example below we encode txids at indices 0 and 3:

index
txid

0

e86ec5732f55490a73677fe88a37c875cea49f572e4bc822b83fe96093bb008c

3

3b5a16dc41bbed3e58ad2a9017fb8954e7541975e2a4f37343761d96f431b3e5

By convention we reverse the bytes of a txid hex string so these sequences will be seen in their inverse endian form below.

Hex

020101cd73c0c6bb645581816fa960fd2f1636062fcbf23cb57981074ab8d708a76e3b02003470d882cf556a4b943639eba15dc795dffdbebdc98b9a98e3637fda96e3811e01c58e40f22b9e9fcd05a09689a9b19e6e62dbfd3335c5253d09a7a7cd755d9a3c04008c00bb9360e93fb822c84b2e579fa4ce75c8378ae87f67730a49552f73c56ee801da256f78ae0ad74bbf539662cdb9122aa02ba9a9d883f1d52468d96290515adb02b4c8d919190a090e77b73ffcd52b85babaaeeb62da000473102aca7f070facef03e5b331f4961d764373f3a4e2751954e75489fb17902aad583eedbb41dc165a3b

Bytewise Breakdown

02 // height = 2
01 // nLeafs at this height VarInt
// ----------------------
01 // offset VarInt
cd73c0c6bb645581816fa960fd2f1636062fcbf23cb57981074ab8d708a76e3b // 32 byte hash
// ----------------------
// implied end of leaves at this height
// height of next leaves is therefore 1
02 // nLeafs at this height VarInt
// ----------------------
00 // offset VarInt
3470d882cf556a4b943639eba15dc795dffdbebdc98b9a98e3637fda96e3811e // 32 byte hash
// ----------------------
01 // offset VarInt
c58e40f22b9e9fcd05a09689a9b19e6e62dbfd3335c5253d09a7a7cd755d9a3c // 32 byte hash
// ----------------------
// implied end of leaves at this height
// height of next leaves is therefore 0
04 // nLeafs at this height VarInt
// ----------------------
00 // offset VarInt
8c00bb9360e93fb822c84b2e579fa4ce75c8378ae87f67730a49552f73c56ee8 // 32 byte hash (this is the txid at index 0)
// ----------------------
01 // offset VarInt
da256f78ae0ad74bbf539662cdb9122aa02ba9a9d883f1d52468d96290515adb // 32 byte hash
// ----------------------
02 // offset VarInt
b4c8d919190a090e77b73ffcd52b85babaaeeb62da000473102aca7f070facef // 32 byte hash
// ----------------------
03 // offset VarInt
e5b331f4961d764373f3a4e2751954e75489fb17902aad583eedbb41dc165a3b // 32 byte hash (this is the txid at index 3)
// ----------------------
// implied end of data because new height would be -1

Implementation

Let's start by dumping this format as hex into a Buffer and parsing it into an object with a Buffer Reader. Then we construct an object

const { Br } = require('openspv')
const reader = new Br()
reader.buf = Buffer.from('020101cd73c0c6bb645581816fa960fd2f1636062fcbf23cb57981074ab8d708a76e3b02003470d882cf556a4b943639eba15dc795dffdbebdc98b9a98e3637fda96e3811e01c58e40f22b9e9fcd05a09689a9b19e6e62dbfd3335c5253d09a7a7cd755d9a3c04008c00bb9360e93fb822c84b2e579fa4ce75c8378ae87f67730a49552f73c56ee801da256f78ae0ad74bbf539662cdb9122aa02ba9a9d883f1d52468d96290515adb02b4c8d919190a090e77b73ffcd52b85babaaeeb62da000473102aca7f070facef03e5b331f4961d764373f3a4e2751954e75489fb17902aad583eedbb41dc165a3b', 'hex')

let maxHeight = parseInt(reader.read(1).toString('hex'), 16)
let compoundPath = Array(maxHeight + 1).fill(0).map(() => ({}))
let height = maxHeight
let x = 0
let nLeavesAtThisHeight = reader.readVarIntNum()
let startOfNextHeight = nLeavesAtThisHeight
while (height >= 0) {
  offset = reader.readVarIntNum()
  hash = reader.read(32).reverse().toString('hex')
  compoundPath[height][hash] = offset
  x++
  if (x == startOfNextHeight) {
    height--
    if (height < 0) break
    nLeavesAtThisHeight = reader.readVarIntNum()
    startOfNextHeight = nLeavesAtThisHeight + x
  }
}

console.log({ compoundPath })

JSON Encoding of a Compound Merkle Path

If we JSON encode the leaves we get the following. Height is encoded as the position of the leaf object within the outermost array.

[ // index within the outer array corresponds to the height
	{ // within each height there must be one or more hashes with their corresponding offsets { [hash]: offset }
		"e86ec5732f55490a73677fe88a37c875cea49f572e4bc822b83fe96093bb008c": 0, // txid at index 0
		"db5a519062d96824d5f183d8a9a92ba02a12b9cd629653bf4bd70aae786f25da": 1,
		"efac0f077fca2a10730400da62ebaebaba852bd5fc3fb7770e090a1919d9c8b4": 2,
		"3b5a16dc41bbed3e58ad2a9017fb8954e7541975e2a4f37343761d96f431b3e5": 3 // txid at index 3
	},
    {
		"1e81e396da7f63e3989a8bc9bdbefddf95c75da1eb3936944b6a55cf82d87034": 0,
		"3c9a5d75cda7a7093d25c53533fddb626e9eb1a98996a005cd9f9e2bf2408ec5": 1
	},
	{
		"3b6ea708d7b84a078179b53cf2cb2f0636162ffd60a96f81815564bbc6c073cd": 1
	}
]

You can derive individual paths for particular transaction indices as necessary using the following algorithm:

Reading index 3 from the Compound Merkle Path.


// let's say we want to derive the path for txid 3b5a16dc41bbed3e58ad2a9017fb8954e7541975e2a4f37343761d96f431b3e5 
// first we determine from the 0th array in the compound merkle path that the block index associated is 3.

const example = {
  index: 3,
  path: []
} 

compoundPath.map((leaves, height) => {
  const indexOffset = example.index >> height ^ 1
  for (const hash in leaves) {
    if (leaves[hash] === indexOffset) {
      example.path.push(hash)
      return true
    }
  }
  return Error(`We do not have a hash for this index at height: ${height}`)
})

Which yields:

{
  index: 3,
  path: [
    '3b6ea708d7b84a078179b53cf2cb2f0636162ffd60a96f81815564bbc6c073cd',
    '1e81e396da7f63e3989a8bc9bdbefddf95c75da1eb3936944b6a55cf82d87034',
    'efac0f077fca2a10730400da62ebaebaba852bd5fc3fb7770e090a1919d9c8b4'
  ]
}

Important to understand that we only kept the paths for indices 0 and 3 for this particular example. If you attempt to run the algo above for any other indices, an error would be thrown. This allowed us to keep 7 hashes out of 14 total. Keeping each path separately we'd have to keep minimum 6 hashes, and if we added another index then the compound method would only require one more hash, whereas saving individual paths would require another 3. The total bandwidth saving would be significant if we had thousands of transactions all in the same block.

Merkle Proof

We use this to prove inclusion in a block by running a Merkle Proof algorithm on the txid, index, and path. We arrive at a Merkle root hash. This can then be used as the key in a Block Header lookup to determine whether the txid is included within a block which is part of the longest chain of work.

// pseudocode
function merkleProof(txid, index, path) {
  try {
    const root = deriveRootFromPath(txid, index, path)
    const blockHeader = await lookupHeaderByRoot(root)
    if (blockHeader.state === 'LONGEST_CHAIN') return true
    else return false
  } catch (error) {
    console.log({ error })
    return false
  }
}