aboutsummaryrefslogtreecommitdiffstats
path: root/swarm/storage/mru/request.go
blob: 03ffbf0380079921eae86ee6b06bd0c4aee684c2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package mru

import (
    "bytes"
    "encoding/json"
    "hash"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/common/hexutil"
    "github.com/ethereum/go-ethereum/swarm/storage"
    "github.com/ethereum/go-ethereum/swarm/storage/mru/lookup"
)

// Request represents an update and/or resource create message
type Request struct {
    Update     // actual content that will be put on the chunk, less signature
    Signature  *Signature
    idAddr     storage.Address // cached chunk address for the update (not serialized, for internal use)
    binaryData []byte          // cached serialized data (does not get serialized again!, for efficiency/internal use)
}

// updateRequestJSON represents a JSON-serialized UpdateRequest
type updateRequestJSON struct {
    ID
    ProtocolVersion uint8  `json:"protocolVersion"`
    Data            string `json:"data,omitempty"`
    Signature       string `json:"signature,omitempty"`
}

// Request layout
// Update bytes
// SignatureLength bytes
const minimumSignedUpdateLength = minimumUpdateDataLength + signatureLength

// NewFirstRequest returns a ready to sign request to publish a first feed update
func NewFirstRequest(topic Topic) *Request {

    request := new(Request)

    // get the current time
    now := TimestampProvider.Now().Time
    request.Epoch = lookup.GetFirstEpoch(now)
    request.Feed.Topic = topic
    request.Header.Version = ProtocolVersion

    return request
}

// SetData stores the payload data the resource will be updated with
func (r *Request) SetData(data []byte) {
    r.data = data
    r.Signature = nil
}

// IsUpdate returns true if this request models a signed update or otherwise it is a signature request
func (r *Request) IsUpdate() bool {
    return r.Signature != nil
}

// Verify checks that signatures are valid and that the signer owns the resource to be updated
func (r *Request) Verify() (err error) {
    if len(r.data) == 0 {
        return NewError(ErrInvalidValue, "Update does not contain data")
    }
    if r.Signature == nil {
        return NewError(ErrInvalidSignature, "Missing signature field")
    }

    digest, err := r.GetDigest()
    if err != nil {
        return err
    }

    // get the address of the signer (which also checks that it's a valid signature)
    r.Feed.User, err = getUserAddr(digest, *r.Signature)
    if err != nil {
        return err
    }

    // check that the lookup information contained in the chunk matches the updateAddr (chunk search key)
    // that was used to retrieve this chunk
    // if this validation fails, someone forged a chunk.
    if !bytes.Equal(r.idAddr, r.Addr()) {
        return NewError(ErrInvalidSignature, "Signature address does not match with update user address")
    }

    return nil
}

// Sign executes the signature to validate the resource
func (r *Request) Sign(signer Signer) error {
    r.Feed.User = signer.Address()
    r.binaryData = nil           //invalidate serialized data
    digest, err := r.GetDigest() // computes digest and serializes into .binaryData
    if err != nil {
        return err
    }

    signature, err := signer.Sign(digest)
    if err != nil {
        return err
    }

    // Although the Signer interface returns the public address of the signer,
    // recover it from the signature to see if they match
    userAddr, err := getUserAddr(digest, signature)
    if err != nil {
        return NewError(ErrInvalidSignature, "Error verifying signature")
    }

    if userAddr != signer.Address() { // sanity check to make sure the Signer is declaring the same address used to sign!
        return NewError(ErrInvalidSignature, "Signer address does not match update user address")
    }

    r.Signature = &signature
    r.idAddr = r.Addr()
    return nil
}

// GetDigest creates the resource update digest used in signatures
// the serialized payload is cached in .binaryData
func (r *Request) GetDigest() (result common.Hash, err error) {
    hasher := hashPool.Get().(hash.Hash)
    defer hashPool.Put(hasher)
    hasher.Reset()
    dataLength := r.Update.binaryLength()
    if r.binaryData == nil {
        r.binaryData = make([]byte, dataLength+signatureLength)
        if err := r.Update.binaryPut(r.binaryData[:dataLength]); err != nil {
            return result, err
        }
    }
    hasher.Write(r.binaryData[:dataLength]) //everything except the signature.

    return common.BytesToHash(hasher.Sum(nil)), nil
}

