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  }