Implement pack and unpack of big.Int
This commit is contained in:
parent
75d48376e1
commit
e558c10da9
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user