mirror of
https://github.com/luxfi/corona.git
synced 2026-07-27 02:50:34 +00:00
Closes the CRIT-1 residual (D1-2) sid-entropy gap and the CT/doc hygiene
gaps to bring Corona to the no-leak/no-gap bar. EasyCrypt proofs untouched.
PRIORITY 1 — anti-nonce-reuse / no-leak durability:
- The Round-1 nonce-PRF key no longer keys on a bare 64-bit sid. It now
derives from a 256-bit domain-separated SessionID (primitives.DeriveSessionID:
TranscriptHash("corona.sign.session-id.v1" || be64(sid) || T)) AND a fresh
per-signature 256-bit hedge salt drawn inside the kernel from party.Rand
(default crypto/rand). PRNGKeyForRound = PRF(skShare, "CoronaNonceV3" ||
sessionID || salt). Hedging restores threshold-Raccoon's fresh-per-signature
nonce posture: reuse durability no longer rests on the external consensus
layer never reissuing an sid — even an sid collision yields distinct R with
prob 2^-256. A deterministic 256-bit SessionID alone is necessary but NOT
sufficient (it repeats when sid repeats); the salt is what closes the leak.
- SignRound1 is fail-closed: rejects an all-zero derived SessionID or all-zero
salt with ErrDegenerateSession, and surfaces a short-read error. The
consensus slot-uniqueness invariant is documented as a HARD precondition at
SignRound1 and Signer.Round1 (no longer a buried comment).
- KAT/oracle determinism preserved via one seam: sign.DeterministicNonceSource
(KeyedPRNG over seed || "corona.sign.nonce-salt.v1" || partyIndex), set on
Party.Rand / Signer.SetNonceRand only by reproducibility harnesses.
- SignRound1 / Signer.Round1 now return an error; all call sites updated.
- KAT REGEN: only sign_verify_e2e.json changes (nonce-key bytes moved);
transcript_hash.json / MAC / legacy PRNGKey vectors are byte-stable, proving
the consensus-agreed transcript path was left untouched. Regenerated via
`bash scripts/regen-kats.sh`; `--verify` confirms byte-determinism (10 files).
- Regression: sign/nonce_reuse_test.go proves same-(skShare,sid) yields distinct
D (fresh R), pinned-nonce reproduces byte-identically, and the degenerate-
session guard fires. TestE2EKATReplayDeterminism rewritten to assert both the
hedged-differs and pinned-reproduces properties (was a defanged no-op).
PRIORITY 2 — constant-time hygiene:
- reshare/commit.go already used a constant-time comparator; the real gap was
the verbatim duplication of constTimePolyEqual+uint64SliceToBytes across dkg2
and reshare. Consolidated to one canonical utils.ConstantTimePolyEqual; both
delegate (no dkg2<->reshare dependency). Orphaned imports removed.
- FullRankCheck and the reshare commit path are now covered in the CT review
with their public-operand justification.
PRIORITY 3 — doc accuracy:
- CONSTANT-TIME-REVIEW.md rewritten Corona-specific and file:line-accurate:
drops the stale Pulsar/lens/warp/secp256k1 content; audits the real call
sites (hedged nonce key, masking PRF, lattigo samplers as the residual TCB
axiom, utils.ConstantTimePolyEqual, CheckL2Norm/Verify/FullRankCheck big.Int
variable-time on PUBLIC operands, activation/commit-digest array equality,
keyera/reshare zeroization).
- PROOF-CLAIMS.md §1: threshold.Combine / sign.LocalSign (nonexistent) -> the
real sign.Party.SignFinalize exposed as threshold.Signer.Finalize.
- threshold/threshold.go package doc: Ring-LWE -> Module-LWE.
312 lines
10 KiB
Go
312 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"log"
|
|
"math/big"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/luxfi/corona/networking"
|
|
"github.com/luxfi/corona/primitives"
|
|
"github.com/luxfi/corona/sign"
|
|
|
|
"github.com/luxfi/lattice/v7/ring"
|
|
"github.com/luxfi/lattice/v7/utils/sampling"
|
|
"github.com/luxfi/lattice/v7/utils/structs"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
fmt.Println("Usage: go run main.go partyID")
|
|
os.Exit(1)
|
|
}
|
|
|
|
partyIDString := os.Args[1]
|
|
|
|
iters, err := strconv.Atoi(os.Args[2])
|
|
if err != nil {
|
|
fmt.Println("Error: Please enter a valid integer.")
|
|
os.Exit(1)
|
|
}
|
|
|
|
parties, err := strconv.Atoi(os.Args[3])
|
|
if err != nil {
|
|
fmt.Println("Error: Please enter a valid integer.")
|
|
os.Exit(1)
|
|
}
|
|
sign.K = parties
|
|
sign.Threshold = parties
|
|
|
|
if partyIDString == "l" {
|
|
sign.LocalRun(iters)
|
|
return
|
|
}
|
|
|
|
partyID, err := strconv.Atoi(partyIDString)
|
|
if err != nil {
|
|
fmt.Println("Error: Please enter a valid integer.")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Initialize P2P communication over ZAP transport.
|
|
//
|
|
// Peers and their listen addresses come from the CORONA_PEERS env
|
|
// var, formatted as "rank=host:port,rank=host:port,...". The local
|
|
// party's own rank is excluded; every other rank in the ceremony
|
|
// must be present. The local listen port is derived from the same
|
|
// list (peerAddrs[partyID] specifies "host:port" we bind on).
|
|
peerAddrs, listenPort, err := parsePeerEnv(os.Getenv("CORONA_PEERS"), partyID)
|
|
if err != nil {
|
|
log.Fatalf("CORONA_PEERS: %v", err)
|
|
}
|
|
comm, err := networking.NewP2PComm(partyID, listenPort, true /*noDiscovery*/, nil)
|
|
if err != nil {
|
|
log.Fatalf("NewP2PComm: %v", err)
|
|
}
|
|
defer comm.Close()
|
|
// Suppress unused-import error from prior wiring.
|
|
_ = (*net.Conn)(nil)
|
|
|
|
if err := comm.EstablishConnectionsZAP(partyID, sign.K, peerAddrs); err != nil {
|
|
log.Fatalf("EstablishConnectionsZAP: %v", err)
|
|
}
|
|
|
|
var setupDuration, genDuration, signRound1Duration, signRound2PreprocessDuration, signRound2Duration, finalizeDuration, verifyDuration time.Duration
|
|
var genStart, genEnd, signRound1Start, signRound1End, signRound2Start, signRound2End, combinerReceiveEnd, combinerFinalizeEnd time.Time
|
|
var A structs.Matrix[ring.Poly]
|
|
var b structs.Vector[ring.Poly]
|
|
randomKey := make([]byte, sign.KeySize)
|
|
r, _ := ring.NewRing(1<<sign.LogN, []uint64{sign.Q})
|
|
r_xi, _ := ring.NewRing(1<<sign.LogN, []uint64{sign.QXi})
|
|
r_nu, _ := ring.NewRing(1<<sign.LogN, []uint64{sign.QNu})
|
|
prng, _ := sampling.NewKeyedPRNG(randomKey)
|
|
uniformSampler := ring.NewUniformSampler(prng, r)
|
|
trustedDealerKey := randomKey
|
|
// Create your own party
|
|
party := sign.NewParty(partyID, r, r_xi, r_nu, uniformSampler)
|
|
|
|
// Variables to be used in SIGNATURE ROUNDS
|
|
D := make(map[int]structs.Matrix[ring.Poly])
|
|
MACs := make(map[int]map[int][]byte)
|
|
mu := "Message"
|
|
T := make([]int, sign.K)
|
|
for i := 0; i < sign.K; i++ {
|
|
T[i] = i
|
|
}
|
|
lagrangeCoeffs := primitives.ComputeLagrangeCoefficients(r, T, big.NewInt(int64(sign.Q)))
|
|
|
|
// Trusted dealer
|
|
if partyID == sign.TrustedDealerID {
|
|
// GEN: Generate secret shares, seeds, and MAC keys
|
|
start := time.Now()
|
|
AMat, skShares, seeds, MACKeys, bVec := sign.Gen(r, r_xi, uniformSampler, trustedDealerKey, lagrangeCoeffs)
|
|
|
|
b = bVec
|
|
A = AMat
|
|
genDuration = time.Since(start)
|
|
genStart = time.Now()
|
|
|
|
// Give party 0 its own values
|
|
party.SkShare = skShares[partyID]
|
|
party.Seed = seeds
|
|
party.MACKeys = MACKeys[partyID]
|
|
|
|
// Send out public information & trusted dealer data
|
|
var sendWg sync.WaitGroup
|
|
for i := 0; i < sign.K; i++ {
|
|
if i != sign.TrustedDealerID {
|
|
sendWg.Add(1)
|
|
go func(i int) {
|
|
defer sendWg.Done()
|
|
writer := bufio.NewWriter(*comm.GetSock(i))
|
|
comm.SendVector(writer, i, b)
|
|
comm.SendMatrix(writer, i, A)
|
|
comm.SendVector(writer, i, skShares[i])
|
|
comm.SendBytesSliceMap(writer, i, seeds)
|
|
comm.SendBytesMap(writer, i, MACKeys[i])
|
|
}(i)
|
|
}
|
|
}
|
|
sendWg.Wait()
|
|
genEnd = time.Now()
|
|
} else {
|
|
reader := bufio.NewReader(*comm.GetSock(sign.TrustedDealerID))
|
|
b = comm.RecvVector(reader, sign.TrustedDealerID, sign.M)
|
|
A = comm.RecvMatrix(reader, sign.TrustedDealerID, sign.M)
|
|
party.SkShare = comm.RecvVector(reader, sign.TrustedDealerID, sign.N)
|
|
party.Seed = comm.RecvBytesSliceMap(reader, sign.TrustedDealerID)
|
|
party.MACKeys = comm.RecvBytesMap(reader, sign.TrustedDealerID)
|
|
}
|
|
|
|
time.Sleep(time.Second * 5)
|
|
// SIGNATURE ROUND 1
|
|
sid := 1
|
|
PRFKey := "PRFKey"
|
|
|
|
r.NTT(lagrangeCoeffs[partyID], lagrangeCoeffs[partyID])
|
|
r.MForm(lagrangeCoeffs[partyID], lagrangeCoeffs[partyID])
|
|
party.Lambda = lagrangeCoeffs[partyID]
|
|
|
|
fmt.Printf("Timestamp before Sign Round 1 compute: %s\n", time.Now().Format("15:04:05.000000"))
|
|
start := time.Now()
|
|
var r1err error
|
|
D[partyID], MACs[partyID], r1err = party.SignRound1(A, sid, []byte(PRFKey), T)
|
|
if r1err != nil {
|
|
log.Fatalf("SignRound1 failed for party %d: %v", partyID, r1err)
|
|
}
|
|
signRound1Duration = time.Since(start)
|
|
log.Println("Completed R1")
|
|
|
|
signRound1Start = time.Now()
|
|
// Concurrently send and receive data
|
|
var round1Wg sync.WaitGroup
|
|
for i := 0; i < sign.K; i++ {
|
|
if i != partyID {
|
|
round1Wg.Add(2)
|
|
go func(i int) {
|
|
defer round1Wg.Done()
|
|
writer := bufio.NewWriter(*comm.GetSock(i))
|
|
comm.SendMatrix(writer, i, D[partyID])
|
|
comm.SendBytesMap(writer, i, MACs[partyID])
|
|
}(i)
|
|
|
|
go func(i int) {
|
|
defer round1Wg.Done()
|
|
reader := bufio.NewReader(*comm.GetSock(i))
|
|
D[i] = comm.RecvMatrix(reader, i, sign.M)
|
|
MACs[i] = comm.RecvBytesMap(reader, i)
|
|
}(i)
|
|
}
|
|
}
|
|
round1Wg.Wait()
|
|
signRound1End = time.Now()
|
|
|
|
// SIGN ROUND 2
|
|
z := make(map[int]structs.Vector[ring.Poly])
|
|
|
|
fmt.Printf("Timestamp before Sign Round 1 verify: %s\n", time.Now().Format("15:04:05.000000"))
|
|
start = time.Now()
|
|
valid, DSum, hash := party.SignRound2Preprocess(A, b, D, MACs, sid, T)
|
|
if !valid {
|
|
log.Fatalf("MAC verification failed for party %d", partyID)
|
|
} else {
|
|
log.Println("Verification passed, moving onto round 2")
|
|
}
|
|
signRound2PreprocessDuration = time.Since(start)
|
|
|
|
fmt.Printf("Timestamp before Sign Round 2 compute: %s\n", time.Now().Format("15:04:05.000000"))
|
|
start = time.Now()
|
|
z[partyID] = party.SignRound2(A, b, DSum, sid, mu, T, []byte(PRFKey), hash)
|
|
signRound2Duration = time.Since(start)
|
|
|
|
signRound2Start = time.Now()
|
|
if partyID != sign.CombinerID {
|
|
writer := bufio.NewWriter(*comm.GetSock(sign.CombinerID))
|
|
comm.SendVector(writer, sign.CombinerID, z[partyID])
|
|
signRound2End = time.Now()
|
|
} else {
|
|
for i := 0; i < sign.K; i++ {
|
|
if i != sign.CombinerID {
|
|
reader := bufio.NewReader(*comm.GetSock(i))
|
|
z[i] = comm.RecvVector(reader, i, sign.N)
|
|
}
|
|
}
|
|
combinerReceiveEnd = time.Now()
|
|
|
|
fmt.Printf("Timestamp before Finalize: %s\n", time.Now().Format("15:04:05.000000"))
|
|
// SIGNATURE FINALIZE
|
|
start := time.Now()
|
|
_, sig, Delta := party.SignFinalize(z, A, b)
|
|
finalizeDuration = time.Since(start)
|
|
combinerFinalizeEnd = time.Now()
|
|
// Verify the signature
|
|
start = time.Now()
|
|
verified := sign.Verify(r, r_xi, r_nu, sig, A, mu, b, party.C, Delta)
|
|
verifyDuration = time.Since(start)
|
|
fmt.Printf("Signature Verification Result: %v\n", verified)
|
|
}
|
|
|
|
// Print all durations
|
|
fmt.Println("Setup duration:", setupDuration)
|
|
fmt.Println("Gen duration:", genDuration)
|
|
fmt.Println("Signature Round 1 duration:", signRound1Duration)
|
|
fmt.Println("Signature Round 1 verify duration:", signRound2PreprocessDuration)
|
|
fmt.Println("Signature Round 2 duration:", signRound2Duration)
|
|
fmt.Println("Finalize duration:", finalizeDuration)
|
|
fmt.Println("Verify duration:", verifyDuration)
|
|
|
|
// Print timestamps for networking
|
|
fmt.Printf("Gen start timestamp: %s\n", genStart.Format("15:04:05.000000"))
|
|
fmt.Printf("Gen end timestamp: %s\n", genEnd.Format("15:04:05.000000"))
|
|
|
|
fmt.Printf("Sign Round 1 sending/receiving start timestamp: %s\n", signRound1Start.Format("15:04:05.000000"))
|
|
fmt.Printf("Sign Round 1 sending/receiving end timestamp: %s\n", signRound1End.Format("15:04:05.000000"))
|
|
|
|
fmt.Printf("Sign Round 2 sending/receiving start timestamp: %s\n", signRound2Start.Format("15:04:05.000000"))
|
|
fmt.Printf("Sign Round 2 sending end timestamp: %s\n", signRound2End.Format("15:04:05.000000"))
|
|
|
|
if partyID == sign.CombinerID {
|
|
fmt.Printf("Combiner receive end timestamp: %s\n", combinerReceiveEnd.Format("15:04:05.000000"))
|
|
fmt.Printf("Combiner finalize end timestamp: %s\n", combinerFinalizeEnd.Format("15:04:05.000000"))
|
|
}
|
|
}
|
|
|
|
// parsePeerEnv parses the CORONA_PEERS env var into a per-rank address
|
|
// map, and returns the local listen port for partyID.
|
|
//
|
|
// Format: "0=host:port,1=host:port,...". Every rank in the ceremony
|
|
// must appear. The local party's entry is dropped from the returned
|
|
// map but its port is harvested as the listen port.
|
|
//
|
|
// Returns an error on malformed input or when partyID is absent.
|
|
func parsePeerEnv(raw string, partyID int) (map[int]string, int, error) {
|
|
if strings.TrimSpace(raw) == "" {
|
|
return nil, 0, fmt.Errorf("CORONA_PEERS is empty (format: rank=host:port,rank=host:port,...)")
|
|
}
|
|
peerAddrs := make(map[int]string)
|
|
listenPort := 0
|
|
for _, entry := range strings.Split(raw, ",") {
|
|
entry = strings.TrimSpace(entry)
|
|
if entry == "" {
|
|
continue
|
|
}
|
|
eq := strings.IndexByte(entry, '=')
|
|
if eq < 0 {
|
|
return nil, 0, fmt.Errorf("CORONA_PEERS entry %q missing '='", entry)
|
|
}
|
|
rank, err := strconv.Atoi(strings.TrimSpace(entry[:eq]))
|
|
if err != nil {
|
|
return nil, 0, fmt.Errorf("CORONA_PEERS entry %q: %w", entry, err)
|
|
}
|
|
addr := strings.TrimSpace(entry[eq+1:])
|
|
if addr == "" {
|
|
return nil, 0, fmt.Errorf("CORONA_PEERS entry %q has empty address", entry)
|
|
}
|
|
if rank == partyID {
|
|
// Local listen port from "host:port" — the host is ignored
|
|
// (we always listen on all interfaces).
|
|
_, portStr, err := net.SplitHostPort(addr)
|
|
if err != nil {
|
|
return nil, 0, fmt.Errorf("CORONA_PEERS local addr %q: %w", addr, err)
|
|
}
|
|
p, err := strconv.Atoi(portStr)
|
|
if err != nil {
|
|
return nil, 0, fmt.Errorf("CORONA_PEERS local port %q: %w", portStr, err)
|
|
}
|
|
listenPort = p
|
|
continue
|
|
}
|
|
peerAddrs[rank] = addr
|
|
}
|
|
if listenPort == 0 {
|
|
return nil, 0, fmt.Errorf("CORONA_PEERS missing entry for local rank %d", partyID)
|
|
}
|
|
return peerAddrs, listenPort, nil
|
|
}
|