package sign import ( "crypto/ed25519" "crypto/rand" "encoding/base64" "fmt" "strings" ) // GenerateKey creates a fresh Ed25519 keypair for offline use. func GenerateKey() (ed25519.PublicKey, ed25519.PrivateKey, error) { return ed25519.GenerateKey(rand.Reader) } // EncodePrivate / EncodePublic render keys as base64 (std) for storage. The // private encoding is meant to be written to an OFFLINE medium only. func EncodePrivate(priv ed25519.PrivateKey) string { return base64.StdEncoding.EncodeToString(priv) } func EncodePublic(pub ed25519.PublicKey) string { return base64.StdEncoding.EncodeToString(pub) } // DecodePrivate parses a base64-encoded Ed25519 private key. func DecodePrivate(s string) (ed25519.PrivateKey, error) { b, err := base64.StdEncoding.DecodeString(strings.TrimSpace(s)) if err != nil { return nil, fmt.Errorf("sign: private key not valid base64: %w", err) } if len(b) != ed25519.PrivateKeySize { return nil, fmt.Errorf("sign: private key wrong size: got %d want %d", len(b), ed25519.PrivateKeySize) } return ed25519.PrivateKey(b), nil } // DecodePublic parses a base64-encoded Ed25519 public key. func DecodePublic(s string) (ed25519.PublicKey, error) { b, err := base64.StdEncoding.DecodeString(strings.TrimSpace(s)) if err != nil { return nil, fmt.Errorf("sign: public key not valid base64: %w", err) } if len(b) != ed25519.PublicKeySize { return nil, fmt.Errorf("sign: public key wrong size: got %d want %d", len(b), ed25519.PublicKeySize) } return ed25519.PublicKey(b), nil } // ParseKeyRing builds a KeyRing from "keyid=base64pub" specs. Multiple specs // (comma- or repeat-supplied) enable a rotation window where either key // validates a document. func ParseKeyRing(specs []string) (KeyRing, error) { ring := KeyRing{} for _, spec := range specs { for _, part := range strings.Split(spec, ",") { part = strings.TrimSpace(part) if part == "" { continue } id, b64, ok := strings.Cut(part, "=") if !ok { return nil, fmt.Errorf("sign: key ring spec %q must be keyid=base64pubkey", part) } pub, err := DecodePublic(b64) if err != nil { return nil, err } ring[strings.TrimSpace(id)] = pub } } if len(ring) == 0 { return nil, fmt.Errorf("sign: empty key ring") } return ring, nil }