mirror of
https://github.com/luxfi/corona.git
synced 2026-09-24 00:25:54 +00:00
Forked from github.com/luxfi/corona @ d09b2c2. Pulsar inherits the
2-round signing math byte-equal but adds blockchain-grade key
lifecycle and a SHA3-based domain-separated hash profile for
permissionless validator-set rotation.
Inherits from upstream Corona (byte-equal):
- Sign1/Sign2/Combine 2-round signing math
- Lattice ring R_q = Z_q[X]/(X^256+1), q = 0x1000000004A01
- Module-LWE / Module-SIS hardness
- NTT, Discrete-Gaussian Ziggurat, MAC layer
Replaces from upstream:
- Broken Feldman-style DKG (pseudoinverse-recoverable). Replaced
with: (a) one-time foundation MPC ceremony / trusted-dealer
Bootstrap; (b) Pedersen DKG over R_q (research, dkg2/) hiding
under MLWE on B / binding under MSIS on [A | B].
- Trusted-dealer-per-epoch lifecycle (wrong shape for permissionless
rotation). Replaced with Verifiable Secret Resharing (VSR) every
epoch under the persistent group public key.
Key-era lifecycle (KeyEraID / Generation / RollbackFrom):
- KeyEraID bumps only at Reanchor (rare governance event)
- Generation bumps every Refresh / Reshare under the same GroupKey
- RollbackFrom records prior generation reverted from (0 = forward)
- Activation cert (QUASAR-PULSAR-ACTIVATE-v1) gates new generations
under the unchanged GroupKey
Pulsar-SHA3 hash profile (NIST FIPS 202 / SP 800-185):
- Hc: cSHAKE256("PULSAR-HC-v1", transcript) → challenge
- Hu: cSHAKE256("PULSAR-HU-v1", transcript) → XOF stream
- TranscriptHash: TupleHash256("PULSAR-TRANSCRIPT-v1", parts...)
- PRF: KMAC256(key, msg, "PULSAR-PRF-v1")
- MAC: KMAC256(key, msg, "PULSAR-MAC-v1")
- Pairwise: KMAC256(kex, encode(...), "PULSAR-PAIRWISE-v1")
- Default suite is Pulsar-SHA3; Pulsar-BLAKE3 retained as optional
non-normative fast profile. HashSuiteID is bound into transcripts.
- Implementation at pulsar/hash/ with HashSuite interface.
- All KATs regenerated under SHA3 profile; vectors in
luxcpp/crypto/pulsar/test/kat/.
Nebula root binding (TranscriptInputs + ActivationMessage canonical
bytes, all bound under TupleHash256):
chain_id, network_id, group_id,
key_era_id, old_generation, new_generation,
old_epoch_id, new_epoch_id,
old_set_hash, new_set_hash,
threshold_old, threshold_new,
group_public_key_hash,
nebula_root,
hash_suite_id,
implementation_version,
variant.
Packages:
primitives/ Shamir over R_q, Lagrange, hash helpers
sign/ 2-round sign math (byte-equal upstream)
threshold/ GroupKey, KeyShare, Signer types
reshare/ VSR kernel — Refresh (HJKY97 zero-poly), Reshare
(Desmedt-Jajodia), commit (Pedersen R_q), complaint,
transcript, pairwise KEX, activation cert
hash/ HashSuite interface; PulsarSHA3 (default), PulsarBLAKE3
dkg2/ Pedersen DKG over R_q (research, reference only)
keyera/ Lifecycle wrapper: Bootstrap → Reshare → Reanchor
papers/ LP-073-pulsar paper (sections + bibliography)
cmd/*_oracle/ KAT generators (Go ↔ C++ byte-equal validation)
DESIGN.md is the single source of truth:
- The Pulsar metaphor (rotating beam over persistent body)
- Vocabulary stack per LP-105: Nebula / Photon / Lumen / Beam /
Pulsar / Pulse / Prism / Horizon / Quasar
- Pulsar ≠ Lumen (PQ stream is separate, planned)
- Pulsar / Lens / LSS three-layer architecture
- Bootstrap Dealer vs Signature Coordinator
- No-slashing failure ladder (timeout → retry → rollback → reanchor)
- MVP VSR vs Robust VSR phasing
- Borrowed-brand terms ("X-Wing" used casually) replaced with
"hybrid KEM" / "Hybrid Lumen Handshake"; X-Wing cited only as
the IETF combiner primitive name where exact reference matters.
Tests: full suite green
hash 5+/5+, keyera 5/5, reshare 45+/45+, threshold, sign, dkg,
dkg2, primitives, networking, utils, corona_oracle_v2 KAT —
all pass.
Honest claim: the crypto primitives are not new (HJKY97,
Desmedt-Jajodia97, Wong-Wang-Wing02, Corona, Hermine, Threshold
Raccoon all exist). The novel contribution is the systems
composition: a permissionless-consensus deployment architecture that
turns static two-round PQ threshold signatures into a dynamic,
leaderless, validator-rotation-tolerant finality layer.
Companion docs: papers/lp-073-pulsar/ (academic paper); LP-073 +
LP-103 + LP-105 in github.com/luxfi/lps; threshold/protocols/lss/
lss_pulsar.go (LSS adapter wrapping this kernel).
243 lines
7.5 KiB
Go
243 lines
7.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"log"
|
|
"math/big"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/luxfi/pulsar/networking"
|
|
"github.com/luxfi/pulsar/primitives"
|
|
"github.com/luxfi/pulsar/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
|
|
comm := &networking.P2PComm{
|
|
Socks: make(map[int]*net.Conn),
|
|
Rank: partyID,
|
|
}
|
|
|
|
// Establish connections
|
|
var connWg sync.WaitGroup
|
|
connWg.Add(1)
|
|
go networking.EstablishConnections(&connWg, comm, partyID, sign.K)
|
|
connWg.Wait()
|
|
|
|
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()
|
|
D[partyID], MACs[partyID] = party.SignRound1(A, sid, []byte(PRFKey), T)
|
|
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"))
|
|
}
|
|
}
|