Both failures appear in bnb-chain/tss-lib’s crypto/vss/feldman_vss.go and were disclosed together by Trail of Bits. They were fixed in a single PR #149.
Failure 1: zero index mod $q$. Before the fix, Create checked the party index against the integer literal 0 without reducing modulo $q$ first (source):
1// crypto/vss/feldman_vss.go, bnb-chain/tss-lib (vulnerable, pre-PR #149)
2for i := 0; i < num; i++ {
3 if indexes[i].Cmp(big.NewInt(0)) == 0 {
4 return nil, nil, fmt.Errorf("party index should not be 0")
5 }
6 // indexes[i] == q passes the check; evaluatePolynomial(q) ≡ f(0) = secret
7 share := evaluatePolynomial(ec, threshold, poly, indexes[i])
8 shares[i] = &Share{Threshold: threshold, ID: indexes[i], Share: share}
9}
A malicious party submits index $i = q$. The literal-zero check passes, but evaluatePolynomial(q) ≡ evaluatePolynomial(0) = f(0) = s, handing the attacker the shared secret as their share.
Failure 2: duplicate indices mod $q$. The same file’s ReConstruct performs Lagrange interpolation by inverting the index difference $x_j - x_k$ via ModInverse (source):
1// crypto/vss/feldman_vss.go, bnb-chain/tss-lib (Lagrange step in ReConstruct)
2sub := modN.Sub(xs[j], share.ID)
3subInv := modN.ModInverse(sub) // nil if sub ≡ 0 mod q
4div := modN.Mul(xs[j], subInv) // nil-pointer dereference
5times = modN.Mul(times, div)
A malicious party submits $x_j = x_k + q$ for some honest party $k$. The raw integers differ, so any non-modular != check passes; modular reduction makes $x_j \equiv x_k$, sub is zero, ModInverse returns nil, and the next operation panics, DoS-ing the signing ceremony.
Unified fix: CheckIndexes. PR #149 added a single validation helper called at the start of Create. It reduces each index modulo $q$, rejects zero, and rejects duplicates in one pass (source):
1// crypto/vss/feldman_vss.go, bnb-chain/tss-lib (fixed, PR #149)
2func CheckIndexes(ec elliptic.Curve, indexes []*big.Int) ([]*big.Int, error) {
3 visited := make(map[string]struct{})
4 for _, v := range indexes {
5 vMod := new(big.Int).Mod(v, ec.Params().N)
6 if vMod.Cmp(zero) == 0 {
7 return nil, errors.New("party index should not be 0")
8 }
9 vModStr := vMod.String()
10 if _, ok := visited[vModStr]; ok {
11 return nil, fmt.Errorf("duplicate indexes %s", vModStr)
12 }
13 visited[vModStr] = struct{}{}
14 }
15 return indexes, nil
16}