swisspost-evoting-go-poc/pkg/transport/envelope.go
saymrwulf bc43c3d3aa Instrument real crypto ops to emit live LaTeX events
The running cryptography now narrates itself. Each headline operation emits a
trace.Event carrying its LaTeX notation plus the actual runtime values, the
instant it executes:

- Ed25519 signature on every inter-party message (envelope.Seal)
- X25519 ECDH key agreement for confidential card delivery (NewSecureChannel)
- ElGamal ballot encryption E1 = (g^r, pk^r·m) with the real r (castBallot)
- Fiat-Shamir challenge e = H(...) mod q (Schnorr proof)
- Bayer-Groth verifiable shuffle C' = {ReEnc_pk(C_π(i))} with N (mix-net)

Ceremony phases set trace phase/party context so events are attributed to the
acting stakeholder and phase. Instrumentation is behind the enabled-check, so
normal runs pay nothing.

Test: a full traced ceremony captures 184 live events across all five headline
kinds, each with non-empty LaTeX and live values, correctly phase/party-tagged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 14:06:08 +02:00

103 lines
3.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package transport
import (
"crypto/sha256"
"encoding/binary"
"encoding/json"
"fmt"
"github.com/user/evote/pkg/trace"
"github.com/user/evote/pkg/transportsec"
)
// Envelope is a single authenticated message from one party to another. The
// signature (Ed25519, produced in Rust) covers the canonical byte encoding of
// all other fields, so From/To/Type/Nonce/Payload are all integrity-protected.
// Encrypted marks whether Payload is AES-GCM ciphertext (see SecureChannel).
type Envelope struct {
From string `json:"from"`
To string `json:"to"`
Type string `json:"type"`
Nonce uint64 `json:"nonce"`
Encrypted bool `json:"encrypted"`
Payload []byte `json:"payload"`
Signature []byte `json:"signature"`
}
// signingBytes returns the canonical bytes covered by the signature: a
// length-prefixed concatenation of every field except the signature itself.
// Length-prefixing makes the encoding injective (no field-boundary ambiguity).
func (e *Envelope) signingBytes() []byte {
var b []byte
appendField := func(data []byte) {
var l [8]byte
binary.BigEndian.PutUint64(l[:], uint64(len(data)))
b = append(b, l[:]...)
b = append(b, data...)
}
appendField([]byte(e.From))
appendField([]byte(e.To))
appendField([]byte(e.Type))
var nonce [8]byte
binary.BigEndian.PutUint64(nonce[:], e.Nonce)
appendField(nonce[:])
if e.Encrypted {
appendField([]byte{1})
} else {
appendField([]byte{0})
}
appendField(e.Payload)
// Hash the concatenation to a fixed 32-byte digest that is what actually
// gets signed (keeps signed inputs short and uniform).
h := sha256.Sum256(b)
return h[:]
}
// Seal signs the envelope with the sender identity's Ed25519 key (via Rust).
func (e *Envelope) Seal(sender *Identity) error {
sig, err := transportsec.Ed25519Sign(sender.SigningSeed(), e.signingBytes())
if err != nil {
return fmt.Errorf("seal %s->%s: %w", e.From, e.To, err)
}
e.Signature = sig
trace.EmitFunc(func() trace.Event {
return trace.Event{
Party: e.From,
Kind: trace.KindSign,
Caption: fmt.Sprintf("%s signs %q → %s", e.From, e.Type, e.To),
LaTeX: `\sigma \gets \mathrm{Ed25519.Sign}_{sk_{\text{\VAL{party}}}}\!\big(\mathrm{SHA256}(\text{envelope})\big),\quad |\sigma| = 64\text{ B}`,
ASCII: "σ ← Ed25519.Sign(sk, H(envelope))",
Values: map[string]string{
"party": e.From,
"to": e.To,
"type": e.Type,
"sigma": hexOf(sig),
},
}
})
return nil
}
func hexOf(b []byte) string {
const hexdigits = "0123456789abcdef"
out := make([]byte, len(b)*2)
for i, c := range b {
out[i*2] = hexdigits[c>>4]
out[i*2+1] = hexdigits[c&0x0f]
}
return string(out)
}
// Verify checks the envelope signature against senderEdPub (via Rust).
func (e *Envelope) Verify(senderEdPub []byte) error {
if err := transportsec.Ed25519Verify(senderEdPub, e.signingBytes(), e.Signature); err != nil {
return fmt.Errorf("envelope %s->%s type=%s: %w", e.From, e.To, e.Type, err)
}
return nil
}
// MarshalPayload JSON-encodes v into the payload.
func MarshalPayload(v any) ([]byte, error) { return json.Marshal(v) }
// UnmarshalPayload JSON-decodes the payload into v.
func UnmarshalPayload(data []byte, v any) error { return json.Unmarshal(data, v) }