Files
corona/keyera/keyera.go
T
zeekay 2cecc08c63 refactor(corona): delete the dkg2 package — superseded by luxfi/dkg vss
The Pedersen-VSS DKG share-dealing now runs entirely on github.com/luxfi/dkg
(proven byte-identical at cutover); corona's own copy is dead. Remove it and its
now-orphaned tooling, keeping only corona-specific code (Ringtail signing, β
rounding, Path (a) finalize — all in sign/, utils/, keyera/, untouched).

Deleted:
  - dkg2/ (the whole package: Round1/Round2/commits/complaint/GPU dispatch).
  - cmd/dkg2_oracle/ (the dkg2 KAT generator; the DKG KAT now lives in
    luxfi/dkg's own ring/vss vectors).
  - keyera/cutover_gate_test.go (the transitional dkg2-vs-vss equality gate; its
    job is done — permanent byte-stability is pinned by the golden KAT, and the
    PIN-4 convention assertion is preserved dkg2-free in pin4_convention_test.go).

Updated:
  - cmd/cross_runtime_oracle drops the dkg2 manifest leg (the luxcpp C++ dkg2
    cross-runtime gate migrates to luxfi/dkg separately — flagged, not silently
    broken).
  - keyera/reanchor doc comments + threshold bench comment point at luxfi/dkg.

go build ./... + go test ./... -count=1 green (13 ok / 0 fail), go vet + gofmt
clean. The golden byte-stability KAT + Bootstrap→sign→verify + dishonest-dealer
abort all still pass on the vss path.
2026-06-27 22:03:55 -07:00

587 lines
22 KiB
Go

// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package keyera is the lifecycle wrapper for a Corona group lineage.
//
// One KeyEra is opened by Bootstrap (legacy trusted dealer) or by
// BootstrapPedersen (public-BFT-safe, recommended). Subsequent
// validator-set rotations call Reshare, which preserves the GroupKey
// (A, bTilde) and rotates only the share distribution; no trusted
// dealer is needed for resharing. Reanchor opens a new era with a
// fresh GroupKey for security-event response (rare, governance-gated)
// and routes through Pedersen-DKG by default. ReanchorTrustedDealer
// is retained as an explicit opt-in alias for HSM/TEE ceremonies.
//
// The single source of truth for the lifecycle is
// `~/work/lux/pulsar/DESIGN.md`.
//
// Invariants (enforced loudly):
//
// BLS lane: each validator has its OWN keypair.
// ML-DSA lane: each validator has its OWN keypair.
// Corona lane: each validator has a SHARE of one group key.
//
// Within a key era:
//
// - The same hidden signing secret s is preserved across epochs.
// - The same public matrix A is preserved.
// - The same public key bTilde is preserved (the GroupKey is byte-
// identical across resharing).
// - The error e is NOT reshared. It was used only at Bootstrap to
// form the LWE public key; dealer state is erased.
// - Only the share distribution of s rotates per epoch.
//
// Across key eras (Reanchor): A, s, e, bTilde all change.
package keyera
import (
"crypto/rand"
"errors"
"fmt"
"io"
"math/big"
"github.com/luxfi/corona/hash"
"github.com/luxfi/corona/primitives"
"github.com/luxfi/corona/reshare"
"github.com/luxfi/corona/sign"
"github.com/luxfi/corona/threshold"
"github.com/luxfi/corona/utils"
"github.com/luxfi/lattice/v7/ring"
"github.com/luxfi/lattice/v7/utils/sampling"
"github.com/luxfi/lattice/v7/utils/structs"
)
// Errors returned by the package.
var (
ErrUninitialized = errors.New("keyera: era is uninitialized")
ErrInvalidThreshold = errors.New("keyera: threshold must satisfy 1 <= t <= n")
ErrEmptyValidators = errors.New("keyera: validator set is empty")
ErrMissingShare = errors.New("keyera: share missing for validator")
)
// CoronaKeyEraID is a monotonically increasing identifier for a key era.
// Bumped only on Reanchor (rare governance event). All resharings
// within an era keep the same era ID.
type CoronaKeyEraID uint64
// CoronaGroupID identifies one Corona group for grouped Quasar setups
// where validator sets are partitioned into smaller groups, each with
// its own GroupKey lineage. For the single-group case it is zero.
type CoronaGroupID uint64
// KeyEra is one Corona group lineage. The GroupKey (A, bTilde) is set at
// Bootstrap and persists across every Reshare within the era. State is
// the current epoch's share distribution; it rotates each Reshare.
//
// HashSuiteID pins the hash profile this era was opened under. It is
// recorded at Bootstrap and remains immutable through every Reshare in
// the era (per pulsar/proofs/hash-suite-separation.tex Remark on
// era-pinning). Reanchor MAY change the suite for the new era. The
// field is read-only after Bootstrap returns; Reshare propagates it
// without parameterisation.
type KeyEra struct {
EraID CoronaKeyEraID
GroupID CoronaGroupID
GroupKey *threshold.GroupKey
GenesisEpoch uint64
HashSuiteID string
State *EpochShareState
}
// EpochShareState is the per-epoch share distribution for a key era.
// Replaces the legacy "EpochKeys" naming — distinguishes "share
// rotation" from "key rotation".
//
// Three lineage fields, kept distinct (do not collapse — they mean
// different things):
//
// - KeyEraID: Corona group-key lineage. Bumps only at Reanchor (fresh
// GroupKey).
// - Generation: LSS resharing version within this key era. Bumps
// every Refresh / Reshare under the same GroupKey. Aligns with
// LSS's Generation field; managed by threshold/protocols/lss when
// this state is driven through the LSS-Corona adapter.
// - RollbackFrom: nonzero only when this state descends from a
// Rollback (= the prior Generation that was reverted from). Zero
// on ordinary forward transitions.
//
// In production, threshold/protocols/lss owns Generation/RollbackFrom
// updates. In the in-process keyera reference path (used for tests +
// KAT replay), keyera updates them itself.
type EpochShareState struct {
// Lineage (changes only at Reanchor — fresh GroupKey).
KeyEraID uint64
// HashSuiteID is the pinned hash profile for this share state.
// Mirrored from the parent KeyEra at Bootstrap and propagated
// without modification through every Reshare. NEVER set this
// field on a state that descends from a Reshare; use the parent
// era's HashSuiteID. Reanchor opens a NEW era with a fresh value.
HashSuiteID string
// LSS lifecycle.
Generation uint64
RollbackFrom uint64
// Per-epoch state (rotates every Refresh / Reshare).
Epoch uint64
Validators []string
Threshold int
Shares map[string]*threshold.KeyShare
}
// Bootstrap opens a new key era WITHOUT a trusted dealer.
//
// TRUST MODEL — PUBLIC-BFT-SAFE (default).
// This function routes the keygen ceremony through the shared
// github.com/luxfi/dkg Pedersen-VSS (vss, parameterized by
// ring.Ringtail()) + corona's Path (a) noise flooding so no single
// party ever holds the master secret s at any point in the ceremony.
// Every validator runs the vss Round 1 independently; the per-party
// noise contributions aggregate into a Corona-Sign-shaped public key
// `bTilde = Round_Xi(A·s + e”)` where each party adds one Gaussian
// `e_j'` slice under σ” = κ·σ_E·√n. Identifiable abort: a
// misbehaving sender is named in a signed luxfi/dkg blame complaint
// carrying re-checkable Pedersen evidence; the chain commits the abort
// transcript and stays at the previous epoch.
//
// On success returns (era, transcript, nil). The transcript is the
// public, byte-stable record of the ceremony; honest validators that
// observe the same cohort messages compute identical bytes. The
// consensus layer commits to `transcript.TranscriptHash` to ratify
// the era. On identifiable abort returns (nil, nil, err wrapping
// ErrBootstrapPedersenAbort); extract via ExtractAbortEvidence.
//
// LEGACY ALTERNATIVE: use BootstrapTrustedDealer for HSM/TEE
// ceremonies where the operator explicitly chooses a non-distributed
// trust root (publicly observable HSM-bound ceremony with commit-
// and-reveal entropy from genesis validators, dealer state zeroed
// before ceremony close). The dealer's host platform is in the trust
// boundary for the duration of the call. See DEPLOYMENT-RUNBOOK.md
// §Bootstrap-Trust for the trust-model trade-off documentation.
//
// Use crypto/rand.Reader for the kernel's randomness when no specific
// ceremony source is provided. Tests pass a deterministic source for
// KAT replay.
//
// Bootstrap pins the production HashSuite (Corona-SHA3). Use
// BootstrapWithSuite to open an era under the legacy Corona-BLAKE3
// profile (for cross-suite KAT replay only — NOT for production).
func Bootstrap(t int, validators []string, groupID CoronaGroupID, eraID CoronaKeyEraID, entropy io.Reader) (*KeyEra, *BootstrapTranscript, error) {
return BootstrapPedersen(hash.Default(), t, validators, groupID, eraID, entropy)
}
// BootstrapWithSuite is the suite-explicit form of Bootstrap. It
// routes through BootstrapPedersen (public-BFT-safe, no trusted
// dealer) with the supplied HashSuite; pass nil to use the
// production default (Corona-SHA3). See the Bootstrap docstring for
// the trust-model disclosure.
//
// LEGACY ALTERNATIVE: use BootstrapTrustedDealerWithSuite for HSM/TEE
// ceremony scenarios where a non-distributed trust root is acceptable
// by policy.
func BootstrapWithSuite(suite hash.HashSuite, t int, validators []string, groupID CoronaGroupID, eraID CoronaKeyEraID, entropy io.Reader) (*KeyEra, *BootstrapTranscript, error) {
return BootstrapPedersen(suite, t, validators, groupID, eraID, entropy)
}
// bootstrapTrustedDealerImpl is the historical trusted-dealer
// implementation, retained as the body of BootstrapTrustedDealer /
// BootstrapTrustedDealerWithSuite. Single source of truth so the
// public-facing aliases cannot drift from each other. NO production
// code path should call this directly; use BootstrapTrustedDealer
// instead so the name signals the trust-model decision at the call
// site.
func bootstrapTrustedDealerImpl(suite hash.HashSuite, t int, validators []string, groupID CoronaGroupID, eraID CoronaKeyEraID, entropy io.Reader) (*KeyEra, error) {
if len(validators) == 0 {
return nil, ErrEmptyValidators
}
n := len(validators)
if t < 1 || t > n {
return nil, fmt.Errorf("%w: t=%d n=%d", ErrInvalidThreshold, t, n)
}
if entropy == nil {
entropy = rand.Reader
}
suite = hash.Resolve(suite)
suiteID := suite.ID()
gk, sStd, err := genGroupKey(entropy)
if err != nil {
return nil, fmt.Errorf("keyera: bootstrap genGroupKey: %w", err)
}
r := gk.Params.R
skShares := primitives.ShamirSecretSharingGeneral(r, []ring.Poly(sStd), t, n)
for _, sh := range skShares {
utils.ConvertVectorToNTT(r, sh)
}
seeds, macKeysByParty := derivePairwiseMaterial(n, entropy)
lagrange := computeFullCommitteeLagrange(r, n)
state := &EpochShareState{
KeyEraID: uint64(eraID),
HashSuiteID: suiteID,
Generation: 0,
RollbackFrom: 0,
Epoch: 0,
Validators: append([]string(nil), validators...),
Threshold: t,
Shares: make(map[string]*threshold.KeyShare, n),
}
for i, v := range validators {
lambda := r.NewPoly()
lambda.Copy(lagrange[i])
r.NTT(lambda, lambda)
r.MForm(lambda, lambda)
state.Shares[v] = &threshold.KeyShare{
Index: i,
SkShare: skShares[i],
Seeds: seeds,
MACKeys: macKeysByParty[i],
Lambda: lambda,
GroupKey: gk,
}
}
// The dealer's master secret is no longer needed. Zero the standard-
// form copy so it is at least overwritten in this stack frame; the
// NTT-Mont copy was destroyed by ConvertVectorToNTT in place.
for i := range sStd {
for k := range sStd[i].Coeffs {
coeffs := sStd[i].Coeffs[k]
for j := range coeffs {
coeffs[j] = 0
}
}
}
return &KeyEra{
EraID: eraID,
GroupID: groupID,
GroupKey: gk,
GenesisEpoch: 0,
HashSuiteID: suiteID,
State: state,
}, nil
}
// Reshare evolves the era to a new committee while preserving GroupKey.
//
// The bare Shamir kernel runs in-process; for distributed deployments
// the consensus layer wraps this in the full Verifiable Secret Resharing
// (VSR) exchange (commits, complaints, activation cert) defined in
// corona/reshare. This kernel exists to (a) drive the cryptographic core,
// (b) be reused as the trusted-collaborator path for single-process
// integration tests, and (c) provide a reference against which the
// distributed protocol can be byte-equality checked.
//
// rand defaults to crypto/rand.Reader. Pass a deterministic source for
// KAT replay.
func (era *KeyEra) Reshare(newValidators []string, newThreshold int, randSource io.Reader) (*EpochShareState, error) {
if era == nil || era.GroupKey == nil || era.State == nil {
return nil, ErrUninitialized
}
if len(newValidators) == 0 {
return nil, ErrEmptyValidators
}
K := len(newValidators)
if newThreshold < 1 || newThreshold > K {
return nil, fmt.Errorf("%w: t=%d n=%d", ErrInvalidThreshold, newThreshold, K)
}
if randSource == nil {
randSource = rand.Reader
}
r := era.GroupKey.Params.R
oldSharesByID := make(map[int]reshare.Share, len(era.State.Shares))
for i, v := range era.State.Validators {
ks, ok := era.State.Shares[v]
if !ok {
return nil, fmt.Errorf("%w: %s", ErrMissingShare, v)
}
stdShare := cloneVectorAsStandard(r, ks.SkShare)
oldSharesByID[i+1] = reshare.Share(stdShare)
}
newCommitteeIDs := make([]int, K)
for i := range newValidators {
newCommitteeIDs[i] = i + 1
}
newSharesByID, err := reshare.Reshare(
r,
oldSharesByID,
era.State.Threshold,
newCommitteeIDs,
newThreshold,
randSource,
)
if err != nil {
return nil, fmt.Errorf("keyera: reshare kernel: %w", err)
}
lagrange := computeFullCommitteeLagrange(r, K)
seeds, macKeys := derivePairwiseMaterial(K, randSource)
nextState := &EpochShareState{
// Lineage preserved across resharing (KeyEraID never changes
// inside an era).
KeyEraID: era.State.KeyEraID,
// HashSuiteID is era-pinned; propagated, NOT a parameter.
// Reshare cannot change the suite — see hash-suite-separation
// theorem (proofs/pulsar/hash-suite-separation.tex).
HashSuiteID: era.HashSuiteID,
// LSS Generation increments by 1 on every successful Reshare.
Generation: era.State.Generation + 1,
// Ordinary forward transition; not a Rollback.
RollbackFrom: 0,
Epoch: era.State.Epoch + 1,
Validators: append([]string(nil), newValidators...),
Threshold: newThreshold,
Shares: make(map[string]*threshold.KeyShare, K),
}
for idx, v := range newValidators {
partyID := idx + 1
skShareNTT := stdShareToNTT(r, newSharesByID[partyID])
lambda := r.NewPoly()
lambda.Copy(lagrange[idx])
r.NTT(lambda, lambda)
r.MForm(lambda, lambda)
nextState.Shares[v] = &threshold.KeyShare{
Index: idx,
SkShare: skShareNTT,
Seeds: seeds,
MACKeys: macKeys[idx],
Lambda: lambda,
GroupKey: era.GroupKey,
}
}
era.State = nextState
return nextState, nil
}
// Reanchor opens a new key era with a fresh GroupKey via Pedersen-DKG
// + Path (a) noise flooding. Use ONLY for security-event response —
// long-tail share leakage, suspected master-secret compromise, etc.
// The chain governance MUST authorize this; it is not a routine
// operation. For ordinary validator-set rotation use Reshare, which
// preserves the GroupKey.
//
// Reanchor inherits the prior era's HashSuiteID. To migrate to a
// different suite (e.g. moving from legacy Corona-BLAKE3 to production
// Corona-SHA3) call ReanchorWithSuite.
//
// TRUST MODEL — PUBLIC-BFT-SAFE.
// This function routes through the shared github.com/luxfi/dkg
// Pedersen-VSS (vss) so no party ever holds the master secret s for
// the new era at any point in the rotation. The dealer that previously
// held s for the prior era is NOT re-trusted; every validator runs the
// vss Round 1 independently and Path (a) noise flooding aggregates the
// result.
//
// LEGACY ALTERNATIVE: use ReanchorTrustedDealer, which runs the
// single-dealer Shamir share-out (byte-equivalent to the v0.7.3 and
// earlier Reanchor behavior). Retained for HSM / TEE / publicly-
// observable foundation ceremonies where a non-distributed trust root
// is acceptable by policy. See DEPLOYMENT-RUNBOOK.md §Bootstrap-Trust.
//
// Returns the new *KeyEra (EraID = prev.EraID+1, GenesisEpoch =
// prev.State.Epoch+1), the public *BootstrapTranscript for chain
// commit, and nil on success. On identifiable abort returns
// (nil, nil, err wrapping ErrBootstrapPedersenAbort); extract via
// ExtractAbortEvidence.
func Reanchor(prev *KeyEra, t int, validators []string, groupID CoronaGroupID, entropy io.Reader) (*KeyEra, *BootstrapTranscript, error) {
return ReanchorPedersen(prev, t, validators, groupID, entropy)
}
// ReanchorWithSuite opens a new key era with a fresh GroupKey under
// the supplied HashSuite via Pedersen-DKG + Path (a) noise flooding.
// Reanchor is the ONLY lifecycle entrypoint that may pin a hash
// profile different from the prior era's (Reshare cannot — that is
// enforced by Reshare not accepting a suite parameter). nil suite
// resolves to the production default (Corona-SHA3).
//
// See Reanchor for the trust-model disclosure; ReanchorWithSuite is
// the suite-explicit form and shares the same public-BFT-safe routing.
func ReanchorWithSuite(prev *KeyEra, suite hash.HashSuite, t int, validators []string, groupID CoronaGroupID, entropy io.Reader) (*KeyEra, *BootstrapTranscript, error) {
return ReanchorPedersenWithSuite(prev, suite, t, validators, groupID, entropy)
}
// ReanchorTrustedDealer is the legacy single-trusted-party reanchor
// retained for ceremony scenarios where a non-distributed trust root
// is acceptable (e.g. publicly observable HSM / TEE ceremony with
// commit-and-reveal entropy from the new committee). It is byte-
// equivalent to the historical Reanchor entrypoint (v0.7.3 and earlier).
//
// PREFER Reanchor (which routes through Pedersen-DKG) for any
// deployment where no single party is trusted to discard the new
// era's master secret. See DEPLOYMENT-RUNBOOK.md §Bootstrap-Trust
// for the trust-model trade-off documentation.
func ReanchorTrustedDealer(prev *KeyEra, t int, validators []string, groupID CoronaGroupID, entropy io.Reader) (*KeyEra, error) {
var suite hash.HashSuite
if prev != nil && prev.HashSuiteID == hash.LegacyBLAKE3ID {
suite = hash.NewCoronaBLAKE3()
} else {
suite = hash.Default()
}
return ReanchorTrustedDealerWithSuite(prev, suite, t, validators, groupID, entropy)
}
// ReanchorTrustedDealerWithSuite is the suite-explicit form of
// ReanchorTrustedDealer. See its docstring for the trust-model
// disclosure.
func ReanchorTrustedDealerWithSuite(prev *KeyEra, suite hash.HashSuite, t int, validators []string, groupID CoronaGroupID, entropy io.Reader) (*KeyEra, error) {
var nextEraID CoronaKeyEraID
var nextEpoch uint64
if prev != nil {
nextEraID = prev.EraID + 1
if prev.State != nil {
nextEpoch = prev.State.Epoch + 1
}
}
next, err := bootstrapTrustedDealerImpl(suite, t, validators, groupID, nextEraID, entropy)
if err != nil {
return nil, err
}
next.GenesisEpoch = nextEpoch
next.State.Epoch = nextEpoch
return next, nil
}
// genGroupKey samples (A, s, e), forms b = A*s + e, rounds to bTilde,
// and returns a populated *threshold.GroupKey along with the master
// secret s in standard (non-NTT) form. The caller is responsible for
// either sharing s and erasing it (Bootstrap) or zeroing it (Reanchor).
func genGroupKey(entropy io.Reader) (*threshold.GroupKey, structs.Vector[ring.Poly], error) {
params, err := threshold.NewParams()
if err != nil {
return nil, nil, err
}
r := params.R
rXi := params.RXi
seed := make([]byte, sign.KeySize)
if _, err := io.ReadFull(entropy, seed); err != nil {
return nil, nil, err
}
prng, err := sampling.NewKeyedPRNG(seed)
if err != nil {
return nil, nil, err
}
uniformSampler := ring.NewUniformSampler(prng, r)
gaussianParams := ring.DiscreteGaussian{Sigma: sign.SigmaE, Bound: sign.BoundE}
gaussianSampler := ring.NewGaussianSampler(prng, r, gaussianParams, false)
A := utils.SamplePolyMatrix(r, sign.M, sign.N, uniformSampler, true, true)
sStd := utils.SamplePolyVector(r, sign.N, gaussianSampler, false, false)
// Compute b = A*s + e. We need NTT-Mont s for the matrix multiply,
// but we want to keep a standard-form copy of s for the Shamir
// sharing step.
sNTT := make(structs.Vector[ring.Poly], len(sStd))
for i := range sStd {
sNTT[i] = r.NewPoly()
sNTT[i].Copy(sStd[i])
}
utils.ConvertVectorToNTT(r, sNTT)
e := utils.SamplePolyVector(r, sign.M, gaussianSampler, true, true)
b := utils.InitializeVector(r, sign.M)
utils.MatrixVectorMul(r, A, sNTT, b)
utils.VectorAdd(r, b, e, b)
utils.ConvertVectorFromNTT(r, b)
bTilde := utils.RoundVector(r, rXi, b, sign.Xi)
// Erase the dealer's e from this stack. Quasar never reshares e —
// it was only needed to form bTilde at genesis.
for i := range e {
for k := range e[i].Coeffs {
coeffs := e[i].Coeffs[k]
for j := range coeffs {
coeffs[j] = 0
}
}
}
return &threshold.GroupKey{
A: A,
BTilde: bTilde,
Params: params,
}, sStd, nil
}
// computeFullCommitteeLagrange returns Lagrange coefficients for the
// committee positions [0, 1, ..., n-1] (0-indexed evaluation points;
// primitives.ComputeLagrangeCoefficients adds 1 internally).
func computeFullCommitteeLagrange(r *ring.Ring, n int) []ring.Poly {
T := make([]int, n)
for i := range T {
T[i] = i
}
return primitives.ComputeLagrangeCoefficients(r, T, big.NewInt(int64(sign.Q)))
}
// derivePairwiseMaterial generates per-pair PRF seeds and MAC keys for
// a committee of size K.
//
// seeds[i][j] : sign.KeySize bytes, present for every (i, j).
// macKeys[i][j] : sign.KeySize bytes, present for i != j; symmetric.
//
// In a single-process simulation the material is freshly drawn from
// randSource. In a distributed deployment the consensus layer overrides
// this with authenticated pairwise KEX from corona/reshare/pairwise.go,
// ensuring both endpoints derive the same value without a shared
// trusted dealer.
func derivePairwiseMaterial(K int, randSource io.Reader) (map[int][][]byte, []map[int][]byte) {
seeds := make(map[int][][]byte, K)
macKeys := make([]map[int][]byte, K)
for i := 0; i < K; i++ {
seeds[i] = make([][]byte, K)
macKeys[i] = make(map[int][]byte, K-1)
}
for i := 0; i < K; i++ {
for j := 0; j < K; j++ {
buf := make([]byte, sign.KeySize)
_, _ = io.ReadFull(randSource, buf)
seeds[i][j] = buf
}
}
for i := 0; i < K; i++ {
for j := i + 1; j < K; j++ {
buf := make([]byte, sign.KeySize)
_, _ = io.ReadFull(randSource, buf)
macKeys[i][j] = buf
macKeys[j][i] = buf
}
}
return seeds, macKeys
}
// cloneVectorAsStandard copies an NTT-Mont vector into a fresh standard-
// form vector, leaving the input untouched. Used by Reshare to feed the
// reshare kernel without mutating the caller's KeyShare.
func cloneVectorAsStandard(r *ring.Ring, in structs.Vector[ring.Poly]) structs.Vector[ring.Poly] {
out := make(structs.Vector[ring.Poly], len(in))
for i := range in {
out[i] = r.NewPoly()
out[i].Copy(in[i])
r.IMForm(out[i], out[i])
r.INTT(out[i], out[i])
}
return out
}
// stdShareToNTT converts a standard-form share (Reshare output) into
// the NTT-Mont form required by sign.Party. The input is consumed; the
// caller must not reuse the standard-form vector after this returns.
func stdShareToNTT(r *ring.Ring, in reshare.Share) structs.Vector[ring.Poly] {
out := structs.Vector[ring.Poly](in)
utils.ConvertVectorToNTT(r, out)
return out
}