Files
zeekayandHanzo Dev 440958b215 converge the C++ consensus: absorb the quasar witness, drop the "2"
This is not version 2 of anything. It is the same 1.x Lux consensus written in
C++, and the suffix said otherwise in 182 places. Rename the whole surface —
namespace lux::consensus2 -> lux::consensus, include/lux/consensus2/ ->
include/lux/consensus/, CONSENSUS2_* -> CONSENSUS_* — so the name states what
the thing is rather than which attempt it was.

Absorb the half of luxcpp/consensus that was real. That repo held two unrelated
things under one tagline. The witness surface is genuine and unique: it is the
only C ABI in either tree (the gate has none), it aggregates threshold shares on
Go's (u32_be index || 96-byte G2) record form where the gate's aggregate_sigs
takes a flat concatenation with no index, and lux-private/multichain-cpp already
links it by name and fails closed without it. It arrives verbatim — headers,
bodies, both tests, all five Go fixtures — with the tests renamed onto the
one-list-one-loop convention. Their fixture fallback is __FILE__/../../testdata,
so moving tests/ to test/ makes the corpus resolve at this repo's root with no
argument.

Quasar is a SIBLING of the gate, not a layer of it. No gate source calls
lux::quasar, so linking them would fabricate a dependency neither has; they share
only the from-source blst and the NUL DST. The namespace stays lux::quasar and
the library keeps the name luxconsensus_quasar — that pair is a published
contract, not an implementation detail.

The other half of that repo does not come across. Its lux::consensus header is a
count-only toy with no cryptography, an XOR fold where a hash belongs, and a
k==1 config — the exact shape toy_stub_killer_test exists to reject. It is also
a hard blocker: its `enum class Decision` and wave.hpp's collide outright once
the rename lands ("multiple definition of 'enum class lux::consensus::Decision'").
Its chain.cpp has never compiled in any commit, its tests and examples were never
added to any build, and its MLX paths sit behind a HAS_MLX that nothing defines.
Nothing is lost: that tree is preserved whole at luxcpp/consensus-legacy.

The oracle's relative replace paths are re-aimed at this repo's home under
luxcpp, four levels up rather than three. The short form is worse than broken
there: ~/work/luxcpp/crypto exists and is the C++ crypto, so a stale path
resolves to the wrong repo instead of to nothing.

13/13 tests pass: the 11 gate tests plus both witness tests.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-29 09:00:38 -07:00

121 lines
5.3 KiB
C

