Files
zeekay 165ae41805 luxfi/esign v0.2: Ed25519 sign/verify + ZAP RPC server + esignd
This commit lands the first end-to-end signable surface:

  * pkg/sign — Signer/Verifier interfaces, Ed25519 implementation
    (stdlib crypto/ed25519), method-dispatch helper. 8 unit tests:
    round-trip, deterministic-from-seed, bad-seed-length, tampered
    payload + sig, bad pubkey, bad sig length, zero-value verifier.

  * pkg/envelope — added Signature.SignerHashHex() and SeqString
    helper used by the verifier-side error reporting.

  * pkg/api — Handler with pluggable Store, in-memory MemStore for
    tests + small deployments. CreateRequest / SignRequest /
    TransitionRequest / VerifyResponse types form the wire surface.
    Handler.Sign runs cryptographic verify BEFORE calling
    envelope.AddSignature so a malformed sig never lands in the
    envelope. 3 e2e tests cover happy path, sig tampering detection,
    and crypto-fails-before-store ordering.

  * pkg/api — opcode table (Create/Get/Sign/Transition/Verify/Audit
    + Ping). ZAPServer serves [4B BE length][2B LE opcode][JSON body]
    framing with per-conn pipelining + idle timeout. Tests: ping,
    end-to-end create→sent→sign→verify, unknown opcode error,
    bad-JSON error.

  * cmd/esignd — standalone daemon. `--zap :9650` by default.
    SIGINT/SIGTERM shutdown drains inflight conns before exit.

Test summary: 3 packages, 22 tests, all green.
2026-06-07 11:28:46 -07:00

69 lines
2.0 KiB
Go

// Copyright 2026 Lux Industries Inc. and the luxfi/esign authors.
// Licensed under the Apache License, Version 2.0 (see LICENSE).
package sign
import (
"crypto/ed25519"
"crypto/rand"
)
// Ed25519Signer wraps a stdlib ed25519.PrivateKey. The zero value is not
// usable; call NewEd25519Signer to construct one, or build with a known
// seed using NewEd25519FromSeed.
type Ed25519Signer struct {
priv ed25519.PrivateKey
pub ed25519.PublicKey
}
// NewEd25519Signer returns a freshly-keyed signer using crypto/rand.
func NewEd25519Signer() (*Ed25519Signer, error) {
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, err
}
return &Ed25519Signer{priv: priv, pub: pub}, nil
}
// NewEd25519FromSeed deterministically derives a signer from a 32-byte
// seed. Useful for tests and KMS-managed keys where the seed comes back
// over the wire.
func NewEd25519FromSeed(seed []byte) (*Ed25519Signer, error) {
if len(seed) != ed25519.SeedSize {
return nil, ErrBadPubKey
}
priv := ed25519.NewKeyFromSeed(seed)
pub := priv.Public().(ed25519.PublicKey)
return &Ed25519Signer{priv: priv, pub: pub}, nil
}
// Sign returns a 64-byte Ed25519 signature over payload.
func (s *Ed25519Signer) Sign(payload []byte) ([]byte, error) {
return ed25519.Sign(s.priv, payload), nil
}
// Public returns the 32-byte Ed25519 public key.
func (s *Ed25519Signer) Public() []byte {
out := make([]byte, len(s.pub))
copy(out, s.pub)
return out
}
// Ed25519Verifier verifies Ed25519 signatures. Stateless — no key
// material held; the public key arrives per Verify call.
type Ed25519Verifier struct{}
// Verify checks a 64-byte Ed25519 sig against pubKey (32 bytes) and payload.
func (Ed25519Verifier) Verify(pubKey, payload, sig []byte) error {
if len(pubKey) != ed25519.PublicKeySize {
return ErrBadPubKey
}
if len(sig) != ed25519.SignatureSize {
return ErrBadSignature
}
if !ed25519.Verify(ed25519.PublicKey(pubKey), payload, sig) {
return ErrBadSignature
}
return nil
}