Files
magnetar/ref/go/pkg/thbs/tree.go
T
zeekay d3b45ffa78 v0.4.2: TRUE threshold HBS (pkg/thbs) + public-BFT-safe aggregate-sigs
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).
2026-05-21 17:34:41 -07:00

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
}