Implement pack and unpack of big.Int
This commit is contained in:
parent
75d48376e1
commit
e558c10da9
@ -6,6 +6,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
@ -1072,3 +1073,62 @@ func unpackVarint(buf []byte) (int32, int) {
|
|||||||
i, ofs := vlq.Int(buf)
|
i, ofs := vlq.Int(buf)
|
||||||
return int32(i), ofs
|
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
|
package db
|
||||||
|
|
||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"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