Table of Contents

MerkleBlockAccumulator Class

Definition

Namespace
Bodu.Security.Cryptography
Assembly
Bodu.Security.Cryptography.dll
Package
Bodu.Security.Cryptography 1.2.0
Source
MerkleBlockAccumulator.cs

Accumulates a byte stream into fixed-size Merkle leaves as it is written, so a root can be produced from the same calls that already feed a flat digest - without a second pass over the input.

public sealed class MerkleBlockAccumulator : IDisposable
Inheritance
MerkleBlockAccumulator
Implements
Inherited Members
Extension Methods

Examples

var tree = new MerkleTree(SHA256.Create);
using MerkleBlockAccumulator merkle = tree.CreateBlockAccumulator(blockSize: 1 << 20);
using IncrementalHash digest = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);

while (TryReadChunk(out ReadOnlySpan<byte> chunk))
{
    digest.AppendData(chunk);   // the flat digest the index already carries
    merkle.Append(chunk);       // the Merkle root, from the same bytes, in the same pass
}

byte[] flatDigest = digest.GetHashAndReset();
byte[] boundRoot = merkle.FinishBound();   // H(0x02 || u64_be(length) || MTH)

Remarks

A writer that streams bytes to storage typically feeds them to an incremental digest as they go. This type gives the Merkle root the same shape: Append(ReadOnlySpan<byte>) takes bytes in whatever sizes they arrive, re-blocks them into BlockSize-byte leaves internally, and folds each completed leaf into the tree at once. The root is therefore independent of how the input was split across calls, and equals the root MerkleTree.ComputeRootOfBlocks computes over the same bytes at the same block size.

Memory is one block plus one pending hash per tree level - logarithmic in the leaf count - unless RetainsLeafHashes was requested, in which case every leaf hash is kept so FinishComputation() can hand back a MerkleBlockComputation for building authentication paths. A final short block is hashed at its actual length, never padded, and an input that ends exactly on a block boundary produces no empty leaf. An empty input folds to the empty tree's root, H().

Lifecycle. Append until the input ends, then call one of the Finish members - any number of times, they return the same result - and Reset() to start over with the same algorithm and buffer. Appending after a finish throws; a reset clears the finished state. An instance owns one HashAlgorithm from the tree's factory and is used from one thread at a time; dispose it when done. A MerkleTreeDiagnostics supplied at creation keeps recording across resets, so use a fresh accumulator when a clean trace is wanted.

Properties

BlockSize

Gets the size, in bytes, of each leaf block.

public int BlockSize { get; }

Property Value

int

HashLength

Gets the length, in bytes, of every leaf hash and of the root.

public int HashLength { get; }

Property Value

int

IsFinished

Gets a value indicating whether a Finish member has been called since construction or the last Reset(), after which Append(ReadOnlySpan<byte>) is refused.

public bool IsFinished { get; }

Property Value

bool

LeafCount

Gets the number of leaves hashed so far.

public long LeafCount { get; }

Property Value

long

The number of completed blocks; a partially filled final block is counted only once a Finish member has hashed it.

Length

Gets the number of bytes appended since construction or the last Reset().

public long Length { get; }

Property Value

long

RetainsLeafHashes

Gets a value indicating whether every leaf hash is being kept, so FinishComputation() is available.

public bool RetainsLeafHashes { get; }

Property Value

bool

Methods

Append(ReadOnlySpan<byte>)

Appends bytes to the input, hashing every leaf block they complete.

public void Append(ReadOnlySpan<byte> source)

Parameters

source ReadOnlySpan<byte>

The next bytes of the input, in any size.

Remarks

Bytes are copied into the block being filled and hashed only when it is full, so a partial block is never emitted mid-stream and the leaves are the same however the input was chunked. An empty span is accepted and changes nothing.

Exceptions

ObjectDisposedException

The accumulator has been disposed.

InvalidOperationException

A Finish member has already been called; call Reset() to start a new computation.

Dispose()

Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.

public void Dispose()

Remarks

Disposes the owned HashAlgorithm and clears the block buffer. Every other member then throws ObjectDisposedException; disposing again does nothing.

Finish()

Hashes any partial final block and returns the tree's root.

public byte[] Finish()

Returns

byte[]

The root; the empty tree's root, H(), when nothing was appended.

Remarks

The first call completes the computation; later calls return the same root without further work. The root is identical to MerkleTree.ComputeRootOfBlocks over the same bytes at BlockSize.

Exceptions

ObjectDisposedException

The accumulator has been disposed.

FinishBound()

Finishes the computation and returns the root bound to the input's byte length, H(0x02 || u64_be(length) || root).

public byte[] FinishBound()

Returns

byte[]

The length-bound root.

Remarks

This is the commitment to publish when a verifier will later be told the input's length by a party it does not trust: VerifyBlockInclusion(ReadOnlySpan<byte>, long, int, long, ReadOnlySpan<byte>, IReadOnlyList<ReadOnlyMemory<byte>>) derives the tree size from the bound length, so a misstated length fails closed. It is exactly BindRoot(ReadOnlySpan<byte>, long) applied to Finish() and Length.

Exceptions

ObjectDisposedException

The accumulator has been disposed.

FinishComputation()

Finishes the computation and returns it together with the retained leaf hashes, so authentication paths can be built without a second pass over the input.

public MerkleBlockComputation FinishComputation()

Returns

MerkleBlockComputation

The root, input length, block size and ordered leaf hashes.

Remarks

The result is what MerkleTree.ComputeBlocked would have returned over the same bytes. A subsequent Reset() does not disturb it: the returned computation keeps its own list.

Exceptions

ObjectDisposedException

The accumulator has been disposed.

InvalidOperationException

The accumulator was created without retainLeafHashes, so no leaf hashes were kept.

Reset()

Discards the current input and any finished root so the accumulator can take a new input.

public void Reset()

Remarks

The hash algorithm and the block buffer are reused; the fold and, when leaf hashes are retained, the list are replaced, so a MerkleBlockComputation already handed out is unaffected.

Exceptions

ObjectDisposedException

The accumulator has been disposed.

Applies to

ProductVersions
.NET8, 10

See Also