github.com/sunrise-zone/sunrise-node@v0.13.1-sr2/share/ipld/nmt.go (about)

     1  package ipld
     2  
     3  import (
     4  	"context"
     5  	"crypto/sha256"
     6  	"errors"
     7  	"fmt"
     8  	"hash"
     9  	"math/rand"
    10  
    11  	"github.com/ipfs/boxo/blockservice"
    12  	blocks "github.com/ipfs/go-block-format"
    13  	"github.com/ipfs/go-cid"
    14  	ipld "github.com/ipfs/go-ipld-format"
    15  	logging "github.com/ipfs/go-log/v2"
    16  	mh "github.com/multiformats/go-multihash"
    17  	mhcore "github.com/multiformats/go-multihash/core"
    18  
    19  	"github.com/celestiaorg/nmt"
    20  	"github.com/sunrise-zone/sunrise-app/pkg/appconsts"
    21  	"github.com/sunrise-zone/sunrise-app/pkg/da"
    22  
    23  	"github.com/sunrise-zone/sunrise-node/share"
    24  )
    25  
    26  var (
    27  	log = logging.Logger("ipld")
    28  )
    29  
    30  const (
    31  	// Below used multiformats (one codec, one multihash) seem free:
    32  	// https://github.com/multiformats/multicodec/blob/master/table.csv
    33  
    34  	// nmtCodec is the codec used for leaf and inner nodes of a Namespaced Merkle Tree.
    35  	nmtCodec = 0x7700
    36  
    37  	// sha256NamespaceFlagged is the multihash code used to hash blocks
    38  	// that contain an NMT node (inner and leaf nodes).
    39  	sha256NamespaceFlagged = 0x7701
    40  
    41  	// NmtHashSize is the size of a digest created by an NMT in bytes.
    42  	NmtHashSize = 2*share.NamespaceSize + sha256.Size
    43  
    44  	// innerNodeSize is the size of data in inner nodes.
    45  	innerNodeSize = NmtHashSize * 2
    46  
    47  	// leafNodeSize is the size of data in leaf nodes.
    48  	leafNodeSize = share.NamespaceSize + appconsts.ShareSize
    49  
    50  	// cidPrefixSize is the size of the prepended buffer of the CID encoding
    51  	// for NamespacedSha256. For more information, see:
    52  	// https://multiformats.io/multihash/#the-multihash-format
    53  	cidPrefixSize = 4
    54  
    55  	// NMTIgnoreMaxNamespace is currently used value for IgnoreMaxNamespace option in NMT.
    56  	// IgnoreMaxNamespace defines whether the largest possible Namespace MAX_NID should be 'ignored'.
    57  	// If set to true, this allows for shorter proofs in particular use-cases.
    58  	NMTIgnoreMaxNamespace = true
    59  )
    60  
    61  func init() {
    62  	// required for Bitswap to hash and verify inbound data correctly
    63  	mhcore.Register(sha256NamespaceFlagged, func() hash.Hash {
    64  		nh := nmt.NewNmtHasher(sha256.New(), share.NamespaceSize, true)
    65  		nh.Reset()
    66  		return nh
    67  	})
    68  }
    69  
    70  func GetNode(ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid) (ipld.Node, error) {
    71  	block, err := bGetter.GetBlock(ctx, root)
    72  	if err != nil {
    73  		var errNotFound ipld.ErrNotFound
    74  		if errors.As(err, &errNotFound) {
    75  			return nil, ErrNodeNotFound
    76  		}
    77  		return nil, err
    78  	}
    79  
    80  	return nmtNode{Block: block}, nil
    81  }
    82  
    83  type nmtNode struct {
    84  	blocks.Block
    85  }
    86  
    87  func newNMTNode(id cid.Cid, data []byte) nmtNode {
    88  	b, err := blocks.NewBlockWithCid(data, id)
    89  	if err != nil {
    90  		panic(fmt.Sprintf("wrong hash for block, cid: %s", id.String()))
    91  	}
    92  	return nmtNode{Block: b}
    93  }
    94  
    95  func (n nmtNode) Copy() ipld.Node {
    96  	d := make([]byte, len(n.RawData()))
    97  	copy(d, n.RawData())
    98  	return newNMTNode(n.Cid(), d)
    99  }
   100  
   101  func (n nmtNode) Links() []*ipld.Link {
   102  	switch len(n.RawData()) {
   103  	default:
   104  		panic(fmt.Sprintf("unexpected size %v", len(n.RawData())))
   105  	case innerNodeSize:
   106  		leftCid := MustCidFromNamespacedSha256(n.RawData()[:NmtHashSize])
   107  		rightCid := MustCidFromNamespacedSha256(n.RawData()[NmtHashSize:])
   108  
   109  		return []*ipld.Link{{Cid: leftCid}, {Cid: rightCid}}
   110  	case leafNodeSize:
   111  		return nil
   112  	}
   113  }
   114  
   115  func (n nmtNode) Resolve([]string) (interface{}, []string, error) {
   116  	panic("method not implemented")
   117  }
   118  
   119  func (n nmtNode) Tree(string, int) []string {
   120  	panic("method not implemented")
   121  }
   122  
   123  func (n nmtNode) ResolveLink([]string) (*ipld.Link, []string, error) {
   124  	panic("method not implemented")
   125  }
   126  
   127  func (n nmtNode) Stat() (*ipld.NodeStat, error) {
   128  	panic("method not implemented")
   129  }
   130  
   131  func (n nmtNode) Size() (uint64, error) {
   132  	panic("method not implemented")
   133  }
   134  
   135  // CidFromNamespacedSha256 uses a hash from an nmt tree to create a CID
   136  func CidFromNamespacedSha256(namespacedHash []byte) (cid.Cid, error) {
   137  	if got, want := len(namespacedHash), NmtHashSize; got != want {
   138  		return cid.Cid{}, fmt.Errorf("invalid namespaced hash length, got: %v, want: %v", got, want)
   139  	}
   140  	buf, err := mh.Encode(namespacedHash, sha256NamespaceFlagged)
   141  	if err != nil {
   142  		return cid.Undef, err
   143  	}
   144  	return cid.NewCidV1(nmtCodec, buf), nil
   145  }
   146  
   147  // MustCidFromNamespacedSha256 is a wrapper around cidFromNamespacedSha256 that panics
   148  // in case of an error. Use with care and only in places where no error should occur.
   149  func MustCidFromNamespacedSha256(hash []byte) cid.Cid {
   150  	cidFromHash, err := CidFromNamespacedSha256(hash)
   151  	if err != nil {
   152  		panic(
   153  			fmt.Sprintf("malformed hash: %s, codec: %v",
   154  				err,
   155  				mh.Codes[sha256NamespaceFlagged]),
   156  		)
   157  	}
   158  	return cidFromHash
   159  }
   160  
   161  // Translate transforms square coordinates into IPLD NMT tree path to a leaf node.
   162  // It also adds randomization to evenly spread fetching from Rows and Columns.
   163  func Translate(dah *da.DataAvailabilityHeader, row, col int) (cid.Cid, int) {
   164  	if rand.Intn(2) == 0 { //nolint:gosec
   165  		return MustCidFromNamespacedSha256(dah.ColumnRoots[col]), row
   166  	}
   167  
   168  	return MustCidFromNamespacedSha256(dah.RowRoots[row]), col
   169  }
   170  
   171  // NamespacedSha256FromCID derives the Namespaced hash from the given CID.
   172  func NamespacedSha256FromCID(cid cid.Cid) []byte {
   173  	return cid.Hash()[cidPrefixSize:]
   174  }