Skip to content

Commit d4c19d9

Browse files
committed
Tidy up note package
1 parent ee22b41 commit d4c19d9

4 files changed

Lines changed: 232 additions & 88 deletions

File tree

note/note.go

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package note
6+
7+
import (
8+
"crypto/ed25519"
9+
"errors"
10+
"fmt"
11+
"strings"
12+
"strconv"
13+
"encoding/base64"
14+
"golang.org/x/mod/sumdb/note"
15+
)
16+
17+
// These algorithm identifiers correspond to those in https://c2sp.org/signed-note@v1.0.0
18+
const (
19+
algEd25519 = 1
20+
algECDSAWithSHA256 = 2
21+
algEd25519CosignatureV1 = 4
22+
algRFC6962STH = 5
23+
algMLDSA44 = 6
24+
)
25+
26+
const (
27+
// keyHashSize is the size in bytes of the key hash.
28+
keyHashSize = 4
29+
)
30+
31+
var (
32+
errSignerID = errors.New("malformed signer id")
33+
errSignerAlg = errors.New("unknown signer algorithm")
34+
errVerifierID = errors.New("malformed verifier id")
35+
errVerifierAlg = errors.New("unknown verifier algorithm")
36+
errInvalidHash = errors.New("invalid key hash")
37+
errMalformedSig = errors.New("malformed signature")
38+
errSignerHash = errors.New("invalid verifier hash")
39+
)
40+
41+
// NewSigner returns a new Signer for keys with the following algorithms:
42+
// - 0x01 The original SumDB Note ed25519 algorithm.
43+
// - 0x04 ed25519 Cosignature/V1
44+
// - 0x06 ML-DSA-44 Cosignature/V1
45+
func NewSigner(skey string) (Signer, error) {
46+
priv1, skey, _ := strings.Cut(skey, "+")
47+
priv2, skey, _ := strings.Cut(skey, "+")
48+
name, skey, _ := strings.Cut(skey, "+")
49+
hash16, key64, _ := strings.Cut(skey, "+")
50+
key, err := base64.StdEncoding.DecodeString(key64)
51+
if priv1 != "PRIVATE" || priv2 != "KEY" || len(hash16) != 8 || err != nil || !isValidName(name) || len(key) == 0 {
52+
return nil, errSignerID
53+
}
54+
55+
alg, key := key[0], key[1:]
56+
switch alg {
57+
case algEd25519:
58+
// We "re-implement" this here so as to be able to return a local Signer instance (as
59+
// opposed to a note.Signer).
60+
// This has the benefit that all signers from this packge carry a Verifier() getter.
61+
62+
// Adapted from sumdb/note/note.go
63+
hash, err := strconv.ParseUint(hash16, 16, 32)
64+
if err != nil {
65+
return nil, errSignerID
66+
}
67+
68+
// Note: hash is the hash of the public key and we have the private key.
69+
// Must verify hash after deriving public key.
70+
71+
s := &signer{
72+
name: name,
73+
hash: uint32(hash),
74+
}
75+
if len(key) != 32 {
76+
return nil, errSignerID
77+
}
78+
key = ed25519.NewKeyFromSeed(key)
79+
pubkey := append([]byte{algEd25519}, key[32:]...)
80+
if uint32(hash) != keyHashEd25519(name, pubkey) {
81+
return nil, errSignerHash
82+
}
83+
84+
s.sign = func(msg []byte) ([]byte, error) {
85+
return ed25519.Sign(key, msg), nil
86+
}
87+
s.verify = func(msg, sig []byte) bool {
88+
return ed25519.Verify(key, msg, sig)
89+
}
90+
91+
return s, nil
92+
case algEd25519CosignatureV1, algMLDSA44:
93+
return NewSignerForCosignatureV1(skey)
94+
default:
95+
return nil, errSignerAlg
96+
}
97+
}
98+
99+
// NewVerifier returns a verifier for the given key, if the key's algo is known.
100+
func NewVerifier(key string) (note.Verifier, error) {
101+
parts := strings.SplitN(key, "+", 3)
102+
if got, want := len(parts), 3; got != want {
103+
return nil, fmt.Errorf("key has %d parts, expected %d: %q", got, want, key)
104+
}
105+
keyBytes, err := base64.StdEncoding.DecodeString(parts[2])
106+
if err != nil {
107+
return nil, fmt.Errorf("key has invalid base64 %q: %v", parts[2], err)
108+
}
109+
if len(keyBytes) < 2 {
110+
return nil, fmt.Errorf("invalid key, key bytes too short")
111+
}
112+
113+
switch keyBytes[0] {
114+
case algECDSAWithSHA256:
115+
return NewECDSAVerifier(key)
116+
case algEd25519CosignatureV1, algMLDSA44:
117+
return NewVerifierForCosignatureV1(key)
118+
case algRFC6962STH:
119+
return NewRFC6962Verifier(key)
120+
default:
121+
return note.NewVerifier(key)
122+
}
123+
}
124+
125+
// The structs below are used by most/all of the signers and verifiers in this package.
126+
127+
// Signer is a note.Signer which also provides access to the corresponding Verifier.
128+
type Signer interface {
129+
note.Signer
130+
Verifier() note.Verifier
131+
}
132+
133+
// signer is a concrete implementation of the extended Signer interface above.
134+
type signer struct {
135+
name string
136+
hash uint32
137+
sign func([]byte) ([]byte, error)
138+
verify func(msg, sig []byte) bool
139+
}
140+
141+
func (s *signer) Name() string { return s.name }
142+
func (s *signer) KeyHash() uint32 { return s.hash }
143+
func (s *signer) Sign(msg []byte) ([]byte, error) { return s.sign(msg) }
144+
145+
func (s *signer) Verifier() note.Verifier {
146+
return &verifier{
147+
name: s.name,
148+
keyHash: s.hash,
149+
v: s.verify,
150+
}
151+
}
152+
153+
// verifier is a note-compatible verifier.
154+
type verifier struct {
155+
name string
156+
keyHash uint32
157+
v func(msg, sig []byte) bool
158+
}
159+
160+
// Name returns the name associated with the key this verifier is based on.
161+
func (v *verifier) Name() string {
162+
return v.name
163+
}
164+
165+
// KeyHash returns a truncated hash of the key this verifier is based on.
166+
func (v *verifier) KeyHash() uint32 {
167+
return v.keyHash
168+
}
169+
170+
// Verify checks that the provided sig is valid over msg for the key this verifier is based on.
171+
func (v *verifier) Verify(msg, sig []byte) bool {
172+
return v.v(msg, sig)
173+
}
174+
175+

