mirror of
https://github.com/luxfi/esign.git
synced 2026-10-30 16:58:18 +00:00
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.
121 lines
3.6 KiB
Go
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)
|
|
}
|
|
}
|