diff --git a/note/note_verifier.go b/note/note_verifier.go index f6ecd7a..260dead 100644 --- a/note/note_verifier.go +++ b/note/note_verifier.go @@ -17,10 +17,12 @@ package note import ( "crypto/ecdsa" + "crypto/ed25519" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/binary" + "errors" "fmt" "strconv" "strings" @@ -28,6 +30,47 @@ import ( "golang.org/x/mod/sumdb/note" ) +// NewEd25519SignerVerifier returns a note Signer and Verifier given an +// Ed25519 private key in the standard note-formatted form, e.g. +// `PRIVATE+KEY+logandmap+38581672+AXJ0FKWOcO2ch6WC8kP705Ed3Gxu7pVtZLhfHAQwp+FE`. +func NewEd25519SignerVerifier(skey string) (note.Signer, note.Verifier, error) { + const algEd25519 = 1 + s, err := note.NewSigner(skey) + if err != nil { + return nil, nil, err + } + parts := strings.SplitN(skey, "+", 5) + if n := len(parts); n != 5 { + return nil, nil, fmt.Errorf("expected 5 parts but got %d", n) + } + if parts[0] != "PRIVATE" || parts[1] != "KEY" { + return nil, nil, fmt.Errorf("expected first tokens to be [PRIVATE, KEY]") + } + key, err := base64.StdEncoding.DecodeString(parts[4]) + if err != nil { + return nil, nil, fmt.Errorf("failed to decode base64: %v", err) + } + + alg, key := key[0], key[1:] + if alg != algEd25519 { + return nil, nil, errors.New("unsupported algorithm") + } + if l := len(key); l != ed25519.SeedSize { + return nil, nil, fmt.Errorf("expected key seed of size %d but got %d", ed25519.SeedSize, l) + } + publicKey := ed25519.NewKeyFromSeed(key).Public().(ed25519.PublicKey) + vkey, err := note.NewEd25519VerifierKey(s.Name(), publicKey) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate verifier from key: %v", err) + + } + v, err := note.NewVerifier(vkey) + if err != nil { + return nil, nil, fmt.Errorf("failed to create verifier from vkey: %v", err) + } + return s, v, err +} + // NewVerifier returns a verifier for the given key, if the key's algo is known. func NewVerifier(key string) (note.Verifier, error) { parts := strings.SplitN(key, "+", 3) diff --git a/note/note_verifier_test.go b/note/note_verifier_test.go index 34a4f8b..f7da03b 100644 --- a/note/note_verifier_test.go +++ b/note/note_verifier_test.go @@ -33,6 +33,42 @@ const ( pixelKey = "pixel6_transparency_log" + "+" + pixelKeyHash + "+" + pixelKeyMaterial ) +func TestSignerVerifierFromEd25519Key(t *testing.T) { + for _, test := range []struct { + name string + key string + wantErr bool + }{ + { + name: "Ed25519 private key", + key: "PRIVATE+KEY+logandmap+38581672+AXJ0FKWOcO2ch6WC8kP705Ed3Gxu7pVtZLhfHAQwp+FE", + }, + { + name: "Ed25519 public key", + key: "logandmap+38581672+Ab/PCr1eCclRPRMBqw/r5An1xO71MCnImLiospEq6b4l", + wantErr: true, + }, + } { + t.Run(test.name, func(t *testing.T) { + s, v, err := NewEd25519SignerVerifier(test.key) + if gotErr := err != nil; gotErr != test.wantErr { + t.Fatalf("NewVerifier: %v, wantErr %t", err, test.wantErr) + } + if test.wantErr { + return + } + msg := []byte("hello") + sig, err := s.Sign(msg) + if err != nil { + t.Fatal(err) + } + if !v.Verify(msg, sig) { + t.Fatal("Failed to verify signature from signer") + } + }) + } +} + func TestNewVerifier(t *testing.T) { for _, test := range []struct { name string