Documentation
¶
Overview ¶
Package sign implements the FROST 2-round Schnorr threshold signing protocol (RFC 9591). The math is upstream-equivalent — the only Lens-specific decisions are:
- Domain-separation tags for the binding factor and challenge ("LENS-RHO-v1", "LENS-CHALLENGE-v1") so that a Lens signature cannot be replayed under any other Schnorr profile.
- The hash transcript uses BLAKE3 over canonical point and party-ID encodings to match the Lens hash suite contract.
Round 1: every party samples (d_i, e_i), broadcasts (D_i = d_i·G,
E_i = e_i·G).
Round 2: every party computes
- the binding factors ρ_i = H_rho(message, B, i)
- R_shares[i] = D_i + ρ_i · E_i
- R = Σ R_shares[l]
- challenge c = H_c(R, X, message)
- z_i = d_i + ρ_i · e_i + λ_i · s_i · c
and broadcasts z_i. Aggregate: any party computes z = Σ z_l. (R, z) is a Schnorr signature under the unchanged group public key X.
The Verify routine takes the canonical (R, z) signature, recomputes c, and checks z·G ?= R + c·X.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrIdentityCommit = errors.New("sign: nonce commitment is the identity point") ErrSignerSetMismatch = errors.New("sign: signer set mismatch between rounds") ErrMissingCommit = errors.New("sign: missing commit from signer") ErrMissingResponse = errors.New("sign: missing response from signer") ErrInvalidResponse = errors.New("sign: partial response failed verification") ErrInvalidSignature = errors.New("sign: signature failed Schnorr verification") )
Errors returned by the sign package.
Functions ¶
Types ¶
type CommitMsg ¶
type CommitMsg struct {
PartyID int
D primitives.Point
E primitives.Point
}
CommitMsg is one signer's Round-1 broadcast.
type ResponseMsg ¶
type ResponseMsg struct {
PartyID int
Z primitives.Scalar
}
ResponseMsg is one signer's Round-2 broadcast.
type Signature ¶
type Signature struct {
R primitives.Point
Z primitives.Scalar
}
Signature is the aggregated Schnorr signature.
func Aggregate ¶
func Aggregate( gk *threshold.GroupKey, message []byte, signers []int, commits map[int]*CommitMsg, responses map[int]*ResponseMsg, verShares map[int]primitives.Point, ) (*Signature, error)
Aggregate combines the Round-1 commits and Round-2 responses into a final (R, z) Schnorr signature under the GroupKey. Any signer can run this — the result is byte-identical regardless of who computes it.
type Signer ¶
type Signer struct {
// contains filtered or unexported fields
}
Signer holds the per-round state of one party in the FROST signing protocol.
func NewSigner ¶
NewSigner constructs a Signer from a KeyShare. The Lambda field on the share is used during Round 2 — it MUST be set to the Lagrange coefficient λ_i evaluated at 0 over the current signing set.
func (*Signer) Round1 ¶
Round1 produces the (d_i, e_i) nonces and (D_i, E_i) commitments for this signer. The CommitMsg is broadcast to every signer in the set.
The randomness is hedged: a deterministic component derived from the secret share + message + signer set, XORed with fresh randomness from `rand`. This protects against bad randomness without sacrificing determinism for KAT replay (use a deterministic `rand` for replay).