github.com/divan/go-ethereum@v1.8.14-0.20180820134928-1de9ada4016d/swarm/storage/hasherstore.go (about)

     1  // Copyright 2018 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package storage
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sync"
    23  
    24  	"github.com/ethereum/go-ethereum/crypto/sha3"
    25  	"github.com/ethereum/go-ethereum/swarm/chunk"
    26  	"github.com/ethereum/go-ethereum/swarm/storage/encryption"
    27  )
    28  
    29  type chunkEncryption struct {
    30  	spanEncryption encryption.Encryption
    31  	dataEncryption encryption.Encryption
    32  }
    33  
    34  type hasherStore struct {
    35  	store           ChunkStore
    36  	hashFunc        SwarmHasher
    37  	chunkEncryption *chunkEncryption
    38  	hashSize        int   // content hash size
    39  	refSize         int64 // reference size (content hash + possibly encryption key)
    40  	wg              *sync.WaitGroup
    41  	closed          chan struct{}
    42  }
    43  
    44  func newChunkEncryption(chunkSize, refSize int64) *chunkEncryption {
    45  	return &chunkEncryption{
    46  		spanEncryption: encryption.New(0, uint32(chunkSize/refSize), sha3.NewKeccak256),
    47  		dataEncryption: encryption.New(int(chunkSize), 0, sha3.NewKeccak256),
    48  	}
    49  }
    50  
    51  // NewHasherStore creates a hasherStore object, which implements Putter and Getter interfaces.
    52  // With the HasherStore you can put and get chunk data (which is just []byte) into a ChunkStore
    53  // and the hasherStore will take core of encryption/decryption of data if necessary
    54  func NewHasherStore(chunkStore ChunkStore, hashFunc SwarmHasher, toEncrypt bool) *hasherStore {
    55  	var chunkEncryption *chunkEncryption
    56  
    57  	hashSize := hashFunc().Size()
    58  	refSize := int64(hashSize)
    59  	if toEncrypt {
    60  		refSize += encryption.KeyLength
    61  		chunkEncryption = newChunkEncryption(chunk.DefaultSize, refSize)
    62  	}
    63  
    64  	return &hasherStore{
    65  		store:           chunkStore,
    66  		hashFunc:        hashFunc,
    67  		chunkEncryption: chunkEncryption,
    68  		hashSize:        hashSize,
    69  		refSize:         refSize,
    70  		wg:              &sync.WaitGroup{},
    71  		closed:          make(chan struct{}),
    72  	}
    73  }
    74  
    75  // Put stores the chunkData into the ChunkStore of the hasherStore and returns the reference.
    76  // If hasherStore has a chunkEncryption object, the data will be encrypted.
    77  // Asynchronous function, the data will not necessarily be stored when it returns.
    78  func (h *hasherStore) Put(ctx context.Context, chunkData ChunkData) (Reference, error) {
    79  	c := chunkData
    80  	size := chunkData.Size()
    81  	var encryptionKey encryption.Key
    82  	if h.chunkEncryption != nil {
    83  		var err error
    84  		c, encryptionKey, err = h.encryptChunkData(chunkData)
    85  		if err != nil {
    86  			return nil, err
    87  		}
    88  	}
    89  	chunk := h.createChunk(c, size)
    90  
    91  	h.storeChunk(ctx, chunk)
    92  
    93  	return Reference(append(chunk.Addr, encryptionKey...)), nil
    94  }
    95  
    96  // Get returns data of the chunk with the given reference (retrieved from the ChunkStore of hasherStore).
    97  // If the data is encrypted and the reference contains an encryption key, it will be decrypted before
    98  // return.
    99  func (h *hasherStore) Get(ctx context.Context, ref Reference) (ChunkData, error) {
   100  	key, encryptionKey, err := parseReference(ref, h.hashSize)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	toDecrypt := (encryptionKey != nil)
   105  
   106  	chunk, err := h.store.Get(ctx, key)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	chunkData := chunk.SData
   112  	if toDecrypt {
   113  		var err error
   114  		chunkData, err = h.decryptChunkData(chunkData, encryptionKey)
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  	}
   119  	return chunkData, nil
   120  }
   121  
   122  // Close indicates that no more chunks will be put with the hasherStore, so the Wait
   123  // function can return when all the previously put chunks has been stored.
   124  func (h *hasherStore) Close() {
   125  	close(h.closed)
   126  }
   127  
   128  // Wait returns when
   129  //    1) the Close() function has been called and
   130  //    2) all the chunks which has been Put has been stored
   131  func (h *hasherStore) Wait(ctx context.Context) error {
   132  	<-h.closed
   133  	h.wg.Wait()
   134  	return nil
   135  }
   136  
   137  func (h *hasherStore) createHash(chunkData ChunkData) Address {
   138  	hasher := h.hashFunc()
   139  	hasher.ResetWithLength(chunkData[:8]) // 8 bytes of length
   140  	hasher.Write(chunkData[8:])           // minus 8 []byte length
   141  	return hasher.Sum(nil)
   142  }
   143  
   144  func (h *hasherStore) createChunk(chunkData ChunkData, chunkSize int64) *Chunk {
   145  	hash := h.createHash(chunkData)
   146  	chunk := NewChunk(hash, nil)
   147  	chunk.SData = chunkData
   148  	chunk.Size = chunkSize
   149  
   150  	return chunk
   151  }
   152  
   153  func (h *hasherStore) encryptChunkData(chunkData ChunkData) (ChunkData, encryption.Key, error) {
   154  	if len(chunkData) < 8 {
   155  		return nil, nil, fmt.Errorf("Invalid ChunkData, min length 8 got %v", len(chunkData))
   156  	}
   157  
   158  	encryptionKey, err := encryption.GenerateRandomKey()
   159  	if err != nil {
   160  		return nil, nil, err
   161  	}
   162  
   163  	encryptedSpan, err := h.chunkEncryption.spanEncryption.Encrypt(chunkData[:8], encryptionKey)
   164  	if err != nil {
   165  		return nil, nil, err
   166  	}
   167  	encryptedData, err := h.chunkEncryption.dataEncryption.Encrypt(chunkData[8:], encryptionKey)
   168  	if err != nil {
   169  		return nil, nil, err
   170  	}
   171  	c := make(ChunkData, len(encryptedSpan)+len(encryptedData))
   172  	copy(c[:8], encryptedSpan)
   173  	copy(c[8:], encryptedData)
   174  	return c, encryptionKey, nil
   175  }
   176  
   177  func (h *hasherStore) decryptChunkData(chunkData ChunkData, encryptionKey encryption.Key) (ChunkData, error) {
   178  	if len(chunkData) < 8 {
   179  		return nil, fmt.Errorf("Invalid ChunkData, min length 8 got %v", len(chunkData))
   180  	}
   181  
   182  	decryptedSpan, err := h.chunkEncryption.spanEncryption.Decrypt(chunkData[:8], encryptionKey)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	decryptedData, err := h.chunkEncryption.dataEncryption.Decrypt(chunkData[8:], encryptionKey)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	// removing extra bytes which were just added for padding
   193  	length := ChunkData(decryptedSpan).Size()
   194  	for length > chunk.DefaultSize {
   195  		length = length + (chunk.DefaultSize - 1)
   196  		length = length / chunk.DefaultSize
   197  		length *= h.refSize
   198  	}
   199  
   200  	c := make(ChunkData, length+8)
   201  	copy(c[:8], decryptedSpan)
   202  	copy(c[8:], decryptedData[:length])
   203  
   204  	return c[:length+8], nil
   205  }
   206  
   207  func (h *hasherStore) RefSize() int64 {
   208  	return h.refSize
   209  }
   210  
   211  func (h *hasherStore) storeChunk(ctx context.Context, chunk *Chunk) {
   212  	h.wg.Add(1)
   213  	go func() {
   214  		<-chunk.dbStoredC
   215  		h.wg.Done()
   216  	}()
   217  	h.store.Put(ctx, chunk)
   218  }
   219  
   220  func parseReference(ref Reference, hashSize int) (Address, encryption.Key, error) {
   221  	encryptedKeyLength := hashSize + encryption.KeyLength
   222  	switch len(ref) {
   223  	case KeyLength:
   224  		return Address(ref), nil, nil
   225  	case encryptedKeyLength:
   226  		encKeyIdx := len(ref) - encryption.KeyLength
   227  		return Address(ref[:encKeyIdx]), encryption.Key(ref[encKeyIdx:]), nil
   228  	default:
   229  		return nil, nil, fmt.Errorf("Invalid reference length, expected %v or %v got %v", hashSize, encryptedKeyLength, len(ref))
   230  	}
   231  
   232  }