mirror of
https://github.com/luxfi/threshold.git
synced 2026-10-30 15:15:53 +00:00
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.
134 lines
3.5 KiB
Go
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"
|
|
}
|