Common MPC Pitfalls

bnb-chain/tss-lib variadic `SHA512_256`

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}