note/note_cosigv1.go

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,6 @@ import (
2525
)
2626

2727
const (
28-
algEd25519 = 1
29-
algECDSAWithSHA256 = 2
30-
algEd25519CosignatureV1 = 4
31-
algRFC6962STH = 5
32-
algMLDSA44 = 6
33-
)
34-
35-
const (
36-
keyHashSize = 4
3728
timestampSize = 8
3829
)
3930

@@ -423,40 +414,9 @@ func formatMLDSACosignatureV1(cosignerName string, timestamp uint64, logOrigin s
423414
}
424415

425416
var (
426-
errSignerID = errors.New("malformed signer id")
427-
errSignerAlg = errors.New("unknown signer algorithm")
428-
errVerifierID = errors.New("malformed verifier id")
429-
errVerifierAlg = errors.New("unknown verifier algorithm")
430-
errInvalidHash = errors.New("invalid key hash")
431-
errMalformedSig = errors.New("malformed signature")
432417
errInvalidTimestamp = errors.New("invalid timestamp")
433418
)
434419

435-
// Signer is a note.Signer which also provides access to the corresponding Verifier.
436-
type Signer interface {
437-
note.Signer
438-
Verifier() note.Verifier
439-
}
440-
441-
// signer is a concrete implementation of the extended Signer interface above.
442-
type signer struct {
443-
name string
444-
hash uint32
445-
sign func([]byte) ([]byte, error)
446-
verify func(msg, sig []byte) bool
447-
}
448-
449-
func (s *signer) Name() string { return s.name }
450-
func (s *signer) KeyHash() uint32 { return s.hash }
451-
func (s *signer) Sign(msg []byte) ([]byte, error) { return s.sign(msg) }
452-
453-
func (s *signer) Verifier() note.Verifier {
454-
return &verifier{
455-
name: s.name,
456-
keyHash: s.hash,
457-
v: s.verify,
458-
}
459-
}
460420

