diff --git a/zip-0221.html b/zip-0221.html index ab92f52c..3a98e8be 100644 --- a/zip-0221.html +++ b/zip-0221.html @@ -362,7 +362,6 @@ License: MIT

Once the MMR has been generated, we produce hashChainHistoryRoot, which we define as the BLAKE2b-256 digest of the serialization of the root node.

Tree nodes and hashing (pseudocode)

-

Note that this pseudocode reflects the specification prior to NU5 activation.

def H(msg: bytes, consensusBranchId: bytes) -> bytes:
     return blake2b256(msg, personalization=b'ZcashHistory' + consensusBranchId)
 
@@ -377,12 +376,16 @@ License: MIT
nLatestTimestamp: int nEarliestTargetBits: int nLatestTargetBits: int - hashEarliestSaplingRoot: bytes # left child's sapling root - hashLatestSaplingRoot: bytes # right child's sapling root + hashEarliestSaplingRoot: bytes # left child's Sapling root + hashLatestSaplingRoot: bytes # right child's Sapling root nSubTreeTotalWork: int # total difficulty accumulated within each subtree nEarliestHeight: int nLatestHeight: int nSaplingTxCount: int # number of Sapling transactions in block + # NU5 only. + hashEarliestOrchardRoot: Optional[bytes] # left child's Orchard root + hashLatestOrchardRoot: Optional[bytes] # right child's Orchard root + nSaplingTxCount: Optional[int] # number of Orchard transactions in block consensusBranchId: bytes @@ -403,12 +406,14 @@ License: MIT nEarliestHeight=block.height, nLatestHeight=block.height, nSaplingTxCount=block.sapling_tx_count, + hashEarliestOrchardRoot=block.orchard_root, + hashLatestOrchardRoot=block.orchard_root, + nOrchardTxCount=block.orchard_tx_count, consensusBranchId=block.consensusBranchId) def serialize(self) -> bytes: '''serializes a node''' - return ( - self.hashSubtreeCommitment + buf = (self.hashSubtreeCommitment + serialize_uint32(self.nEarliestTimestamp) + serialize_uint32(self.nLatestTimestamp) + serialize_uint32(self.nEarliestTargetBits) @@ -419,6 +424,11 @@ License: MIT + serialize_compact_uint(self.nEarliestHeight) + serialize_compact_uint(self.nLatestHeight) + serialize_compact_uint(self.nSaplingTxCount)) + if hashEarliestOrchardRoot is not None: + buf += (hashEarliestOrchardRoot + + hashLatestOrchardRoot + + serialize_compact_uint(self.nOrchardTxCount)) + return buf def make_parent( @@ -439,6 +449,11 @@ License: MIT nEarliestHeight=left_child.nEarliestHeight, nLatestHeight=right_child.nLatestHeight, nSaplingTxCount=left_child.nSaplingTxCount + right_child.nSaplingTxCount, + hashEarliestOrchardRoot=left_child.orchard_root, + hashLatestOrchardRoot=right_child.orchard_root, + nOrchardTxCount=(left_child.nOrchardTxCount + right_child.nOrchardTxCount + if left_child.nOrchardTxCount is not None and right_child.nOrchardTxCount is not None + else None), consensusBranchId=left_child.consensusBranchId) def make_root_commitment(root: ZcashMMRNode) -> bytes: @@ -556,7 +571,7 @@ License: MIT

Rationale

Tree nodes

Nodes in the commitment tree are canonical and immutable. They are cheap to generate, as (with the exception of nSaplingTxCount and nOrchardTxCount) all metadata is already generated during block construction and/or checked during block validation. Nodes are relatively compact in memory. As of the original publication of this ZIP, approximately 140,000 blocks had elapsed since Sapling activation. Assuming a 164-byte commitment to each of these, we would have generated approximately 24 MB of additional storage cost for the set of leaf nodes (and an additional ~24 MB for storage of intermediate nodes).

-

hashSubtreeCommitment forms the strucuture of the commitment tree. Other metadata commitments were chosen to serve specific purposes. Originally variable-length commitments were placed last, so that most metadata in a node could be directly indexed. We considered using fixed-length commitments here, but opted for variable-length, in order to marginally reduce the memory requirements for managing and updating the commitment trees.

+

hashSubtreeCommitment forms the structure of the commitment tree. Other metadata commitments were chosen to serve specific purposes. Originally variable-length commitments were placed last, so that most metadata in a node could be directly indexed. We considered using fixed-length commitments here, but opted for variable-length, in order to marginally reduce the memory requirements for managing and updating the commitment trees.

Orchard fields are placed last, in order to avoid complicating existing uses of the other fields.

