github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/ipfs/plugin/nmt.go (about)

     1  package plugin
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"crypto/sha256"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  
    11  	blocks "github.com/ipfs/go-block-format"
    12  	"github.com/ipfs/go-cid"
    13  	ipld "github.com/ipfs/go-ipld-format"
    14  
    15  	"github.com/lazyledger/lazyledger-core/types/consts"
    16  	"github.com/lazyledger/nmt"
    17  	mh "github.com/multiformats/go-multihash"
    18  )
    19  
    20  const (
    21  	// Below used multiformats (one codec, one multihash) seem free:
    22  	// https://github.com/multiformats/multicodec/blob/master/table.csv
    23  
    24  	// NmtCodec is the codec used for leaf and inner nodes of an Namespaced Merkle Tree.
    25  	NmtCodec = 0x7700
    26  
    27  	// NmtCodecName is the name used during registry of the NmtCodec codec
    28  	NmtCodecName = "nmt-node"
    29  
    30  	// Sha256Namespace8Flagged is the multihash code used to hash blocks
    31  	// that contain an NMT node (inner and leaf nodes).
    32  	Sha256Namespace8Flagged = 0x7701
    33  
    34  	// DagParserFormatName can be used when putting into the IPLD Dag
    35  	DagParserFormatName = "extended-square-row-or-col"
    36  
    37  	// FIXME: These are the same as types.ShareSize and consts.NamespaceSize.
    38  	// Repeated here to avoid a dependency to the wrapping repo as this makes
    39  	// it hard to compile and use the plugin against a local ipfs version.
    40  	// TODO: plugins have config options; make this configurable instead
    41  	namespaceSize = 8
    42  	shareSize     = 256
    43  	// nmtHashSize is the size of a digest created by an NMT in bytes.
    44  	nmtHashSize = 2*namespaceSize + sha256.Size
    45  )
    46  
    47  func init() {
    48  	mustRegisterNamespacedCodec(
    49  		Sha256Namespace8Flagged,
    50  		"sha2-256-namespace8-flagged",
    51  		nmtHashSize,
    52  		sumSha256Namespace8Flagged,
    53  	)
    54  	// this should already happen when the plugin is injected but it doesn't for some CI tests
    55  	ipld.DefaultBlockDecoder.Register(NmtCodec, NmtNodeParser)
    56  	// register the codecs in the global maps
    57  	cid.Codecs[NmtCodecName] = NmtCodec
    58  	cid.CodecToStr[NmtCodec] = NmtCodecName
    59  }
    60  
    61  func mustRegisterNamespacedCodec(
    62  	codec uint64,
    63  	name string,
    64  	defaultLength int,
    65  	hashFunc mh.HashFunc,
    66  ) {
    67  	if _, ok := mh.Codes[codec]; !ok {
    68  		// make sure that the Codec wasn't registered from somewhere different than this plugin already:
    69  		if _, found := mh.Codes[codec]; found {
    70  			panic(fmt.Sprintf("Codec 0x%X is already present: %v", codec, mh.Codes[codec]))
    71  		}
    72  		// add to mh.Codes map first, otherwise mh.RegisterHashFunc would err:
    73  		mh.Codes[codec] = name
    74  		mh.Names[name] = codec
    75  		mh.DefaultLengths[codec] = defaultLength
    76  
    77  		if err := mh.RegisterHashFunc(codec, hashFunc); err != nil {
    78  			panic(fmt.Sprintf("could not register hash function: %v", mh.Codes[codec]))
    79  		}
    80  	}
    81  }
    82  
    83  // sumSha256Namespace8Flagged is the mh.HashFunc used to hash leaf and inner nodes.
    84  // It is registered as a mh.HashFunc in the go-multihash module.
    85  func sumSha256Namespace8Flagged(data []byte, _length int) ([]byte, error) {
    86  	isLeafData := data[0] == nmt.LeafPrefix
    87  	if isLeafData {
    88  		return nmt.Sha256Namespace8FlaggedLeaf(data[1:]), nil
    89  	}
    90  	return nmt.Sha256Namespace8FlaggedInner(data[1:]), nil
    91  }
    92  
    93  // DataSquareRowOrColumnRawInputParser reads the raw shares and extract the IPLD nodes from the NMT tree.
    94  // Note, to parse without any error the input has to be of the form:
    95  //
    96  // <share_0>| ... |<share_numOfShares - 1>
    97  //
    98  // To determine the share and the namespace size the constants
    99  // types.ShareSize and consts.NamespaceSize are redefined here to avoid
   100  // lazyledger-core as a dependency.
   101  //
   102  // Note while this coredag.DagParser is implemented here so this plugin can be used from
   103  // the commandline, the ipld Nodes will rather be created together with the NMT
   104  // root instead of re-computing it here.
   105  func DataSquareRowOrColumnRawInputParser(r io.Reader, _mhType uint64, _mhLen int) ([]ipld.Node, error) {
   106  	br := bufio.NewReader(r)
   107  	collector := newNodeCollector()
   108  
   109  	n := nmt.New(
   110  		consts.NewBaseHashFunc,
   111  		nmt.NamespaceIDSize(namespaceSize),
   112  		nmt.NodeVisitor(collector.visit),
   113  	)
   114  
   115  	for {
   116  		namespacedLeaf := make([]byte, shareSize+namespaceSize)
   117  		if _, err := io.ReadFull(br, namespacedLeaf); err != nil {
   118  			if err == io.EOF {
   119  				break
   120  			}
   121  			return nil, err
   122  		}
   123  		if err := n.Push(namespacedLeaf); err != nil {
   124  			return nil, err
   125  		}
   126  	}
   127  	// to trigger the collection of nodes:
   128  	_ = n.Root()
   129  	return collector.ipldNodes(), nil
   130  }
   131  
   132  // nmtNodeCollector creates and collects ipld.Nodes if inserted into a nmt tree.
   133  // It is mainly used for testing.
   134  type nmtNodeCollector struct {
   135  	nodes []ipld.Node
   136  }
   137  
   138  func newNodeCollector() *nmtNodeCollector {
   139  	// The extendedRowOrColumnSize is hardcode this here to avoid importing:
   140  	// https://github.com/lazyledger/lazyledger-core/blob/585566317e519bbb6d35d149b7e856c4c1e8657c/types/consts.go#L23
   141  	const extendedRowOrColumnSize = 2 * 128
   142  	return &nmtNodeCollector{nodes: make([]ipld.Node, 0, extendedRowOrColumnSize)}
   143  }
   144  
   145  func (n nmtNodeCollector) ipldNodes() []ipld.Node {
   146  	return n.nodes
   147  }
   148  
   149  func (n *nmtNodeCollector) visit(hash []byte, children ...[]byte) {
   150  	cid := MustCidFromNamespacedSha256(hash)
   151  	switch len(children) {
   152  	case 1:
   153  		n.nodes = prependNode(nmtLeafNode{
   154  			cid:  cid,
   155  			Data: children[0],
   156  		}, n.nodes)
   157  	case 2:
   158  		n.nodes = prependNode(nmtNode{
   159  			cid: cid,
   160  			l:   children[0],
   161  			r:   children[1],
   162  		}, n.nodes)
   163  	default:
   164  		panic("expected a binary tree")
   165  	}
   166  }
   167  
   168  func prependNode(newNode ipld.Node, nodes []ipld.Node) []ipld.Node {
   169  	nodes = append(nodes, ipld.Node(nil))
   170  	copy(nodes[1:], nodes)
   171  	nodes[0] = newNode
   172  	return nodes
   173  }
   174  
   175  func NmtNodeParser(block blocks.Block) (ipld.Node, error) {
   176  	// length of the domain separator for leaf and inner nodes:
   177  	const prefixOffset = 1
   178  	var (
   179  		leafPrefix  = []byte{nmt.LeafPrefix}
   180  		innerPrefix = []byte{nmt.NodePrefix}
   181  	)
   182  	data := block.RawData()
   183  	if len(data) == 0 {
   184  		return &nmtLeafNode{
   185  			cid:  cid.Undef,
   186  			Data: nil,
   187  		}, nil
   188  	}
   189  	domainSeparator := data[:prefixOffset]
   190  	if bytes.Equal(domainSeparator, leafPrefix) {
   191  		return &nmtLeafNode{
   192  			cid:  block.Cid(),
   193  			Data: data[prefixOffset:],
   194  		}, nil
   195  	}
   196  	if bytes.Equal(domainSeparator, innerPrefix) {
   197  		return nmtNode{
   198  			cid: block.Cid(),
   199  			l:   data[prefixOffset : prefixOffset+nmtHashSize],
   200  			r:   data[prefixOffset+nmtHashSize:],
   201  		}, nil
   202  	}
   203  	return nil, fmt.Errorf(
   204  		"expected first byte of block to be either the leaf or inner node prefix: (%x, %x), got: %x)",
   205  		leafPrefix,
   206  		innerPrefix,
   207  		domainSeparator,
   208  	)
   209  }
   210  
   211  var _ ipld.Node = (*nmtNode)(nil)
   212  var _ ipld.Node = (*nmtLeafNode)(nil)
   213  
   214  type nmtNode struct {
   215  	// TODO(ismail): we might want to export these later
   216  	cid  cid.Cid
   217  	l, r []byte
   218  }
   219  
   220  func NewNMTNode(id cid.Cid, l, r []byte) ipld.Node {
   221  	return nmtNode{id, l, r}
   222  }
   223  
   224  func (n nmtNode) RawData() []byte {
   225  	return append([]byte{nmt.NodePrefix}, append(n.l, n.r...)...)
   226  }
   227  
   228  func (n nmtNode) Cid() cid.Cid {
   229  	return n.cid
   230  }
   231  
   232  func (n nmtNode) String() string {
   233  	return fmt.Sprintf(`
   234  node {
   235  	hash: %x,
   236  	l: %x,
   237  	r: %x"
   238  }`, n.cid.Hash(), n.l, n.r)
   239  }
   240  
   241  func (n nmtNode) Loggable() map[string]interface{} {
   242  	return nil
   243  }
   244  
   245  func (n nmtNode) Resolve(path []string) (interface{}, []string, error) {
   246  	switch path[0] {
   247  	case "0":
   248  		left, err := CidFromNamespacedSha256(n.l)
   249  		if err != nil {
   250  			return nil, nil, err
   251  		}
   252  		return &ipld.Link{Cid: left}, path[1:], nil
   253  	case "1":
   254  		right, err := CidFromNamespacedSha256(n.r)
   255  		if err != nil {
   256  			return nil, nil, err
   257  		}
   258  		return &ipld.Link{Cid: right}, path[1:], nil
   259  	default:
   260  		return nil, nil, errors.New("invalid path for inner node")
   261  	}
   262  }
   263  
   264  func (n nmtNode) Tree(path string, depth int) []string {
   265  	if path != "" || depth != -1 {
   266  		panic("proper tree not yet implemented")
   267  	}
   268  
   269  	return []string{
   270  		"0",
   271  		"1",
   272  	}
   273  }
   274  
   275  func (n nmtNode) ResolveLink(path []string) (*ipld.Link, []string, error) {
   276  	obj, rest, err := n.Resolve(path)
   277  	if err != nil {
   278  		return nil, nil, err
   279  	}
   280  
   281  	lnk, ok := obj.(*ipld.Link)
   282  	if !ok {
   283  		return nil, nil, errors.New("was not a link")
   284  	}
   285  
   286  	return lnk, rest, nil
   287  }
   288  
   289  func (n nmtNode) Copy() ipld.Node {
   290  	l := make([]byte, len(n.l))
   291  	copy(l, n.l)
   292  	r := make([]byte, len(n.r))
   293  	copy(r, n.r)
   294  
   295  	return &nmtNode{
   296  		cid: n.cid,
   297  		l:   l,
   298  		r:   r,
   299  	}
   300  }
   301  
   302  func (n nmtNode) Links() []*ipld.Link {
   303  	leftCid := MustCidFromNamespacedSha256(n.l)
   304  	rightCid := MustCidFromNamespacedSha256(n.r)
   305  
   306  	return []*ipld.Link{{Cid: leftCid}, {Cid: rightCid}}
   307  }
   308  
   309  func (n nmtNode) Stat() (*ipld.NodeStat, error) {
   310  	return &ipld.NodeStat{}, nil
   311  }
   312  
   313  func (n nmtNode) Size() (uint64, error) {
   314  	return 0, nil
   315  }
   316  
   317  type nmtLeafNode struct {
   318  	cid  cid.Cid
   319  	Data []byte
   320  }
   321  
   322  func NewNMTLeafNode(id cid.Cid, data []byte) ipld.Node {
   323  	return &nmtLeafNode{id, data}
   324  }
   325  
   326  func (l nmtLeafNode) RawData() []byte {
   327  	return append([]byte{nmt.LeafPrefix}, l.Data...)
   328  }
   329  
   330  func (l nmtLeafNode) Cid() cid.Cid {
   331  	return l.cid
   332  }
   333  
   334  func (l nmtLeafNode) String() string {
   335  	return fmt.Sprintf(`
   336  leaf {
   337  	hash: 		%x,
   338  	len(Data): 	%v
   339  }`, l.cid.Hash(), len(l.Data))
   340  }
   341  
   342  func (l nmtLeafNode) Loggable() map[string]interface{} {
   343  	return nil
   344  }
   345  
   346  func (l nmtLeafNode) Resolve(path []string) (interface{}, []string, error) {
   347  	return nil, nil, errors.New("invalid path for leaf node")
   348  }
   349  
   350  func (l nmtLeafNode) Tree(_path string, _depth int) []string {
   351  	return nil
   352  }
   353  
   354  func (l nmtLeafNode) ResolveLink(path []string) (*ipld.Link, []string, error) {
   355  	obj, rest, err := l.Resolve(path)
   356  	if err != nil {
   357  		return nil, nil, err
   358  	}
   359  
   360  	lnk, ok := obj.(*ipld.Link)
   361  	if !ok {
   362  		return nil, nil, errors.New("was not a link")
   363  	}
   364  	return lnk, rest, nil
   365  }
   366  
   367  func (l nmtLeafNode) Copy() ipld.Node {
   368  	panic("implement me")
   369  }
   370  
   371  func (l nmtLeafNode) Links() []*ipld.Link {
   372  	return []*ipld.Link{{Cid: l.Cid()}}
   373  }
   374  
   375  func (l nmtLeafNode) Stat() (*ipld.NodeStat, error) {
   376  	return &ipld.NodeStat{}, nil
   377  }
   378  
   379  func (l nmtLeafNode) Size() (uint64, error) {
   380  	return 0, nil
   381  }
   382  
   383  // CidFromNamespacedSha256 uses a hash from an nmt tree to create a cide
   384  func CidFromNamespacedSha256(namespacedHash []byte) (cid.Cid, error) {
   385  	if got, want := len(namespacedHash), nmtHashSize; got != want {
   386  		return cid.Cid{}, fmt.Errorf("invalid namespaced hash length, got: %v, want: %v", got, want)
   387  	}
   388  	buf, err := mh.Encode(namespacedHash, Sha256Namespace8Flagged)
   389  	if err != nil {
   390  		return cid.Undef, err
   391  	}
   392  	return cid.NewCidV1(NmtCodec, mh.Multihash(buf)), nil
   393  }
   394  
   395  // MustCidFromNamespacedSha256 is a wrapper around cidFromNamespacedSha256 that panics
   396  // in case of an error. Use with care and only in places where no error should occur.
   397  func MustCidFromNamespacedSha256(hash []byte) cid.Cid {
   398  	cid, err := CidFromNamespacedSha256(hash)
   399  	if err != nil {
   400  		panic(
   401  			fmt.Sprintf("malformed hash: %s, codec: %v",
   402  				err,
   403  				mh.Codes[Sha256Namespace8Flagged]),
   404  		)
   405  	}
   406  	return cid
   407  }