CVE-2022-47931
Description
IO FinNet tss-lib before 2.0.0 allows a collision of hash values.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
CVE-2022-47931: IO FinNet tss-lib before 2.0.0 allows hash collisions, potentially undermining threshold signature security.
Vulnerability
Overview CVE-2022-47931 affects IO FinNet's threshold signature scheme library (tss-lib) prior to version 2.0.0. The vulnerability allows a collision of hash values, as described in the official CVE description [3]. This means that an attacker may be able to find two different inputs that produce the same hash output, undermining the integrity of the cryptographic operations that depend on collision resistance.
Exploitation
Context The tss-lib implements multi-party threshold ECDSA and EdDSA signatures [1]. The vulnerability was addressed via a commit that fixed the size of length parameters in hash computations, as seen in the related pull request and audit fix [2][4]. Exploitation would likely require the attacker to craft specific inputs to the hash function during signing or key generation, potentially allowing them to produce valid signatures without possessing the necessary secret shares. The attack surface includes any application using the vulnerable library, particularly those in cryptocurrency wallet or multi-party signing contexts [1].
Impact
If successfully exploited, the hash collision could allow an attacker to subvert the threshold signature protocol. This could lead to forging signatures without meeting the required threshold of signers, or manipulating the key generation process. The impact is particularly severe in blockchain and cryptocurrency applications, where trustless multi-party signing is critical for security.
Mitigation
Users should upgrade to tss-lib version 2.0.0 or later, which contains the fix for this vulnerability [2]. The fix ensures that hash values are computed with the correct length check, preventing collisions [4]. Organizations relying on this library should apply the update promptly.
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/bnb-chain/tss-libGo | < 1.3.6-0.20230324145555-bb6fb30bd3eb | 1.3.6-0.20230324145555-bb6fb30bd3eb |
Affected products
2- IO FinNet/tss-libdescription
Patches
21 file changed · +12 −2
common/hash.go+12 −2 modified@@ -35,11 +35,16 @@ func SHA512_256(in ...[]byte) []byte { for _, bz := range in { bzSize += len(bz) } - data = make([]byte, 0, len(inLenBz)+bzSize+inLen) + dataCap := len(inLenBz) + bzSize + inLen + (inLen * 8) + data = make([]byte, 0, dataCap) data = append(data, inLenBz...) for _, bz := range in { data = append(data, bz...) data = append(data, hashInputDelimiter) // safety delimiter + dataLen := make([]byte, 8) // 64-bits + binary.LittleEndian.PutUint64(dataLen, uint64(len(bz))) + data = append(data, dataLen...) // Security audit: length of each byte buffer should be added after + // each security delimiters in order to enforce proper domain separation } // n < len(data) or an error will never happen. // see: https://golang.org/pkg/hash/#Hash and https://github.com/golang/go/wiki/Hashing#the-hashhash-interface @@ -68,11 +73,16 @@ func SHA512_256i(in ...*big.Int) *big.Int { ptrs[i] = n.Bytes() bzSize += len(ptrs[i]) } - data = make([]byte, 0, len(inLenBz)+bzSize+inLen) + dataCap := len(inLenBz) + bzSize + inLen + (inLen * 8) + data = make([]byte, 0, dataCap) data = append(data, inLenBz...) for i := range in { data = append(data, ptrs[i]...) data = append(data, hashInputDelimiter) // safety delimiter + dataLen := make([]byte, 8) // 64-bits + binary.LittleEndian.PutUint64(dataLen, uint64(len(ptrs[i]))) + data = append(data, dataLen...) // Security audit: length of each byte buffer should be added after + // each security delimiters in order to enforce proper domain separation } // n < len(data) or an error will never happen. // see: https://golang.org/pkg/hash/#Hash and https://github.com/golang/go/wiki/Hashing#the-hashhash-interface
369ec50be143KS-IOF-F-02 Security audit. Collision of Hash Values: fix size of length parts (#16)
4 files changed · +160 −14
common/hash.go+13 −9 modified@@ -10,8 +10,8 @@ import ( "crypto" _ "crypto/sha512" "encoding/binary" + big "github.com/binance-chain/tss-lib/common/int" - "strconv" ) const ( @@ -29,20 +29,22 @@ func SHA512_256(in ...[]byte) []byte { } bzSize := 0 // prevent hash collisions with this prefix containing the block count - inLenBz := make([]byte, 64/8) + inLenBz := make([]byte, 8) // 64-bits // converting between int and uint64 doesn't change the sign bit, but it may be interpreted as a larger value. // this prefix is never read/interpreted, so that doesn't matter. binary.LittleEndian.PutUint64(inLenBz, uint64(inLen)) for _, bz := range in { bzSize += len(bz) } - data = make([]byte, 0, len(inLenBz)+bzSize+inLen) + dataCap := len(inLenBz) + bzSize + inLen + (inLen * 8) + data = make([]byte, 0, dataCap) data = append(data, inLenBz...) for _, bz := range in { data = append(data, bz...) data = append(data, hashInputDelimiter) // safety delimiter - l := []byte(strconv.Itoa(len(bz))) - data = append(data, l...) // Security audit: length of each byte buffer should be added after + dataLen := make([]byte, 8) // 64-bits + binary.LittleEndian.PutUint64(dataLen, uint64(len(bz))) + data = append(data, dataLen...) // Security audit: length of each byte buffer should be added after // each security delimiters in order to enforce proper domain separation } // n < len(data) or an error will never happen. @@ -63,7 +65,7 @@ func SHA512_256i(in ...*big.Int) *big.Int { } bzSize := 0 // prevent hash collisions with this prefix containing the block count - inLenBz := make([]byte, 64/8) + inLenBz := make([]byte, 8) // 64-bits // converting between int and uint64 doesn't change the sign bit, but it may be interpreted as a larger value. // this prefix is never read/interpreted, so that doesn't matter. binary.LittleEndian.PutUint64(inLenBz, uint64(inLen)) @@ -72,13 +74,15 @@ func SHA512_256i(in ...*big.Int) *big.Int { ptrs[i] = append(n.Bytes(), byte(n.Sign())) bzSize += len(ptrs[i]) } - data = make([]byte, 0, len(inLenBz)+bzSize+inLen) + dataCap := len(inLenBz) + bzSize + inLen + (inLen * 8) + data = make([]byte, 0, dataCap) data = append(data, inLenBz...) for i := range in { data = append(data, ptrs[i]...) data = append(data, hashInputDelimiter) // safety delimiter - l := []byte(strconv.Itoa(len(ptrs[i]))) - data = append(data, l...) // Security audit: length of each byte buffer should be added after + dataLen := make([]byte, 8) // 64-bits + binary.LittleEndian.PutUint64(dataLen, uint64(len(ptrs[i]))) + data = append(data, dataLen...) // Security audit: length of each byte buffer should be added after // each security delimiters in order to enforce proper domain separation } // n < len(data) or an error will never happen.
common/hash_test.go+140 −0 added@@ -0,0 +1,140 @@ +package common_test + +import ( + "testing" + + . "github.com/binance-chain/tss-lib/common" + big "github.com/binance-chain/tss-lib/common/int" + "github.com/stretchr/testify/assert" +) + +func TestSHA512_256(t *testing.T) { + input := [][]byte{[]byte("abc"), []byte("def"), []byte("ghi")} + input2 := [][]byte{[]byte("abc"), []byte("def"), []byte("gh")} + type args struct { + in [][]byte + } + tests := []struct { + name string + args args + want []byte + wantDiff bool + wantLen int + }{{ + name: "same inputs produce the same hash", + args: args{input}, + want: SHA512_256(input...), + wantLen: 256 / 8, + }, { + name: "different inputs produce a differing hash", + args: args{input2}, + want: SHA512_256(input...), + wantDiff: true, + wantLen: 256 / 8, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := SHA512_256(tt.args.in...) + if tt.wantDiff { + if !assert.NotEqualf(t, tt.want, got, "SHA512_256(%v)", tt.args.in) { + t.Errorf("SHA512_256() = %v, do not want %v", got, tt.want) + } + } else { + if !assert.Equalf(t, tt.want, got, "SHA512_256(%v)", tt.args.in) { + t.Errorf("SHA512_256() = %v, want %v", got, tt.want) + } + } + if tt.wantLen != len(got) { + t.Errorf("SHA512_256() = bitlen %d, want %d", len(got), tt.wantLen) + } + }) + } +} + +func TestSHA512_256i(t *testing.T) { + input := ByteSlicesToBigInts([][]byte{[]byte("abc"), []byte("def"), []byte("ghi")}) + input2 := ByteSlicesToBigInts([][]byte{[]byte("abc"), []byte("def"), []byte("gh")}) + input3 := new(big.Int).SetBytes([]byte("abc")) + t.Logf("%d", input3.Int64()) + t.Logf("%d", new(big.Int).Neg(input3).Int64()) + type args struct { + in []*big.Int + } + tests := []struct { + name string + args args + want *big.Int + wantDiff bool + }{{ + name: "same inputs produce the same hash", + args: args{input}, + want: SHA512_256i(input...), + }, { + name: "different inputs produce a differing hash", + args: args{input2}, + want: SHA512_256i(input...), + wantDiff: true, + }, { + name: "different inputs produce a differing hash: Hash(-a) != Hash(a)", + args: args{[]*big.Int{new(big.Int).Neg(input3)}}, + want: SHA512_256i(input3), + wantDiff: true, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := SHA512_256i(tt.args.in...) + if tt.wantDiff { + if !assert.NotEqualf(t, tt.want, got, "SHA512_256i(%v)", tt.args.in) { + t.Errorf("SHA512_256i() = %v, do not want %v", got, tt.want) + } + } else { + if !assert.Equalf(t, tt.want, got, "SHA512_256i(%v)", tt.args.in) { + t.Errorf("SHA512_256i() = %v, want %v", got, tt.want) + } + } + }) + } +} + +func TestSHA512_256iOne(t *testing.T) { + input := new(big.Int).SetBytes([]byte("abc")) + input2 := new(big.Int).SetBytes([]byte("ab")) + input3 := new(big.Int).SetBytes([]byte("cd")) + type args struct { + in *big.Int + } + tests := []struct { + name string + args args + want *big.Int + wantDiff bool + }{{ + name: "same inputs produce the same hash", + args: args{input}, + want: SHA512_256iOne(input), + }, { + name: "different inputs produce a differing hash", + args: args{input2}, + want: SHA512_256iOne(input), + wantDiff: true, + }, { + name: "different inputs produce a differing hash: Hash(-a) != Hash(a)", + args: args{new(big.Int).Neg(input3)}, + want: SHA512_256i(input3), + wantDiff: true, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := SHA512_256iOne(tt.args.in) + if tt.wantDiff { + if !assert.NotEqualf(t, tt.want, got, "SHA512_256iOne(%v)", tt.args.in) { + t.Errorf("SHA512_256iOne() = %v, do not want %v", got, tt.want) + } + } else { + if !assert.Equalf(t, tt.want, got, "SHA512_256iOne(%v)", tt.args.in) { + t.Errorf("SHA512_256iOne() = %v, want %v", got, tt.want) + } + } + }) + } +}
common/int/bigint.go+5 −3 modified@@ -79,8 +79,9 @@ func (z *Int) Clone() *Int { z.ensureInitialized() z.mutex.Lock() defer z.mutex.Unlock() - z.i = z.i.Clone() - return z + cloned := new(Int) + cloned.i = z.i.Clone() + return cloned } func (z *Int) Resize(cap int) *Int { z.ensureInitialized() @@ -127,7 +128,8 @@ func (z *Int) Neg(x *Int) *Int { x.ensureInitialized() z.mutex.Lock() defer z.mutex.Unlock() - z.i = x.i.Neg(1) + // allocate a new *Int otherwise we will mutate arg `x` + z.i = x.Clone().i.Neg(1) return z } func (z *Int) SetNeg() *Int {
crypto/vss/feldman_vss_test.go+2 −2 modified@@ -128,6 +128,6 @@ func TestReconstruct(t *testing.T) { assert.NoError(t, err5) assert.NotZero(t, secret5) - assert.EqualValues(t, secret, secret4, "secrets must be the same") - assert.EqualValues(t, secret4, secret5, "secrets must be the same") + assert.Equal(t, secret.Int64(), secret4.Int64(), "secrets must be the same") + assert.Equal(t, secret4.Int64(), secret5.Int64(), "secrets must be the same") }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
11- github.com/advisories/GHSA-cvcx-g7wh-x8rfghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-47931ghsaADVISORY
- github.com/IoFinnet/threshlib/commit/369ec50be1437588a9733443bcb2f15b794601d4ghsaWEB
- github.com/IoFinnet/threshlib/releases/tag/v2.0.0ghsaWEB
- github.com/bnb-chain/tss-lib/commit/bb6fb30bd3ebd35c755109836aa1a5ee6126c8a0ghsaWEB
- github.com/bnb-chain/tss-lib/pull/233ghsaWEB
- github.com/golang/vulndb/blob/master/data/reports/GO-2023-1904.yamlghsaWEB
- medium.com/@iofinnet/security-disclosure-for-ecdsa-and-eddsa-threshold-signature-schemes-4e969af7155bghsaWEB
- github.com/IoFinnet/tss-lib/commit/369ec50be1437588a9733443bcb2f15b794601d4mitre
- github.com/IoFinnet/tss-lib/releases/tag/v2.0.0mitre
- medium.com/%40iofinnet/security-disclosure-for-ecdsa-and-eddsa-threshold-signature-schemes-4e969af7155bmitre
News mentions
0No linked articles in our index yet.