mirror of
https://github.com/lux-cpp/consensus.git
synced 2026-10-30 18:14:55 +00:00
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]>
121 lines
5.3 KiB
C
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
|