Files
zeekay 54ebb24ab7 pedersen: clone before saferith.Nat.Cmp in ValidateParameters
Same saferith.Nat.Cmp limb-storage mutation defect as paillier's
EncWithNonce range check. ValidateParameters is invoked concurrently
across parties — CMP keygen round3 verifies the peer's Pedersen
aux, and pkg/zk/prm.NewProof verifies its own aux — both racing on
the same shared *saferith.Nat for s and t. Clone into per-call
scratch Nats before Cmp.

Per CGGMP21 round-trip race-detector findings in the 2026-05-31
audit; closes the remaining surface after paillier's clone.
2026-06-01 00:09:31 -07:00

134 lines
3.5 KiB
Go

package pedersen
import (
"fmt"
"io"
"github.com/cronokirby/saferith"
"github.com/luxfi/threshold/internal/params"
"github.com/luxfi/threshold/pkg/math/arith"
)
type Error string
const (
ErrNilFields Error = "contains nil field"
ErrSEqualT Error = "S cannot be equal to T"
ErrNotValidModN Error = "S and T must be in [1,…,N-1] and coprime to N"
)
func (e Error) Error() string {
return fmt.Sprintf("pedersen: %s", string(e))
}
type Parameters struct {
n *arith.Modulus
s, t *saferith.Nat
}
// New returns a new set of Pedersen parameters.
// Assumes ValidateParameters(n, s, t) returns nil.
func New(n *arith.Modulus, s, t *saferith.Nat) *Parameters {
return &Parameters{
s: s,
t: t,
n: n,
}
}
// ValidateParameters check n, s and t, and returns an error if any of the following is true:
// - n, s, or t is nil.
// - s, t are not in [1, …,n-1].
// - s, t are not coprime to N.
// - s = t.
func ValidateParameters(n *saferith.Modulus, s, t *saferith.Nat) error {
if n == nil || s == nil || t == nil {
return ErrNilFields
}
// s, t ∈ ℤₙˣ
if !arith.IsValidNatModN(n, s, t) {
return ErrNotValidModN
}
// s ≡ t — Cmp mutates limb storage on both sides via resizedLimbs even
// on the equality path, so when ValidateParameters is invoked
// concurrently across parties (e.g. from CMP keygen round3 +
// pkg/zk/prm proof verify on the same shared aux), we MUST compare
// against private scratch copies. Two clones is the cheap fix.
sScratch := new(saferith.Nat).SetNat(s)
tScratch := new(saferith.Nat).SetNat(t)
if _, eq, _ := sScratch.Cmp(tScratch); eq == 1 {
return ErrSEqualT
}
return nil
}
// N = p•q, p ≡ q ≡ 3 mod 4.
func (p Parameters) N() *saferith.Modulus { return p.n.Modulus }
// N, but as an arith modulus, which is sometimes useful
func (p Parameters) NArith() *arith.Modulus { return p.n }
// S = r² mod N.
func (p Parameters) S() *saferith.Nat { return p.s }
// T = Sˡ mod N.
func (p Parameters) T() *saferith.Nat { return p.t }
// Commit computes sˣ tʸ (mod N)
//
// x and y are taken as saferith.Int, because we want to keep these values in secret,
// in general. The commitment produced, on the other hand, hides their values,
// and can be safely shared.
func (p Parameters) Commit(x, y *saferith.Int) *saferith.Nat {
sx := p.n.ExpI(p.s, x)
ty := p.n.ExpI(p.t, y)
result := sx.ModMul(sx, ty, p.n.Modulus)
return result
}
// Verify returns true if sᵃ tᵇ ≡ S Tᵉ (mod N).
func (p Parameters) Verify(a, b, e *saferith.Int, S, T *saferith.Nat) bool {
if a == nil || b == nil || S == nil || T == nil || e == nil {
return false
}
nMod := p.n.Modulus
if !arith.IsValidNatModN(nMod, S, T) {
return false
}
sa := p.n.ExpI(p.s, a) // sᵃ (mod N)
tb := p.n.ExpI(p.t, b) // tᵇ (mod N)
lhs := sa.ModMul(sa, tb, nMod) // lhs = sᵃ⋅tᵇ (mod N)
te := p.n.ExpI(T, e) // Tᵉ (mod N)
rhs := te.ModMul(te, S, nMod) // rhs = S⋅Tᵉ (mod N)
return lhs.Eq(rhs) == 1
}
// WriteTo implements io.WriterTo and should be used within the hash.Hash function.
func (p *Parameters) WriteTo(w io.Writer) (int64, error) {
if p == nil {
return 0, io.ErrUnexpectedEOF
}
nAll := int64(0)
buf := make([]byte, params.BytesIntModN)
// write N, S, T
for _, i := range []*saferith.Nat{p.n.Nat(), p.s, p.t} {
i.FillBytes(buf)
n, err := w.Write(buf)
nAll += int64(n)
if err != nil {
return nAll, err
}
}
return nAll, nil
}
// Domain implements hash.WriterToWithDomain, and separates this type within hash.Hash.
func (Parameters) Domain() string {
return "Pedersen Parameters"
}