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.
177 lines
6.2 KiB
Go
177 lines
6.2 KiB
Go
package sign
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/luxfi/corona/primitives"
|
|
"math/big"
|
|
"time"
|
|
|
|
"github.com/luxfi/lattice/v7/ring"
|
|
"github.com/luxfi/lattice/v7/utils/sampling"
|
|
"github.com/luxfi/lattice/v7/utils/structs"
|
|
"github.com/montanaflynn/stats"
|
|
)
|
|
|
|
var K int
|
|
var Threshold int
|
|
|
|
// main function orchestrates the threshold signature protocol
|
|
func LocalRun(x int) {
|
|
var totalGenDuration, totalFinalizeDuration, totalVerifyDuration time.Duration
|
|
|
|
// Create maps to collect durations across all runs
|
|
totalSignRound1Durations := make(map[int]float64)
|
|
totalSignRound2PreprocessDurations := make(map[int]float64)
|
|
totalSignRound2Durations := make(map[int]float64)
|
|
|
|
for run := 0; run < x; run++ {
|
|
log.Println("RUN:", run)
|
|
var genDuration, finalizeDuration, verifyDuration time.Duration
|
|
|
|
randomKey := make([]byte, KeySize)
|
|
|
|
r, _ := ring.NewRing(1<<LogN, []uint64{Q})
|
|
r_xi, _ := ring.NewRing(1<<LogN, []uint64{QXi})
|
|
r_nu, _ := ring.NewRing(1<<LogN, []uint64{QNu})
|
|
|
|
prng, _ := sampling.NewKeyedPRNG(randomKey)
|
|
uniformSampler := ring.NewUniformSampler(prng, r)
|
|
trustedDealerKey := randomKey
|
|
|
|
parties := make([]*Party, K)
|
|
for i := range parties {
|
|
prng, _ := sampling.NewKeyedPRNG(randomKey)
|
|
uniformSampler := ring.NewUniformSampler(prng, r)
|
|
parties[i] = NewParty(i, r, r_xi, r_nu, uniformSampler)
|
|
}
|
|
|
|
// GEN: Generate secret shares, seeds, and MAC keys
|
|
T := make([]int, K) // Active parties
|
|
for i := 0; i < K; i++ {
|
|
T[i] = i
|
|
}
|
|
lagrangeCoeffs := primitives.ComputeLagrangeCoefficients(r, T, big.NewInt(int64(Q)))
|
|
log.Println("Gen")
|
|
|
|
start := time.Now()
|
|
A, skShares, seeds, MACKeys, b := Gen(r, r_xi, uniformSampler, trustedDealerKey, lagrangeCoeffs)
|
|
genDuration = time.Since(start)
|
|
log.Println("Gen Duration:", genDuration)
|
|
for partyID := 0; partyID < K; partyID++ {
|
|
parties[partyID].SkShare = skShares[partyID]
|
|
parties[partyID].Seed = seeds
|
|
parties[partyID].MACKeys = MACKeys[partyID]
|
|
}
|
|
|
|
// Create maps to collect durations for this run
|
|
signRound1Durations := make(map[int]time.Duration)
|
|
signRound2PreprocessDurations := make(map[int]time.Duration)
|
|
signRound2Durations := make(map[int]time.Duration)
|
|
|
|
// SIGNATURE ROUND 1
|
|
mu := "Message"
|
|
sid := 1
|
|
PRFKey := primitives.GenerateRandomSeed()
|
|
log.Println("Generating lagrange coefficients...")
|
|
D := make(map[int]structs.Matrix[ring.Poly])
|
|
MACs := make(map[int]map[int][]byte)
|
|
for _, partyID := range T {
|
|
r.NTT(lagrangeCoeffs[partyID], lagrangeCoeffs[partyID])
|
|
r.MForm(lagrangeCoeffs[partyID], lagrangeCoeffs[partyID])
|
|
parties[partyID].Lambda = lagrangeCoeffs[partyID]
|
|
parties[partyID].Seed = seeds
|
|
log.Println("Sign Round 1, party", partyID)
|
|
start = time.Now()
|
|
var r1err error
|
|
D[partyID], MACs[partyID], r1err = parties[partyID].SignRound1(A, sid, []byte(PRFKey), T)
|
|
if r1err != nil {
|
|
log.Fatalf("SignRound1 failed for party %d: %v", partyID, r1err)
|
|
}
|
|
signRound1Durations[partyID] = time.Since(start)
|
|
}
|
|
|
|
// SIGNATURE ROUND 2
|
|
z := make(map[int]structs.Vector[ring.Poly])
|
|
|
|
for _, partyID := range T {
|
|
log.Println("Sign Round 2 preprocess, party", partyID)
|
|
start = time.Now()
|
|
valid, DSum, hash := parties[partyID].SignRound2Preprocess(A, b, D, MACs, sid, T)
|
|
if !valid {
|
|
log.Fatalf("MAC verification failed for party %d", partyID)
|
|
}
|
|
signRound2PreprocessDurations[partyID] = time.Since(start)
|
|
|
|
log.Println("Sign round 2 party", partyID)
|
|
start = time.Now()
|
|
z[partyID] = parties[partyID].SignRound2(A, b, DSum, sid, mu, T, []byte(PRFKey), hash)
|
|
signRound2Durations[partyID] = time.Since(start)
|
|
}
|
|
|
|
// SIGNATURE FINALIZE
|
|
log.Println("finalizing...")
|
|
finalParty := parties[0]
|
|
start = time.Now()
|
|
_, sig, Delta := finalParty.SignFinalize(z, A, b)
|
|
finalizeDuration = time.Since(start)
|
|
|
|
// Verify the signature
|
|
start = time.Now()
|
|
valid := Verify(r, r_xi, r_nu, sig, A, mu, b, finalParty.C, Delta)
|
|
verifyDuration = time.Since(start)
|
|
fmt.Printf("Signature Verification Result: %v\n", valid)
|
|
|
|
// Accumulate durations
|
|
totalGenDuration += genDuration
|
|
totalFinalizeDuration += finalizeDuration
|
|
totalVerifyDuration += verifyDuration
|
|
|
|
// Accumulate phase durations
|
|
for partyID, duration := range signRound1Durations {
|
|
totalSignRound1Durations[partyID] += float64(duration.Nanoseconds()) / 1e6
|
|
}
|
|
for partyID, duration := range signRound2PreprocessDurations {
|
|
totalSignRound2PreprocessDurations[partyID] += float64(duration.Nanoseconds()) / 1e6
|
|
}
|
|
for partyID, duration := range signRound2Durations {
|
|
totalSignRound2Durations[partyID] += float64(duration.Nanoseconds()) / 1e6
|
|
}
|
|
}
|
|
|
|
// Print averaged durations
|
|
fmt.Println("Averaged durations over", x, "runs:")
|
|
fmt.Printf("Gen duration: %.3f ms\n", float64(totalGenDuration.Nanoseconds())/1e6/float64(x))
|
|
fmt.Printf("Finalize duration: %.3f ms\n", float64(totalFinalizeDuration.Nanoseconds())/1e6/float64(x))
|
|
fmt.Printf("Verify duration: %.3f ms\n", float64(totalVerifyDuration.Nanoseconds())/1e6/float64(x))
|
|
|
|
// Calculate and print averaged statistics for each phase
|
|
printAveragedStats("Signature Round 1", totalSignRound1Durations, x)
|
|
printAveragedStats("Signature Round 2 Preprocess", totalSignRound2PreprocessDurations, x)
|
|
printAveragedStats("Signature Round 2", totalSignRound2Durations, x)
|
|
|
|
// Calculate and print total signing and offline durations
|
|
totalSigningDurations := make(map[int]float64)
|
|
for partyID := range totalSignRound1Durations {
|
|
totalSigningDurations[partyID] = totalSignRound1Durations[partyID] + totalSignRound2PreprocessDurations[partyID] + totalSignRound2Durations[partyID]
|
|
}
|
|
printAveragedStats("Total Signing", totalSigningDurations, x)
|
|
}
|
|
|
|
// printAveragedStats prints the mean, median, and standard deviation for a map of durations averaged over x runs
|
|
func printAveragedStats(phaseName string, totalDurations map[int]float64, x int) {
|
|
var values []float64
|
|
for _, totalDuration := range totalDurations {
|
|
values = append(values, totalDuration/float64(x))
|
|
}
|
|
mean, _ := stats.Mean(values)
|
|
median, _ := stats.Median(values)
|
|
stddev, _ := stats.StandardDeviation(values)
|
|
|
|
fmt.Printf("%s averaged duration stats over %d runs:\n", phaseName, x)
|
|
fmt.Printf(" Mean: %.3f ms\n", mean)
|
|
fmt.Printf(" Median: %.3f ms\n", median)
|
|
fmt.Printf(" Standard Deviation: %.3f ms\n", stddev)
|
|
}
|