phase 3
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
// Package keypair provides Ed25519 keypair generation and signing helpers
|
||||
// over opaque []byte blobs. The package is network-free, storage-free,
|
||||
// and TinyGo-friendly: it does not import `crypto/x509`, `encoding/pem`,
|
||||
// or `os`. Random bytes are not read internally; callers pass an io.Reader
|
||||
// (typically `crypto/rand.Reader` on host builds, or a `crypto.getRandomValues`
|
||||
// adapter on WASM).
|
||||
//
|
||||
// Public APIs return raw byte blobs (32-byte public keys, 64-byte private
|
||||
// keys, 64-byte signatures) so the WASM bridge in later phases can hand
|
||||
// them back and forth across the JS boundary as Uint8Array. The package
|
||||
// never re-exports `crypto/ed25519` types in its surface.
|
||||
package keypair
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidPrivateKey reports that a private key blob does not have the
|
||||
// required Ed25519 private-key length.
|
||||
ErrInvalidPrivateKey = errors.New("private_key must be a 64-byte Ed25519 private key")
|
||||
|
||||
// ErrInvalidPublicKey reports that a public key blob does not have the
|
||||
// required Ed25519 public-key length.
|
||||
ErrInvalidPublicKey = errors.New("public_key must be a 32-byte Ed25519 public key")
|
||||
|
||||
// ErrInvalidPublicKeyEncoding reports that a marshaled public key is not a
|
||||
// strict base64 encoding of a 32-byte Ed25519 public key.
|
||||
ErrInvalidPublicKeyEncoding = errors.New("public_key is not a valid base64-encoded Ed25519 public key")
|
||||
)
|
||||
|
||||
// Generate reads 32 seed bytes from reader and derives an Ed25519 keypair.
|
||||
// The returned slices are independent copies; callers may retain or zero
|
||||
// them without affecting subsequent calls.
|
||||
func Generate(reader io.Reader) (privateKey, publicKey []byte, err error) {
|
||||
if reader == nil {
|
||||
return nil, nil, errors.New("keypair.Generate: reader must not be nil")
|
||||
}
|
||||
|
||||
pub, priv, err := ed25519.GenerateKey(reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("keypair.Generate: %w", err)
|
||||
}
|
||||
|
||||
return bytes.Clone(priv), bytes.Clone(pub), nil
|
||||
}
|
||||
|
||||
// Sign returns the raw 64-byte Ed25519 signature of message under privateKey.
|
||||
func Sign(privateKey, message []byte) ([]byte, error) {
|
||||
if len(privateKey) != ed25519.PrivateKeySize {
|
||||
return nil, ErrInvalidPrivateKey
|
||||
}
|
||||
|
||||
signature := ed25519.Sign(ed25519.PrivateKey(privateKey), message)
|
||||
return bytes.Clone(signature), nil
|
||||
}
|
||||
|
||||
// Verify reports whether signature authenticates message under publicKey.
|
||||
// It returns false if any input has the wrong length.
|
||||
func Verify(publicKey, message, signature []byte) bool {
|
||||
if len(publicKey) != ed25519.PublicKeySize || len(signature) != ed25519.SignatureSize {
|
||||
return false
|
||||
}
|
||||
|
||||
return ed25519.Verify(ed25519.PublicKey(publicKey), message, signature)
|
||||
}
|
||||
|
||||
// MarshalPublicKey returns the base64 (StdEncoding) representation of the raw
|
||||
// 32-byte Ed25519 public key. The encoding matches docs/ARCHITECTURE.md §15:
|
||||
// the backend stores client public keys in this exact form and the gateway
|
||||
// reads them out of session cache as base64 strings.
|
||||
func MarshalPublicKey(publicKey []byte) (string, error) {
|
||||
if len(publicKey) != ed25519.PublicKeySize {
|
||||
return "", ErrInvalidPublicKey
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(publicKey), nil
|
||||
}
|
||||
|
||||
// UnmarshalPublicKey decodes a strict base64 (StdEncoding) representation of
|
||||
// a raw 32-byte Ed25519 public key.
|
||||
func UnmarshalPublicKey(value string) ([]byte, error) {
|
||||
decoded, err := base64.StdEncoding.Strict().DecodeString(value)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidPublicKeyEncoding
|
||||
}
|
||||
if len(decoded) != ed25519.PublicKeySize {
|
||||
return nil, ErrInvalidPublicKey
|
||||
}
|
||||
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
// PublicKeyFromPrivate extracts the Ed25519 public key embedded in privateKey.
|
||||
// The returned slice is an independent copy.
|
||||
func PublicKeyFromPrivate(privateKey []byte) ([]byte, error) {
|
||||
if len(privateKey) != ed25519.PrivateKeySize {
|
||||
return nil, ErrInvalidPrivateKey
|
||||
}
|
||||
|
||||
pub, _ := ed25519.PrivateKey(privateKey).Public().(ed25519.PublicKey)
|
||||
return bytes.Clone(pub), nil
|
||||
}
|
||||
Reference in New Issue
Block a user