mirror of
https://github.com/luxfi/lens.git
synced 2026-10-30 16:21:16 +00:00
Lens is the curve-based threshold signing kernel for Lux. It is the classical analogue of Pulsar (the lattice kernel at github.com/luxfi/pulsar). Both kernels share the same lifecycle shape — Bootstrap → Reshare* → Reanchor — and the same activation circuit-breaker contract. They differ only in the math field: Pulsar over R_q (Module-LWE), Lens over a prime-order curve group (discrete log). Curves shipped: Ed25519, secp256k1, Ristretto255. Each has a deterministically-derived secondary Pedersen generator H from a Lens-specific nothing-up-my-sleeve tag. Sign math: FROST 2-round Schnorr threshold signing per RFC 9591 (Komlo-Goldberg 2020). Verifier checks z·G ?= R + c·X under the unchanged group public key. Reshare math: HJKY97 zero-poly Refresh + Desmedt-Jajodia ReshareToNewSet over the curve scalar field. Master secret s is preserved; only the share distribution rotates per epoch. Activation cert message format: QUASAR-LENS-ACTIVATE-v1, byte-equivalent shape to QUASAR-PULSAR-ACTIVATE-v1. Hash suite: HashSuite interface with Lens-SHA3 (production, cSHAKE256 / KMAC256 / TupleHash256) and Lens-BLAKE3 (legacy) profiles. DKG: distributed FROST DKG with Feldman commitments. No trusted dealer required at genesis if the chain prefers proper distributed DKG. KAT oracles: cmd/reshare_oracle and cmd/dkg_oracle emit deterministic JSON vectors for cross-language byte-equality validation against a future luxcpp/crypto/lens/ port. Tests: full unit + integration coverage on every shipped curve. 60 unique tests, 0 failures.
75 lines
1.7 KiB
Go
75 lines
1.7 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package threshold
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"testing"
|
|
|
|
"github.com/luxfi/lens/primitives"
|
|
)
|
|
|
|
func TestNewGroupKeyHasGAndH(t *testing.T) {
|
|
for _, c := range []primitives.Curve{
|
|
primitives.NewEd25519(),
|
|
primitives.NewSecp256k1(),
|
|
primitives.NewRistretto255(),
|
|
} {
|
|
t.Run(c.Name(), func(t *testing.T) {
|
|
s, _ := c.SampleScalar(rand.Reader)
|
|
X := s.ActOnBase()
|
|
gk := NewGroupKey(c, X)
|
|
if gk.G.IsIdentity() {
|
|
t.Error("G is identity")
|
|
}
|
|
if gk.H.IsIdentity() {
|
|
t.Error("H is identity")
|
|
}
|
|
if gk.G.Equal(gk.H) {
|
|
t.Error("G == H")
|
|
}
|
|
if !gk.X.Equal(X) {
|
|
t.Error("X mismatch")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGroupKeyBytesStable(t *testing.T) {
|
|
c := primitives.NewEd25519()
|
|
s, _ := c.SampleScalar(rand.Reader)
|
|
gk := NewGroupKey(c, s.ActOnBase())
|
|
a := gk.Bytes()
|
|
b := gk.Bytes()
|
|
if string(a) != string(b) {
|
|
t.Fatal("Bytes() not deterministic")
|
|
}
|
|
if len(a) == 0 {
|
|
t.Fatal("Bytes() empty")
|
|
}
|
|
}
|
|
|
|
func TestKeyShareConsistency(t *testing.T) {
|
|
c := primitives.NewSecp256k1()
|
|
s, _ := c.SampleScalar(rand.Reader)
|
|
gk := NewGroupKey(c, s.ActOnBase())
|
|
skShare, _ := c.SampleScalar(rand.Reader)
|
|
ks := &KeyShare{
|
|
Index: 0,
|
|
PartyID: 1,
|
|
SkShare: skShare,
|
|
VerificationShare: skShare.ActOnBase(),
|
|
GroupKey: gk,
|
|
}
|
|
if err := ks.CheckConsistency(); err != nil {
|
|
t.Errorf("honest share failed: %v", err)
|
|
}
|
|
// Tamper VerificationShare.
|
|
other, _ := c.SampleScalar(rand.Reader)
|
|
ks.VerificationShare = other.ActOnBase()
|
|
if err := ks.CheckConsistency(); err == nil {
|
|
t.Error("inconsistent share accepted")
|
|
}
|
|
}
|