Skip to content

Commit be3b100

Browse files
authored
Added function to generate vkey from skey (#199)
This is scoped only to Ed25519 keys. This comes up in many implementations where an operator wants to sign new messages, but also authenticate previous messages they may have made (e.g. witnesses, logs).
1 parent 6b30ca0 commit be3b100

2 files changed

Lines changed: 79 additions & 0 deletions

File tree

note/note_verifier.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,60 @@ package note
1717

1818
import (
1919
"crypto/ecdsa"
20+
"crypto/ed25519"
2021
"crypto/sha256"
2122
"crypto/x509"
2223
"encoding/base64"
2324
"encoding/binary"
25+
"errors"
2426
"fmt"
2527
"strconv"
2628
"strings"
2729

2830
"golang.org/x/mod/sumdb/note"
2931
)
3032

33+
// NewEd25519SignerVerifier returns a note Signer and Verifier given an
34+
// Ed25519 private key in the standard note-formatted form, e.g.
35+
// `PRIVATE+KEY+logandmap+38581672+AXJ0FKWOcO2ch6WC8kP705Ed3Gxu7pVtZLhfHAQwp+FE`.
36+
func NewEd25519SignerVerifier(skey string) (note.Signer, note.Verifier, error) {
37+
const algEd25519 = 1
38+
s, err := note.NewSigner(skey)
39+
if err != nil {
40+
return nil, nil, err
41+
}
42+
parts := strings.SplitN(skey, "+", 5)
43+
if n := len(parts); n != 5 {
44+
return nil, nil, fmt.Errorf("expected 5 parts but got %d", n)
45+
}
46+
if parts[0] != "PRIVATE" || parts[1] != "KEY" {
47+
return nil, nil, fmt.Errorf("expected first tokens to be [PRIVATE, KEY]")
48+
}
49+
key, err := base64.StdEncoding.DecodeString(parts[4])
50+
if err != nil {
51+
return nil, nil, fmt.Errorf("failed to decode base64: %v", err)
52+
}
53+
54+
alg, key := key[0], key[1:]
55+
if alg != algEd25519 {
56+
return nil, nil, errors.New("unsupported algorithm")
57+
}
58+
if l := len(key); l != ed25519.SeedSize {
59+
return nil, nil, fmt.Errorf("expected key seed of size %d but got %d", ed25519.SeedSize, l)
60+
}
61+
publicKey := ed25519.NewKeyFromSeed(key).Public().(ed25519.PublicKey)
62+
vkey, err := note.NewEd25519VerifierKey(s.Name(), publicKey)
63+
if err != nil {
64+
return nil, nil, fmt.Errorf("failed to generate verifier from key: %v", err)
65+
66+
}
67+
v, err := note.NewVerifier(vkey)
68+
if err != nil {
69+
return nil, nil, fmt.Errorf("failed to create verifier from vkey: %v", err)
70+
}
71+
return s, v, err
72+
}
73+
3174
// NewVerifier returns a verifier for the given key, if the key's algo is known.
3275
func NewVerifier(key string) (note.Verifier, error) {
3376
parts := strings.SplitN(key, "+", 3)

note/note_verifier_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,42 @@ const (
3333
pixelKey = "pixel6_transparency_log" + "+" + pixelKeyHash + "+" + pixelKeyMaterial
3434
)
3535

36+
func TestSignerVerifierFromEd25519Key(t *testing.T) {
37+
for _, test := range []struct {
38+
name string
39+
key string
40+
wantErr bool
41+
}{
42+
{
43+
name: "Ed25519 private key",
44+
key: "PRIVATE+KEY+logandmap+38581672+AXJ0FKWOcO2ch6WC8kP705Ed3Gxu7pVtZLhfHAQwp+FE",
45+
},
46+
{
47+
name: "Ed25519 public key",
48+
key: "logandmap+38581672+Ab/PCr1eCclRPRMBqw/r5An1xO71MCnImLiospEq6b4l",
49+
wantErr: true,
50+
},
51+
} {
52+
t.Run(test.name, func(t *testing.T) {
53+
s, v, err := NewEd25519SignerVerifier(test.key)
54+
if gotErr := err != nil; gotErr != test.wantErr {
55+
t.Fatalf("NewVerifier: %v, wantErr %t", err, test.wantErr)
56+
}
57+
if test.wantErr {
58+
return
59+
}
60+
msg := []byte("hello")
61+
sig, err := s.Sign(msg)
62+
if err != nil {
63+
t.Fatal(err)
64+
}
65+
if !v.Verify(msg, sig) {
66+
t.Fatal("Failed to verify signature from signer")
67+
}
68+
})
69+
}
70+
}
71+
3672
func TestNewVerifier(t *testing.T) {
3773
for _, test := range []struct {
3874
name string

0 commit comments

Comments
 (0)