The audit finding KS-IOF-F-02 pointed out that bnb-chain’s tss-lib applied an ambiguous encoding by using a single dollar-sign delimiter with no per-element length tag.
The vulnerable helper represented that delimiter as '$'
(source):
1// common/hash.go — bnb-chain/tss-lib v1.3.5 (vulnerable)
2const hashInputDelimiter = byte('$')
3
4func SHA512_256(in ...[]byte) []byte {
5 inLenBz := make([]byte, 8)
6 binary.LittleEndian.PutUint64(inLenBz, uint64(len(in))) // counts inputs, not sizes
7 data = append(data, inLenBz...)
8 for _, bz := range in {
9 data = append(data, bz...)
10 data = append(data, hashInputDelimiter) // no length tag per element
11 }
12}
The collision: SHA512_256([]byte("a$"), []byte("b")) and SHA512_256([]byte("a"), []byte("$b"))
both serialize to a$$b$ and therefore produce the same digest. The
fix (IoFinnet’s commit 369ec50,
imported into bnb-chain/tss-lib as
PR #233) appends an 8-byte length
tag after each delimiter
(source):
1// common/hash.go — bnb-chain/tss-lib v2.0.0 (fixed)
2for _, bz := range in {
3 data = append(data, bz...)
4 data = append(data, hashInputDelimiter)
5 dataLen := make([]byte, 8)
6 binary.LittleEndian.PutUint64(dataLen, uint64(len(bz)))
7 data = append(data, dataLen...) // length tag makes encoding injective
8}