2020-06-04 18:11:27 +00:00
|
|
|
package bigint
|
2020-01-17 10:09:41 +00:00
|
|
|
|
|
|
|
import (
|
2023-01-10 18:40:15 +00:00
|
|
|
"math"
|
2020-01-17 10:09:41 +00:00
|
|
|
"math/big"
|
|
|
|
"math/bits"
|
2021-07-14 12:05:30 +00:00
|
|
|
|
2021-07-18 12:55:37 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util/slice"
|
2020-01-17 10:09:41 +00:00
|
|
|
)
|
|
|
|
|
2020-09-21 18:51:33 +00:00
|
|
|
const (
|
2022-04-20 18:30:09 +00:00
|
|
|
// MaxBytesLen is the maximum length of a serialized integer suitable for Neo VM.
|
2022-05-31 13:51:19 +00:00
|
|
|
MaxBytesLen = 32 // 256-bit signed integer
|
2021-05-12 20:17:03 +00:00
|
|
|
// wordSizeBytes is a size of a big.Word (uint) in bytes.
|
2020-09-21 18:51:33 +00:00
|
|
|
wordSizeBytes = bits.UintSize / 8
|
|
|
|
)
|
2020-01-17 10:09:41 +00:00
|
|
|
|
2021-11-30 18:10:48 +00:00
|
|
|
var bigOne = big.NewInt(1)
|
|
|
|
|
2021-07-14 12:05:30 +00:00
|
|
|
// FromBytesUnsigned converts data in little-endian format to an unsigned integer.
|
|
|
|
func FromBytesUnsigned(data []byte) *big.Int {
|
2021-07-18 12:55:37 +00:00
|
|
|
bs := slice.CopyReverse(data)
|
2021-07-14 12:05:30 +00:00
|
|
|
return new(big.Int).SetBytes(bs)
|
|
|
|
}
|
|
|
|
|
2020-06-04 18:11:27 +00:00
|
|
|
// FromBytes converts data in little-endian format to
|
2020-01-17 10:09:41 +00:00
|
|
|
// an integer.
|
2020-06-04 18:11:27 +00:00
|
|
|
func FromBytes(data []byte) *big.Int {
|
2020-01-17 10:09:41 +00:00
|
|
|
n := new(big.Int)
|
|
|
|
size := len(data)
|
|
|
|
if size == 0 {
|
2021-03-05 15:09:05 +00:00
|
|
|
if data == nil {
|
|
|
|
panic("nil slice provided to `FromBytes`")
|
|
|
|
}
|
2020-01-17 10:09:41 +00:00
|
|
|
return big.NewInt(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
isNeg := data[size-1]&0x80 != 0
|
|
|
|
|
|
|
|
size = getEffectiveSize(data, isNeg)
|
|
|
|
if size == 0 {
|
|
|
|
if isNeg {
|
|
|
|
return big.NewInt(-1)
|
|
|
|
}
|
|
|
|
|
|
|
|
return big.NewInt(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
lw := size / wordSizeBytes
|
|
|
|
ws := make([]big.Word, lw+1)
|
|
|
|
for i := 0; i < lw; i++ {
|
|
|
|
base := i * wordSizeBytes
|
|
|
|
for j := base + 7; j >= base; j-- {
|
|
|
|
ws[i] <<= 8
|
|
|
|
ws[i] ^= big.Word(data[j])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := size - 1; i >= lw*wordSizeBytes; i-- {
|
|
|
|
ws[lw] <<= 8
|
|
|
|
ws[lw] ^= big.Word(data[i])
|
|
|
|
}
|
|
|
|
|
|
|
|
if isNeg {
|
|
|
|
for i := 0; i <= lw; i++ {
|
|
|
|
ws[i] = ^ws[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
shift := byte(wordSizeBytes-size%wordSizeBytes) * 8
|
|
|
|
ws[lw] = ws[lw] & (^big.Word(0) >> shift)
|
|
|
|
|
|
|
|
n.SetBits(ws)
|
|
|
|
n.Neg(n)
|
|
|
|
|
2021-11-30 18:10:48 +00:00
|
|
|
return n.Sub(n, bigOne)
|
2020-01-17 10:09:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return n.SetBits(ws)
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// getEffectiveSize returns the minimal number of bytes required
|
2020-01-17 10:09:41 +00:00
|
|
|
// to represent a number (two's complement for negatives).
|
|
|
|
func getEffectiveSize(buf []byte, isNeg bool) int {
|
|
|
|
var b byte
|
|
|
|
if isNeg {
|
|
|
|
b = 0xFF
|
|
|
|
}
|
|
|
|
|
|
|
|
size := len(buf)
|
|
|
|
for ; size > 0; size-- {
|
|
|
|
if buf[size-1] != b {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return size
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// ToBytes converts an integer to a slice in little-endian format.
|
2020-04-15 14:02:45 +00:00
|
|
|
// Note: NEO3 serialization differs from default C# BigInteger.ToByteArray()
|
2022-08-08 10:23:21 +00:00
|
|
|
// when n == 0. For zero is equal to empty slice in NEO3.
|
|
|
|
//
|
2020-04-15 14:02:45 +00:00
|
|
|
// https://github.com/neo-project/neo-vm/blob/master/src/neo-vm/Types/Integer.cs#L16
|
2020-06-04 18:11:27 +00:00
|
|
|
func ToBytes(n *big.Int) []byte {
|
|
|
|
return ToPreallocatedBytes(n, []byte{})
|
2020-04-21 13:45:48 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// ToPreallocatedBytes converts an integer to a slice in little-endian format using the given
|
2020-06-04 18:11:27 +00:00
|
|
|
// byte array for conversion result.
|
|
|
|
func ToPreallocatedBytes(n *big.Int, data []byte) []byte {
|
2020-01-17 10:09:41 +00:00
|
|
|
sign := n.Sign()
|
|
|
|
if sign == 0 {
|
2022-05-31 20:10:56 +00:00
|
|
|
return data[:0]
|
2020-01-17 10:09:41 +00:00
|
|
|
}
|
|
|
|
|
2022-05-31 09:45:34 +00:00
|
|
|
if sign < 0 {
|
2023-01-10 18:40:15 +00:00
|
|
|
bits := n.Bits()
|
|
|
|
carry := true
|
|
|
|
nonZero := false
|
|
|
|
for i := range bits {
|
|
|
|
if carry {
|
|
|
|
bits[i]--
|
|
|
|
carry = (bits[i] == math.MaxUint)
|
|
|
|
}
|
|
|
|
nonZero = nonZero || (bits[i] != 0)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
var carry = true
|
|
|
|
for i := range bits {
|
|
|
|
if carry {
|
|
|
|
bits[i]++
|
|
|
|
carry = (bits[i] == 0)
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
if !nonZero { // n == -1
|
2022-05-31 20:10:56 +00:00
|
|
|
return append(data[:0], 0xFF)
|
2020-01-17 10:09:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-31 20:10:56 +00:00
|
|
|
lb := n.BitLen()/8 + 1
|
2022-05-31 09:45:34 +00:00
|
|
|
|
2020-04-21 13:45:48 +00:00
|
|
|
if c := cap(data); c < lb {
|
2022-05-31 20:10:56 +00:00
|
|
|
data = make([]byte, lb)
|
2020-04-21 13:45:48 +00:00
|
|
|
} else {
|
|
|
|
data = data[:lb]
|
|
|
|
}
|
2022-05-31 20:10:56 +00:00
|
|
|
_ = n.FillBytes(data)
|
|
|
|
slice.Reverse(data)
|
2020-01-17 10:09:41 +00:00
|
|
|
|
|
|
|
if sign == -1 {
|
|
|
|
for i := range data {
|
|
|
|
data[i] = ^data[i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return data
|
|
|
|
}
|