// create an update chunk.
func (r *Request) toChunk() (storage.Chunk, error) {

    // Check that the update is signed and serialized
    // For efficiency, data is serialized during signature and cached in
    // the binaryData field when computing the signature digest in .getDigest()
    if r.Signature == nil || r.binaryData == nil {
        return nil, NewError(ErrInvalidSignature, "toChunk called without a valid signature or payload data. Call .Sign() first.")
    }

    updateLength := r.Update.binaryLength()

    // signature is the last item in the chunk data
    copy(r.binaryData[updateLength:], r.Signature[:])

    chunk := storage.NewChunk(r.idAddr, r.binaryData)
    return chunk, nil
}

// fromChunk populates this structure from chunk data. It does not verify the signature is valid.
func (r *Request) fromChunk(updateAddr storage.Address, chunkdata []byte) error {
    // for update chunk layout see Request definition

    //deserialize the resource update portion
    if err := r.Update.binaryGet(chunkdata[:len(chunkdata)-signatureLength]); err != nil {
        return err
    }

    // Extract the signature
    var signature *Signature
    cursor := r.Update.binaryLength()
    sigdata := chunkdata[cursor : cursor+signatureLength]
    if len(sigdata) > 0 {
        signature = &Signature{}
        copy(signature[:], sigdata)
    }

    r.Signature = signature
    r.idAddr = updateAddr
    r.binaryData = chunkdata

    return nil

}

// FromValues deserializes this instance from a string key-value store
// useful to parse query strings
func (r *Request) FromValues(values Values, data []byte) error {
    signatureBytes, err := hexutil.Decode(values.Get("signature"))
    if err != nil {
        r.Signature = nil
    } else {
        if len(signatureBytes) != signatureLength {
            return NewError(ErrInvalidSignature, "Incorrect signature length")
        }
        r.Signature = new(Signature)
        copy(r.Signature[:], signatureBytes)
    }
    err = r.Update.FromValues(values, data)
    if err != nil {
        return err
    }
    r.idAddr = r.Addr()
    return err
}

// AppendValues serializes this structure into the provided string key-value store
// useful to build query strings
func (r *Request) AppendValues(values Values) []byte {
    if r.Signature != nil {
        values.Set("signature", hexutil.Encode(r.Signature[:]))
    }
    return r.Update.AppendValues(values)
}

// fromJSON takes an update request JSON and populates an UpdateRequest
func (r *Request) fromJSON(j *updateRequestJSON) error {

    r.ID = j.ID
    r.Header.Version = j.ProtocolVersion

    var err error
    if j.Data != "" {
        r.data, err = hexutil.Decode(j.Data)
        if err != nil {
            return NewError(ErrInvalidValue, "Cannot decode data")
        }
    }

    if j.Signature != "" {
        sigBytes, err := hexutil.Decode(j.Signature)
        if err != nil || len(sigBytes) != signatureLength {
            return NewError(ErrInvalidSignature, "Cannot decode signature")
        }
        r.Signature = new(Signature)
        r.idAddr = r.Addr()
        copy(r.Signature[:], sigBytes)
    }
    return nil
}

// UnmarshalJSON takes a JSON structure stored in a byte array and populates the Request object
// Implements json.Unmarshaler interface
func (r *Request) UnmarshalJSON(rawData []byte) error {
    var requestJSON updateRequestJSON
    if err := json.Unmarshal(rawData, &requestJSON); err != nil {
        return err
    }
    return r.fromJSON(&requestJSON)
}

// MarshalJSON takes an update request and encodes it as a JSON structure into a byte array
// Implements json.Marshaler interface
func (r *Request) MarshalJSON() (rawData []byte, err error) {
    var signatureString, dataString string
    if r.Signature != nil {
        signatureString = hexutil.Encode(r.Signature[:])
    }
    if r.data != nil {
        dataString = hexutil.Encode(r.data)
    }

    requestJSON := &updateRequestJSON{
        ID:              r.ID,
        ProtocolVersion: r.Header.Version,
        Data:            dataString,
        Signature:       signatureString,
    }

    return json.Marshal(requestJSON)
}