MerkleBlockAccumulator Class
Definition
- Namespace
- Bodu.Security.Cryptography
- Assembly
- Bodu.Security.Cryptography.dll
- Package
- Bodu.Security.Cryptography 1.2.0
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
HashLength
Gets the length, in bytes, of every leaf hash and of the root.
public int HashLength { get; }
Property Value
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
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
Finishmember has hashed it.
Length
Gets the number of bytes appended since construction or the last Reset().
public long Length { get; }
Property Value
RetainsLeafHashes
Gets a value indicating whether every leaf hash is being kept, so FinishComputation() is available.
public bool RetainsLeafHashes { get; }
Property Value
Methods
Append(ReadOnlySpan<byte>)
Appends bytes to the input, hashing every leaf block they complete.
public void Append(ReadOnlySpan<byte> source)
Parameters
sourceReadOnlySpan<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
Finishmember 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
| Product | Versions |
|---|---|
| .NET | 8, 10 |