policy: image signature verification support
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
# Checkpoint format
|
||||
|
||||
This directory contains a description and supporting golang code for
|
||||
a reusable Checkpoint format which the TrustFabric team uses in various
|
||||
projects.
|
||||
|
||||
The format itself is heavily based on the
|
||||
[golang sumbdb head](https://sum.golang.org/latest), and corresponding
|
||||
[signed note](https://pkg.go.dev/golang.org/x/mod/sumdb/note) formats,
|
||||
and consists of two parts: a signed envelope, and a body.
|
||||
|
||||
### Signed envelope
|
||||
|
||||
The envelope (signed note) is of the form:
|
||||
|
||||
* One or more non-empty lines, each terminated by `\n` (the `body`)
|
||||
* One line consisting of just one `\n` (i.e. a blank line)
|
||||
* One or more `signature` lines, each terminated by `\n`
|
||||
|
||||
All signatures commit to the body only (including its trailing newline, but not
|
||||
the blank line's newline - see below for an example).
|
||||
|
||||
The signature(s) themselves are in the sumdb note format (concrete example
|
||||
[below](#example)):
|
||||
|
||||
`– <identity> <key_hint+signature_bytes>`
|
||||
where:
|
||||
|
||||
* `–` is an emdash (U+2014)
|
||||
* `<identity>` gives a human-readable representation of the signing ID
|
||||
|
||||
and the `signature_bytes` are prefixed with the first 4 bytes of the SHA256 hash
|
||||
of the associated public key to act as a hint in identifying the correct key to
|
||||
verify with.
|
||||
|
||||
For guidance on generating keys, see the
|
||||
[note documentation](https://pkg.go.dev/golang.org/x/mod/sumdb/note#hdr-Generating_Keys)
|
||||
and [implementation](https://cs.opensource.google/go/x/mod/+/master:sumdb/note/note.go;l=368;drc=ed3ec21bb8e252814c380df79a80f366440ddb2d).
|
||||
Of particular note is that the public key and its hash commit to the algorithm
|
||||
identifier.
|
||||
|
||||
**Differences from sumdb note:**
|
||||
Whereas the golang signed note *implementation* currently supports only Ed25519
|
||||
signatures, the format itself is not restricted to this scheme.
|
||||
|
||||
### Checkpoint body
|
||||
|
||||
The checkpoint body is of the form:
|
||||
|
||||
```text
|
||||
<Origin string>
|
||||
<Decimal log size>
|
||||
<Base64 log root hash>
|
||||
[otherdata]
|
||||
```
|
||||
|
||||
The first 3 lines of the body **MUST** be present in all Checkpoints.
|
||||
|
||||
* `<Origin string>` should be a unique identifier for the log identity which issued the checkpoint.
|
||||
The format SHOULD be a URI-like structure like `<dns_name>[/<suffix>]`, where the log operator
|
||||
controls `<dns_name>`, e.g `example.com/log42`. This is only a recommendation, and clients MUST
|
||||
NOT assume that the origin is a URI following this format. This structure reduces the likelihood
|
||||
of origin collision, and gives clues to humans about the log operator and what is in the log. The
|
||||
suffix is optional and can be anything. It is used to disambiguate logs owned under the same
|
||||
prefix.
|
||||
|
||||
The presence of this identifier forms part of the log claim, and guards against two
|
||||
logs producing bytewise identical checkpoints.
|
||||
|
||||
* `<Decimal log size>` is the ASCII decimal representation of the number of leaves committed
|
||||
to by this checkpoint. It should not have leading zeroes.
|
||||
|
||||
* `<Base64 log root hash>` is an
|
||||
[RFC4684 standard encoding](https://datatracker.ietf.org/doc/html/rfc4648#section-4) base-64
|
||||
representation of the log root hash at the specified log size.
|
||||
|
||||
* `[otherdata]` is opaque and optional, and, if necessary, can be used to tie extra
|
||||
data to the checkpoint, however its format must conform to the sumdb signed
|
||||
note spec (e.g. it must not contain blank lines.)
|
||||
|
||||
> Note that golang sumdb implementation is already compatible with this
|
||||
`[otherdata]` extension (see
|
||||
<https://github.com/golang/mod/blob/d6ab96f2441f9631f81862375ef66782fc4a9c12/sumdb/tlog/note.go#L52>).
|
||||
If you plan to use `otherdata` in your log, see the section on [merging checkpoints](#merging-checkpoints).
|
||||
|
||||
The first signature on a checkpoint should be from the log which issued it, but there MUST NOT
|
||||
be more than one signature from a log identity present on the checkpoint.
|
||||
|
||||
## Example
|
||||
|
||||
An annotated example signed checkpoint in this format is shown below:
|
||||
|
||||

|
||||
|
||||
|
||||
This checkpoint was issued by the log known as "Moon Log", the log's size is
|
||||
4027504, in the `other data` section a timestamp is encoded as a 64bit hex
|
||||
value, and further application-specific data relating to the phase of the moon
|
||||
at the point the checkpoint was issued is supplied following that.
|
||||
|
||||
## Merging Checkpoints
|
||||
|
||||
This checkpoint format allows a checkpoint that has been independently signed by
|
||||
multiple identities to be merged, creating a single checkpoint with multiple
|
||||
signatures. This is particularly useful for witnessing, where witnesses will
|
||||
independently check consistency of the log and produce a counter-signed copy
|
||||
containing two signatures: one for the log, and one for the witness.
|
||||
|
||||
The ability to merge signatures for the same body is a useful optimization.
|
||||
Clients that require N witness signatures will not be required to fetch N checkpoints.
|
||||
Instead they can fetch a single checkpoint and confirm it has the N required
|
||||
signatures (in addition to the log signature).
|
||||
|
||||
Note that this optimization requires the checkpoint _body_ to be byte-equivalent.
|
||||
The log signature does not need to be equal; when merging, only one of the log's
|
||||
signatures over this body will be propagated. The core checkpoint format above
|
||||
allows merging for any two consistent checkpoints for the same tree size.
|
||||
However, if the `otherdata` extension is used then this can lead to checkpoints
|
||||
that cannot be merged, even at the same tree size.
|
||||
|
||||
We recommend that log operators using `otherdata` consider carefully what
|
||||
information is included in this. If data is included in `otherdata` that is not
|
||||
fixed for a given tree size, then this can easily lead to unmergeable checkpoints.
|
||||
The most commonly anticipated cause for this would be including the timestamp at
|
||||
which the checkpoint was requested within the `otherdata`. In this case, no two
|
||||
witnesses are likely to ever acquire the same checkpoint body. There may be cases
|
||||
where this is unavoidable, but this consequence should be considered in the design.
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
// Copyright 2021 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 log provides basic support for the common log checkpoint and proof
|
||||
// format described by the README in this directory.
|
||||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Checkpoint represents a minimal log checkpoint (STH).
|
||||
type Checkpoint struct {
|
||||
// Origin is the string identifying the log which issued this checkpoint.
|
||||
Origin string
|
||||
// Size is the number of entries in the log at this checkpoint.
|
||||
Size uint64
|
||||
// Hash is the hash which commits to the contents of the entire log.
|
||||
Hash []byte
|
||||
}
|
||||
|
||||
// Marshal returns the common format representation of this Checkpoint.
|
||||
func (c Checkpoint) Marshal() []byte {
|
||||
return []byte(fmt.Sprintf("%s\n%d\n%s\n", c.Origin, c.Size, base64.StdEncoding.EncodeToString(c.Hash)))
|
||||
}
|
||||
|
||||
// Unmarshal parses the common formatted checkpoint data and stores the result
|
||||
// in the Checkpoint.
|
||||
//
|
||||
// The supplied data is expected to begin with the following 3 lines of text,
|
||||
// each followed by a newline:
|
||||
// - <origin string>
|
||||
// - <decimal representation of log size>
|
||||
// - <base64 representation of root hash>
|
||||
//
|
||||
// Any trailing data after this will be returned.
|
||||
func (c *Checkpoint) Unmarshal(data []byte) ([]byte, error) {
|
||||
l := bytes.SplitN(data, []byte("\n"), 4)
|
||||
if len(l) < 4 {
|
||||
return nil, errors.New("invalid checkpoint - too few newlines")
|
||||
}
|
||||
origin := string(l[0])
|
||||
if len(origin) == 0 {
|
||||
return nil, errors.New("invalid checkpoint - empty origin")
|
||||
}
|
||||
size, err := strconv.ParseUint(string(l[1]), 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid checkpoint - size invalid: %w", err)
|
||||
}
|
||||
h, err := base64.StdEncoding.DecodeString(string(l[2]))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid checkpoint - invalid hash: %w", err)
|
||||
}
|
||||
var rest []byte
|
||||
if len(l[3]) > 0 {
|
||||
rest = l[3]
|
||||
}
|
||||
*c = Checkpoint{
|
||||
Origin: origin,
|
||||
Size: size,
|
||||
Hash: h,
|
||||
}
|
||||
return rest, nil
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// Copyright 2021 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 log
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ID returns the identifier to use for a log given the Origin. This is the ID
|
||||
// used to find checkpoints for this log at distributors, and that will be used
|
||||
// to feed checkpoints to witnesses.
|
||||
func ID(origin string) string {
|
||||
s := sha256.New()
|
||||
s.Write([]byte("o:"))
|
||||
s.Write([]byte(origin))
|
||||
return fmt.Sprintf("%x", s.Sum(nil))
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// Copyright 2021 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 log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/mod/sumdb/note"
|
||||
)
|
||||
|
||||
// ParseCheckpoint takes a raw checkpoint as bytes and returns a parsed checkpoint
|
||||
// and any otherData in the body, providing that:
|
||||
// * a valid log signature is found; and
|
||||
// * the checkpoint unmarshals correctly; and
|
||||
// * the log origin is that expected.
|
||||
// In all other cases, an empty checkpoint is returned. The underlying note is always
|
||||
// returned where possible.
|
||||
// The signatures on the note will include the log signature if no error is returned,
|
||||
// plus any signatures from otherVerifiers that were found.
|
||||
func ParseCheckpoint(chkpt []byte, origin string, logVerifier note.Verifier, otherVerifiers ...note.Verifier) (*Checkpoint, []byte, *note.Note, error) {
|
||||
vs := append(append(make([]note.Verifier, 0, len(otherVerifiers)+1), logVerifier), otherVerifiers...)
|
||||
verifiers := note.VerifierList(vs...)
|
||||
|
||||
n, err := note.Open(chkpt, verifiers)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to verify signatures on checkpoint: %v", err)
|
||||
}
|
||||
|
||||
for _, s := range n.Sigs {
|
||||
if s.Hash == logVerifier.KeyHash() && s.Name == logVerifier.Name() {
|
||||
// The log has signed this checkpoint. It is now safe to parse.
|
||||
cp := &Checkpoint{}
|
||||
var otherData []byte
|
||||
if otherData, err = cp.Unmarshal([]byte(n.Text)); err != nil {
|
||||
return nil, nil, n, fmt.Errorf("failed to unmarshal checkpoint: %v", err)
|
||||
}
|
||||
if cp.Origin != origin {
|
||||
return nil, nil, n, fmt.Errorf("got Origin %q but expected %q", cp.Origin, origin)
|
||||
}
|
||||
return cp, otherData, n, nil
|
||||
}
|
||||
}
|
||||
return nil, nil, n, fmt.Errorf("no log signature found on note")
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
run:
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
deadline: 90s
|
||||
|
||||
linters-settings:
|
||||
depguard:
|
||||
list-type: blacklist
|
||||
packages:
|
||||
- golang.org/x/net/context
|
||||
- github.com/gogo/protobuf/proto
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
# MERKLE changelog
|
||||
|
||||
## HEAD
|
||||
|
||||
## v0.0.2
|
||||
* Fuzzing support
|
||||
* Dependency updates, notably to go1.19
|
||||
|
||||
## v0.0.1
|
||||
Initial release
|
||||
+1
@@ -0,0 +1 @@
|
||||
* @transparency-dev/core-team
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
# How to contribute #
|
||||
|
||||
We'd love to accept your patches and contributions to this project. There are
|
||||
a just a few small guidelines you need to follow.
|
||||
|
||||
|
||||
## Contributor License Agreement ##
|
||||
|
||||
Contributions to any Google project must be accompanied by a Contributor
|
||||
License Agreement. This is not a copyright **assignment**, it simply gives
|
||||
Google permission to use and redistribute your contributions as part of the
|
||||
project.
|
||||
|
||||
* If you are an individual writing original source code and you're sure you
|
||||
own the intellectual property, then you'll need to sign an [individual
|
||||
CLA][].
|
||||
|
||||
* If you work for a company that wants to allow you to contribute your work,
|
||||
then you'll need to sign a [corporate CLA][].
|
||||
|
||||
You generally only need to submit a CLA once, so if you've already submitted
|
||||
one (even if it was for a different project), you probably don't need to do it
|
||||
again.
|
||||
|
||||
[individual CLA]: https://developers.google.com/open-source/cla/individual
|
||||
[corporate CLA]: https://developers.google.com/open-source/cla/corporate
|
||||
|
||||
Once your CLA is submitted (or if you already submitted one for
|
||||
another Google project), make a commit adding yourself to the
|
||||
[AUTHORS][] and [CONTRIBUTORS][] files. This commit can be part
|
||||
of your first [pull request][].
|
||||
|
||||
[AUTHORS]: AUTHORS
|
||||
[CONTRIBUTORS]: CONTRIBUTORS
|
||||
|
||||
|
||||
## Submitting a patch ##
|
||||
|
||||
1. It's generally best to start by opening a new issue describing the bug or
|
||||
feature you're intending to fix. Even if you think it's relatively minor,
|
||||
it's helpful to know what people are working on. Mention in the initial
|
||||
issue that you are planning to work on that bug or feature so that it can
|
||||
be assigned to you.
|
||||
|
||||
1. Follow the normal process of [forking][] the project, and setup a new
|
||||
branch to work in. It's important that each group of changes be done in
|
||||
separate branches in order to ensure that a pull request only includes the
|
||||
commits related to that bug or feature.
|
||||
|
||||
1. Do your best to have [well-formed commit messages][] for each change.
|
||||
This provides consistency throughout the project, and ensures that commit
|
||||
messages are able to be formatted properly by various git tools.
|
||||
|
||||
1. Finally, push the commits to your fork and submit a [pull request][].
|
||||
|
||||
[forking]: https://help.github.com/articles/fork-a-repo
|
||||
[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
|
||||
[pull request]: https://help.github.com/articles/creating-a-pull-request
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Merkle
|
||||
|
||||
[](https://pkg.go.dev/github.com/transparency-dev/merkle)
|
||||
[](https://goreportcard.com/report/github.com/transparency-dev/merkle)
|
||||
[](https://codecov.io/gh/transparency-dev/merkle)
|
||||
[](https://gtrillian.slack.com/)
|
||||
|
||||
## Overview
|
||||
|
||||
This repository contains Go code to help create and manipulate Merkle trees, as
|
||||
well as constructing and verifying various types of proof.
|
||||
|
||||
This is the data structure which is used by projects such as
|
||||
[Trillian](https://github.com/google/trillian) to provide
|
||||
[verifiable logs](https://transparency.dev/verifiable-data-structures/#verifiable-log).
|
||||
|
||||
|
||||
## Support
|
||||
* Mailing list: https://groups.google.com/forum/#!forum/trillian-transparency
|
||||
* Slack: https://gtrillian.slack.com/ (invitation)
|
||||
|
||||
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
timeout: 300s
|
||||
options:
|
||||
machineType: E2_HIGHCPU_32
|
||||
volumes:
|
||||
- name: go-modules
|
||||
path: /go
|
||||
env:
|
||||
- GOPROXY=https://proxy.golang.org
|
||||
- PROJECT_ROOT=github.com/transparency-dev/merkle
|
||||
- GOPATH=/go
|
||||
|
||||
# Cloud Build logs sent to GCS bucket
|
||||
logsBucket: 'gs://trillian-cloudbuild-logs'
|
||||
|
||||
steps:
|
||||
- id: 'lint'
|
||||
name: "golangci/golangci-lint:v1.51"
|
||||
args: ["golangci-lint", "run", "--timeout", "10m"]
|
||||
|
||||
- id: 'unit tests'
|
||||
name: 'golang:1.19'
|
||||
args: ['go', 'test', './...']
|
||||
|
||||
- id: 'build'
|
||||
name: 'golang:1.19'
|
||||
args: ['go', 'build', './...']
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
// Copyright 2019 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 compact
|
||||
|
||||
import "math/bits"
|
||||
|
||||
// NodeID identifies a node of a Merkle tree.
|
||||
//
|
||||
// The ID consists of a level and index within this level. Levels are numbered
|
||||
// from 0, which corresponds to the tree leaves. Within each level, nodes are
|
||||
// numbered with consecutive indices starting from 0.
|
||||
//
|
||||
// L4: ┌───────0───────┐ ...
|
||||
// L3: ┌───0───┐ ┌───1───┐ ┌─── ...
|
||||
// L2: ┌─0─┐ ┌─1─┐ ┌─2─┐ ┌─3─┐ ┌─4─┐ ...
|
||||
// L1: ┌0┐ ┌1┐ ┌2┐ ┌3┐ ┌4┐ ┌5┐ ┌6┐ ┌7┐ ┌8┐ ┌9┐ ...
|
||||
// L0: 0 1 2 3 4 5 6 7 8 9 ... ... ... ... ... ...
|
||||
//
|
||||
// When the tree is not perfect, the nodes that would complement it to perfect
|
||||
// are called ephemeral. Algorithms that operate with ephemeral nodes still map
|
||||
// them to the same address space.
|
||||
type NodeID struct {
|
||||
Level uint
|
||||
Index uint64
|
||||
}
|
||||
|
||||
// NewNodeID returns a NodeID with the passed in node coordinates.
|
||||
func NewNodeID(level uint, index uint64) NodeID {
|
||||
return NodeID{Level: level, Index: index}
|
||||
}
|
||||
|
||||
// Parent returns the ID of the parent node.
|
||||
func (id NodeID) Parent() NodeID {
|
||||
return NewNodeID(id.Level+1, id.Index>>1)
|
||||
}
|
||||
|
||||
// Sibling returns the ID of the sibling node.
|
||||
func (id NodeID) Sibling() NodeID {
|
||||
return NewNodeID(id.Level, id.Index^1)
|
||||
}
|
||||
|
||||
// Coverage returns the [begin, end) range of leaves covered by the node.
|
||||
func (id NodeID) Coverage() (uint64, uint64) {
|
||||
return id.Index << id.Level, (id.Index + 1) << id.Level
|
||||
}
|
||||
|
||||
// RangeNodes appends the IDs of the nodes that comprise the [begin, end)
|
||||
// compact range to the given slice, and returns the new slice. The caller may
|
||||
// pre-allocate space with the help of the RangeSize function.
|
||||
func RangeNodes(begin, end uint64, ids []NodeID) []NodeID {
|
||||
left, right := Decompose(begin, end)
|
||||
|
||||
pos := begin
|
||||
// Iterate over perfect subtrees along the left border of the range, ordered
|
||||
// from lower to upper levels.
|
||||
for bit := uint64(0); left != 0; pos, left = pos+bit, left^bit {
|
||||
level := uint(bits.TrailingZeros64(left))
|
||||
bit = uint64(1) << level
|
||||
ids = append(ids, NewNodeID(level, pos>>level))
|
||||
}
|
||||
|
||||
// Iterate over perfect subtrees along the right border of the range, ordered
|
||||
// from upper to lower levels.
|
||||
for bit := uint64(0); right != 0; pos, right = pos+bit, right^bit {
|
||||
level := uint(bits.Len64(right)) - 1
|
||||
bit = uint64(1) << level
|
||||
ids = append(ids, NewNodeID(level, pos>>level))
|
||||
}
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
// RangeSize returns the number of nodes in the [begin, end) compact range.
|
||||
func RangeSize(begin, end uint64) int {
|
||||
left, right := Decompose(begin, end)
|
||||
return bits.OnesCount64(left) + bits.OnesCount64(right)
|
||||
}
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
// Copyright 2019 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 compact provides compact Merkle tree data structures.
|
||||
package compact
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// HashFn computes an internal node's hash using the hashes of its child nodes.
|
||||
type HashFn func(left, right []byte) []byte
|
||||
|
||||
// VisitFn visits the node with the specified ID and hash.
|
||||
type VisitFn func(id NodeID, hash []byte)
|
||||
|
||||
// RangeFactory allows creating compact ranges with the specified hash
|
||||
// function, which must not be nil, and must not be changed.
|
||||
type RangeFactory struct {
|
||||
Hash HashFn
|
||||
}
|
||||
|
||||
// NewRange creates a Range for [begin, end) with the given set of hashes. The
|
||||
// hashes correspond to the roots of the minimal set of perfect sub-trees
|
||||
// covering the [begin, end) leaves range, ordered left to right.
|
||||
func (f *RangeFactory) NewRange(begin, end uint64, hashes [][]byte) (*Range, error) {
|
||||
if end < begin {
|
||||
return nil, fmt.Errorf("invalid range: end=%d, want >= %d", end, begin)
|
||||
}
|
||||
if got, want := len(hashes), RangeSize(begin, end); got != want {
|
||||
return nil, fmt.Errorf("invalid hashes: got %d values, want %d", got, want)
|
||||
}
|
||||
return &Range{f: f, begin: begin, end: end, hashes: hashes}, nil
|
||||
}
|
||||
|
||||
// NewEmptyRange returns a new Range for an empty [begin, begin) range. The
|
||||
// value of begin defines where the range will start growing from when entries
|
||||
// are appended to it.
|
||||
func (f *RangeFactory) NewEmptyRange(begin uint64) *Range {
|
||||
return &Range{f: f, begin: begin, end: begin}
|
||||
}
|
||||
|
||||
// Range represents a compact Merkle tree range for leaf indices [begin, end).
|
||||
//
|
||||
// It contains the minimal set of perfect subtrees whose leaves comprise this
|
||||
// range. The structure is efficiently mergeable with other compact ranges that
|
||||
// share one of the endpoints with it.
|
||||
//
|
||||
// For more details, see
|
||||
// https://github.com/transparency-dev/merkle/blob/main/docs/compact_ranges.md.
|
||||
type Range struct {
|
||||
f *RangeFactory
|
||||
begin uint64
|
||||
end uint64
|
||||
hashes [][]byte
|
||||
}
|
||||
|
||||
// Begin returns the first index covered by the range (inclusive).
|
||||
func (r *Range) Begin() uint64 {
|
||||
return r.begin
|
||||
}
|
||||
|
||||
// End returns the last index covered by the range (exclusive).
|
||||
func (r *Range) End() uint64 {
|
||||
return r.end
|
||||
}
|
||||
|
||||
// Hashes returns sub-tree hashes corresponding to the minimal set of perfect
|
||||
// sub-trees covering the [begin, end) range, ordered left to right.
|
||||
func (r *Range) Hashes() [][]byte {
|
||||
return r.hashes
|
||||
}
|
||||
|
||||
// Append extends the compact range by appending the passed in hash to it. It
|
||||
// reports all the added nodes through the visitor function (if non-nil).
|
||||
func (r *Range) Append(hash []byte, visitor VisitFn) error {
|
||||
if visitor != nil {
|
||||
visitor(NewNodeID(0, r.end), hash)
|
||||
}
|
||||
return r.appendImpl(r.end+1, hash, nil, visitor)
|
||||
}
|
||||
|
||||
// AppendRange extends the compact range by merging in the other compact range
|
||||
// from the right. It uses the tree hasher to calculate hashes of newly created
|
||||
// nodes, and reports them through the visitor function (if non-nil).
|
||||
func (r *Range) AppendRange(other *Range, visitor VisitFn) error {
|
||||
if other.f != r.f {
|
||||
return errors.New("incompatible ranges")
|
||||
}
|
||||
if got, want := other.begin, r.end; got != want {
|
||||
return fmt.Errorf("ranges are disjoint: other.begin=%d, want %d", got, want)
|
||||
}
|
||||
if len(other.hashes) == 0 { // The other range is empty, merging is trivial.
|
||||
return nil
|
||||
}
|
||||
return r.appendImpl(other.end, other.hashes[0], other.hashes[1:], visitor)
|
||||
}
|
||||
|
||||
// GetRootHash returns the root hash of the Merkle tree represented by this
|
||||
// compact range. Requires the range to start at index 0. If the range is
|
||||
// empty, returns nil.
|
||||
//
|
||||
// If visitor is not nil, it is called with all "ephemeral" nodes (i.e. the
|
||||
// ones rooting imperfect subtrees) along the right border of the tree.
|
||||
func (r *Range) GetRootHash(visitor VisitFn) ([]byte, error) {
|
||||
if r.begin != 0 {
|
||||
return nil, fmt.Errorf("begin=%d, want 0", r.begin)
|
||||
}
|
||||
ln := len(r.hashes)
|
||||
if ln == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
hash := r.hashes[ln-1]
|
||||
// All non-perfect subtree hashes along the right border of the tree
|
||||
// correspond to the parents of all perfect subtree nodes except the lowest
|
||||
// one (therefore the loop skips it).
|
||||
for i, size := ln-2, r.end; i >= 0; i-- {
|
||||
hash = r.f.Hash(r.hashes[i], hash)
|
||||
if visitor != nil {
|
||||
size &= size - 1 // Delete the previous node.
|
||||
level := uint(bits.TrailingZeros64(size)) + 1 // Compute the parent level.
|
||||
index := size >> level // And its horizontal index.
|
||||
visitor(NewNodeID(level, index), hash)
|
||||
}
|
||||
}
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
// Equal compares two Ranges for equality.
|
||||
func (r *Range) Equal(other *Range) bool {
|
||||
if r.f != other.f || r.begin != other.begin || r.end != other.end {
|
||||
return false
|
||||
}
|
||||
if len(r.hashes) != len(other.hashes) {
|
||||
return false
|
||||
}
|
||||
for i := range r.hashes {
|
||||
if !bytes.Equal(r.hashes[i], other.hashes[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// appendImpl extends the compact range by merging the [r.end, end) compact
|
||||
// range into it. The other compact range is decomposed into a seed hash and
|
||||
// all the other hashes (possibly none). The method uses the tree hasher to
|
||||
// calculate hashes of newly created nodes, and reports them through the
|
||||
// visitor function (if non-nil).
|
||||
func (r *Range) appendImpl(end uint64, seed []byte, hashes [][]byte, visitor VisitFn) error {
|
||||
// Bits [low, high) of r.end encode the merge path, i.e. the sequence of node
|
||||
// merges that transforms the two compact ranges into one.
|
||||
low, high := getMergePath(r.begin, r.end, end)
|
||||
if high < low {
|
||||
high = low
|
||||
}
|
||||
index := r.end >> low
|
||||
// Now bits [0, high-low) of index encode the merge path.
|
||||
|
||||
// The number of one bits in index is the number of nodes from the left range
|
||||
// that will be merged, and zero bits correspond to the nodes in the right
|
||||
// range. Below we make sure that both ranges have enough hashes, which can
|
||||
// be false only in case the data is corrupted in some way.
|
||||
ones := bits.OnesCount64(index & (1<<(high-low) - 1))
|
||||
if ln := len(r.hashes); ln < ones {
|
||||
return fmt.Errorf("corrupted lhs range: got %d hashes, want >= %d", ln, ones)
|
||||
}
|
||||
if ln, zeros := len(hashes), int(high-low)-ones; ln < zeros {
|
||||
return fmt.Errorf("corrupted rhs range: got %d hashes, want >= %d", ln+1, zeros+1)
|
||||
}
|
||||
|
||||
// Some of the trailing nodes of the left compact range, and some of the
|
||||
// leading nodes of the right range, are sequentially merged with the seed,
|
||||
// according to the mask. All new nodes are reported through the visitor.
|
||||
idx1, idx2 := len(r.hashes), 0
|
||||
for h := low; h < high; h++ {
|
||||
if index&1 == 0 {
|
||||
seed = r.f.Hash(seed, hashes[idx2])
|
||||
idx2++
|
||||
} else {
|
||||
idx1--
|
||||
seed = r.f.Hash(r.hashes[idx1], seed)
|
||||
}
|
||||
index >>= 1
|
||||
if visitor != nil {
|
||||
visitor(NewNodeID(h+1, index), seed)
|
||||
}
|
||||
}
|
||||
|
||||
// All nodes from both ranges that have not been merged are bundled together
|
||||
// with the "merged" seed node.
|
||||
r.hashes = append(append(r.hashes[:idx1], seed), hashes[idx2:]...)
|
||||
r.end = end
|
||||
return nil
|
||||
}
|
||||
|
||||
// getMergePath returns the merging path between the compact range [begin, mid)
|
||||
// and [mid, end). The path is represented as a range of bits within mid, with
|
||||
// bit indices [low, high). A bit value of 1 on level i of mid means that the
|
||||
// node on this level merges with the corresponding node in the left compact
|
||||
// range, whereas 0 represents merging with the right compact range. If the
|
||||
// path is empty then high <= low.
|
||||
//
|
||||
// The output is not specified if begin <= mid <= end doesn't hold, but the
|
||||
// function never panics.
|
||||
func getMergePath(begin, mid, end uint64) (uint, uint) {
|
||||
low := bits.TrailingZeros64(mid)
|
||||
high := 64
|
||||
if begin != 0 {
|
||||
high = bits.Len64(mid ^ (begin - 1))
|
||||
}
|
||||
if high2 := bits.Len64((mid - 1) ^ end); high2 < high {
|
||||
high = high2
|
||||
}
|
||||
return uint(low), uint(high - 1)
|
||||
}
|
||||
|
||||
// Decompose splits the [begin, end) range into a minimal number of sub-ranges,
|
||||
// each of which is of the form [m * 2^k, (m+1) * 2^k), i.e. of length 2^k, for
|
||||
// some integers m, k >= 0.
|
||||
//
|
||||
// The sequence of sizes is returned encoded as bitmasks left and right, where:
|
||||
// - a 1 bit in a bitmask denotes a sub-range of the corresponding size 2^k
|
||||
// - left mask bits in LSB-to-MSB order encode the left part of the sequence
|
||||
// - right mask bits in MSB-to-LSB order encode the right part
|
||||
//
|
||||
// The corresponding values of m are not returned (they can be calculated from
|
||||
// begin and the sub-range sizes).
|
||||
//
|
||||
// For example, (begin, end) values of (0b110, 0b11101) would indicate a
|
||||
// sequence of tree sizes: 2,8; 8,4,1.
|
||||
//
|
||||
// The output is not specified if begin > end, but the function never panics.
|
||||
func Decompose(begin, end uint64) (uint64, uint64) {
|
||||
// Special case, as the code below works only if begin != 0, or end < 2^63.
|
||||
if begin == 0 {
|
||||
return 0, end
|
||||
}
|
||||
xbegin := begin - 1
|
||||
// Find where paths to leaves #begin-1 and #end diverge, and mask the upper
|
||||
// bits away, as only the nodes strictly below this point are in the range.
|
||||
d := bits.Len64(xbegin^end) - 1
|
||||
mask := uint64(1)<<uint(d) - 1
|
||||
// The left part of the compact range consists of all nodes strictly below
|
||||
// and to the right from the path to leaf #begin-1, corresponding to zero
|
||||
// bits in the masked part of begin-1. Likewise, the right part consists of
|
||||
// nodes below and to the left from the path to leaf #end, corresponding to
|
||||
// ones in the masked part of end.
|
||||
return ^xbegin & mask, end & mask
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
// Copyright 2017 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 merkle provides Merkle tree interfaces and implementation.
|
||||
package merkle
|
||||
|
||||
// TODO(pavelkalinnikov): Remove this root package. The only interface provided
|
||||
// here does not have to exist, and can be [re-]defined on the user side, such
|
||||
// as in compact or proof package.
|
||||
|
||||
// LogHasher provides the hash functions needed to compute dense merkle trees.
|
||||
type LogHasher interface {
|
||||
// EmptyRoot supports returning a special case for the root of an empty tree.
|
||||
EmptyRoot() []byte
|
||||
// HashLeaf computes the hash of a leaf that exists.
|
||||
HashLeaf(leaf []byte) []byte
|
||||
// HashChildren computes interior nodes.
|
||||
HashChildren(l, r []byte) []byte
|
||||
// Size returns the number of bytes the Hash* functions will return.
|
||||
Size() int
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
// 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]
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
// Copyright 2017 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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/bits"
|
||||
|
||||
"github.com/transparency-dev/merkle"
|
||||
)
|
||||
|
||||
// RootMismatchError occurs when an inclusion proof fails.
|
||||
type RootMismatchError struct {
|
||||
ExpectedRoot []byte
|
||||
CalculatedRoot []byte
|
||||
}
|
||||
|
||||
func (e RootMismatchError) Error() string {
|
||||
return fmt.Sprintf("calculated root:\n%v\n does not match expected root:\n%v", e.CalculatedRoot, e.ExpectedRoot)
|
||||
}
|
||||
|
||||
func verifyMatch(calculated, expected []byte) error {
|
||||
if !bytes.Equal(calculated, expected) {
|
||||
return RootMismatchError{ExpectedRoot: expected, CalculatedRoot: calculated}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyInclusion verifies the correctness of the inclusion proof for the leaf
|
||||
// with the specified hash and index, relatively to the tree of the given size
|
||||
// and root hash. Requires 0 <= index < size.
|
||||
func VerifyInclusion(hasher merkle.LogHasher, index, size uint64, leafHash []byte, proof [][]byte, root []byte) error {
|
||||
calcRoot, err := RootFromInclusionProof(hasher, index, size, leafHash, proof)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return verifyMatch(calcRoot, root)
|
||||
}
|
||||
|
||||
// RootFromInclusionProof calculates the expected root hash for a tree of the
|
||||
// given size, provided a leaf index and hash with the corresponding inclusion
|
||||
// proof. Requires 0 <= index < size.
|
||||
func RootFromInclusionProof(hasher merkle.LogHasher, index, size uint64, leafHash []byte, proof [][]byte) ([]byte, error) {
|
||||
if index >= size {
|
||||
return nil, fmt.Errorf("index is beyond size: %d >= %d", index, size)
|
||||
}
|
||||
if got, want := len(leafHash), hasher.Size(); got != want {
|
||||
return nil, fmt.Errorf("leafHash has unexpected size %d, want %d", got, want)
|
||||
}
|
||||
|
||||
inner, border := decompInclProof(index, size)
|
||||
if got, want := len(proof), inner+border; got != want {
|
||||
return nil, fmt.Errorf("wrong proof size %d, want %d", got, want)
|
||||
}
|
||||
|
||||
res := chainInner(hasher, leafHash, proof[:inner], index)
|
||||
res = chainBorderRight(hasher, res, proof[inner:])
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// VerifyConsistency checks that the passed-in consistency proof is valid
|
||||
// between the passed in tree sizes, with respect to the corresponding root
|
||||
// hashes. Requires 0 <= size1 <= size2.
|
||||
func VerifyConsistency(hasher merkle.LogHasher, size1, size2 uint64, proof [][]byte, root1, root2 []byte) error {
|
||||
switch {
|
||||
case size2 < size1:
|
||||
return fmt.Errorf("size2 (%d) < size1 (%d)", size1, size2)
|
||||
case size1 == size2:
|
||||
if len(proof) > 0 {
|
||||
return errors.New("size1=size2, but proof is not empty")
|
||||
}
|
||||
return verifyMatch(root1, root2)
|
||||
case size1 == 0:
|
||||
// Any size greater than 0 is consistent with size 0.
|
||||
if len(proof) > 0 {
|
||||
return fmt.Errorf("expected empty proof, but got %d components", len(proof))
|
||||
}
|
||||
return nil // Proof OK.
|
||||
case len(proof) == 0:
|
||||
return errors.New("empty proof")
|
||||
}
|
||||
|
||||
inner, border := decompInclProof(size1-1, size2)
|
||||
shift := bits.TrailingZeros64(size1)
|
||||
inner -= shift // Note: shift < inner if size1 < size2.
|
||||
|
||||
// The proof includes the root hash for the sub-tree of size 2^shift.
|
||||
seed, start := proof[0], 1
|
||||
if size1 == 1<<uint(shift) { // Unless size1 is that very 2^shift.
|
||||
seed, start = root1, 0
|
||||
}
|
||||
if got, want := len(proof), start+inner+border; got != want {
|
||||
return fmt.Errorf("wrong proof size %d, want %d", got, want)
|
||||
}
|
||||
proof = proof[start:]
|
||||
// Now len(proof) == inner+border, and proof is effectively a suffix of
|
||||
// inclusion proof for entry |size1-1| in a tree of size |size2|.
|
||||
|
||||
// Verify the first root.
|
||||
mask := (size1 - 1) >> uint(shift) // Start chaining from level |shift|.
|
||||
hash1 := chainInnerRight(hasher, seed, proof[:inner], mask)
|
||||
hash1 = chainBorderRight(hasher, hash1, proof[inner:])
|
||||
if err := verifyMatch(hash1, root1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify the second root.
|
||||
hash2 := chainInner(hasher, seed, proof[:inner], mask)
|
||||
hash2 = chainBorderRight(hasher, hash2, proof[inner:])
|
||||
return verifyMatch(hash2, root2)
|
||||
}
|
||||
|
||||
// decompInclProof breaks down inclusion proof for a leaf at the specified
|
||||
// |index| in a tree of the specified |size| into 2 components. The splitting
|
||||
// point between them is where paths to leaves |index| and |size-1| diverge.
|
||||
// Returns lengths of the bottom and upper proof parts correspondingly. The sum
|
||||
// of the two determines the correct length of the inclusion proof.
|
||||
func decompInclProof(index, size uint64) (int, int) {
|
||||
inner := innerProofSize(index, size)
|
||||
border := bits.OnesCount64(index >> uint(inner))
|
||||
return inner, border
|
||||
}
|
||||
|
||||
func innerProofSize(index, size uint64) int {
|
||||
return bits.Len64(index ^ (size - 1))
|
||||
}
|
||||
|
||||
// chainInner computes a subtree hash for a node on or below the tree's right
|
||||
// border. Assumes |proof| hashes are ordered from lower levels to upper, and
|
||||
// |seed| is the initial subtree/leaf hash on the path located at the specified
|
||||
// |index| on its level.
|
||||
func chainInner(hasher merkle.LogHasher, seed []byte, proof [][]byte, index uint64) []byte {
|
||||
for i, h := range proof {
|
||||
if (index>>uint(i))&1 == 0 {
|
||||
seed = hasher.HashChildren(seed, h)
|
||||
} else {
|
||||
seed = hasher.HashChildren(h, seed)
|
||||
}
|
||||
}
|
||||
return seed
|
||||
}
|
||||
|
||||
// chainInnerRight computes a subtree hash like chainInner, but only takes
|
||||
// hashes to the left from the path into consideration, which effectively means
|
||||
// the result is a hash of the corresponding earlier version of this subtree.
|
||||
func chainInnerRight(hasher merkle.LogHasher, seed []byte, proof [][]byte, index uint64) []byte {
|
||||
for i, h := range proof {
|
||||
if (index>>uint(i))&1 == 1 {
|
||||
seed = hasher.HashChildren(h, seed)
|
||||
}
|
||||
}
|
||||
return seed
|
||||
}
|
||||
|
||||
// chainBorderRight chains proof hashes along tree borders. This differs from
|
||||
// inner chaining because |proof| contains only left-side subtree hashes.
|
||||
func chainBorderRight(hasher merkle.LogHasher, seed []byte, proof [][]byte) []byte {
|
||||
for _, h := range proof {
|
||||
seed = hasher.HashChildren(h, seed)
|
||||
}
|
||||
return seed
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
// Copyright 2016 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 rfc6962 provides hashing functionality according to RFC6962.
|
||||
package rfc6962
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
_ "crypto/sha256" // SHA256 is the default algorithm.
|
||||
)
|
||||
|
||||
// Domain separation prefixes
|
||||
const (
|
||||
RFC6962LeafHashPrefix = 0
|
||||
RFC6962NodeHashPrefix = 1
|
||||
)
|
||||
|
||||
// DefaultHasher is a SHA256 based LogHasher.
|
||||
var DefaultHasher = New(crypto.SHA256)
|
||||
|
||||
// Hasher implements the RFC6962 tree hashing algorithm.
|
||||
type Hasher struct {
|
||||
crypto.Hash
|
||||
}
|
||||
|
||||
// New creates a new Hashers.LogHasher on the passed in hash function.
|
||||
func New(h crypto.Hash) *Hasher {
|
||||
return &Hasher{Hash: h}
|
||||
}
|
||||
|
||||
// EmptyRoot returns a special case for an empty tree.
|
||||
func (t *Hasher) EmptyRoot() []byte {
|
||||
return t.New().Sum(nil)
|
||||
}
|
||||
|
||||
// HashLeaf returns the Merkle tree leaf hash of the data passed in through leaf.
|
||||
// The data in leaf is prefixed by the LeafHashPrefix.
|
||||
func (t *Hasher) HashLeaf(leaf []byte) []byte {
|
||||
h := t.New()
|
||||
h.Write([]byte{RFC6962LeafHashPrefix})
|
||||
h.Write(leaf)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
// HashChildren returns the inner Merkle tree node hash of the two child nodes l and r.
|
||||
// The hashed structure is NodeHashPrefix||l||r.
|
||||
func (t *Hasher) HashChildren(l, r []byte) []byte {
|
||||
h := t.New()
|
||||
b := append(append(append(
|
||||
make([]byte, 0, 1+len(l)+len(r)),
|
||||
RFC6962NodeHashPrefix),
|
||||
l...),
|
||||
r...)
|
||||
|
||||
h.Write(b)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
Reference in New Issue
Block a user