Skip to content

Commit

Permalink
fix: reduce calls to MinNamespace & MaxNamespace
Browse files Browse the repository at this point in the history
  • Loading branch information
bruscar010 committed Jan 16, 2025
1 parent 2fb6332 commit 05e3447
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 90 deletions.
82 changes: 46 additions & 36 deletions hasher.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,9 @@ func (n *NmtHasher) BlockSize() int {

func (n *NmtHasher) EmptyRoot() []byte {
n.baseHasher.Reset()
emptyNs := bytes.Repeat([]byte{0}, int(n.NamespaceLen))
emptyNs := bytes.Repeat([]byte{0}, int(n.NamespaceLen)*2)
h := n.baseHasher.Sum(nil)
digest := append(append(emptyNs, emptyNs...), h...)
digest := append(emptyNs, h...)

return digest
}
Expand Down Expand Up @@ -212,56 +212,68 @@ func (n *NmtHasher) MustHashLeaf(ndata []byte) []byte {
return res
}

// ValidateNodeFormat checks whether the supplied node conforms to the
// namespaced hash format and returns ErrInvalidNodeLen if not.
func (n *NmtHasher) ValidateNodeFormat(node []byte) (err error) {
type nsIDRange struct {
Min, Max namespace.ID
}

// tryFetchNodeNSRange attempts to return the min and max namespace ids.
// It will return an ErrInvalidNodeLen | ErrInvalidNodeNamespaceOrder
// if the supplied node does not conform to the namespaced hash format.
func (n *NmtHasher) tryFetchNodeNSRange(node []byte) (nsIDRange, error) {
expectedNodeLen := n.Size()
nodeLen := len(node)
if nodeLen != expectedNodeLen {
return fmt.Errorf("%w: got: %v, want %v", ErrInvalidNodeLen, nodeLen, expectedNodeLen)
return nsIDRange{}, fmt.Errorf("%w: got: %v, want %v", ErrInvalidNodeLen, nodeLen, expectedNodeLen)
}
// check the namespace order
minNID := namespace.ID(MinNamespace(node, n.NamespaceSize()))
maxNID := namespace.ID(MaxNamespace(node, n.NamespaceSize()))
if maxNID.Less(minNID) {
return fmt.Errorf("%w: max namespace ID %d is less than min namespace ID %d ", ErrInvalidNodeNamespaceOrder, maxNID, minNID)
return nsIDRange{}, fmt.Errorf("%w: max namespace ID %d is less than min namespace ID %d ", ErrInvalidNodeNamespaceOrder, maxNID, minNID)
}
return nil
return nsIDRange{Min: minNID, Max: maxNID}, nil
}

// ValidateNodeFormat checks whether the supplied node conforms to the
// namespaced hash format and returns ErrInvalidNodeLen if not.
func (n *NmtHasher) ValidateNodeFormat(node []byte) (err error) {
_, err = n.tryFetchNodeNSRange(node)
return
}

// validateSiblingsNamespaceOrder checks whether left and right as two sibling
// nodes in an NMT have correct namespace IDs relative to each other, more
// specifically, the maximum namespace ID of the left sibling should not exceed
// the minimum namespace ID of the right sibling. It returns ErrUnorderedSiblings error if the check fails.
func (n *NmtHasher) validateSiblingsNamespaceOrder(left, right []byte) (err error) {
if err := n.ValidateNodeFormat(left); err != nil {
return fmt.Errorf("%w: left node does not match the namesapce hash format", err)
// tryFetchLeftAndRightNSRange attempts to return the min/max namespace ids of both
// the left and right nodes. It verifies whether left
// and right comply by the namespace hash format, and are correctly ordered
// according to their namespace IDs.
func (n *NmtHasher) tryFetchLeftAndRightNSRanges(left, right []byte) (
lNsRange nsIDRange,
rNsRange nsIDRange,
err error,
) {
lNsRange, err = n.tryFetchNodeNSRange(left)
if err != nil {
return
}
if err := n.ValidateNodeFormat(right); err != nil {
return fmt.Errorf("%w: right node does not match the namesapce hash format", err)
rNsRange, err = n.tryFetchNodeNSRange(right)
if err != nil {
return
}
leftMaxNs := namespace.ID(MaxNamespace(left, n.NamespaceSize()))
rightMinNs := namespace.ID(MinNamespace(right, n.NamespaceSize()))

// check the namespace range of the left and right children
if rightMinNs.Less(leftMaxNs) {
return fmt.Errorf("%w: the maximum namespace of the left child %x is greater than the min namespace of the right child %x", ErrUnorderedSiblings, leftMaxNs, rightMinNs)
if rNsRange.Min.Less(lNsRange.Max) {
err = fmt.Errorf("%w: the maximum namespace of the left child %x is greater than the min namespace of the right child %x", ErrUnorderedSiblings, lNsRange.Max, rNsRange.Min)
return
}
return nil
return
}

// ValidateNodes is a helper function to verify the
// validity of the inputs of HashNode. It verifies whether left
// and right comply by the namespace hash format, and are correctly ordered
// according to their namespace IDs.
func (n *NmtHasher) ValidateNodes(left, right []byte) error {
if err := n.ValidateNodeFormat(left); err != nil {
return err
}
if err := n.ValidateNodeFormat(right); err != nil {
return err
}
return n.validateSiblingsNamespaceOrder(left, right)
_, _, err := n.tryFetchLeftAndRightNSRanges(left, right)
return err
}

// HashNode calculates a namespaced hash of a node using the supplied left and
Expand All @@ -278,21 +290,19 @@ func (n *NmtHasher) ValidateNodes(left, right []byte) error {
// If the namespace range of the right child is start=end=MAXNID, indicating that it represents the root of a subtree whose leaves all have the namespace ID of `MAXNID`, then exclude the right child from the namespace range calculation. Instead,
// assign the namespace range of the left child as the parent's namespace range.
func (n *NmtHasher) HashNode(left, right []byte) ([]byte, error) {
// validate the inputs
if err := n.ValidateNodes(left, right); err != nil {
// validate the inputs & fetch the namespace ranges
lRange, rRange, err := n.tryFetchLeftAndRightNSRanges(left, right)
if err != nil {
return nil, err
}

h := n.baseHasher
h.Reset()

leftMinNs, leftMaxNs := MinNamespace(left, n.NamespaceLen), MaxNamespace(left, n.NamespaceLen)
rightMinNs, rightMaxNs := MinNamespace(right, n.NamespaceLen), MaxNamespace(right, n.NamespaceLen)

// compute the namespace range of the parent node
minNs, maxNs := computeNsRange(leftMinNs, leftMaxNs, rightMinNs, rightMaxNs, n.ignoreMaxNs, n.precomputedMaxNs)
minNs, maxNs := computeNsRange(lRange.Min, lRange.Max, rRange.Min, rRange.Max, n.ignoreMaxNs, n.precomputedMaxNs)

res := make([]byte, 0)
res := make([]byte, 0, len(minNs)*2)
res = append(res, minNs...)
res = append(res, maxNs...)

Expand Down
50 changes: 0 additions & 50 deletions hasher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,56 +283,6 @@ func TestHashNode_Error(t *testing.T) {
}
}

func TestValidateSiblings(t *testing.T) {
// create a dummy hash to use as the digest of the left and right child
randHash := createByteSlice(sha256.Size, 0x01)

type children struct {
l []byte // namespace hash of the left child with the format of MinNs||MaxNs||h
r []byte // namespace hash of the right child with the format of MinNs||MaxNs||h
}

tests := []struct {
name string
nidLen namespace.IDSize
children children
wantErr bool
}{
{
"wrong left node format", 2,
children{concat([]byte{0, 0, 1, 1}, randHash[:len(randHash)-1]), concat([]byte{0, 0, 1, 1}, randHash)},
true,
},
{
"wrong right node format", 2,
children{concat([]byte{0, 0, 1, 1}, randHash), concat([]byte{0, 0, 1, 1}, randHash[:len(randHash)-1])},
true,
},
{
"left.maxNs>right.minNs", 2,
children{concat([]byte{0, 0, 1, 1}, randHash), concat([]byte{0, 0, 1, 1}, randHash)},
true,
},
{
"left.maxNs=right.minNs", 2,
children{concat([]byte{0, 0, 1, 1}, randHash), concat([]byte{1, 1, 2, 2}, randHash)},
false,
},
{
"left.maxNs<right.minNs", 2,
children{concat([]byte{0, 0, 1, 1}, randHash), concat([]byte{2, 2, 3, 3}, randHash)},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
n := NewNmtHasher(sha256.New(), tt.nidLen, false)
err := n.validateSiblingsNamespaceOrder(tt.children.l, tt.children.r)
assert.Equal(t, tt.wantErr, err != nil)
})
}
}

func TestValidateNodeFormat(t *testing.T) {
hashValue := createByteSlice(sha256.Size, 0x01)
minNID := createByteSlice(2, 0x00)
Expand Down
6 changes: 2 additions & 4 deletions nmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -682,16 +682,14 @@ type LeafRange struct {
// which is formatted as: minimum namespace ID || maximum namespace ID || hash
// digest.
func MinNamespace(hash []byte, size namespace.IDSize) []byte {
min := make([]byte, 0, size)
return append(min, hash[:size]...)
return hash[:size:size]
}

// MaxNamespace extracts the maximum namespace ID from a given namespace hash,
// which is formatted as: minimum namespace ID || maximum namespace ID || hash
// digest.
func MaxNamespace(hash []byte, size namespace.IDSize) []byte {
max := make([]byte, 0, size)
return append(max, hash[size:size*2]...)
return hash[size : size*2 : size*2]
}

// Size returns the number of leaves in the tree.
Expand Down
1 change: 1 addition & 0 deletions nmt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,7 @@ func BenchmarkComputeRoot(b *testing.B) {
{"64-leaves", 64, 8, 256},
{"128-leaves", 128, 8, 256},
{"256-leaves", 256, 8, 256},
//{"20k-leaves", 20000, 8, 512},
}

for _, tt := range tests {
Expand Down

0 comments on commit 05e3447

Please sign in to comment.