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.
80 lines
2.3 KiB
Go
80 lines
2.3 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package primitives
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
// Shamir produces a (t, n)-Shamir secret sharing of `secret` over the
|
|
// curve's scalar field. Returns:
|
|
//
|
|
// - The polynomial f with f(0) = secret and (t-1) random higher-
|
|
// order coefficients drawn from `rand`.
|
|
// - A map partyID → f(partyID) for partyID in [1, n].
|
|
//
|
|
// The threshold t is the minimum number of shares needed to recover
|
|
// the secret (so the polynomial degree is t-1).
|
|
//
|
|
// Note that party IDs are 1-indexed; ID 0 is reserved (evaluating at 0
|
|
// reveals the secret).
|
|
func Shamir(
|
|
curve Curve,
|
|
secret Scalar,
|
|
threshold, n int,
|
|
rand io.Reader,
|
|
) (*Polynomial, map[int]Scalar, error) {
|
|
if threshold < 1 {
|
|
return nil, nil, fmt.Errorf("lens/primitives: Shamir threshold %d < 1", threshold)
|
|
}
|
|
if n < threshold {
|
|
return nil, nil, fmt.Errorf("lens/primitives: Shamir n=%d < t=%d", n, threshold)
|
|
}
|
|
poly, err := NewPolynomial(curve, threshold-1, secret, rand)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
shares := make(map[int]Scalar, n)
|
|
for j := 1; j <= n; j++ {
|
|
x := curve.NewScalar().SetUint64(uint64(j))
|
|
shares[j] = poly.Evaluate(x)
|
|
}
|
|
return poly, shares, nil
|
|
}
|
|
|
|
// LagrangeRecover reconstructs the secret f(0) from the supplied
|
|
// (partyID → share) map, using the smallest-ID t-element subset.
|
|
// Intended for tests and KAT verification — calling this in production
|
|
// gives the caller the master secret.
|
|
func LagrangeRecover(curve Curve, shares map[int]Scalar, threshold int) (Scalar, error) {
|
|
if len(shares) < threshold {
|
|
return nil, fmt.Errorf("lens/primitives: LagrangeRecover need %d shares, got %d",
|
|
threshold, len(shares))
|
|
}
|
|
ids := make([]int, 0, len(shares))
|
|
for id := range shares {
|
|
ids = append(ids, id)
|
|
}
|
|
ids = SortedInts(ids)
|
|
if len(ids) > threshold {
|
|
ids = ids[:threshold]
|
|
}
|
|
lambda, err := Lagrange(curve, ids)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := curve.NewScalar()
|
|
for _, id := range ids {
|
|
term := curve.NewScalar().Set(lambda[id]).Mul(shares[id])
|
|
out.Add(term)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// ErrShamirInsufficient is returned when fewer than `threshold` shares
|
|
// are supplied to a reconstruction routine.
|
|
var ErrShamirInsufficient = errors.New("lens/primitives: insufficient shares for Shamir recovery")
|