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 }