mirror of
https://github.com/luxfi/magnetar.git
synced 2026-09-24 00:25:23 +00:00
Introduce TWO new signing modes plus a clarifying rename of the legacy
v0.1 path.
1. pkg/thbs/ — TRUE threshold hash-based signatures in the McGrew et al.
sense (IACR ePrint 2019/793 / IRTF draft-mcgrew-hash-sigs line). For
HBS schemes the signature reveals SELECTED secret elements (WOTS+
chain heads selected by the message-digest base-w digits + FORS
secret leaves selected by the FORS index digest). Threshold signing
here Shamir-shares each secret element across the committee; for
each message parties release shares ONLY for the SELECTED elements;
the combiner Lagrange-reconstructs just those elements; the
verifier sees an ordinary HBS-style signature.
Subpackage layout:
- thbs.go — types per the requested API shape (DKGConfig,
PublicKey, PrivateShare, PartialSignature,
FinalSignature, Evidence, EquivocationError).
- dealer.go — v1 dealer-backed DKG. The dealer Shamir-shares each
secret element across the committee via per-byte
GF(257); the dealer seed is zeroised before return.
v2 will replace with public DKG.
- wots.go — WOTS+ (Winternitz w=16, FIPS 205-style base-w digit
+ checksum decomposition; cSHAKE-256 hash chains).
- fors.go — FORS (k subtrees, height a, per-leaf binary Merkle).
- tree.go — public Merkle tree over WOTS+ leaf-roots.
- slot.go — anti-equivocation slot guard. Same-slot-different-
digest emits Evidence{party, slot, digest_a/b,
share_a/b} for the slashing layer.
- sign.go — SignShare + Aggregate + Verify.
- shamir.go — byte-wise Shamir over GF(257); elements are shared
directly, not seeds.
- hash.go — cSHAKE-256 with the "Magnetar-THBS" function-name
and per-tag domain separation.
24 unit tests pin every invariant: no-seed-exposure,
selected-elements-only for both WOTS+ and FORS, t-of-n threshold,
anti-equivocation, cross-slot/cross-message rejection, tamper
detection.
Honest v1 scope (documented in THBS-SPEC.md):
- Setup is DEALER-BACKED. v2 replaces with public DKG.
- Helper data shipped alongside the public key (McGrew et al.
permit this).
- Verifier is a CUSTOM HBS verifier; v3 will produce FIPS 205-byte-
identical output.
Hard invariant enforced by the package shape:
OK: reconstructElement(slot, elementID, shares)
Forbidden: ReconstructSeed, ReconstructPrivateKey,
ExpandPrivateKey, DeriveAllFutureElements
The only Reconstruct symbol in thbs/*.go is the unexported
reconstructElement in shamir.go.
2. pkg/magnetar/aggregate.go — public-BFT-safe N-of-N collected
signatures. Each validator holds its OWN SLH-DSA keypair (no DKG,
no shared seed). Primitives: GenerateValidatorKey, SignBundle,
VerifyBundle, AggregateSignatures, VerifyAggregated. 10 tests.
3. pkg/magnetar/combine.go — Combine renamed to
CombineWithSeedReconstruction throughout the package + callers
(e2e_test, threshold_test, n1_byte_equality_test, fuzz_test,
genkat, ct/dudect bridge) to make the TEE-only trust caveat
explicit at the API surface. KAT byte-equality preserved: the
function body is unchanged.
Refs: McGrew, Fluhrer, Gazdag, Kampanakis, Morton, Westerbaan,
"Coalition and Threshold Hash-Based Signatures" (IACR ePrint 2019/793);
Bonte, Smart, Tan, "Threshold SPHINCS+", PKC 2024 (the negative result
informing our v1-ships-a-custom-HBS-verifier scope choice).
74 lines
2.2 KiB
Go
74 lines
2.2 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package thbs
|
|
|
|
// tree.go — top-level Merkle tree over WOTS+ leaf-roots.
|
|
//
|
|
// This is the analog of the XMSS hypertree in FIPS 205 SLH-DSA. v1
|
|
// uses a single tree of height Height (1024 leaves); v3 will stack
|
|
// trees XMSS_MT-style. There are NO secrets in this file: the tree
|
|
// is built over PUBLIC WOTS+ leaf roots (the per-slot commitments
|
|
// to the WOTSChains public chain endpoints).
|
|
|
|
// treeBuild constructs the top Merkle tree over leafRoots. Returns
|
|
// (root, levels). levels[0] = leafRoots, levels[Height] = [root].
|
|
func treeBuild(params HBSParams, leafRoots [][]byte) ([]byte, [][][]byte) {
|
|
h := params.Height
|
|
levels := make([][][]byte, h+1)
|
|
levels[0] = leafRoots
|
|
for l := 1; l <= h; l++ {
|
|
prev := levels[l-1]
|
|
next := make([][]byte, len(prev)/2)
|
|
for i := 0; i < len(next); i++ {
|
|
next[i] = treeNode(params, prev[2*i], prev[2*i+1], l, i)
|
|
}
|
|
levels[l] = next
|
|
}
|
|
return levels[h][0], levels
|
|
}
|
|
|
|
// treeNode hashes two child nodes into a parent. Level and position
|
|
// bound prevents inter-tree collisions.
|
|
func treeNode(params HBSParams, left, right []byte, level, pos int) []byte {
|
|
bind := []byte{
|
|
byte(level),
|
|
byte(pos >> 24), byte(pos >> 16), byte(pos >> 8), byte(pos),
|
|
}
|
|
return hashN(params.N, tagTreeNode, left, right, bind)
|
|
}
|
|
|
|
// treeAuthPath extracts the authentication path for leaf `slot` from a
|
|
// built tree. Returns Height entries top-down.
|
|
func treeAuthPath(levels [][][]byte, slot uint32, height int) [][]byte {
|
|
path := make([][]byte, height)
|
|
idx := slot
|
|
for l := 0; l < height; l++ {
|
|
sibling := idx ^ 1
|
|
path[l] = append([]byte{}, levels[l][sibling]...)
|
|
idx >>= 1
|
|
}
|
|
return path
|
|
}
|
|
|
|
// treeVerifyAuth reconstructs the top-tree root given a leaf root, the
|
|
// slot index, and the authentication path.
|
|
func treeVerifyAuth(params HBSParams, leafRoot []byte, slot uint32, authPath [][]byte) []byte {
|
|
cur := append([]byte{}, leafRoot...)
|
|
idx := slot
|
|
for l := 0; l < params.Height; l++ {
|
|
sibling := authPath[l]
|
|
var left, right []byte
|
|
if idx&1 == 0 {
|
|
left = cur
|
|
right = sibling
|
|
} else {
|
|
left = sibling
|
|
right = cur
|
|
}
|
|
cur = treeNode(params, left, right, l+1, int(idx>>1))
|
|
idx >>= 1
|
|
}
|
|
return cur
|
|
}
|