diff options
Diffstat (limited to 'pkg/merkletree/merkletree.go')
-rw-r--r-- | pkg/merkletree/merkletree.go | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/pkg/merkletree/merkletree.go b/pkg/merkletree/merkletree.go new file mode 100644 index 000000000..906f67943 --- /dev/null +++ b/pkg/merkletree/merkletree.go @@ -0,0 +1,135 @@ +// Copyright 2020 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package merkletree implements Merkle tree generating and verification. +package merkletree + +import ( + "crypto/sha256" + "io" + + "gvisor.dev/gvisor/pkg/usermem" +) + +const ( + // sha256DigestSize specifies the digest size of a SHA256 hash. + sha256DigestSize = 32 +) + +// Size defines the scale of a Merkle tree. +type Size struct { + // blockSize is the size of a data block to be hashed. + blockSize int64 + // digestSize is the size of a generated hash. + digestSize int64 + // hashesPerBlock is the number of hashes in a block. For example, if + // blockSize is 4096 bytes, and digestSize is 32 bytes, there will be 128 + // hashesPerBlock. Therefore 128 hashes in a lower level will be put into a + // block and generate a single hash in an upper level. + hashesPerBlock int64 + // levelStart is the start block index of each level. The number of levels in + // the tree is the length of the slice. The leafs (level 0) are hashes of + // blocks in the input data. The levels above are hashes of lower level + // hashes. The highest level is the root hash. + levelStart []int64 +} + +// MakeSize initializes and returns a new Size object describing the structure +// of a tree. dataSize specifies the number of the file system size in bytes. +func MakeSize(dataSize int64) Size { + size := Size{ + blockSize: usermem.PageSize, + // TODO(b/156980949): Allow config other hash methods (SHA384/SHA512). + digestSize: sha256DigestSize, + hashesPerBlock: usermem.PageSize / sha256DigestSize, + } + numBlocks := (dataSize + size.blockSize - 1) / size.blockSize + level := int64(0) + offset := int64(0) + + // Calcuate the number of levels in the Merkle tree and the beginning offset + // of each level. Level 0 is the level directly above the data blocks, while + // level NumLevels - 1 is the root. + for numBlocks > 1 { + size.levelStart = append(size.levelStart, offset) + // Round numBlocks up to fill up a block. + numBlocks += (size.hashesPerBlock - numBlocks%size.hashesPerBlock) % size.hashesPerBlock + offset += numBlocks / size.hashesPerBlock + numBlocks = numBlocks / size.hashesPerBlock + level++ + } + size.levelStart = append(size.levelStart, offset) + return size +} + +// Generate constructs a Merkle tree for the contents of data. The output is +// written to treeWriter. The treeReader should be able to read the tree after +// it has been written. That is, treeWriter and treeReader should point to the +// same underlying data but have separate cursors. +func Generate(data io.Reader, dataSize int64, treeReader io.Reader, treeWriter io.Writer) ([]byte, error) { + size := MakeSize(dataSize) + + numBlocks := (dataSize + size.blockSize - 1) / size.blockSize + + var root []byte + for level := 0; level < len(size.levelStart); level++ { + for i := int64(0); i < numBlocks; i++ { + buf := make([]byte, size.blockSize) + var ( + n int + err error + ) + if level == 0 { + // Read data block from the target file since level 0 is directly above + // the raw data block. + n, err = data.Read(buf) + } else { + // Read data block from the tree file since levels higher than 0 are + // hashing the lower level hashes. + n, err = treeReader.Read(buf) + } + + // err is populated as long as the bytes read is smaller than the buffer + // size. This could be the case if we are reading the last block, and + // break in that case. If this is the last block, the end of the block + // will be zero-padded. + if n == 0 && err == io.EOF { + break + } else if err != nil && err != io.EOF { + return nil, err + } + // Hash the bytes in buf. + digest := sha256.Sum256(buf) + + if level == len(size.levelStart)-1 { + root = digest[:] + } + + // Write the generated hash to the end of the tree file. + if _, err = treeWriter.Write(digest[:]); err != nil { + return nil, err + } + } + // If the genereated digests do not round up to a block, zero-padding the + // remaining of the last block. But no need to do so for root. + if level != len(size.levelStart)-1 && numBlocks%size.hashesPerBlock != 0 { + zeroBuf := make([]byte, size.blockSize-(numBlocks%size.hashesPerBlock)*size.digestSize) + if _, err := treeWriter.Write(zeroBuf[:]); err != nil { + return nil, err + } + } + numBlocks = (numBlocks + size.hashesPerBlock - 1) / size.hashesPerBlock + } + return root, nil +} |