Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ This unreleased set is delivered through a stack of fork pull requests. **Every
belongs to PR #2 (the base BNB hardening integration) unless it is tagged with another
`PR #N`.** Composing PRs:
- **PR #2** — base BNB hardening integration.
- **PR #4** — BNB #332 tBTC-relevant hardening backport (stacked on PR #2).

### ⚠️ Compatibility — read before upgrading

Expand Down Expand Up @@ -195,6 +196,41 @@ rejecting input that an honest caller would previously have produced.
after completion (`BNB #276`, `f3aad28`); the EdDSA resharing round-1 party-0 broadcast
and EdDSA keygen `NewECPoint`-error path are guarded against nil dereference
(`BNB #282`, `BNB 5d0d0f3`).
- **Panic/DoS guards on EC point operations (PR #4):** `ECPoint.ScalarMult`,
`ScalarBaseMult`, `Add`, `SetCurve`, `EightInvEight`, and `isOnCurve` return nil/error on
nil or invalid inputs instead of panicking (`crypto/ecpoint.go`). Signatures unchanged;
honest callers never pass nil. _Provenance: `BNB #332`, PR #4._
- **Schnorr verifier pre-checks (PR #4):** `Verify`/`VerifyWithSession` reject nil/invalid
public points, zero or out-of-range scalars, a zero challenge, and nil scalar-mult results
before use (`crypto/schnorr/schnorr_proof.go`). Challenge derivation is unchanged;
malicious/degenerate input only. _Provenance: `BNB #332`, PR #4._
- **DLN verifier canonical `Alpha` check (PR #4):** `Verify` requires each `Alpha` to be
canonically in `(1, N)` instead of accepting values that only landed in range after
reduction mod `N`, and guards nil `h1`/`h2`/`N`/`Alpha`/`T` (`crypto/dlnproof/proof.go`).
Honest provers already produce canonical `Alpha`. _Provenance: `BNB #332`, PR #4._
- **Paillier FactorProof response bounds (PR #4):** the verifier rejects `W1`, `W2`,
`Sigma`, and `V` outside their absolute bounds (keeping the existing inclusive `Z1`/`Z2`
style) before verification (`crypto/paillier/factor_proof.go`). Honest proofs pass.
_Provenance: `BNB #332`, PR #4._
- **MtA / range-proof bounds and nil guards (PR #4):** `ProofBob`/`ProofBobWC`/
`RangeProofAlice` verifiers reject nil moduli/inputs, tighten the `S2`/`T2` upper bounds to
exclusive, and add an explicit nil-result check after `xE.Add(pf.U)` (`crypto/mta/*.go`).
Honest proofs pass. _Provenance: `BNB #332`, PR #4._
- **VSS share-verification guards (PR #4):** `Verify` rejects nil, zero, and out-of-range
shares and verifier points (and validates each commitment point) before scalar
multiplication (`crypto/vss/feldman_vss.go`). _Provenance: `BNB #332`, PR #4._
- **ECDSA keygen round-1 modulus-width check (PR #4):** `KGRound1Message.ValidateBasic`
rejects Paillier `N` / `NTilde` that are not exactly 2048 bits, failing fast on malformed
peer messages and mirroring the pre-existing round-2 contract. Honest 2048-bit keys are
unaffected. _Provenance: `BNB #332`, PR #4._
- **ECDSA signing round-9 decommitment guard fix (PR #4):** corrected the de-commitment
validation from `!ok && len(values) != 4` to `!ok || len(values) != 4` (extracted as
`decommitFour`), closing a soundness/DoS gap where a malformed or oversized de-commitment
could be read as attacker-chosen point coordinates or cause an out-of-range panic
(`ecdsa/signing/round_9.go`). _Provenance: `BNB #332`, PR #4._
- **ECDSA signing round-4 nil theta-inverse guard (PR #4):** a non-invertible theta
(`ModInverse` returning nil) is rejected with a clean error instead of propagating nil
(`ecdsa/signing/round_4.go`). _Provenance: `BNB #332`, PR #4._

### Added

Expand All @@ -209,6 +245,9 @@ rejecting input that an honest caller would previously have produced.
- `schnorr.NewZKProofWithSession`, `NewZKVProofWithSession`, `VerifyWithSession` — session-
aware Schnorr proof overloads (the original signatures are retained and delegate with a
nil session).
- `mta.ErrRangeProofVerify` (PR #4) — sentinel error letting ECDSA signing round 2 attribute
a peer's MtA range-proof rejection to the offending party (`crypto/mta/share_protocol.go`,
`ecdsa/signing/round_2.go`). _Provenance: `BNB #332`, PR #4._

### Notes

Expand Down
11 changes: 2 additions & 9 deletions crypto/dlnproof/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (p *Proof) Verify(h1, h2, N *big.Int, session ...[]byte) bool {
if p == nil {
return false
}
if N.Sign() != 1 {
if h1 == nil || h2 == nil || N == nil || N.Sign() != 1 {
return false
}
modN := common.ModInt(N)
Expand All @@ -82,21 +82,14 @@ func (p *Proof) Verify(h1, h2, N *big.Int, session ...[]byte) bool {
}
}
for i := range p.Alpha {
if p.Alpha[i] == nil {
return false
}
a := new(big.Int).Mod(p.Alpha[i], N)
if a.Cmp(one) <= 0 || a.Cmp(N) >= 0 {
if p.Alpha[i] == nil || p.Alpha[i].Cmp(one) <= 0 || p.Alpha[i].Cmp(N) >= 0 {
return false
}
}
msg := append([]*big.Int{h1, h2, N}, p.Alpha[:]...)
c := common.SHA512_256i_TAGGED(Session, msg...)
cIBI := new(big.Int)
for i := 0; i < Iterations; i++ {
if p.Alpha[i] == nil || p.T[i] == nil {
return false
}
cI := c.Bit(i)
cIBI = cIBI.SetInt64(int64(cI))
h1ExpTi := modN.Exp(h1, p.T[i])
Expand Down
65 changes: 65 additions & 0 deletions crypto/dlnproof/proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,71 @@ func TestDLNProofVerifyRejectsOverwideT(t *testing.T) {
}
}

func TestDLNProofVerifyRejectsOverwideAlpha(t *testing.T) {
proof := &Proof{}
for i := 0; i < Iterations; i++ {
proof.Alpha[i] = big.NewInt(2)
proof.T[i] = big.NewInt(2)
}
proof.Alpha[0] = big.NewInt(25)

if proof.Verify(big.NewInt(2), big.NewInt(3), big.NewInt(23)) {
t.Fatal("Verify must reject Alpha values outside [2, N)")
}
}

func TestDLNProofVerifyRejectsNilInputs(t *testing.T) {
proof := &Proof{}
for i := 0; i < Iterations; i++ {
proof.Alpha[i] = big.NewInt(2)
proof.T[i] = big.NewInt(2)
}

if proof.Verify(nil, big.NewInt(3), big.NewInt(23)) {
t.Fatal("Verify must reject nil h1")
}
if proof.Verify(big.NewInt(2), nil, big.NewInt(23)) {
t.Fatal("Verify must reject nil h2")
}
if proof.Verify(big.NewInt(2), big.NewInt(3), nil) {
t.Fatal("Verify must reject nil N")
}
}

func TestDLNProofVerifyRejectsNilProofElements(t *testing.T) {
proof := &Proof{}
for i := 0; i < Iterations; i++ {
proof.Alpha[i] = big.NewInt(2)
proof.T[i] = big.NewInt(2)
}

badAlpha := *proof
badAlpha.Alpha[0] = nil
assertNotPanics(t, func() {
if badAlpha.Verify(big.NewInt(2), big.NewInt(3), big.NewInt(23)) {
t.Fatal("Verify must reject nil Alpha values")
}
})

badT := *proof
badT.T[0] = nil
assertNotPanics(t, func() {
if badT.Verify(big.NewInt(2), big.NewInt(3), big.NewInt(23)) {
t.Fatal("Verify must reject nil T values")
}
})
}

func assertNotPanics(t *testing.T, f func()) {
t.Helper()
defer func() {
if r := recover(); r != nil {
t.Fatalf("unexpected panic: %v", r)
}
}()
f()
}

func assertPanics(t *testing.T, f func()) {
t.Helper()
defer func() {
Expand Down
27 changes: 23 additions & 4 deletions crypto/ecpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,22 @@ func (p *ECPoint) Y() *big.Int {
}

func (p *ECPoint) Add(p1 *ECPoint) (*ECPoint, error) {
if p == nil || p1 == nil {
return nil, errors.New("Add: point is nil")
}
x, y := p.curve.Add(p.X(), p.Y(), p1.X(), p1.Y())
return NewECPoint(p.curve, x, y)
}

// ScalarMult returns p * k. If the input is nil, k is nil, or the curve
// operation returns the identity/off-curve coordinates, ScalarMult returns nil
// instead of panicking. Callers must check the returned pointer before use.
func (p *ECPoint) ScalarMult(k *big.Int) *ECPoint {
if p == nil || k == nil {
return nil
}
x, y := p.curve.ScalarMult(p.X(), p.Y(), k.Bytes())
newP, _ := NewECPoint(p.curve, x, y) // it must be on the curve, no need to check.
newP, _ := NewECPoint(p.curve, x, y)
return newP
}

Expand Down Expand Up @@ -89,6 +98,9 @@ func (p *ECPoint) Equals(p2 *ECPoint) bool {
}

func (p *ECPoint) SetCurve(curve elliptic.Curve) *ECPoint {
if p == nil {
return nil
}
p.curve = curve
return p
}
Expand All @@ -98,17 +110,24 @@ func (p *ECPoint) ValidateBasic() bool {
}

func (p *ECPoint) EightInvEight() *ECPoint {
return p.ScalarMult(eight).ScalarMult(eightInv)
q := p.ScalarMult(eight)
if q == nil {
return nil
}
return q.ScalarMult(eightInv)
}

func ScalarBaseMult(curve elliptic.Curve, k *big.Int) *ECPoint {
if curve == nil || k == nil {
return nil
}
x, y := curve.ScalarBaseMult(k.Bytes())
p, _ := NewECPoint(curve, x, y) // it must be on the curve, no need to check.
p, _ := NewECPoint(curve, x, y)
return p
}

func isOnCurve(c elliptic.Curve, x, y *big.Int) bool {
if x == nil || y == nil {
if c == nil || x == nil || y == nil {
return false
}
P := c.Params().P
Expand Down
11 changes: 11 additions & 0 deletions crypto/ecpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,17 @@ func TestNewECPointRejectsNonCanonicalCoordinates(t *testing.T) {
assert.Error(t, err)
}

func TestScalarMultReturnsNilForInvalidInputs(t *testing.T) {
curve := tss.EC()
point := ScalarBaseMult(curve, big.NewInt(1))

assert.Nil(t, ScalarBaseMult(curve, big.NewInt(0)))
assert.Nil(t, ScalarBaseMult(curve, nil))
assert.Nil(t, ScalarBaseMult(nil, big.NewInt(1)))
assert.Nil(t, point.ScalarMult(big.NewInt(0)))
assert.Nil(t, point.ScalarMult(nil))
}

func TestS256EcpointJsonSerialization(t *testing.T) {
ec := btcec.S256()
tss.RegisterCurve("secp256k1", ec)
Expand Down
19 changes: 13 additions & 6 deletions crypto/mta/proofs.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,8 @@ func ProofBobFromBytes(bzs [][]byte) (*ProofBob, error) {
func (pf *ProofBobWC) Verify(ec elliptic.Curve, pk *paillier.PublicKey, NTilde, h1, h2, c1, c2 *big.Int, X *crypto.ECPoint, session ...[]byte) bool {
Session := optionalProofSession(session)
if pf == nil || pf.ProofBob == nil ||
pk == nil || NTilde == nil || h1 == nil || h2 == nil || c1 == nil || c2 == nil {
ec == nil || pk == nil || pk.N == nil ||
NTilde == nil || h1 == nil || h2 == nil || c1 == nil || c2 == nil {
return false
}
if X != nil {
Expand All @@ -208,6 +209,8 @@ func (pf *ProofBobWC) Verify(ec elliptic.Curve, pk *paillier.PublicKey, NTilde,
q3 = new(big.Int).Mul(q, q3)
q7 := new(big.Int).Mul(q3, q3)
q7 = new(big.Int).Mul(q7, q)
// Honest S2/T2 = e*rho + rho' with e < q, rho < q*NTilde,
// rho' < q^3*NTilde, hence S2/T2 < 2*q^3*NTilde.
q3NTilde := new(big.Int).Mul(q3, NTilde)
maxS2 := new(big.Int).Lsh(q3NTilde, 1)
maxT2 := new(big.Int).Set(maxS2)
Expand Down Expand Up @@ -275,13 +278,13 @@ func (pf *ProofBobWC) Verify(ec elliptic.Curve, pk *paillier.PublicKey, NTilde,
if pf.S1.Cmp(q3) > 0 {
return false
}
if pf.S2.Cmp(maxS2) > 0 {
if pf.S2.Cmp(maxS2) >= 0 {
return false
}
if pf.T1.Cmp(q7) > 0 {
return false
}
if pf.T2.Cmp(maxT2) > 0 {
if pf.T2.Cmp(maxT2) >= 0 {
return false
}

Expand All @@ -293,7 +296,7 @@ func (pf *ProofBobWC) Verify(ec elliptic.Curve, pk *paillier.PublicKey, NTilde,
if X == nil {
eHash = common.SHA512_256i_TAGGED(Session, append(pk.AsInts(), NTilde, h1, h2, c1, c2, pf.Z, pf.ZPrm, pf.T, pf.V, pf.W)...)
} else {
if !tss.SameCurve(ec, X.Curve()) {
if !X.ValidateBasic() || !tss.SameCurve(ec, X.Curve()) {
return false
}
eHash = common.SHA512_256i_TAGGED(Session, append(pk.AsInts(), NTilde, h1, h2, X.X(), X.Y(), c1, c2, pf.U.X(), pf.U.Y(), pf.Z, pf.ZPrm, pf.T, pf.V, pf.W)...)
Expand All @@ -307,8 +310,12 @@ func (pf *ProofBobWC) Verify(ec elliptic.Curve, pk *paillier.PublicKey, NTilde,
if X != nil {
s1ModQ := new(big.Int).Mod(pf.S1, ec.Params().N)
gS1 := crypto.ScalarBaseMult(ec, s1ModQ)
xEU, err := X.ScalarMult(e).Add(pf.U)
if err != nil || !gS1.Equals(xEU) {
xE := X.ScalarMult(e)
if xE == nil {
return false
}
xEU, err := xE.Add(pf.U)
if err != nil || xEU == nil || !gS1.Equals(xEU) {
return false
}
}
Expand Down
8 changes: 6 additions & 2 deletions crypto/mta/range_proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ func RangeProofAliceFromBytes(bzs [][]byte) (*RangeProofAlice, error) {

func (pf *RangeProofAlice) Verify(ec elliptic.Curve, pk *paillier.PublicKey, NTilde, h1, h2, c *big.Int, session ...[]byte) bool {
Session := optionalProofSession(session)
if pf == nil || !pf.ValidateBasic() || pk == nil || NTilde == nil || h1 == nil || h2 == nil || c == nil {
if pf == nil || !pf.ValidateBasic() || ec == nil ||
pk == nil || pk.N == nil ||
NTilde == nil || h1 == nil || h2 == nil || c == nil {
return false
}
if new(big.Int).GCD(nil, nil, c, pk.N).Cmp(one) != 0 {
Expand All @@ -114,6 +116,8 @@ func (pf *RangeProofAlice) Verify(ec elliptic.Curve, pk *paillier.PublicKey, NTi
q := ec.Params().N
q3 := new(big.Int).Mul(q, q)
q3 = new(big.Int).Mul(q, q3)
// Honest S2 = e*rho + gamma with e < q, rho < q*NTilde,
// gamma < q^3*NTilde, hence S2 < 2*q^3*NTilde.
q3NTilde := new(big.Int).Mul(q3, NTilde)
maxS2 := new(big.Int).Lsh(q3NTilde, 1)

Expand Down Expand Up @@ -164,7 +168,7 @@ func (pf *RangeProofAlice) Verify(ec elliptic.Curve, pk *paillier.PublicKey, NTi
if pf.S1.Cmp(q3) == 1 {
return false
}
if pf.S2.Cmp(maxS2) > 0 {
if pf.S2.Cmp(maxS2) >= 0 {
return false
}

Expand Down
9 changes: 7 additions & 2 deletions crypto/mta/share_protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import (
"github.com/bnb-chain/tss-lib/crypto/paillier"
)

// ErrRangeProofVerify signals that BobMid/BobMidWC rejected the peer-supplied
// RangeProofAlice. Callers can use errors.Is to preserve peer attribution while
// distinguishing proof rejection from local arithmetic failures.
var ErrRangeProofVerify = errors.New("RangeProofAlice.Verify() returned false")

func AliceInit(
ec elliptic.Curve,
pkA *paillier.PublicKey,
Expand All @@ -38,7 +43,7 @@ func BobMid(
session ...[]byte,
) (beta, cB, betaPrm *big.Int, piB *ProofBob, err error) {
if !pf.Verify(ec, pkA, NTildeB, h1B, h2B, cA, session...) {
err = errors.New("RangeProofAlice.Verify() returned false")
err = ErrRangeProofVerify
return
}
q := ec.Params().N
Expand Down Expand Up @@ -72,7 +77,7 @@ func BobMidWC(
session ...[]byte,
) (beta, cB, betaPrm *big.Int, piB *ProofBobWC, err error) {
if !pf.Verify(ec, pkA, NTildeB, h1B, h2B, cA, session...) {
err = errors.New("RangeProofAlice.Verify() returned false")
err = ErrRangeProofVerify
return
}
q := ec.Params().N
Expand Down
Loading
Loading