mirror of
https://github.com/saymrwulf/swisspost-evoting-go-poc.git
synced 2026-07-23 19:32:29 +00:00
The foundation for the "watch the mathematics execute" cockpit. Each meaningful crypto operation will emit one Event carrying its LaTeX notation plus the real runtime values, tagged with party + phase. One stream feeds both a terminal view (ASCII/Unicode) and a browser view (typeset KaTeX) — no double-maintenance. - Off by default with near-zero cost: Emit/EmitFunc return immediately when no sink is attached (atomic fast path), so instrumentation is free in normal runs. - Sinks: ChanSink (buffered, drops rather than stalling the ceremony), SliceSink (tests). Short() elides ~77-digit values for compact display. Tests: disabled-is-cheap, monotonic sequence + context stamping, elision. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
140 lines
4.5 KiB
Go
140 lines
4.5 KiB
Go
// Package trace is a live event stream for the cryptographic operations the
|
|
// system performs. Each meaningful operation (sampling, encryption, commitment,
|
|
// Fiat-Shamir challenge, shuffle, signature, key agreement) emits one Event
|
|
// carrying its notation as a LaTeX template plus the REAL runtime values, so a
|
|
// renderer can show the mathematics that is executing at the instant it runs.
|
|
//
|
|
// The stream is surface-agnostic: a terminal view renders ASCII/Unicode, a
|
|
// browser view renders typeset LaTeX (KaTeX) — both consume the same events.
|
|
//
|
|
// Tracing is off by default and has near-zero cost when no sink is attached:
|
|
// Emit returns immediately if there are no subscribers. Instrumentation sites
|
|
// wrap value formatting in a closure (via EmitFunc) so that formatting work is
|
|
// skipped entirely when tracing is off.
|
|
package trace
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
// Kind categorizes an operation so a renderer can group or icon it.
|
|
type Kind string
|
|
|
|
const (
|
|
KindSample Kind = "sample" // random sampling from Z_q / permutations
|
|
KindEncrypt Kind = "encrypt" // ElGamal encryption
|
|
KindDecrypt Kind = "decrypt" // (partial) decryption
|
|
KindExp Kind = "exp" // group exponentiation of note
|
|
KindCommit Kind = "commit" // Pedersen commitment
|
|
KindChallenge Kind = "challenge" // Fiat-Shamir challenge derivation
|
|
KindProof Kind = "proof" // ZK proof generation / verification
|
|
KindShuffle Kind = "shuffle" // mix-net permutation + re-encryption
|
|
KindSign Kind = "sign" // Ed25519 signature (transport)
|
|
KindVerify Kind = "verify" // signature / proof verification
|
|
KindKeyEx Kind = "keyex" // X25519 key agreement
|
|
KindNote Kind = "note" // phase/step narration, no math
|
|
)
|
|
|
|
// Event is one instrumented operation.
|
|
type Event struct {
|
|
Seq uint64 `json:"seq"` // monotonic sequence number
|
|
Party string `json:"party"` // which stakeholder performed it
|
|
Phase string `json:"phase"` // setup / voting / tally / verify
|
|
Kind Kind `json:"kind"` // operation category
|
|
Caption string `json:"caption"` // one-line human description
|
|
LaTeX string `json:"latex"` // notation, with \VAL{name} placeholders
|
|
ASCII string `json:"ascii"` // terminal-friendly fallback (optional)
|
|
Values map[string]string `json:"values"` // placeholder name -> real runtime value (decimal/hex)
|
|
}
|
|
|
|
// Sink receives events. Implementations must be safe for concurrent use and must
|
|
// not block the emitter for long (buffer or drop internally if needed).
|
|
type Sink interface {
|
|
Handle(Event)
|
|
}
|
|
|
|
var (
|
|
mu sync.RWMutex
|
|
sinks []Sink
|
|
active atomic.Bool // fast path: true when at least one sink is attached
|
|
seqCtr atomic.Uint64
|
|
curParty atomic.Value // string: default party if an emit omits one
|
|
curPhase atomic.Value // string: current phase
|
|
)
|
|
|
|
// Subscribe attaches a sink and returns an unsubscribe function.
|
|
func Subscribe(s Sink) func() {
|
|
mu.Lock()
|
|
sinks = append(sinks, s)
|
|
active.Store(true)
|
|
mu.Unlock()
|
|
return func() {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
for i, x := range sinks {
|
|
if x == s {
|
|
sinks = append(sinks[:i], sinks[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
active.Store(len(sinks) > 0)
|
|
}
|
|
}
|
|
|
|
// Enabled reports whether any sink is attached. Instrumentation can check this
|
|
// to skip expensive value formatting.
|
|
func Enabled() bool { return active.Load() }
|
|
|
|
// SetContext sets the default party/phase stamped onto events that omit them.
|
|
func SetContext(party, phase string) {
|
|
curParty.Store(party)
|
|
curPhase.Store(phase)
|
|
}
|
|
|
|
// Phase sets just the current phase.
|
|
func Phase(phase string) { curPhase.Store(phase) }
|
|
|
|
func ctxParty() string {
|
|
if v, ok := curParty.Load().(string); ok {
|
|
return v
|
|
}
|
|
return ""
|
|
}
|
|
func ctxPhase() string {
|
|
if v, ok := curPhase.Load().(string); ok {
|
|
return v
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Emit publishes an event. It fills Seq, and Party/Phase from context if unset.
|
|
// Returns immediately when tracing is disabled.
|
|
func Emit(e Event) {
|
|
if !active.Load() {
|
|
return
|
|
}
|
|
e.Seq = seqCtr.Add(1)
|
|
if e.Party == "" {
|
|
e.Party = ctxParty()
|
|
}
|
|
if e.Phase == "" {
|
|
e.Phase = ctxPhase()
|
|
}
|
|
mu.RLock()
|
|
current := sinks
|
|
mu.RUnlock()
|
|
for _, s := range current {
|
|
s.Handle(e)
|
|
}
|
|
}
|
|
|
|
// EmitFunc builds and emits an event only when tracing is enabled, so callers
|
|
// can defer all value formatting into build. Use this at hot instrumentation
|
|
// sites where formatting big integers would otherwise cost even when tracing off.
|
|
func EmitFunc(build func() Event) {
|
|
if !active.Load() {
|
|
return
|
|
}
|
|
Emit(build())
|
|
}
|