Skip to content

Commit

Permalink
Merge branch 'master' into rp/go-1.21
Browse files Browse the repository at this point in the history
  • Loading branch information
rootulp authored Aug 28, 2023
2 parents 893960a + 3311036 commit e9978af
Show file tree
Hide file tree
Showing 13 changed files with 1,128 additions and 85 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ func main() {
}
}
// compute the root
root := tree.Root()
root, err := tree.Root()
if err != nil {
panic(fmt.Sprintf("unexpected error: %v", err))
}
// the root's min/max namespace is the min and max namespace of all leaves:
minNS := nmt.MinNamespace(root, tree.NamespaceSize())
maxNS := nmt.MaxNamespace(root, tree.NamespaceSize())
Expand Down
21 changes: 11 additions & 10 deletions docs/nmt-lib.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,22 +67,22 @@ func (n *NamespacedMerkleTree) Push(namespacedData namespace.PrefixedData) error
E.g.,

```go
d := append(namespace.ID{0}, []byte("leaf_0")...) // the first `tree.NamespaceSize()` bytes of each data item is treated as its namespace ID.
d := namespace.PrefixedData(append(namespace.ID{0}, []byte("leaf_0")...)) // the first `tree.NamespaceSize()` bytes of each data item is treated as its namespace.
if err := tree.Push(d); err != nil {
// something went wrong
// something went wrong
}
// add a few more data items
d1 := append(namespace.ID{0}, []byte("leaf_1")...)
d1 := namespace.PrefixedData(append(namespace.ID{0}, []byte("leaf_1")...))
if err := tree.Push(d1); err != nil {
// something went wrong
// something went wrong
}
d2 := append(namespace.ID{1}, []byte("leaf_2")...)
d2 := namespace.PrefixedData(append(namespace.ID{1}, []byte("leaf_2")...))
if err := tree.Push(d2); err != nil {
// something went wrong
// something went wrong
}
d3 := append(namespace.ID{3}, []byte("leaf_3")...)
d3 := namespace.PrefixedData(append(namespace.ID{3}, []byte("leaf_3")...))
if err := tree.Push(d3); err != nil {
// something went wrong
// something went wrong
}
```

Expand Down Expand Up @@ -116,16 +116,17 @@ Figure 1.
## Get Root

The `Root()` method calculates the NMT root based on the data that has been added through the use of the `Push` method.
The root value is valid if the method does not return an error.

```go
func (n *NamespacedMerkleTree) Root() []byte
func (n *NamespacedMerkleTree) Root() ([]byte, error)
```

For example:

```go
// compute the root
root := tree.Root()
root, err := tree.Root()
```

In the provided code example, the root would be `00 03 b1c2cc5` (as also illustrated in Figure 1).
Expand Down
140 changes: 107 additions & 33 deletions docs/spec/nmt.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/celestiaorg/nmt
go 1.21