/* Copyright (c) 2026 Lux Industries Inc.
* SPDX-License-Identifier: BSD-3-Clause-Eco
*
* Quasar witness BLS verification — C ABI for the consensus hot path.
*
* One and only one C entry point per verb. Go's cgo bridge binds these
* three symbols and nothing else; the C++ surface in `quasar.hpp` is the
* implementation, not the contract.
*
* Wire formats are byte-identical to luxfi/consensus/protocol/quasar:
* - Group public key: 48-byte Zcash-compressed BLS12-381 G1
* - Aggregated signature: 96-byte Zcash-compressed BLS12-381 G2
* - Domain-separation tag: BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_
* (matches Go's `bls.Verify` via cloudflare/circl + blst with
* `dstSignature = ..._NUL_`). NOT the POP_ DST used by the
* cevm precompile path — different protocol, different DST.
*
* Threshold-share input layout for `lux_quasar_witness_aggregate`:
* N records of `(index : u32_be) || (sig : 96 bytes)` concatenated.
* Mirrors the wire form of luxfi/crypto/threshold/bls.SignatureShare.
*
* All entry points are allocation-free on the success path. Subgroup
* checks on both points are enforced (cofactor-cleared input is
* required for sound verification per RFC 9380 §4.1).
*
* Hot path: `lux_quasar_witness_verify` is the per-round verifier;
* aggregate calls happen once per round when a fresh threshold cert is
* assembled. Both are CPU pairings today; a future GPU backend can be
* swapped in behind the same C ABI without recompiling Go consumers.
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Status enum. Negative values are reserved for future structural
* errors (size mismatch, NULL pointer). Verify-mismatch and signature-
* decode failure are positive so cgo callers can treat all errors as a
* single non-OK branch and still log the specific cause. */
typedef int32_t lux_quasar_status;
#define LUX_QUASAR_OK ((lux_quasar_status) 0)
#define LUX_QUASAR_ERR_INVALID ((lux_quasar_status) 1) /* NULL ptr / wrong length */
#define LUX_QUASAR_ERR_SIG ((lux_quasar_status) 2) /* sig or pk decode failed */
#define LUX_QUASAR_ERR_VERIFY ((lux_quasar_status) 3) /* pairing rejected */
#define LUX_QUASAR_ABI_VERSION 1u
/* Exact wire widths. Mirror luxfi/crypto/bls.{PublicKey,Signature}Len. */
#define LUX_QUASAR_PK_LEN 48u
#define LUX_QUASAR_SIG_LEN 96u
/* Verify a Quasar witness: returns LUX_QUASAR_OK iff the aggregated
* threshold signature `sig` verifies under `group_key` over `msg`.
*
* - group_key : 48-byte compressed G1 point (Zcash format)
* - msg : arbitrary-length message (may be NULL iff msg_len == 0)
* - sig : 96-byte compressed G2 point (Zcash format)
*
* Returns:
* LUX_QUASAR_OK — signature verifies
* LUX_QUASAR_ERR_INVALID — NULL pointer or length mismatch
* LUX_QUASAR_ERR_SIG — pk or sig is not a valid curve point
* (or fails subgroup check)
* LUX_QUASAR_ERR_VERIFY — pairing equation does not hold
*
* Allocation-free in steady state. Subgroup checks on both points are
* always performed (matches Go's `sig.SigValidate(false)` +
* `pk.Validate()` semantics).
*/
lux_quasar_status
lux_quasar_witness_verify(const uint8_t* group_key, size_t group_key_len,
const uint8_t* msg, size_t msg_len,
const uint8_t* sig, size_t sig_len);
/* Aggregate `shares_n` threshold-signature shares into a single 96-byte
* BLS aggregated signature.
*
* - group_key : 48-byte compressed G1 (currently unused for aggregation
* but preserved in the ABI so a future Lagrange-weighted
* aggregator can bind output to the group); pass the
* canonical group key to keep call sites stable.
* - shares : concatenation of `shares_n` records, each
* (index : u32 big-endian) || (sig : 96 bytes) — total
* size = shares_n * 100 bytes. Matches the wire format
* of luxfi/crypto/threshold/bls.SignatureShare.Bytes.
* - out_sig : 96-byte output buffer for the aggregated signature.
*
* Returns:
* LUX_QUASAR_OK — aggregation succeeded
* LUX_QUASAR_ERR_INVALID — NULL pointer / zero shares / wrong length
* LUX_QUASAR_ERR_SIG — at least one share is not a valid G2
* curve point (or fails subgroup check)
*
* Byte-identical to Go's `bls.AggregateSignatures(sigs).Compress()`
* when shares' Lagrange coefficients reduce to identity (the n-of-n
* case the Go aggregator takes); for t-of-n weighted aggregation the
* caller must apply Lagrange scaling on its side before invoking this
* function (the threshold/bls Aggregator does so in Go).
*/
lux_quasar_status
lux_quasar_witness_aggregate(const uint8_t* group_key, size_t group_key_len,
const uint8_t* shares, size_t shares_n,
uint8_t out_sig[96]);
/* Human-readable description of a status code. Lifetime: process. */
const char* lux_quasar_status_str(lux_quasar_status s);
/* Returns LUX_QUASAR_ABI_VERSION (compile-time constant). Cgo callers
* use this to detect ABI skew at load time. */
uint32_t lux_quasar_abi_version(void);
#ifdef __cplusplus
} /* extern "C" */
#endif