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 }