-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathspicy.go
More file actions
153 lines (144 loc) · 4.87 KB
/
Copy pathspicy.go
File metadata and controls
153 lines (144 loc) · 4.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package torchwood
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"strconv"
"strings"
"golang.org/x/mod/sumdb/tlog"
)
// FormatProof formats a tlog record inclusion proof (a "spicy signature") for
// the record at index idx with proof p and signed checkpoint signedCheckpoint.
//
// The returned byte slice is encoded according to c2sp.org/tlog-proof@v1.
func FormatProof(idx int64, p tlog.RecordProof, signedCheckpoint []byte) []byte {
return formatProof(idx, p, signedCheckpoint, nil, false)
}
// FormatProofWithExtraData formats a tlog record inclusion proof (a "spicy
// signature") for the record at index idx with proof p and signed checkpoint
// signedCheckpoint, including extra data.
//
// The returned byte slice is encoded according to c2sp.org/tlog-proof@v1.
func FormatProofWithExtraData(idx int64, extra []byte, p tlog.RecordProof, signedCheckpoint []byte) []byte {
return formatProof(idx, p, signedCheckpoint, extra, true)
}
func formatProof(idx int64, p tlog.RecordProof, signedCheckpoint []byte, extra []byte, withExtra bool) []byte {
var buf bytes.Buffer
fmt.Fprintf(&buf, "c2sp.org/tlog-proof@v1\n")
if withExtra {
fmt.Fprintf(&buf, "extra %s\n", base64.StdEncoding.EncodeToString([]byte(extra)))
}
fmt.Fprintf(&buf, "index %d\n", idx)
for _, h := range p {
fmt.Fprintf(&buf, "%s\n", h)
}
fmt.Fprintf(&buf, "\n")
buf.Write(signedCheckpoint)
return buf.Bytes()
}
// VerifyRecordError is returned by [VerifyProof] when the inclusion proof does
// not verify. It can be used to diagnose the issue or print a better error
// message. All of its fields are unauthenticated and must not be trusted.
type VerifyRecordError struct {
Index int64
Extra []byte
}
func (e *VerifyRecordError) Error() string {
return fmt.Sprintf("tlog record inclusion proof verification failed for index %d", e.Index)
}
// VerifyProof verifies a proof (a "spicy signature" encoded according to
// c2sp.org/tlog-proof@v1) for a record hash rh (generally produced with
// [tlog.RecordHash]).
//
// If the note signatures do not satisfy the provided policy, an error wrapping
// *[note.UnverifiedNoteError] is returned. If the proof is valid but does not
// verify the record hash rh at the given index, a *[VerifyRecordError] is
// returned.
func VerifyProof(policy Policy, rh tlog.Hash, proof []byte) error {
hdr, rest, ok := strings.Cut(string(proof), "\n")
if !ok || hdr != "c2sp.org/tlog-proof@v1" {
return errors.New("malformed tlog proof: missing header, this may not be a tlog proof")
}
var extra []byte
if rest, ok = strings.CutPrefix(rest, "extra "); ok {
var s string
s, rest, ok = strings.Cut(rest, "\n")
if !ok {
return errors.New("malformed tlog proof: unexpected end of extra line")
}
var err error
extra, err = base64.StdEncoding.DecodeString(s)
if err != nil {
return fmt.Errorf("malformed tlog proof: invalid extra: %w", err)
}
}
rest, ok = strings.CutPrefix(rest, "index ")
if !ok {
return errors.New("malformed tlog proof: expected index line")
}
s, rest, ok := strings.Cut(rest, "\n")
if !ok {
return errors.New("malformed tlog proof: unexpected end of index line")
}
idx, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return fmt.Errorf("malformed tlog proof: invalid index: %w", err)
}
if idx < 0 {
return fmt.Errorf("malformed tlog proof: negative index")
}
var p tlog.RecordProof
for {
var h64 string
h64, rest, ok = strings.Cut(rest, "\n")
if !ok {
return errors.New("malformed tlog proof: unexpected end of proof lines")
}
if h64 == "" {
break
}
h, err := base64.StdEncoding.DecodeString(h64)
if err != nil {
return fmt.Errorf("malformed tlog proof: invalid hash: %w", err)
}
if len(h) != tlog.HashSize {
return fmt.Errorf("malformed tlog proof: invalid hash length: got %d, want 32", len(h))
}
p = append(p, tlog.Hash(h))
}
c, _, err := VerifyCheckpoint([]byte(rest), policy)
if err != nil {
return err
}
if err := tlog.CheckRecord(p, c.N, c.Hash, idx, rh); err != nil {
return &VerifyRecordError{
Index: idx,
Extra: extra,
}
}
return nil
}
// ProofExtraData extracts the extra data from a tlog proof encoded according to
// c2sp.org/tlog-proof@v1. If no extra data is present, it returns an error.
//
// The extra data is unauthenticated and must not be trusted.
func ProofExtraData(proof []byte) ([]byte, error) {
hdr, rest, ok := strings.Cut(string(proof), "\n")
if !ok || hdr != "c2sp.org/tlog-proof@v1" {
return nil, errors.New("malformed tlog proof: missing header, this may not be a tlog proof")
}
line, _, ok := strings.Cut(rest, "\n")
if !ok {
return nil, errors.New("malformed tlog proof: unexpected end of proof")
}
s, ok := strings.CutPrefix(line, "extra ")
if !ok {
return nil, errors.New("tlog proof does not contain extra data")
}
extra, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return nil, fmt.Errorf("malformed tlog proof: invalid extra: %w", err)
}
return extra, nil
}