github.com/ethersphere/bee/v2@v2.2.0/pkg/postage/stamp.go (about)

     1  // Copyright 2020 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package postage
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  
    13  	"github.com/ethersphere/bee/v2/pkg/crypto"
    14  	"github.com/ethersphere/bee/v2/pkg/storage"
    15  	"github.com/ethersphere/bee/v2/pkg/swarm"
    16  )
    17  
    18  // StampSize is the number of bytes in the serialisation of a stamp
    19  const (
    20  	StampSize   = 113
    21  	IndexSize   = 8
    22  	BucketDepth = 16
    23  )
    24  
    25  var (
    26  	// ErrOwnerMismatch is the error given for invalid signatures.
    27  	ErrOwnerMismatch = errors.New("owner mismatch")
    28  	// ErrInvalidIndex the error given for invalid stamp index.
    29  	ErrInvalidIndex = errors.New("invalid index")
    30  	// ErrStampInvalid is the error given if stamp cannot deserialise.
    31  	ErrStampInvalid = errors.New("invalid stamp")
    32  	// ErrBucketMismatch is the error given if stamp index bucket verification fails.
    33  	ErrBucketMismatch = errors.New("bucket mismatch")
    34  	// ErrInvalidBatchID is the error returned if the batch ID is incorrect
    35  	ErrInvalidBatchID = errors.New("invalid batch ID")
    36  	// ErrInvalidBatchIndex is the error returned if the batch index is incorrect
    37  	ErrInvalidBatchIndex = errors.New("invalid batch index")
    38  	// ErrInvalidBatchTimestamp is the error returned if the batch timestamp is incorrect
    39  	ErrInvalidBatchTimestamp = errors.New("invalid batch timestamp")
    40  	// ErrInvalidBatchSignature is the error returned if the batch signature is incorrect
    41  	ErrInvalidBatchSignature = errors.New("invalid batch signature")
    42  )
    43  
    44  var _ swarm.Stamp = (*Stamp)(nil)
    45  
    46  // Stamp represents a postage stamp as attached to a chunk.
    47  type Stamp struct {
    48  	batchID   []byte // postage batch ID
    49  	index     []byte // index of the batch
    50  	timestamp []byte // to signal order when assigning the indexes to multiple chunks
    51  	sig       []byte // common r[32]s[32]v[1]-style 65 byte ECDSA signature of batchID|index|address by owner or grantee
    52  }
    53  
    54  // NewStamp constructs a new stamp from a given batch ID, index and signatures.
    55  func NewStamp(batchID, index, timestamp, sig []byte) *Stamp {
    56  	return &Stamp{batchID, index, timestamp, sig}
    57  }
    58  
    59  // BatchID returns the batch ID of the stamp.
    60  func (s *Stamp) BatchID() []byte {
    61  	return s.batchID
    62  }
    63  
    64  // Index returns the within-batch index of the stamp.
    65  func (s *Stamp) Index() []byte {
    66  	return s.index
    67  }
    68  
    69  // Sig returns the signature of the stamp by the user
    70  func (s *Stamp) Sig() []byte {
    71  	return s.sig
    72  }
    73  
    74  // Timestamp returns the timestamp of the stamp
    75  func (s *Stamp) Timestamp() []byte {
    76  	return s.timestamp
    77  }
    78  
    79  func (s *Stamp) Clone() swarm.Stamp {
    80  	if s == nil {
    81  		return nil
    82  	}
    83  	return &Stamp{
    84  		batchID:   append([]byte(nil), s.batchID...),
    85  		index:     append([]byte(nil), s.index...),
    86  		timestamp: append([]byte(nil), s.timestamp...),
    87  		sig:       append([]byte(nil), s.sig...),
    88  	}
    89  }
    90  
    91  // Hash returns the hash of the stamp.
    92  func (s *Stamp) Hash() ([]byte, error) {
    93  	hasher := swarm.NewHasher()
    94  	b, err := s.MarshalBinary()
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  	_, err = hasher.Write(b)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	return hasher.Sum(nil), nil
   103  }
   104  
   105  // MarshalBinary gives the byte slice serialisation of a stamp:
   106  // batchID[32]|index[8]|timestamp[8]|Signature[65].
   107  func (s *Stamp) MarshalBinary() ([]byte, error) {
   108  	buf := make([]byte, StampSize)
   109  	if n := copy(buf, s.batchID); n != 32 {
   110  		return nil, ErrInvalidBatchID
   111  	}
   112  	if n := copy(buf[32:40], s.index); n != 8 {
   113  		return nil, ErrInvalidBatchIndex
   114  	}
   115  	if n := copy(buf[40:48], s.timestamp); n != 8 {
   116  		return nil, ErrInvalidBatchTimestamp
   117  	}
   118  	if n := copy(buf[48:], s.sig); n != 65 {
   119  		return nil, ErrInvalidBatchSignature
   120  	}
   121  	return buf, nil
   122  }
   123  
   124  // UnmarshalBinary parses a serialised stamp into id and signature.
   125  func (s *Stamp) UnmarshalBinary(buf []byte) error {
   126  	if len(buf) != StampSize {
   127  		return ErrStampInvalid
   128  	}
   129  	s.batchID = buf[:32]
   130  	s.index = buf[32:40]
   131  	s.timestamp = buf[40:48]
   132  	s.sig = buf[48:]
   133  	return nil
   134  }
   135  
   136  type stampJson struct {
   137  	BatchID   []byte `json:"batchID"`
   138  	Index     []byte `json:"index"`
   139  	Timestamp []byte `json:"timestamp"`
   140  	Sig       []byte `json:"sig"`
   141  }
   142  
   143  func (s *Stamp) MarshalJSON() ([]byte, error) {
   144  	return json.Marshal(&stampJson{
   145  		s.batchID,
   146  		s.index,
   147  		s.timestamp,
   148  		s.sig,
   149  	})
   150  }
   151  
   152  func (a *Stamp) UnmarshalJSON(b []byte) error {
   153  	v := &stampJson{}
   154  	err := json.Unmarshal(b, v)
   155  	if err != nil {
   156  		return err
   157  	}
   158  	a.batchID = v.BatchID
   159  	a.index = v.Index
   160  	a.timestamp = v.Timestamp
   161  	a.sig = v.Sig
   162  	return nil
   163  }
   164  
   165  // ToSignDigest creates a digest to represent the stamp which is to be signed by the owner.
   166  func ToSignDigest(addr, batchId, index, timestamp []byte) ([]byte, error) {
   167  	h := swarm.NewHasher()
   168  	_, err := h.Write(addr)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  	_, err = h.Write(batchId)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	_, err = h.Write(index)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  	_, err = h.Write(timestamp)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  	return h.Sum(nil), nil
   185  }
   186  
   187  type ValidStampFn func(chunk swarm.Chunk) (swarm.Chunk, error)
   188  
   189  // ValidStamp returns a stampvalidator function passed to protocols with chunk entrypoints.
   190  func ValidStamp(batchStore Storer) ValidStampFn {
   191  	return func(chunk swarm.Chunk) (swarm.Chunk, error) {
   192  		stamp := chunk.Stamp()
   193  		b, err := batchStore.Get(stamp.BatchID())
   194  		if err != nil {
   195  			if errors.Is(err, storage.ErrNotFound) {
   196  				return nil, fmt.Errorf("batchstore get: %w, %w", err, ErrNotFound)
   197  			}
   198  			return nil, err
   199  		}
   200  
   201  		if err = NewStamp(stamp.BatchID(), stamp.Index(), stamp.Timestamp(), stamp.Sig()).Valid(chunk.Address(), b.Owner, b.Depth, b.BucketDepth, b.Immutable); err != nil {
   202  			return nil, err
   203  		}
   204  		return chunk.WithStamp(stamp).WithBatch(b.Depth, b.BucketDepth, b.Immutable), nil
   205  	}
   206  }
   207  
   208  // Valid checks the validity of the postage stamp; in particular:
   209  // - authenticity - check batch is valid on the blockchain
   210  // - authorisation - the batch owner is the stamp signer
   211  // the validity  check is only meaningful in its association of a chunk
   212  // this chunk address needs to be given as argument
   213  func (s *Stamp) Valid(chunkAddr swarm.Address, ownerAddr []byte, depth, bucketDepth uint8, immutable bool) error {
   214  	signerAddr, err := RecoverBatchOwner(chunkAddr, s)
   215  	if err != nil {
   216  		return err
   217  	}
   218  	bucket, index := BucketIndexFromBytes(s.index)
   219  	if toBucket(bucketDepth, chunkAddr) != bucket {
   220  		return ErrBucketMismatch
   221  	}
   222  	if index >= 1<<int(depth-bucketDepth) {
   223  		return ErrInvalidIndex
   224  	}
   225  	if !bytes.Equal(signerAddr, ownerAddr) {
   226  		return ErrOwnerMismatch
   227  	}
   228  	return nil
   229  }
   230  
   231  // RecoverBatchOwner returns ethereum address that signed postage batch of supplied stamp.
   232  func RecoverBatchOwner(chunkAddr swarm.Address, stamp swarm.Stamp) ([]byte, error) {
   233  	toSign, err := ToSignDigest(chunkAddr.Bytes(), stamp.BatchID(), stamp.Index(), stamp.Timestamp())
   234  	if err != nil {
   235  		return nil, err
   236  	}
   237  	signerPubkey, err := crypto.Recover(stamp.Sig(), toSign)
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  
   242  	return crypto.NewEthereumAddress(*signerPubkey)
   243  }