461421
// SubtreeSigner is a note.Signer that can additionally produce subtree signatures, and
462422
// provide access to a similarly capable verifier.

note/note_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2026 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package note
6+
7+
import (
8+
"fmt"
9+
"testing"
10+
"golang.org/x/mod/sumdb/note"
11+
)
12+
13+
func TestNoteInterop(t *testing.T) {
14+
sk, vk, err := note.GenerateKey(nil, "example")
15+
if err != nil {
16+
t.Fatalf("GenerateKey: %v", err)
17+
}
18+
19+
sf, err := NewSigner(sk)
20+
if err != nil {
21+
t.Fatalf("NewSigner(%q): %v", sk, err)
22+
}
23+
vf, err := NewVerifier(vk)
24+
if err != nil {
25+
t.Fatalf("NewVerifier: %v", err)
26+
}
27+
28+
sn, err := note.NewSigner(sk)
29+
if err != nil {
30+
t.Fatalf("note.NewSigner: %v", err)
31+
}
32+
vn, err := note.NewVerifier(vk)
33+
if err != nil {
34+
t.Fatalf("note.NewVerifier: %v", err)
35+
}
36+
37+
for i, s := range []note.Signer{sf, sn} {
38+
for j, v := range []note.Verifier{vf, vn} {
39+
msg := fmt.Sprintf("%d, %d\n", i, j)
40+
signed, err := note.Sign(&note.Note{Text: msg}, s)
41+
if err != nil {
42+
t.Errorf("%s failed to sign: %v", msg, err)
43+
continue
44+
}
45+
46+
n, err := note.Open(signed, note.VerifierList(v))
47+
if err != nil {
48+
t.Errorf("%s failed to open: %v", msg, err)
49+
continue
50+
}
51+
52+
if n.Text != msg {
53+
t.Errorf("got %q want %q", n.Text, msg)
54+
}
55+
}
56+
}
57+
}

note/note_verifier.go

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -71,54 +71,6 @@ func NewEd25519SignerVerifier(skey string) (note.Signer, note.Verifier, error) {
7171
return s, v, err
7272
}
7373

74-
// NewVerifier returns a verifier for the given key, if the key's algo is known.
75-
func NewVerifier(key string) (note.Verifier, error) {
76-
parts := strings.SplitN(key, "+", 3)
77-
if got, want := len(parts), 3; got != want {
78-
return nil, fmt.Errorf("key has %d parts, expected %d: %q", got, want, key)
79-
}
80-
keyBytes, err := base64.StdEncoding.DecodeString(parts[2])
81-
if err != nil {
82-
return nil, fmt.Errorf("key has invalid base64 %q: %v", parts[2], err)
83-
}
84-
if len(keyBytes) < 2 {
85-
return nil, fmt.Errorf("invalid key, key bytes too short")
86-
}
87-
88-
switch keyBytes[0] {
89-
case algECDSAWithSHA256:
90-
return NewECDSAVerifier(key)
91-
case algEd25519CosignatureV1, algMLDSA44:
92-
return NewVerifierForCosignatureV1(key)
93-
case algRFC6962STH:
94-
return NewRFC6962Verifier(key)
95-
default:
96-
return note.NewVerifier(key)
97-
}
98-
}
99-
100-
// verifier is a note-compatible verifier.
101-
type verifier struct {
102-
name string
103-
keyHash uint32
104-
v func(msg, sig []byte) bool
105-
}
106-
107-
// Name returns the name associated with the key this verifier is based on.
108-
func (v *verifier) Name() string {
109-
return v.name
110-
}
111-
112-
// KeyHash returns a truncated hash of the key this verifier is based on.
113-
func (v *verifier) KeyHash() uint32 {
114-
return v.keyHash
115-
}
116-
117-
// Verify checks that the provided sig is valid over msg for the key this verifier is based on.
118-
func (v *verifier) Verify(msg, sig []byte) bool {
119-
return v.v(msg, sig)
120-
}
121-
12274
// NewECDSAVerifier creates a new note verifier for checking ECDSA signatures over SHA256 digests.
12375
// This implementation is compatible with the signature scheme used by the Sigstore Rékor Log.
12476
//

0 commit comments

Comments
 (0)