Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Threshold Configuration

This document describes the ThresholdConfig structure and its parameters. For an explanation of how local rejection sampling works, see Local Rejection Sampling in the signing protocol documentation.

ThresholdConfig Structure

The ThresholdConfig structure bundles all parameters for threshold signing with local rejection.

structure ThresholdConfig where
  totalParties : Nat      -- n: total parties in the scheme
  threshold : Nat         -- t: minimum parties for reconstruction
  maxSigners : Nat        -- T: maximum partials aggregated (usually = t)
  globalBound : Nat       -- B_global: Dilithium bound (γ₁ - β)
  localBound : Nat        -- B_local: per-signer bound (B_global / T)
  maxLocalAttempts : Nat  -- maximum rejection attempts before failure

The localBound is derived automatically as globalBound / maxSigners. This ensures that when T signers each produce a partial with norm at most B_local, the aggregate stays within B_global.

Creating Configurations

Use ThresholdConfig.create for custom configurations or the preset constructors for standard security levels.

-- Custom configuration for 5 parties with default threshold
let cfg := ThresholdConfig.create 5 130994

-- Custom threshold (3-of-5 instead of default 4-of-5)
let cfg := ThresholdConfig.create 5 130994 (t := 3) (maxSigners := 3)

-- Standard Dilithium configurations
let cfg2 := ThresholdConfig.dilithium2 5  -- 128-bit security
let cfg3 := ThresholdConfig.dilithium3 5  -- 192-bit security
let cfg5 := ThresholdConfig.dilithium5 5  -- 256-bit security

The create constructor fills validity proofs automatically using omega. If proof obligations fail, the configuration parameters are inconsistent.

Default Threshold

The default threshold uses the 2/3+1 rule for Byzantine fault tolerance.

Parties (n)Threshold (t)Max Faulty (f)
330
431
541
651
752
1073

The formula is t = 2n/3 + 1 (integer division). This ensures honest majority for BFT protocols.

Byzantine Fault Tolerance Context

The default threshold implements the standard BFT assumption. With validators, where at most are Byzantine, a threshold of ensures an honest quorum.

The relationship works as follows. If total validators and at most are faulty, then at least are honest. Any threshold guarantees that any -sized subset must contain at least one honest validator. Conversely, any -sized coalition of malicious validators controls at most validators, so they cannot prevent an honest quorum from forming.

This provides the guarantee needed for threshold signing: no coalition of size can prevent signature production. With local rejection sampling, malicious signers cannot force global rejection. An aggregator can always collect valid partials from honest signers. The signing protocol never stalls due to rejection; it either succeeds or fails only through the usual BFT modes (too many offline/malicious, faulty leader).

In practice, deployments often use small values of :

  • 3-of-3 (n=3, f=0): All parties must participate, zero fault tolerance
  • 3-of-4 (n=4, f=1): Tolerates one faulty party
  • 4-of-7 (n=7, f=2): Tolerates two faulty parties

For most consensus systems, the BFT assumption ( honest out of ) is compatible with the consensus protocol's own fault model. The threshold signing subprotocol inherits the same assumptions, making it compatible with the consensus layer.

Dilithium Security Levels

Ice Nine supports the three NIST-standardized Dilithium parameter sets.

Levelγ₁βB_global (γ₁ - β)Security
Dilithium22¹⁷ (131072)78130994128-bit
Dilithium32¹⁹ (524288)196524092192-bit
Dilithium52¹⁹ (524288)120524168256-bit

The global bound is γ₁ - β where γ₁ is the nonce coefficient range and β = τ · η (challenge weight times secret coefficient bound).

Local Bound Calculation

The local bound is computed as B_local = B_global / T where T is maxSigners. This creates headroom for aggregation.

Signers (T)B_local (Dilithium2)B_local (Dilithium3)
343664174697
432748131023
526198104818
71871374870
101309952409

Smaller local bounds mean tighter rejection sampling. More signers require each signer to produce smaller partials.

Rejection Rate Estimation

The expectedLocalAttempts accessor estimates how many sampling attempts each signer needs on average.

def ThresholdConfig.expectedLocalAttempts (cfg : ThresholdConfig) : Nat :=
  if cfg.globalBound > 2 * cfg.localBound then
    cfg.globalBound / (cfg.globalBound - 2 * cfg.localBound)
  else
    cfg.maxLocalAttempts

The formula approximates 1 / (1 - 2·B_local/B_global). Tighter local bounds increase rejection rates.

Signers (T)Ratio (B_local/B_global)Expected Attempts
30.33~3
40.25~2
50.20~1.7
70.14~1.4
100.10~1.25

For typical configurations, signers need 1-4 attempts on average. The maxLocalAttempts parameter (default 256) provides a safety bound for pathological cases.

Parameter Tuning Guidelines

When tuning parameters, consider these tradeoffs.

Threshold vs Rejection Rate

Lower thresholds mean fewer signers aggregate their partials. Fewer signers means larger local bounds and lower rejection rates. However, lower thresholds reduce fault tolerance.

Security Level vs Performance

Higher security levels (Dilithium3, Dilithium5) have larger global bounds. Larger bounds mean more headroom for local rejection, so rejection rates decrease. The tradeoff is larger signatures and keys.

maxLocalAttempts Setting

The default of 256 attempts is conservative. In practice, signers rarely need more than 10 attempts. Lower values fail faster on misconfigured systems. Higher values handle edge cases but delay failure detection.

For latency-sensitive applications, consider setting maxLocalAttempts to 32 or 64. For maximum reliability, keep the default.

Configuration Validation

The validate method checks configuration consistency.

def ThresholdConfig.validate (cfg : ThresholdConfig) : ConfigValidation

-- Returns .ok for valid configurations, or a specific error:
-- .noParties, .thresholdZero, .thresholdExceedsParties,
-- .maxSignersBelowThreshold, .globalBoundZero, .localBoundZero,
-- .localBoundTooLarge

Always validate configurations from untrusted sources before use. The structure proofs guarantee consistency for configurations created through create, but deserialized configurations should be validated.

Abort Threshold

The abort threshold determines how many parties must agree to abort a session.

def ThresholdConfig.abortThreshold (cfg : ThresholdConfig) : Nat :=
  cfg.maxFaulty + 1

This equals f + 1 where f = n - t is the maximum faulty parties. The +1 ensures at least one honest party agreed to abort. This prevents a coalition of f malicious parties from forcing spurious aborts.

Threshold and Security Margins

Increasing the number of signers T reduces each signer's local bound proportionally. This affects security margins in two ways.

First, smaller local bounds increase the precision required for norm checking. Production implementations must use exact arithmetic or verified bounds.

Second, the aggregate bound T · B_local approaches B_global as T increases. At the limit, there is no margin for rounding errors. Keep T well below the theoretical maximum.

For most deployments, use the default threshold (2/3+1) with the matching maxSigners value. This provides good fault tolerance with comfortable rejection rates.