VYPR
Critical severityNVD Advisory· Published Dec 22, 2022· Updated Apr 15, 2025

CVE-2022-47931

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.

PackageAffected versionsPatched versions
github.com/bnb-chain/tss-libGo
< 1.3.6-0.20230324145555-bb6fb30bd3eb1.3.6-0.20230324145555-bb6fb30bd3eb

Affected products

2

Patches

2
bb6fb30bd3eb

Fix Hash collision (#233)

https://github.com/bnb-chain/tss-libycenMar 24, 2023via ghsa
1 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
    
369ec50be143

KS-IOF-F-02 Security audit. Collision of Hash Values: fix size of length parts (#16)

https://github.com/IoFinnet/threshlibLuke PlasterJun 20, 2022via ghsa
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

News mentions

0

No linked articles in our index yet.