neo-go/pkg/encoding/bigint/bigint.go
Roman Khimov 1b83dc2476 *: improve for loop syntax
Mostly it's about Go 1.22+ syntax with ranging over integers, but it also
prefers ranging over slices where possible (it makes code a little better to
read).

Notice that we have a number of dangerous loops where slices are mutated
during loop execution, many of these can't be converted since we need proper
length evalutation at every iteration.

Signed-off-by: Roman Khimov <roman@nspcc.ru>
2024-08-30 21:45:18 +03:00

153 lines
2.8 KiB
Go

package bigint
import (
"math"
"math/big"
"math/bits"
"slices"
)
const (
// MaxBytesLen is the maximum length of a serialized integer suitable for Neo VM.
MaxBytesLen = 32 // 256-bit signed integer
// wordSizeBytes is a size of a big.Word (uint) in bytes.
wordSizeBytes = bits.UintSize / 8
)
var bigOne = big.NewInt(1)
// FromBytes converts data in little-endian format to
// an integer.
func FromBytes(data []byte) *big.Int {
n := new(big.Int)
size := len(data)
if size == 0 {
if data == nil {
panic("nil slice provided to `FromBytes`")
}
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 := range lw {
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)
return n.Sub(n, bigOne)
}
return n.SetBits(ws)
}
// getEffectiveSize returns the minimal number of bytes required
// 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
}
// ToBytes converts an integer to a slice in little-endian format.
// Note: NEO3 serialization differs from default C# BigInteger.ToByteArray()
// when n == 0. For zero is equal to empty slice in NEO3.
//
// https://github.com/neo-project/neo-vm/blob/master/src/neo-vm/Types/Integer.cs#L16
func ToBytes(n *big.Int) []byte {
return ToPreallocatedBytes(n, []byte{})
}
// ToPreallocatedBytes converts an integer to a slice in little-endian format using the given
// byte array for conversion result.
func ToPreallocatedBytes(n *big.Int, data []byte) []byte {
sign := n.Sign()
if sign == 0 {
return data[:0]
}
if sign < 0 {
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
return append(data[:0], 0xFF)
}
}
lb := n.BitLen()/8 + 1
if c := cap(data); c < lb {
data = make([]byte, lb)
} else {
data = data[:lb]
}
_ = n.FillBytes(data)
slices.Reverse(data)
if sign == -1 {
for i := range data {
data[i] = ^data[i]
}
}
return data
}