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 }