github.com/sunrise-zone/sunrise-node@v0.13.1-sr2/share/eds/byzantine/bad_encoding.go (about) 1 package byzantine 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 8 "github.com/celestiaorg/go-fraud" 9 "github.com/celestiaorg/rsmt2d" 10 "github.com/sunrise-zone/sunrise-app/pkg/wrapper" 11 12 "github.com/sunrise-zone/sunrise-node/header" 13 "github.com/sunrise-zone/sunrise-node/share" 14 pb "github.com/sunrise-zone/sunrise-node/share/eds/byzantine/pb" 15 "github.com/sunrise-zone/sunrise-node/share/ipld" 16 ) 17 18 const ( 19 version = "v0.1" 20 21 BadEncoding fraud.ProofType = "badencoding" + version 22 ) 23 24 type BadEncodingProof struct { 25 headerHash []byte 26 BlockHeight uint64 27 // ShareWithProof contains all shares from row or col. 28 // Shares that did not pass verification in rsmt2d will be nil. 29 // For non-nil shares MerkleProofs are computed. 30 Shares []*ShareWithProof 31 // Index represents the row/col index where ErrByzantineRow/ErrByzantineColl occurred. 32 Index uint32 33 // Axis represents the axis that verification failed on. 34 Axis rsmt2d.Axis 35 } 36 37 // CreateBadEncodingProof creates a new Bad Encoding Fraud Proof that should be propagated through 38 // network. The fraud proof will contain shares that did not pass verification and their relevant 39 // Merkle proofs. 40 func CreateBadEncodingProof( 41 hash []byte, 42 height uint64, 43 errByzantine *ErrByzantine, 44 ) fraud.Proof[*header.ExtendedHeader] { 45 return &BadEncodingProof{ 46 headerHash: hash, 47 BlockHeight: height, 48 Shares: errByzantine.Shares, 49 Index: errByzantine.Index, 50 Axis: errByzantine.Axis, 51 } 52 } 53 54 // Type returns type of fraud proof. 55 func (p *BadEncodingProof) Type() fraud.ProofType { 56 return BadEncoding 57 } 58 59 // HeaderHash returns block hash. 60 func (p *BadEncodingProof) HeaderHash() []byte { 61 return p.headerHash 62 } 63 64 // Height returns block height. 65 func (p *BadEncodingProof) Height() uint64 { 66 return p.BlockHeight 67 } 68 69 // MarshalBinary converts BadEncodingProof to binary. 70 func (p *BadEncodingProof) MarshalBinary() ([]byte, error) { 71 shares := make([]*pb.Share, 0, len(p.Shares)) 72 for _, share := range p.Shares { 73 shares = append(shares, share.ShareWithProofToProto()) 74 } 75 76 badEncodingFraudProof := pb.BadEncoding{ 77 HeaderHash: p.headerHash, 78 Height: p.BlockHeight, 79 Shares: shares, 80 Index: p.Index, 81 Axis: pb.Axis(p.Axis), 82 } 83 return badEncodingFraudProof.Marshal() 84 } 85 86 // UnmarshalBinary converts binary to BadEncodingProof. 87 func (p *BadEncodingProof) UnmarshalBinary(data []byte) error { 88 in := pb.BadEncoding{} 89 if err := in.Unmarshal(data); err != nil { 90 return err 91 } 92 befp := &BadEncodingProof{ 93 headerHash: in.HeaderHash, 94 BlockHeight: in.Height, 95 Shares: ProtoToShare(in.Shares), 96 Index: in.Index, 97 Axis: rsmt2d.Axis(in.Axis), 98 } 99 100 *p = *befp 101 102 return nil 103 } 104 105 var ( 106 errHeightMismatch = errors.New("height reported in proof does not match with the header's height") 107 errIncorrectIndex = errors.New("row/col index is more then the roots amount") 108 errIncorrectAmountOfShares = errors.New("incorrect amount of shares") 109 errIncorrectShare = errors.New("incorrect share received") 110 errNMTTreeRootsMatch = errors.New("recomputed root matches the DAH root") 111 ) 112 113 var ( 114 invalidProofPrefix = fmt.Sprintf("invalid %s proof", BadEncoding) 115 ) 116 117 // Validate ensures that fraud proof is correct. 118 // Validate checks that provided Merkle Proofs correspond to the shares, 119 // rebuilds bad row or col from received shares, computes Merkle Root 120 // and compares it with block's Merkle Root. 121 func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error { 122 if hdr.Height() != p.BlockHeight { 123 log.Debugf("%s: %s. expected block's height: %d, got: %d", 124 invalidProofPrefix, 125 errHeightMismatch, 126 hdr.Height(), 127 p.BlockHeight, 128 ) 129 return errHeightMismatch 130 } 131 132 if len(hdr.DAH.RowRoots) != len(hdr.DAH.ColumnRoots) { 133 // NOTE: This should never happen as callers of this method should not feed it with a 134 // malformed extended header. 135 panic(fmt.Sprintf( 136 "invalid extended header: length of row and column roots do not match. (rowRoots=%d) (colRoots=%d)", 137 len(hdr.DAH.RowRoots), 138 len(hdr.DAH.ColumnRoots)), 139 ) 140 } 141 142 // merkleRoots are the roots against which we are going to check the inclusion of the received 143 // shares. Changing the order of the roots to prove the shares relative to the orthogonal axis, 144 // because inside the rsmt2d library rsmt2d.Row = 0 and rsmt2d.Col = 1 145 merkleRoots := hdr.DAH.RowRoots 146 if p.Axis == rsmt2d.Row { 147 merkleRoots = hdr.DAH.ColumnRoots 148 } 149 150 if int(p.Index) >= len(merkleRoots) { 151 log.Debugf("%s:%s (%d >= %d)", 152 invalidProofPrefix, errIncorrectIndex, int(p.Index), len(merkleRoots), 153 ) 154 return errIncorrectIndex 155 } 156 157 if len(p.Shares) != len(merkleRoots) { 158 // Since p.Shares should contain all the shares from either a row or a 159 // column, it should exactly match the number of row roots. In this 160 // context, the number of row roots is the width of the extended data 161 // square. 162 log.Infof("%s: %s (%d >= %d)", 163 invalidProofPrefix, errIncorrectAmountOfShares, int(p.Index), len(merkleRoots), 164 ) 165 return errIncorrectAmountOfShares 166 } 167 168 odsWidth := uint64(len(merkleRoots) / 2) 169 amount := uint64(0) 170 for _, share := range p.Shares { 171 if share == nil { 172 continue 173 } 174 amount++ 175 if amount == odsWidth { 176 break 177 } 178 } 179 180 if amount < odsWidth { 181 log.Debugf("%s: %s. not enough shares provided to reconstruct row/col", 182 invalidProofPrefix, errIncorrectAmountOfShares) 183 return errIncorrectAmountOfShares 184 } 185 186 // verify that Merkle proofs correspond to particular shares. 187 shares := make([][]byte, len(merkleRoots)) 188 for index, shr := range p.Shares { 189 if shr == nil { 190 continue 191 } 192 // validate inclusion of the share into one of the DAHeader roots 193 if ok := shr.Validate(ipld.MustCidFromNamespacedSha256(merkleRoots[index])); !ok { 194 log.Debugf("%s: %s at index %d", invalidProofPrefix, errIncorrectShare, index) 195 return errIncorrectShare 196 } 197 // NMTree commits the additional namespace while rsmt2d does not know about, so we trim it 198 // this is ugliness from NMTWrapper that we have to embrace ¯\_(ツ)_/¯ 199 shares[index] = share.GetData(shr.Share) 200 } 201 202 codec := share.DefaultRSMT2DCodec() 203 204 // We can conclude that the proof is valid in case we proved the inclusion of `Shares` but 205 // the row/col can't be reconstructed, or the building of NMTree fails. 206 rebuiltShares, err := codec.Decode(shares) 207 if err != nil { 208 log.Debugw("failed to decode shares at height", 209 "height", hdr.Height(), "err", err, 210 ) 211 return nil 212 } 213 214 rebuiltExtendedShares, err := codec.Encode(rebuiltShares[0:odsWidth]) 215 if err != nil { 216 log.Debugw("failed to encode shares at height", 217 "height", hdr.Height(), "err", err, 218 ) 219 return nil 220 } 221 copy(rebuiltShares[odsWidth:], rebuiltExtendedShares) 222 223 tree := wrapper.NewErasuredNamespacedMerkleTree(odsWidth, uint(p.Index)) 224 for _, share := range rebuiltShares { 225 err = tree.Push(share) 226 if err != nil { 227 log.Debugw("failed to build a tree from the reconstructed shares at height", 228 "height", hdr.Height(), "err", err, 229 ) 230 return nil 231 } 232 } 233 234 expectedRoot, err := tree.Root() 235 if err != nil { 236 log.Debugw("failed to build a tree root at height", 237 "height", hdr.Height(), "err", err, 238 ) 239 return nil 240 } 241 242 // root is a merkle root of the row/col where ErrByzantine occurred 243 root := hdr.DAH.RowRoots[p.Index] 244 if p.Axis == rsmt2d.Col { 245 root = hdr.DAH.ColumnRoots[p.Index] 246 } 247 248 // comparing rebuilt Merkle Root of bad row/col with respective Merkle Root of row/col from block. 249 if bytes.Equal(expectedRoot, root) { 250 log.Debugf("invalid %s proof:%s", BadEncoding, errNMTTreeRootsMatch) 251 return errNMTTreeRootsMatch 252 } 253 return nil 254 }