require (
github.com/gogo/protobuf v1.3.2
github.com/google/gofuzz v1.2.0
github.com/stretchr/testify v1.8.1
github.com/tidwall/gjson v1.14.4
Expand Down
31 changes: 31 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand All @@ -19,6 +23,33 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
55 changes: 36 additions & 19 deletions hasher.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const (
NodePrefix = 1
)

var _ hash.Hash = (*Hasher)(nil)
var _ hash.Hash = (*NmtHasher)(nil)

var (
ErrUnorderedSiblings = errors.New("NMT sibling nodes should be ordered lexicographically by namespace IDs")
Expand All @@ -23,7 +23,24 @@ var (
ErrInvalidNodeNamespaceOrder = errors.New("invalid NMT node namespace order")
)

type Hasher struct {
// Hasher describes the interface nmts use to hash leafs and nodes.
//
// Note: it is not advised to create alternative hashers if following the
// specification is desired. The main reason this exists is to not follow the
// specification for testing purposes.
type Hasher interface {
IsMaxNamespaceIDIgnored() bool
NamespaceSize() namespace.IDSize
HashLeaf(data []byte) ([]byte, error)
HashNode(leftChild, rightChild []byte) ([]byte, error)
EmptyRoot() []byte
}

var _ Hasher = &NmtHasher{}

// NmtHasher is the default hasher. It follows the description of the original
// hashing function described in the LazyLedger white paper.
type NmtHasher struct { //nolint:revive
baseHasher hash.Hash
NamespaceLen namespace.IDSize

Expand All @@ -41,16 +58,16 @@ type Hasher struct {
data []byte // written data of the NMT node
}

func (n *Hasher) IsMaxNamespaceIDIgnored() bool {
func (n *NmtHasher) IsMaxNamespaceIDIgnored() bool {
return n.ignoreMaxNs
}

func (n *Hasher) NamespaceSize() namespace.IDSize {
func (n *NmtHasher) NamespaceSize() namespace.IDSize {
return n.NamespaceLen
}

func NewNmtHasher(baseHasher hash.Hash, nidLen namespace.IDSize, ignoreMaxNamespace bool) *Hasher {
return &Hasher{
func NewNmtHasher(baseHasher hash.Hash, nidLen namespace.IDSize, ignoreMaxNamespace bool) *NmtHasher {
return &NmtHasher{
baseHasher: baseHasher,
NamespaceLen: nidLen,
ignoreMaxNs: ignoreMaxNamespace,
Expand All @@ -59,7 +76,7 @@ func NewNmtHasher(baseHasher hash.Hash, nidLen namespace.IDSize, ignoreMaxNamesp
}

// Size returns the number of bytes Sum will return.
func (n *Hasher) Size() int {
func (n *NmtHasher) Size() int {
return n.baseHasher.Size() + int(n.NamespaceLen)*2
}

Expand All @@ -69,7 +86,7 @@ func (n *Hasher) Size() int {
// write is allowed.
// It panics if more than one single write is attempted.
// If the data does not match the format of an NMT non-leaf node or leaf node, an error will be returned.
func (n *Hasher) Write(data []byte) (int, error) {
func (n *NmtHasher) Write(data []byte) (int, error) {
if n.data != nil {
panic("only a single Write is allowed")
}
Expand Down Expand Up @@ -101,7 +118,7 @@ func (n *Hasher) Write(data []byte) (int, error) {
// Sum computes the hash. Does not append the given suffix, violating the
// interface.
// It may panic if the data being hashed is invalid. This should never happen since the Write method refuses an invalid data and errors out.
func (n *Hasher) Sum([]byte) []byte {
func (n *NmtHasher) Sum([]byte) []byte {
switch n.tp {
case LeafPrefix:
res, err := n.HashLeaf(n.data)
Expand All @@ -125,17 +142,17 @@ func (n *Hasher) Sum([]byte) []byte {
}

// Reset resets the Hash to its initial state.
func (n *Hasher) Reset() {
func (n *NmtHasher) Reset() {
n.tp, n.data = 255, nil // reset with an invalid node type, as zero value is a valid Leaf
n.baseHasher.Reset()
}

// BlockSize returns the hash's underlying block size.
func (n *Hasher) BlockSize() int {
func (n *NmtHasher) BlockSize() int {
return n.baseHasher.BlockSize()
}

func (n *Hasher) EmptyRoot() []byte {
func (n *NmtHasher) EmptyRoot() []byte {
n.baseHasher.Reset()
emptyNs := bytes.Repeat([]byte{0}, int(n.NamespaceLen))
h := n.baseHasher.Sum(nil)
Expand All @@ -145,7 +162,7 @@ func (n *Hasher) EmptyRoot() []byte {
}

// ValidateLeaf verifies if data is namespaced and returns an error if not.
func (n *Hasher) ValidateLeaf(data []byte) (err error) {
func (n *NmtHasher) ValidateLeaf(data []byte) (err error) {
nidSize := int(n.NamespaceSize())
lenData := len(data)
if lenData < nidSize {
Expand All @@ -160,7 +177,7 @@ func (n *Hasher) ValidateLeaf(data []byte) (err error) {
// leaves minNs = maxNs = ns(leaf) = leaf[:NamespaceLen]. HashLeaf can return the ErrInvalidNodeLen error if the input is not namespaced.
//
//nolint:errcheck
func (n *Hasher) HashLeaf(ndata []byte) ([]byte, error) {
func (n *NmtHasher) HashLeaf(ndata []byte) ([]byte, error) {
h := n.baseHasher
h.Reset()

Expand All @@ -187,7 +204,7 @@ func (n *Hasher) HashLeaf(ndata []byte) ([]byte, error) {

// MustHashLeaf is a wrapper around HashLeaf that panics if an error is
// encountered. The ndata must be a valid leaf node.
func (n *Hasher) MustHashLeaf(ndata []byte) []byte {
func (n *NmtHasher) MustHashLeaf(ndata []byte) []byte {
res, err := n.HashLeaf(ndata)
if err != nil {
panic(err)
Expand All @@ -197,7 +214,7 @@ func (n *Hasher) MustHashLeaf(ndata []byte) []byte {

// ValidateNodeFormat checks whether the supplied node conforms to the
// namespaced hash format and returns ErrInvalidNodeLen if not.
func (n *Hasher) ValidateNodeFormat(node []byte) (err error) {
func (n *NmtHasher) ValidateNodeFormat(node []byte) (err error) {
expectedNodeLen := n.Size()
nodeLen := len(node)
if nodeLen != expectedNodeLen {
Expand All @@ -216,7 +233,7 @@ func (n *Hasher) ValidateNodeFormat(node []byte) (err error) {
// 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 *Hasher) validateSiblingsNamespaceOrder(left, right []byte) (err error) {
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)
}
Expand All @@ -237,7 +254,7 @@ func (n *Hasher) validateSiblingsNamespaceOrder(left, right []byte) (err error)
// 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 *Hasher) ValidateNodes(left, right []byte) error {
func (n *NmtHasher) ValidateNodes(left, right []byte) error {
if err := n.ValidateNodeFormat(left); err != nil {
return err
}
Expand All @@ -260,7 +277,7 @@ func (n *Hasher) ValidateNodes(left, right []byte) error {
// slightly changes. Let MAXNID be the maximum possible namespace ID value i.e., 2^NamespaceIDSize-1.
// 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 *Hasher) HashNode(left, right []byte) ([]byte, error) {
func (n *NmtHasher) HashNode(left, right []byte) ([]byte, error) {
// validate the inputs
if err := n.ValidateNodes(left, right); err != nil {
return nil, err
Expand Down
4 changes: 2 additions & 2 deletions hasher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ func TestWrite_Err(t *testing.T) {

tests := []struct {
name string
hasher *Hasher
hasher *NmtHasher
data []byte
wantErr bool
errType error
Expand Down Expand Up @@ -639,7 +639,7 @@ func TestSum_Err(t *testing.T) {

tests := []struct {
name string
hasher *Hasher
hasher *NmtHasher
data []byte
nodeType byte
wantWriteErr bool
Expand Down
44 changes: 41 additions & 3 deletions nmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Options struct {
// in the "Hasher.
IgnoreMaxNamespace bool
NodeVisitor NodeVisitorFn
Hasher Hasher
}

type Option func(*Options)
Expand Down Expand Up @@ -82,8 +83,15 @@ func NodeVisitor(nodeVisitorFn NodeVisitorFn) Option {
}
}

// CustomHasher replaces the default hasher.
func CustomHasher(h Hasher) Option {
return func(o *Options) {
o.Hasher = h
}
}

type NamespacedMerkleTree struct {
treeHasher *Hasher
treeHasher Hasher
visit NodeVisitorFn

// just cache stuff until we pass in a store and keep all nodes in there
Expand Down Expand Up @@ -128,9 +136,18 @@ func New(h hash.Hash, setters ...Option) *NamespacedMerkleTree {
for _, setter := range setters {
setter(opts)
}
treeHasher := NewNmtHasher(h, opts.NamespaceIDSize, opts.IgnoreMaxNamespace)

// first create the default hasher using the updated options
hasher := NewNmtHasher(h, opts.NamespaceIDSize, opts.IgnoreMaxNamespace)
opts.Hasher = hasher

// set the options a second time to replace the hasher if needed
for _, setter := range setters {
setter(opts)
}

return &NamespacedMerkleTree{
treeHasher: treeHasher,
treeHasher: opts.Hasher,
visit: opts.NodeVisitor,
leaves: make([][]byte, 0, opts.InitialCapacity),
leafHashes: make([][]byte, 0, opts.InitialCapacity),
Expand Down Expand Up @@ -491,6 +508,27 @@ func (n *NamespacedMerkleTree) MaxNamespace() (namespace.ID, error) {
return MaxNamespace(r, n.NamespaceSize()), nil
}

// ForceAddLeaf adds a namespaced data to the tree without validating its
// namespace ID. This method should only be used by tests that are attempting to
// create out of order trees. The default hasher will fail for trees that are
// out of order.
func (n *NamespacedMerkleTree) ForceAddLeaf(leaf namespace.PrefixedData) error {
nID := namespace.ID(leaf[:n.NamespaceSize()])
// compute the leaf hash
res, err := n.treeHasher.HashLeaf(leaf)
if err != nil {
return err
}

// update relevant "caches":
n.leaves = append(n.leaves, leaf)
n.leafHashes = append(n.leafHashes, res)
n.updateNamespaceRanges()
n.updateMinMaxID(nID)
n.rawRoot = nil
return nil
}

// computeRoot calculates the namespace Merkle root for a tree/sub-tree that
// encompasses the leaves within the range of [start, end).
// Any errors returned by this method are irrecoverable and indicate an illegal state of the tree (n).
Expand Down
Loading

0 comments on commit e9978af

Please sign in to comment.