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.
107 lines
4.0 KiB
Go
107 lines
4.0 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package hash defines the canonical hashing profile used by every
|
|
// Lens reshare / activation / pairwise routine.
|
|
//
|
|
// Two profiles are shipped:
|
|
//
|
|
// - Lens-SHA3 — the production profile. Built on cSHAKE256, KMAC256,
|
|
// and TupleHash256 from FIPS 202 / NIST SP 800-185. KATs in the
|
|
// reshare oracle are emitted under this profile.
|
|
//
|
|
// - Lens-BLAKE3 — the legacy / non-normative profile. Preserved so
|
|
// historical bytes can be reproduced for cross-checks. Marked NOT
|
|
// for production.
|
|
//
|
|
// All operations bind a domain-separation tag through the cSHAKE
|
|
// customization parameter (or the equivalent BLAKE3 keyed personalization
|
|
// when the BLAKE3 suite is active). Tags are version-pinned: bumping a
|
|
// tag invalidates every transcript and every activation cert in flight,
|
|
// which is the correct behaviour when a breaking change ships.
|
|
//
|
|
// Suite contract:
|
|
//
|
|
// Hc(t) → 32 bytes, challenge sampler digest
|
|
// Hu(t, L) → L bytes, XOF stream (e.g. seeding a Gaussian)
|
|
// TranscriptHash(...) → 32 bytes, length-prefixed binding of N parts
|
|
// PRF(K, M, L) → L bytes
|
|
// MAC(K, M, L) → L bytes
|
|
// DerivePairwise(...) → outLen bytes, a single-call KDF over a pair
|
|
//
|
|
// Two suites with the same ID always produce the same bytes for the
|
|
// same inputs (KAT determinism). Two suites with different IDs MUST
|
|
// produce different bytes (cross-profile collision avoidance).
|
|
package hash
|
|
|
|
// HashSuite is the canonical hashing surface every Lens reshare,
|
|
// activation, and pairwise routine uses. Implementations are
|
|
// stateless, goroutine-safe, and deterministic.
|
|
type HashSuite interface {
|
|
// ID returns the profile identifier, e.g. "Lens-SHA3" or
|
|
// "Lens-BLAKE3". Bound into transcripts so two profiles can
|
|
// never collide on the byte level.
|
|
ID() string
|
|
|
|
// Hc returns the 32-byte challenge digest of `transcript`. Used
|
|
// where the protocol needs a single fixed-length sample (e.g. a
|
|
// challenge seed before expansion to a binding factor).
|
|
Hc(transcript []byte) []byte
|
|
|
|
// Hu returns `outLen` XOF bytes from `transcript`. Used where the
|
|
// protocol needs a streaming source — Gaussian sampler seeds,
|
|
// PRNG initialisers, etc.
|
|
Hu(transcript []byte, outLen int) []byte
|
|
|
|
// TranscriptHash returns a 32-byte binding over the ordered list
|
|
// of byte-strings `parts`, with unambiguous length prefixing
|
|
// (TupleHash for SHA3; length-prefixed BLAKE3 for the BLAKE3
|
|
// suite).
|
|
TranscriptHash(parts ...[]byte) [32]byte
|
|
|
|
// PRF computes a pseudorandom output of `outLen` bytes keyed by
|
|
// `key` over message `msg`. Domain separation is built in.
|
|
PRF(key, msg []byte, outLen int) []byte
|
|
|
|
// MAC computes a message-authentication code of `outLen` bytes
|
|
// keyed by `key` over message `msg`. Distinct from PRF by
|
|
// customization tag.
|
|
MAC(key, msg []byte, outLen int) []byte
|
|
|
|
// DerivePairwise derives `outLen` bytes for a pairwise PRF/MAC
|
|
// from `kex` (the authenticated-KEX shared secret) under the
|
|
// canonical (chain_id, group_id, era_id, generation, i, j)
|
|
// labels. The (i, j) pair is canonicalized internally to the
|
|
// smaller-id-first ordering.
|
|
DerivePairwise(
|
|
kex []byte,
|
|
chainID, groupID []byte,
|
|
eraID, generation uint64,
|
|
i, j int,
|
|
outLen int,
|
|
) []byte
|
|
}
|
|
|
|
// Default returns the production hash suite: Lens-SHA3.
|
|
func Default() HashSuite { return defaultSuite }
|
|
|
|
// DefaultID is the string ID of the production suite.
|
|
const DefaultID = "Lens-SHA3"
|
|
|
|
// LegacyBLAKE3ID is the string ID of the non-normative legacy suite.
|
|
const LegacyBLAKE3ID = "Lens-BLAKE3"
|
|
|
|
// resolve picks the suite to use for a given call.
|
|
func resolve(s HashSuite) HashSuite {
|
|
if s == nil {
|
|
return defaultSuite
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Resolve returns `s` if non-nil, otherwise the production default.
|
|
func Resolve(s HashSuite) HashSuite { return resolve(s) }
|
|
|
|
// defaultSuite is the package-level singleton for the production profile.
|
|
var defaultSuite HashSuite = NewLensSHA3()
|