Files
esign/pkg/sign/ed25519_test.go
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

121 lines
3.6 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/sha256"
"testing"
)
// Ed25519 round-trip: generate signer, sign, verify with same pubkey.
func TestEd25519_RoundTrip(t *testing.T) {
s, err := NewEd25519Signer()
if err != nil {
t.Fatalf("NewEd25519Signer: %v", err)
}
payload := sha256.Sum256([]byte("hello world"))
sig, err := s.Sign(payload[:])
if err != nil {
t.Fatalf("Sign: %v", err)
}
if len(sig) != 64 {
t.Fatalf("sig len = %d, want 64", len(sig))
}
if len(s.Public()) != 32 {
t.Fatalf("pub len = %d, want 32", len(s.Public()))
}
if err := (Ed25519Verifier{}).Verify(s.Public(), payload[:], sig); err != nil {
t.Fatalf("Verify: %v", err)
}
}
// Deterministic-from-seed: the same 32-byte seed always yields the
// same (priv, pub) pair, and the same payload yields the same sig.
func TestEd25519_DeterministicSeed(t *testing.T) {
seed := make([]byte, 32)
for i := range seed {
seed[i] = byte(i)
}
s1, err := NewEd25519FromSeed(seed)
if err != nil {
t.Fatalf("NewEd25519FromSeed: %v", err)
}
s2, err := NewEd25519FromSeed(seed)
if err != nil {
t.Fatalf("NewEd25519FromSeed: %v", err)
}
if string(s1.Public()) != string(s2.Public()) {
t.Fatalf("pub mismatch from same seed")
}
payload := sha256.Sum256([]byte("deterministic"))
sig1, _ := s1.Sign(payload[:])
sig2, _ := s2.Sign(payload[:])
if string(sig1) != string(sig2) {
t.Fatalf("sig mismatch from same seed + same payload")
}
}
// Bad seed length is rejected.
func TestEd25519_BadSeedLen(t *testing.T) {
if _, err := NewEd25519FromSeed(make([]byte, 31)); err != ErrBadPubKey {
t.Fatalf("got %v, want ErrBadPubKey", err)
}
if _, err := NewEd25519FromSeed(make([]byte, 33)); err != ErrBadPubKey {
t.Fatalf("got %v, want ErrBadPubKey", err)
}
}
// Tampering with the payload breaks verification.
func TestEd25519_TamperedPayload(t *testing.T) {
s, _ := NewEd25519Signer()
payload := sha256.Sum256([]byte("real document"))
sig, _ := s.Sign(payload[:])
tampered := sha256.Sum256([]byte("forged document"))
if err := (Ed25519Verifier{}).Verify(s.Public(), tampered[:], sig); err != ErrBadSignature {
t.Fatalf("got %v, want ErrBadSignature", err)
}
}
// Tampering with the signature byte breaks verification.
func TestEd25519_TamperedSig(t *testing.T) {
s, _ := NewEd25519Signer()
payload := sha256.Sum256([]byte("doc"))
sig, _ := s.Sign(payload[:])
sig[0] ^= 0x01
if err := (Ed25519Verifier{}).Verify(s.Public(), payload[:], sig); err != ErrBadSignature {
t.Fatalf("got %v, want ErrBadSignature", err)
}
}
// Wrong-length pubkey is rejected with ErrBadPubKey.
func TestEd25519_BadPubKey(t *testing.T) {
s, _ := NewEd25519Signer()
payload := sha256.Sum256([]byte("doc"))
sig, _ := s.Sign(payload[:])
if err := (Ed25519Verifier{}).Verify(s.Public()[:31], payload[:], sig); err != ErrBadPubKey {
t.Fatalf("short pub: got %v, want ErrBadPubKey", err)
}
}
// Wrong-length signature is rejected with ErrBadSignature.
func TestEd25519_BadSigLen(t *testing.T) {
s, _ := NewEd25519Signer()
payload := sha256.Sum256([]byte("doc"))
if err := (Ed25519Verifier{}).Verify(s.Public(), payload[:], make([]byte, 63)); err != ErrBadSignature {
t.Fatalf("short sig: got %v, want ErrBadSignature", err)
}
}
// Verifier is stateless — its zero value works.
func TestEd25519_VerifierZeroValue(t *testing.T) {
s, _ := NewEd25519Signer()
payload := sha256.Sum256([]byte("doc"))
sig, _ := s.Sign(payload[:])
var v Ed25519Verifier
if err := v.Verify(s.Public(), payload[:], sig); err != nil {
t.Fatalf("zero-value verifier: %v", err)
}
}