mirror of https://github.com/containers/podman.git
192 lines
7.3 KiB
Go
192 lines
7.3 KiB
Go
// Copyright 2022 Google LLC. All Rights Reserved.
|
|
//
|
|
// 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 proof contains helpers for constructing log Merkle tree proofs.
|
|
package proof
|
|
|
|
import (
|
|
"fmt"
|
|
"math/bits"
|
|
|
|
"github.com/transparency-dev/merkle/compact"
|
|
)
|
|
|
|
// Nodes contains information on how to construct a log Merkle tree proof. It
|
|
// supports any proof that has at most one ephemeral node, such as inclusion
|
|
// and consistency proofs defined in RFC 6962.
|
|
type Nodes struct {
|
|
// IDs contains the IDs of non-ephemeral nodes sufficient to build the proof.
|
|
// If an ephemeral node is needed for a proof, it can be recomputed based on
|
|
// a subset of nodes in this list.
|
|
IDs []compact.NodeID
|
|
// begin is the beginning index (inclusive) into the IDs[begin:end] subslice
|
|
// of the nodes which will be used to re-create the ephemeral node.
|
|
begin int
|
|
// end is the ending (exclusive) index into the IDs[begin:end] subslice of
|
|
// the nodes which will be used to re-create the ephemeral node.
|
|
end int
|
|
// ephem is the ID of the ephemeral node in the proof. This node is a common
|
|
// ancestor of all nodes in IDs[begin:end]. It is the node that otherwise
|
|
// would have been used in the proof if the tree was perfect.
|
|
ephem compact.NodeID
|
|
}
|
|
|
|
// Inclusion returns the information on how to fetch and construct an inclusion
|
|
// proof for the given leaf index in a log Merkle tree of the given size. It
|
|
// requires 0 <= index < size.
|
|
func Inclusion(index, size uint64) (Nodes, error) {
|
|
if index >= size {
|
|
return Nodes{}, fmt.Errorf("index %d out of bounds for tree size %d", index, size)
|
|
}
|
|
return nodes(index, 0, size).skipFirst(), nil
|
|
}
|
|
|
|
// Consistency returns the information on how to fetch and construct a
|
|
// consistency proof between the two given tree sizes of a log Merkle tree. It
|
|
// requires 0 <= size1 <= size2.
|
|
func Consistency(size1, size2 uint64) (Nodes, error) {
|
|
if size1 > size2 {
|
|
return Nodes{}, fmt.Errorf("tree size %d > %d", size1, size2)
|
|
}
|
|
if size1 == size2 || size1 == 0 {
|
|
return Nodes{IDs: []compact.NodeID{}}, nil
|
|
}
|
|
|
|
// Find the root of the biggest perfect subtree that ends at size1.
|
|
level := uint(bits.TrailingZeros64(size1))
|
|
index := (size1 - 1) >> level
|
|
// The consistency proof consists of this node (except if size1 is a power of
|
|
// two, in which case adding this node would be redundant because the client
|
|
// is assumed to know it from a checkpoint), and nodes of the inclusion proof
|
|
// into this node in the tree of size2.
|
|
p := nodes(index, level, size2)
|
|
|
|
// Handle the case when size1 is a power of 2.
|
|
if index == 0 {
|
|
return p.skipFirst(), nil
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// nodes returns the node IDs necessary to prove that the (level, index) node
|
|
// is included in the Merkle tree of the given size.
|
|
func nodes(index uint64, level uint, size uint64) Nodes {
|
|
// Compute the `fork` node, where the path from root to (level, index) node
|
|
// diverges from the path to (0, size).
|
|
//
|
|
// The sibling of this node is the ephemeral node which represents a subtree
|
|
// that is not complete in the tree of the given size. To compute the hash
|
|
// of the ephemeral node, we need all the non-ephemeral nodes that cover the
|
|
// same range of leaves.
|
|
//
|
|
// The `inner` variable is how many layers up from (level, index) the `fork`
|
|
// and the ephemeral nodes are.
|
|
inner := bits.Len64(index^(size>>level)) - 1
|
|
fork := compact.NewNodeID(level+uint(inner), index>>inner)
|
|
|
|
begin, end := fork.Coverage()
|
|
left := compact.RangeSize(0, begin)
|
|
right := compact.RangeSize(end, size)
|
|
|
|
node := compact.NewNodeID(level, index)
|
|
// Pre-allocate the exact number of nodes for the proof, in order:
|
|
// - The seed node for which we are building the proof.
|
|
// - The `inner` nodes at each level up to the fork node.
|
|
// - The `right` nodes, comprising the ephemeral node.
|
|
// - The `left` nodes, completing the coverage of the whole [0, size) range.
|
|
nodes := append(make([]compact.NodeID, 0, 1+inner+right+left), node)
|
|
|
|
// The first portion of the proof consists of the siblings for nodes of the
|
|
// path going up to the level at which the ephemeral node appears.
|
|
for ; node.Level < fork.Level; node = node.Parent() {
|
|
nodes = append(nodes, node.Sibling())
|
|
}
|
|
// This portion of the proof covers the range [begin, end) under it. The
|
|
// ranges to the left and to the right from it remain to be covered.
|
|
|
|
// Add all the nodes (potentially none) that cover the right range, and
|
|
// represent the ephemeral node. Reverse them so that the Rehash method can
|
|
// process hashes in the convenient order, from lower to upper levels.
|
|
len1 := len(nodes)
|
|
nodes = compact.RangeNodes(end, size, nodes)
|
|
reverse(nodes[len(nodes)-right:])
|
|
len2 := len(nodes)
|
|
// Add the nodes that cover the left range, ordered increasingly by level.
|
|
nodes = compact.RangeNodes(0, begin, nodes)
|
|
reverse(nodes[len(nodes)-left:])
|
|
|
|
// nodes[len1:len2] contains the nodes representing the ephemeral node. If
|
|
// it's empty, make it zero. Note that it can also contain a single node.
|
|
// Depending on the preference of the layer above, it may or may not be
|
|
// considered ephemeral.
|
|
if len1 >= len2 {
|
|
len1, len2 = 0, 0
|
|
}
|
|
|
|
return Nodes{IDs: nodes, begin: len1, end: len2, ephem: fork.Sibling()}
|
|
}
|
|
|
|
// Ephem returns the ephemeral node, and indices begin and end, such that
|
|
// IDs[begin:end] slice contains the child nodes of the ephemeral node.
|
|
//
|
|
// The list is empty iff there are no ephemeral nodes in the proof. Some
|
|
// examples of when this can happen: a proof in a perfect tree; an inclusion
|
|
// proof for a leaf in a perfect subtree at the right edge of the tree.
|
|
func (n Nodes) Ephem() (compact.NodeID, int, int) {
|
|
return n.ephem, n.begin, n.end
|
|
}
|
|
|
|
// Rehash computes the proof based on the slice of node hashes corresponding to
|
|
// their IDs in the n.IDs field. The slices must be of the same length. The hc
|
|
// parameter computes a node's hash based on hashes of its children.
|
|
//
|
|
// Warning: The passed-in slice of hashes can be modified in-place.
|
|
func (n Nodes) Rehash(h [][]byte, hc func(left, right []byte) []byte) ([][]byte, error) {
|
|
if got, want := len(h), len(n.IDs); got != want {
|
|
return nil, fmt.Errorf("got %d hashes but expected %d", got, want)
|
|
}
|
|
cursor := 0
|
|
// Scan the list of node hashes, and store the rehashed list in-place.
|
|
// Invariant: cursor <= i, and h[:cursor] contains all the hashes of the
|
|
// rehashed list after scanning h up to index i-1.
|
|
for i, ln := 0, len(h); i < ln; i, cursor = i+1, cursor+1 {
|
|
hash := h[i]
|
|
if i >= n.begin && i < n.end {
|
|
// Scan the block of node hashes that need rehashing.
|
|
for i++; i < n.end; i++ {
|
|
hash = hc(h[i], hash)
|
|
}
|
|
i--
|
|
}
|
|
h[cursor] = hash
|
|
}
|
|
return h[:cursor], nil
|
|
}
|
|
|
|
func (n Nodes) skipFirst() Nodes {
|
|
n.IDs = n.IDs[1:]
|
|
// Fixup the indices into the IDs slice.
|
|
if n.begin < n.end {
|
|
n.begin--
|
|
n.end--
|
|
}
|
|
return n
|
|
}
|
|
|
|
func reverse(ids []compact.NodeID) {
|
|
for i, j := 0, len(ids)-1; i < j; i, j = i+1, j-1 {
|
|
ids[i], ids[j] = ids[j], ids[i]
|
|
}
|
|
}
|