Files
zeekay 2c63fd34fc lens: classical curve threshold kernel — sister to Pulsar
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.
2026-03-03 12:00:00 -08:00

95 lines
2.8 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"
)
// PedersenCommit returns C = secret·G + blind·H over the supplied
// curve. G is the curve's base point; H is the curve's secondary
// generator (returned by SecondaryGenerator).
//
// The commitment is hiding (when blind is uniform random) and binding
// (under the discrete log assumption between G and H).
func PedersenCommit(curve Curve, secret, blind Scalar) Point {
left := secret.ActOnBase()
right := blind.Act(curve.SecondaryGenerator())
return left.Add(right)
}
// PedersenVectorCommit commits to the t coefficients of a polynomial
// f(X) = c_0 + c_1·X + ... + c_{t-1}·X^{t-1} together with matching
// blinding coefficients r_0, ..., r_{t-1}. Returns the t commitments
// C_k = c_k·G + r_k·H.
func PedersenVectorCommit(curve Curve, coeffs, blinds []Scalar) ([]Point, error) {
if len(coeffs) != len(blinds) {
return nil, fmt.Errorf("lens/primitives: PedersenVectorCommit length mismatch: %d coeffs vs %d blinds",
len(coeffs), len(blinds))
}
commits := make([]Point, len(coeffs))
for k := range coeffs {
commits[k] = PedersenCommit(curve, coeffs[k], blinds[k])
}
return commits, nil
}
// VerifyPedersenShare checks the commitment-equation
//
// share·G + blind·H ?= Σ_{k=0..t-1} x^k · C_k
//
// where (share, blind) is what the recipient at evaluation point x
// received from the dealer, and the C_k are the dealer's broadcast
// Pedersen commits to its (secretCoeffs, blindCoeffs). Returns nil if
// the share is consistent; ErrCommitMismatch otherwise.
func VerifyPedersenShare(
curve Curve,
share, blind Scalar,
commits []Point,
x Scalar,
) error {
if len(commits) == 0 {
return errors.New("lens/primitives: VerifyPedersenShare with empty commits")
}
// LHS: share·G + blind·H
lhs := PedersenCommit(curve, share, blind)
// RHS: Σ x^k · C_k via Horner's method.
rhs := curve.NewPoint()
for k := len(commits) - 1; k >= 0; k-- {
rhs = x.Act(rhs)
rhs = rhs.Add(commits[k])
}
if !lhs.Equal(rhs) {
return ErrCommitMismatch
}
return nil
}
// ErrCommitMismatch is returned when a (share, blind) pair fails the
// Pedersen verification equation.
var ErrCommitMismatch = errors.New("lens/primitives: Pedersen commitment verification failed")
// SamplePolynomialBlind draws (degree+1) uniform blinding scalars used
// alongside a Polynomial of the same degree to commit to its
// coefficients.
func SamplePolynomialBlind(curve Curve, degree int, rand io.Reader) ([]Scalar, error) {
if degree < 0 {
return nil, fmt.Errorf("lens/primitives: SamplePolynomialBlind degree %d", degree)
}
out := make([]Scalar, degree+1)
for i := range out {
s, err := curve.SampleScalar(rand)
if err != nil {
return nil, err
}
out[i] = s
}
return out, nil
}