Implement pack and unpack of big.Int

This commit is contained in:
Martin Boehm 2018-07-27 19:46:21 +02:00
parent 75d48376e1
commit e558c10da9
2 changed files with 152 additions and 1 deletions

View File

@ -6,6 +6,7 @@ import (
"bytes"
"encoding/binary"
"encoding/hex"
"math/big"
"os"
"path/filepath"
"time"
@ -1072,3 +1073,62 @@ func unpackVarint(buf []byte) (int32, int) {
i, ofs := vlq.Int(buf)
return int32(i), ofs
}
const (
// number of bits in a big.Word
wordBits = 32 << (uint64(^big.Word(0)) >> 63)
// number of bytes in a big.Word
wordBytes = wordBits / 8
// max packed bigint words
maxPackedBigintWords = (256 - wordBytes) / wordBytes
)
// big int is packed in BigEndian order without memory allocation as 1 byte length followed by bytes of big int
// number of written bytes is returned
// limitation: bigints longer than 248 bytes are truncated to 248 bytes
// caution: buffer must be big enough to hold the packed big int, buffer 249 bytes big is always safe
func packBigint(bi *big.Int, buf []byte) int {
w := bi.Bits()
lw := len(w)
// zero returns only one byte - zero length
if lw == 0 {
buf[0] = 0
return 1
}
// pack the most significant word in a special way - skip leading zeros
w0 := w[lw-1]
fb := 8
mask := big.Word(0xff) << (wordBits - 8)
for w0&mask == 0 {
fb--
mask >>= 8
}
for i := fb; i > 0; i-- {
buf[i] = byte(w0)
w0 >>= 8
}
// if the big int is too big (> 2^1984), the number of bytes would not fit to 1 byte
// in this case, truncate the number, it is not expected to work with this big numbers as amounts
s := 0
if lw > maxPackedBigintWords {
s = lw - maxPackedBigintWords
}
// pack the rest of the words in reverse order
for j := lw - 2; j >= s; j-- {
d := w[j]
for i := fb + wordBytes; i > fb; i-- {
buf[i] = byte(d)
d >>= 8
}
fb += wordBytes
}
buf[0] = byte(fb)
return fb + 1
}
func unpackBigint(buf []byte) (big.Int, int) {
var r big.Int
l := int(buf[0]) + 1
r.SetBytes(buf[1:l])
return r, l
}

View File

@ -1,4 +1,4 @@
// +build unittest
// build unittest
package db
@ -8,6 +8,7 @@ import (
"encoding/hex"
"fmt"
"io/ioutil"
"math/big"
"os"
"reflect"
"sort"
@ -723,3 +724,93 @@ func Test_unpackBlockAddresses(t *testing.T) {
})
}
}
func Test_packBigint_unpackBigint(t *testing.T) {
bigbig1, _ := big.NewInt(0).SetString("123456789123456789012345", 10)
bigbig2, _ := big.NewInt(0).SetString("12345678912345678901234512389012345123456789123456789012345123456789123456789012345", 10)
bigbigbig := big.NewInt(0)
bigbigbig.Mul(bigbig2, bigbig2)
bigbigbig.Mul(bigbigbig, bigbigbig)
bigbigbig.Mul(bigbigbig, bigbigbig)
tests := []struct {
name string
bi *big.Int
buf []byte
toobiglen int
}{
{
name: "0",
bi: big.NewInt(0),
buf: make([]byte, 249),
},
{
name: "1",
bi: big.NewInt(1),
buf: make([]byte, 249),
},
{
name: "54321",
bi: big.NewInt(54321),
buf: make([]byte, 249),
},
{
name: "12345678",
bi: big.NewInt(12345678),
buf: make([]byte, 249),
},
{
name: "123456789123456789",
bi: big.NewInt(123456789123456789),
buf: make([]byte, 249),
},
{
name: "bigbig1",
bi: bigbig1,
buf: make([]byte, 249),
},
{
name: "bigbig2",
bi: bigbig2,
buf: make([]byte, 249),
},
{
name: "bigbigbig",
bi: bigbigbig,
buf: make([]byte, 249),
toobiglen: 242,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// packBigint
got := packBigint(tt.bi, tt.buf)
if tt.toobiglen == 0 {
// create buffer that we expect
bb := tt.bi.Bytes()
want := append([]byte(nil), byte(len(bb)))
want = append(want, bb...)
if got != len(want) {
t.Errorf("packBigint() = %v, want %v", got, len(want))
}
for i := 0; i < got; i++ {
if tt.buf[i] != want[i] {
t.Errorf("packBigint() buf = %v, want %v", tt.buf[:got], want)
break
}
}
// unpackBigint
got1, got2 := unpackBigint(tt.buf)
if got2 != len(want) {
t.Errorf("unpackBigint() = %v, want %v", got2, len(want))
}
if tt.bi.Cmp(&got1) != 0 {
t.Errorf("unpackBigint() = %v, want %v", got1, tt.bi)
}
} else {
if got != tt.toobiglen {
t.Errorf("packBigint() = %v, want toobiglen %v", got, tt.toobiglen)
}
}
})
}
}