mirror of
https://github.com/lux-apps/papers.git
synced 2026-10-30 18:15:21 +00:00
Resolves high-severity review findings: anachronistic dates aligned to citation floors; arithmetic corrected to match formulas; internal contradictions collapsed to the body-supported value; LP self-references aligned to filenames; liquidity-surface repo paths scrubbed; AI-slop trimmed. PDFs rebuilt separately.
390 lines
17 KiB
TeX
390 lines
17 KiB
TeX
\section{Construction}
|
|
\label{sec:construction}
|
|
|
|
This section specifies the per-validator standalone construction
|
|
byte-for-byte. The pseudocode is keyed to the Go canonical at
|
|
\texttt{ref/go/pkg/magnetar/standalone.go} and
|
|
\texttt{ref/go/pkg/magnetar/wire.go} at tag \texttt{v1.0.0}. The
|
|
THBS-SE permissionless threshold companion is specified in
|
|
\S\ref{sec:thbsse}.
|
|
|
|
\subsection{Hash-domain separation}
|
|
\label{sec:construction:domains}
|
|
|
|
Every cryptographic hash invocation in Magnetar is cSHAKE256
|
|
\cite{sp800185} with function-name string \texttt{"Magnetar"} and a
|
|
per-purpose customisation string. The per-validator standalone path
|
|
uses only the wire-codec canonical magic constants (MAGS / MAGG)
|
|
plus the standard FIPS 205 dispatch; THBS-SE adds four
|
|
per-purpose tags listed in \S\ref{sec:thbsse:slotbind}.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Magnetar v1.0 cSHAKE256 domain separation. Every
|
|
secret-bearing hash invocation is bound to exactly one tag;
|
|
cross-tag replay reduces to cSHAKE256 collision resistance.
|
|
Tags marked \emph{(thresh)} appear only in the threshold-form
|
|
appendices.}
|
|
\label{tab:magnetar-tags}
|
|
\begin{tabular}{lp{8.5cm}}
|
|
\toprule
|
|
Tag & Purpose \\
|
|
\midrule
|
|
\texttt{MAGS} (magic 0x4D414753) & MAGS wire envelope for FIPS 205 signature payload (standalone primary) \\
|
|
\texttt{MAGG} (magic 0x4D414747) & MAGG wire envelope for FIPS 205 public-key payload (standalone primary) \\
|
|
\texttt{MAGNETAR-DKG-COMMIT-V1} & committee-root digest (sorted committee binding) \emph{(thresh)} \\
|
|
\texttt{MAGNETAR-DKG-TRANSCRIPT-V1} & DKG transcript digest (Round-2 equivocation check) \emph{(thresh)} \\
|
|
\texttt{MAGNETAR-SIGN-R1-V1} & threshold-sign Round-1 commit $D_i$ \emph{(thresh)} \\
|
|
\texttt{MAGNETAR-SIGN-MASK-V1} & per-attempt mask $r_i$ derivation \emph{(thresh)} \\
|
|
\texttt{MAGNETAR-SEED-SHARE-V1} & Shamir coefficient stream + final seed mix \emph{(thresh)} \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
The protocol-wide domain separation context is the byte string
|
|
\texttt{"lux-magnetar-v0.5"} (\texttt{tagDomainSep}). It is bound
|
|
into the wire-codec magic constants and into every threshold-form
|
|
transcript binding so that a separate Magnetar version (or a
|
|
different Lux PQ primitive that happened to reuse the same hash
|
|
function) cannot collide.
|
|
|
|
\subsection{Per-validator keypair generation}
|
|
\label{sec:construction:keygen}
|
|
|
|
Each validator $i$ runs \texttt{PerValidatorKeypair} once at
|
|
validator-set onboarding, persists $(sk_i, pk_i)$ to local
|
|
HSM-backed storage, and registers $pk_i$ in the consensus layer's
|
|
validator-set commitment.
|
|
|
|
\paragraph{Algorithm (\texttt{standalone.go:197-199},
|
|
\texttt{keygen.go}).}
|
|
\begin{enumerate}[leftmargin=2em,nosep]
|
|
\item Pull \texttt{params} for the target \texttt{Mode}
|
|
(SHAKE-192s, SHAKE-192f, or SHAKE-256s).
|
|
\item Source $4n$ bytes of entropy from \texttt{rng} (where $n$ is
|
|
the FIPS 205 ``$n$'' parameter: 24 for 192, 32 for 256).
|
|
If \texttt{rng=nil}, source from \texttt{crypto/rand}.
|
|
\item Pass the entropy buffer to
|
|
\texttt{circl.slhdsa.Scheme(params.ID).DeriveKey}.
|
|
\item Output $(sk_i, pk_i)$ in the FIPS 205 wire form: $sk_i =
|
|
(\mathit{SK.seed}, \mathit{SK.prf}, \mathit{PK.seed},
|
|
\mathit{PK.root})$ of length $4n$; $pk_i = (\mathit{PK.seed},
|
|
\mathit{PK.root})$ of length $2n$.
|
|
\end{enumerate}
|
|
|
|
\paragraph{Determinism.} If \texttt{rng} is a deterministic reader
|
|
(e.g., a \texttt{detReader} seeded by the canonical test fixture),
|
|
the output keypair is byte-identical across calls. This is the
|
|
property KAT regeneration relies on.
|
|
|
|
\paragraph{Independence.} No shared state. No correlation between
|
|
$(sk_i, pk_i)$ and $(sk_j, pk_j)$ for $i \ne j$. Each keypair is
|
|
generated on its own host with its own RNG.
|
|
|
|
\subsection{Per-validator signing primitive}
|
|
\label{sec:construction:sign}
|
|
|
|
\texttt{ValidatorSign} is the canonical signing primitive for the
|
|
public-BFT path. It is a thin wrapper over the FIPS 205 reference,
|
|
exposed with a public-BFT-explicit signature.
|
|
|
|
\paragraph{Algorithm (\texttt{standalone.go:231-244}, \texttt{sign.go}).}
|
|
\begin{enumerate}[leftmargin=2em,nosep]
|
|
\item Verify $sk_i$ is non-nil and the embedded mode matches an
|
|
installed \texttt{Params}.
|
|
\item Pull the \texttt{slhdsa.Scheme} for the embedded mode.
|
|
\item If \texttt{rng=nil}, invoke
|
|
\texttt{Scheme.SignDeterministic(sk\_i, msg, ctx=nil)}.
|
|
Otherwise invoke
|
|
\texttt{Scheme.SignRandomized(sk\_i, rng, msg, ctx=nil)}.
|
|
\item Return the raw FIPS 205 signature bytes
|
|
(\texttt{params.SignatureSize}: $16{,}224$, $35{,}664$, or
|
|
$29{,}792$ depending on mode).
|
|
\end{enumerate}
|
|
|
|
\paragraph{Context binding.} The \texttt{ctx} parameter is
|
|
intentionally omitted at the \texttt{ValidatorSign} surface: the
|
|
consensus layer binds chain-id / block-height / epoch into the
|
|
message $\mu$ upstream (the QuasarCert transcript-hash pattern).
|
|
The \texttt{Sign} entry point in \texttt{sign.go} retains the
|
|
\texttt{ctx} parameter for callers that need explicit context
|
|
binding; the \texttt{ValidatorSign} wrapper passes
|
|
\texttt{ctx=nil}.
|
|
|
|
\paragraph{Determinism.} With \texttt{rng=nil}, $\sigma_i$ is byte-
|
|
identical to what a single-party FIPS 205 signer would emit on
|
|
$(sk_i, \mu, \mathit{ctx}=\varepsilon)$. This is the load-bearing
|
|
property pinned by
|
|
$\textsc{TestMagnetar\_Wire\_FIPS205Verifiable}$ at
|
|
\texttt{wire\_test.go:166-220} (cf. \S\ref{sec:security:byte-equality}).
|
|
|
|
\subsection{Aggregate certificate construction}
|
|
\label{sec:construction:cert}
|
|
|
|
The consensus layer collects validator signatures into a
|
|
\texttt{ValidatorAggregateCert} for transport over the wire. The
|
|
cert is intentionally a parallel-slices shape (not a nested per-
|
|
signer record list) because it maps directly to the Z-Chain Groth16
|
|
rollup witness layout.
|
|
|
|
\paragraph{Wire layout
|
|
(\texttt{standalone.go:415-432}, \texttt{wire.go}).}
|
|
\begin{align*}
|
|
\mathit{ValidatorAggregateCert} \;=\; &
|
|
\mathit{Mode}_1 \,\Vert\, N_{\mathit{BE},4} \,\Vert\,
|
|
\mathit{Signers}[0]_{32} \,\Vert\, \cdots \,\Vert\,
|
|
\mathit{Signers}[N-1]_{32} \\
|
|
& \,\Vert\, \mathit{PubKeys}[0]_{|pk|} \,\Vert\, \cdots \,\Vert\,
|
|
\mathit{PubKeys}[N-1]_{|pk|} \\
|
|
& \,\Vert\, \mathit{Sigs}[0]_{|\sigma|} \,\Vert\, \cdots \,\Vert\,
|
|
\mathit{Sigs}[N-1]_{|\sigma|}.
|
|
\end{align*}
|
|
|
|
\paragraph{Algorithm \texttt{BuildAggregateCert}
|
|
(\texttt{standalone.go:443-478}).}
|
|
\begin{enumerate}[leftmargin=2em,nosep]
|
|
\item Validate \texttt{params} matches one of the canonical
|
|
parameter tables.
|
|
\item Validate the three parallel slices
|
|
$(\mathit{signers}, \mathit{pubKeys}, \mathit{sigs})$ have
|
|
the same length $N \ge 1$.
|
|
\item For each $i \in [0, N)$, validate $|pubKeys[i]| =
|
|
params.PublicKeySize$ and $|sigs[i]| = params.SignatureSize$.
|
|
\item Defensively copy each $pubKeys[i]$ and $sigs[i]$ (the
|
|
caller's slices may be aliased; the cert is wire-stable and
|
|
must not move under the caller).
|
|
\item Return the \texttt{ValidatorAggregateCert} record.
|
|
\end{enumerate}
|
|
|
|
\paragraph{Independent verifiability.} Each $\sigma_i$ in the cert
|
|
verifies independently against $pk_i$; there is no cross-signer
|
|
algebraic coupling. A relying party can verify any subset of the
|
|
cert (e.g., for incremental quorum-detection) without sharing any
|
|
secret state with other relying parties.
|
|
|
|
\subsection{Aggregate certificate verification}
|
|
\label{sec:construction:verifycert}
|
|
|
|
\texttt{VerifyAggregateCert} is the consumer-facing verification
|
|
primitive. It enforces registry binding (dropping unknown signers
|
|
and pubkey mismatches), dedupes repeated signers, runs the parallel
|
|
batch verify, and returns the count of valid signers. The quorum
|
|
decision lives at the consumer.
|
|
|
|
\paragraph{Algorithm (\texttt{standalone.go:519-613}).}
|
|
\begin{enumerate}[leftmargin=2em,nosep]
|
|
\item Validate \texttt{cert} is non-nil and the embedded
|
|
\texttt{Mode} matches a canonical parameter set.
|
|
\item Validate $N \ge 1$ and the parallel slices have matching
|
|
lengths.
|
|
\item For each $i \in [0, N)$, validate $|cert.PubKeys[i]| =
|
|
params.PublicKeySize$ and $|cert.Sigs[i]| =
|
|
params.SignatureSize$.
|
|
\item \emph{Phase 1 (entry classification).} For each $i$, dedupe
|
|
against \texttt{seen}, look up
|
|
$registered = knownValidators[cert.Signers[i]]$; mark the
|
|
entry verifiable only if (a) the lookup succeeds, (b)
|
|
$|registered| = params.PublicKeySize$, and (c)
|
|
$\textsc{ctEqualBytes}(cert.PubKeys[i], registered)$. Both
|
|
cases (a)/(b)/(c) failing are counted as INVALID, not
|
|
fatal.
|
|
\item \emph{Phase 2 (parallel batch verify).} Collect the
|
|
verifiable entries into flat slices and invoke
|
|
\texttt{ValidatorBatchVerify}, which dispatches serial or
|
|
goroutine-parallel based on the
|
|
\texttt{verifyAggregatedConcurrentThreshold} constant
|
|
(currently $\sim 16$).
|
|
\item \emph{Phase 3 (tally).} Count verifiable + valid entries.
|
|
Return the count.
|
|
\end{enumerate}
|
|
|
|
\paragraph{Defense-in-depth choices.}
|
|
\begin{enumerate}[leftmargin=2em,nosep]
|
|
\item \textbf{Unknown signers counted as INVALID, not fatal.} A
|
|
cert carrying a signature from a stale or unknown validator
|
|
does not crash the verifier; the verifier reports the
|
|
count, and the quorum policy at the consumer decides
|
|
whether to accept. This is the right discipline when certs
|
|
may be Z-Chain Groth16-rolled-up over a stale validator
|
|
registry: the verifier must not abort the entire batch on
|
|
one stale entry.
|
|
\item \textbf{Pubkey mismatches counted as INVALID, not fatal.}
|
|
Same rationale. An attacker who slips a SignedBundle from
|
|
an unknown validator into the cert does not gain a count
|
|
but does not crash the verifier.
|
|
\item \textbf{Duplicate signers deduped first-wins.} The first
|
|
occurrence wins; subsequent occurrences are silently
|
|
dropped (not counted twice, not counted as invalid). The
|
|
consensus layer can collect proof of double-signing
|
|
separately via per-signer \texttt{Sigs} comparison.
|
|
\item \textbf{Constant-time pubkey comparison.}
|
|
\texttt{ctEqualBytes} short-circuits at the wire-length
|
|
validation and otherwise runs a constant-time
|
|
byte-by-byte XOR-then-OR reduction. No secret-dependent
|
|
timing.
|
|
\end{enumerate}
|
|
|
|
\subsection{Canonical wire codec (MAGS / MAGG)}
|
|
\label{sec:construction:wire}
|
|
|
|
The wire codec at \texttt{wire.go} provides byte-stable framing for
|
|
individual signatures (MAGS) and group keys (MAGG), with a stateless
|
|
verifier dispatcher (\texttt{VerifyBytes}) for consensus-layer
|
|
consumption.
|
|
|
|
\paragraph{MAGS signature envelope.}
|
|
\begin{align*}
|
|
\mathit{MAGS\_frame} \;=\; & \mathtt{0x4D4147} \,\Vert\,
|
|
\mathtt{0x53} \,\Vert\, \mathit{version}_{\mathit{BE},2} \,\Vert\,
|
|
\mathit{mode}_1 \,\Vert\, \mathit{payloadLen}_{\mathit{BE},4} \\
|
|
& \,\Vert\, \mathit{sigPayload}_{params.SignatureSize}.
|
|
\end{align*}
|
|
Total length: $11 + params.SignatureSize$ bytes. The 11-byte fixed
|
|
header carries the magic ASCII string ``MAGS'', the version
|
|
(currently 1), the FIPS 205 mode byte, and the big-endian payload
|
|
length. The payload is the raw FIPS 205 signature bytes verbatim.
|
|
|
|
\paragraph{MAGG group-key envelope.}
|
|
\begin{align*}
|
|
\mathit{MAGG\_frame} \;=\; & \mathtt{0x4D4147} \,\Vert\,
|
|
\mathtt{0x47} \,\Vert\, \mathit{version}_{\mathit{BE},2} \,\Vert\,
|
|
\mathit{mode}_1 \,\Vert\, \mathit{payloadLen}_{\mathit{BE},4} \\
|
|
& \,\Vert\, \mathit{pkPayload}_{params.PublicKeySize}.
|
|
\end{align*}
|
|
Total length: $11 + params.PublicKeySize$ bytes. Same structure as
|
|
MAGS with a distinct magic suffix byte (``G'' vs ``S'').
|
|
|
|
\paragraph{Stateless \texttt{VerifyBytes} dispatcher.}
|
|
\begin{enumerate}[leftmargin=2em,nosep]
|
|
\item Validate $len(groupKeyWire) \ge 11$ and the magic matches
|
|
\texttt{MAGG}.
|
|
\item Validate $len(sigWire) \ge 11$ and the magic matches
|
|
\texttt{MAGS}.
|
|
\item Decode the version, mode, and declared payload length from
|
|
each frame. Validate
|
|
$len(groupKeyWire) = 11 + declared_{gk}$ and
|
|
$len(sigWire) = 11 + declared_{sig}$.
|
|
\item Validate both frames target the same mode.
|
|
\item Strip the 11-byte header from each, recovering the raw
|
|
FIPS 205 $pk_{raw}$ and $\sigma_{raw}$ payloads.
|
|
\item Dispatch to
|
|
\texttt{circl.slhdsa.Scheme(mode).PublicKey.UnmarshalBinary(
|
|
$pk_{raw}$)} and then
|
|
\texttt{Scheme.Verify($pk$, msg, $\sigma_{raw}$, ctx=nil)}.
|
|
\item Return the Boolean from the FIPS 205 verifier.
|
|
\end{enumerate}
|
|
|
|
\paragraph{Byte-identity to FIPS 205.} The wire codec is purely
|
|
additive (an envelope prefix); the payload bytes are the raw FIPS
|
|
205 signature or public-key bytes verbatim. Stripping the 11-byte
|
|
header recovers exactly what \texttt{cloudflare/circl/sign/slhdsa}
|
|
emits on the same $(sk, \mu, \mathit{ctx})$ tuple. The
|
|
$\textsc{TestMagnetar\_Wire\_FIPS205Verifiable}$ regression pins
|
|
this property across all three SHAKE parameter sets.
|
|
|
|
\subsection{Verify (FIPS 205 passthrough)}
|
|
\label{sec:construction:verify}
|
|
|
|
The per-signature \texttt{Verify} entry point at \texttt{verify.go}
|
|
is a thin dispatch to
|
|
\texttt{circl.slhdsa.PublicKey.UnmarshalBinary($pk.Bytes$)} followed
|
|
by
|
|
\texttt{slhdsa.Verify($pk$, NewMessage($\mu$), $\sigma$, ctx)}. No
|
|
Magnetar-specific envelope or wrapper. This is the FIPS 205-byte-
|
|
identity property: the produced signatures live in the exact FIPS
|
|
205 signature space, and any FIPS 205 verifier accepts them
|
|
unmodified after the wire-codec envelope (if present) is stripped.
|
|
|
|
\subsection{Constant-time discipline}
|
|
\label{sec:construction:ct}
|
|
|
|
The per-validator standalone primitive's hot paths are:
|
|
\begin{itemize}[leftmargin=2em,nosep]
|
|
\item \texttt{ValidatorSign} $\to$ \texttt{Sign} $\to$
|
|
\texttt{circl.slhdsa.SignDeterministic} (constant-time per
|
|
circl design).
|
|
\item \texttt{ValidatorBatchVerify} $\to$
|
|
\texttt{circl.slhdsa.Verify} (constant-time per circl
|
|
design; verifier accepts only public inputs).
|
|
\item \texttt{VerifyAggregateCert} pubkey-equality comparison
|
|
via \texttt{ctEqualBytes} (constant-time XOR-then-OR
|
|
reduction).
|
|
\item Wire-codec header validation via fixed-length big-endian
|
|
decode and constant-time magic comparison.
|
|
\end{itemize}
|
|
None of the standalone-path operations branch on secret material:
|
|
$sk_i$ is consumed only by \texttt{circl.slhdsa.SignDeterministic}
|
|
which is constant-time by construction. There are no Shamir
|
|
operations, no Lagrange interpolation, and no aggregator-held
|
|
secrets in the standalone path. The THBS-SE construction
|
|
(\S\ref{sec:thbsse}) carries additional constant-time obligations
|
|
on the byte-wise Lagrange reconstruct and the commit re-derivation
|
|
documented in \S\ref{sec:security:thbsse-tcb}.
|
|
|
|
\subsection{Identifiable abort (consensus layer)}
|
|
\label{sec:construction:abort}
|
|
|
|
The per-validator standalone primitive has a clean abort posture:
|
|
each $\sigma_i$ is a single-party FIPS 205 signature, and
|
|
\texttt{VerifyAggregateCert} reports the per-signer outcome via
|
|
the bitmap from \texttt{ValidatorBatchVerify}. The consensus layer
|
|
collects evidence of misbehaviour:
|
|
\begin{itemize}[leftmargin=2em,nosep]
|
|
\item \textbf{Invalid signature.} The verifier bitmap reports
|
|
$\mathit{false}$ for the offending signer. The
|
|
$(\mathsf{NodeID}_i, pk_i, \sigma_i, \mu)$ quad is
|
|
sufficient on-chain evidence for slashing.
|
|
\item \textbf{Double-signing.} Two valid signatures from the
|
|
same $\mathsf{NodeID}_i$ on distinct messages on the same
|
|
slot constitute evidence; the consensus layer collects
|
|
$(\mathsf{NodeID}_i, pk_i, \sigma_{i,A}, \sigma_{i,B},
|
|
\mu_A, \mu_B)$ as the slashing record.
|
|
\item \textbf{Unregistered signer.} An entry whose
|
|
$\mathsf{NodeID}_i$ does not appear in
|
|
\texttt{knownValidators} is counted as INVALID; the
|
|
consensus layer may treat repeated unregistered-signer
|
|
entries as a peer misbehaviour signal at the gossip layer.
|
|
\item \textbf{Pubkey-registry mismatch.} An entry whose embedded
|
|
$pk_i$ does not byte-equal the registered pubkey for that
|
|
$\mathsf{NodeID}_i$ is counted as INVALID; same handling
|
|
as unregistered signer.
|
|
\end{itemize}
|
|
The identifiable-abort taxonomy of THBS-SE (slot-guard
|
|
equivocation, Round-2 commit-bind failure, wire-size violation,
|
|
slot-mismatch reveal) is documented in
|
|
\S\ref{sec:thbsse:slashing}; both equivocation and malformed-share
|
|
evidence are pure-function third-party-verifiable.
|
|
|
|
\subsection{Wire sizes}
|
|
\label{sec:construction:wire-sizes}
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Magnetar v1.0 per-validator standalone wire sizes.}
|
|
\label{tab:wire-sizes}
|
|
\begin{tabular}{lr}
|
|
\toprule
|
|
Object & Size (bytes) \\
|
|
\midrule
|
|
$\sigma_i$ FIPS 205 payload (SHAKE-192s) & 16\,224 \\
|
|
$\sigma_i$ FIPS 205 payload (SHAKE-192f) & 35\,664 \\
|
|
$\sigma_i$ FIPS 205 payload (SHAKE-256s) & 29\,792 \\
|
|
$pk_i$ FIPS 205 payload (SHAKE-192s / 192f) & 48 \\
|
|
$pk_i$ FIPS 205 payload (SHAKE-256s) & 64 \\
|
|
MAGS envelope header (fixed) & 11 \\
|
|
MAGG envelope header (fixed) & 11 \\
|
|
$\mathsf{NodeID}_i$ in cert & 32 \\
|
|
\midrule
|
|
\texttt{ValidatorAggregateCert} (SHAKE-192s, $K=21$) & $\sim 332$ KiB \\
|
|
\texttt{ValidatorAggregateCert} (SHAKE-192s, $K=21$) post Z-Chain Groth16 rollup & $\sim 192$ B \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
The signature wire format is \emph{exactly} the FIPS 205 wire format
|
|
plus the 11-byte MAGS envelope; there is no Magnetar-specific
|
|
suffix, no per-signer ZK proof, and no out-of-band binding. The
|
|
Z-Chain Groth16 rollup that compresses $N \times |\sigma|$ to a
|
|
constant-size certificate is specified separately at the lux-zchain
|
|
layer and consumes the parallel-slices cert shape verbatim.
|