In leaf nodes, some information is repeated. We chose to do this so that leaf nodes could be treated identically to internal and root nodes for all algorithms and (de)serializers. Leaf nodes are easily identifiable, as they will show proof of work in the hashSubtreeCommitment field (which commits to the block hash for leaf nodes), and their block range (calculated as nLatestHeight - (nEarliestHeight - 1)) will be precisely 1.

Personalized BLAKE2b-256 was selected to match existing Zcash conventions. Adding the consensus branch ID to the hash personalization string ensures that valid nodes from one consensus branch cannot be used to make false statements about parallel consensus branches.

diff --git a/zip-0221.rst b/zip-0221.rst index ab17e2f4..7a7e7ad7 100644 --- a/zip-0221.rst +++ b/zip-0221.rst @@ -376,8 +376,6 @@ the BLAKE2b-256 digest of the serialization of the root node. Tree nodes and hashing (pseudocode) ----------------------------------- -Note that this pseudocode reflects the specification prior to NU5 activation. - .. code-block:: python def H(msg: bytes, consensusBranchId: bytes) -> bytes: @@ -394,12 +392,16 @@ Note that this pseudocode reflects the specification prior to NU5 activation. nLatestTimestamp: int nEarliestTargetBits: int nLatestTargetBits: int - hashEarliestSaplingRoot: bytes # left child's sapling root - hashLatestSaplingRoot: bytes # right child's sapling root + hashEarliestSaplingRoot: bytes # left child's Sapling root + hashLatestSaplingRoot: bytes # right child's Sapling root nSubTreeTotalWork: int # total difficulty accumulated within each subtree nEarliestHeight: int nLatestHeight: int nSaplingTxCount: int # number of Sapling transactions in block + # NU5 only. + hashEarliestOrchardRoot: Optional[bytes] # left child's Orchard root + hashLatestOrchardRoot: Optional[bytes] # right child's Orchard root + nSaplingTxCount: Optional[int] # number of Orchard transactions in block consensusBranchId: bytes @@ -420,12 +422,14 @@ Note that this pseudocode reflects the specification prior to NU5 activation. nEarliestHeight=block.height, nLatestHeight=block.height, nSaplingTxCount=block.sapling_tx_count, + hashEarliestOrchardRoot=block.orchard_root, + hashLatestOrchardRoot=block.orchard_root, + nOrchardTxCount=block.orchard_tx_count, consensusBranchId=block.consensusBranchId) def serialize(self) -> bytes: '''serializes a node''' - return ( - self.hashSubtreeCommitment + buf = (self.hashSubtreeCommitment + serialize_uint32(self.nEarliestTimestamp) + serialize_uint32(self.nLatestTimestamp) + serialize_uint32(self.nEarliestTargetBits) @@ -436,6 +440,11 @@ Note that this pseudocode reflects the specification prior to NU5 activation. + serialize_compact_uint(self.nEarliestHeight) + serialize_compact_uint(self.nLatestHeight) + serialize_compact_uint(self.nSaplingTxCount)) + if hashEarliestOrchardRoot is not None: + buf += (hashEarliestOrchardRoot + + hashLatestOrchardRoot + + serialize_compact_uint(self.nOrchardTxCount)) + return buf def make_parent( @@ -456,6 +465,11 @@ Note that this pseudocode reflects the specification prior to NU5 activation. nEarliestHeight=left_child.nEarliestHeight, nLatestHeight=right_child.nLatestHeight, nSaplingTxCount=left_child.nSaplingTxCount + right_child.nSaplingTxCount, + hashEarliestOrchardRoot=left_child.orchard_root, + hashLatestOrchardRoot=right_child.orchard_root, + nOrchardTxCount=(left_child.nOrchardTxCount + right_child.nOrchardTxCount + if left_child.nOrchardTxCount is not None and right_child.nOrchardTxCount is not None + else None), consensusBranchId=left_child.consensusBranchId) def make_root_commitment(root: ZcashMMRNode) -> bytes: @@ -600,7 +614,7 @@ relatively compact in memory. As of the original publication of this ZIP, approx each of these, we would have generated approximately 24 MB of additional storage cost for the set of leaf nodes (and an additional ~24 MB for storage of intermediate nodes). -``hashSubtreeCommitment`` forms the strucuture of the commitment tree. Other metadata +``hashSubtreeCommitment`` forms the structure of the commitment tree. Other metadata commitments were chosen to serve specific purposes. Originally variable-length commitments were placed last, so that most metadata in a node could be directly indexed. We considered using fixed-length commitments here, but opted for variable-length, in order to marginally