mirror of
https://github.com/saymrwulf/swisspost-evoting-go-poc.git
synced 2026-07-24 19:43:51 +00:00
Setup now generates each voter's return-code card by collecting shares from all four CCs over the bus (the GenEncLongCodeShares exchange) and distributes cards and the mapping table over CONFIDENTIAL channels. - codes.go: deriveReturnCodeKey is the SINGLE key-derivation function used by a CC both when contributing to card assembly and (later) at vote-time extraction — structurally preventing the setup/extraction derivation mismatch (F1). Each CC computes its choice/confirm shares from its private return-code secret; only the shares (validated as G_q members on decode) cross the bus. - RunCards assembles cards, registers mapping-table entries, and delivers cards to voters + the mapping table to the server via sendConfidential (X25519 ECDH session key + AES-256-GCM, then Ed25519-signed) — exercising the secure channel in the ceremony, not just in tests. - returncodes: MappingTable Export/ImportMappingTable for transport. Test confirms every voter receives its card confidentially with the right code count and the server receives the full mapping table + public election keys. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
103 lines
2.9 KiB
Go
103 lines
2.9 KiB
Go
package returncodes
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/user/evote/pkg/hash"
|
|
"github.com/user/evote/pkg/kdf"
|
|
"github.com/user/evote/pkg/symmetric"
|
|
)
|
|
|
|
// MappingTable maps hash(lCC) → encrypted short code.
|
|
type MappingTable struct {
|
|
entries map[string]MappingEntry
|
|
}
|
|
|
|
// MappingEntry holds an encrypted short code with its nonce.
|
|
type MappingEntry struct {
|
|
Ciphertext []byte
|
|
Nonce []byte
|
|
}
|
|
|
|
// NewMappingTable creates an empty mapping table.
|
|
func NewMappingTable() *MappingTable {
|
|
return &MappingTable{entries: make(map[string]MappingEntry)}
|
|
}
|
|
|
|
// Add adds an entry to the mapping table.
|
|
// key = Base64(RecursiveHash(lCC_value))
|
|
// The short code is encrypted under a key derived from lCC_value.
|
|
func (mt *MappingTable) Add(lCCValue *big.Int, shortCode string) {
|
|
// Hash to get lookup key
|
|
hashBytes := hash.RecursiveHash(hash.HashableBigInt{Value: lCCValue})
|
|
key := base64.StdEncoding.EncodeToString(hashBytes)
|
|
|
|
// Derive encryption key from lCC
|
|
lccBytes := hash.IntegerToByteArray(lCCValue)
|
|
encKey := kdf.DeriveKey(lccBytes, nil, 32) // AES-256 key
|
|
|
|
// Encrypt the short code
|
|
ct, nonce, err := symmetric.Encrypt(encKey, []byte(shortCode), nil)
|
|
if err != nil {
|
|
panic("MappingTable.Add: encryption failed: " + err.Error())
|
|
}
|
|
|
|
mt.entries[key] = MappingEntry{Ciphertext: ct, Nonce: nonce}
|
|
}
|
|
|
|
// Lookup retrieves and decrypts a short code from the mapping table.
|
|
func (mt *MappingTable) Lookup(lCCValue *big.Int) (string, error) {
|
|
// Hash to get lookup key
|
|
hashBytes := hash.RecursiveHash(hash.HashableBigInt{Value: lCCValue})
|
|
key := base64.StdEncoding.EncodeToString(hashBytes)
|
|
|
|
entry, ok := mt.entries[key]
|
|
if !ok {
|
|
return "", fmt.Errorf("no entry found for key")
|
|
}
|
|
|
|
// Derive decryption key
|
|
lccBytes := hash.IntegerToByteArray(lCCValue)
|
|
decKey := kdf.DeriveKey(lccBytes, nil, 32)
|
|
|
|
// Decrypt
|
|
plaintext, err := symmetric.Decrypt(decKey, entry.Ciphertext, entry.Nonce, nil)
|
|
if err != nil {
|
|
return "", fmt.Errorf("decryption failed: %w", err)
|
|
}
|
|
|
|
return string(plaintext), nil
|
|
}
|
|
|
|
// Size returns the number of entries.
|
|
func (mt *MappingTable) Size() int {
|
|
return len(mt.entries)
|
|
}
|
|
|
|
// MappingRow is a serializable mapping-table entry (for transport between the
|
|
// setup component and the voting server).
|
|
type MappingRow struct {
|
|
Key string `json:"key"`
|
|
Ciphertext []byte `json:"ciphertext"`
|
|
Nonce []byte `json:"nonce"`
|
|
}
|
|
|
|
// Export returns the table's entries in a serializable form.
|
|
func (mt *MappingTable) Export() []MappingRow {
|
|
rows := make([]MappingRow, 0, len(mt.entries))
|
|
for k, e := range mt.entries {
|
|
rows = append(rows, MappingRow{Key: k, Ciphertext: e.Ciphertext, Nonce: e.Nonce})
|
|
}
|
|
return rows
|
|
}
|
|
|
|
// ImportMappingTable rebuilds a mapping table from exported rows.
|
|
func ImportMappingTable(rows []MappingRow) *MappingTable {
|
|
mt := NewMappingTable()
|
|
for _, r := range rows {
|
|
mt.entries[r.Key] = MappingEntry{Ciphertext: r.Ciphertext, Nonce: r.Nonce}
|
|
}
|
|
return mt
|
|
}
|