github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/encoding/bigint/bigint.go (about)

     1  package bigint
     2  
     3  import (
     4  	"math"
     5  	"math/big"
     6  	"math/bits"
     7  
     8  	"github.com/nspcc-dev/neo-go/pkg/util/slice"
     9  )
    10  
    11  const (
    12  	// MaxBytesLen is the maximum length of a serialized integer suitable for Neo VM.
    13  	MaxBytesLen = 32 // 256-bit signed integer
    14  	// wordSizeBytes is a size of a big.Word (uint) in bytes.
    15  	wordSizeBytes = bits.UintSize / 8
    16  )
    17  
    18  var bigOne = big.NewInt(1)
    19  
    20  // FromBytesUnsigned converts data in little-endian format to an unsigned integer.
    21  func FromBytesUnsigned(data []byte) *big.Int {
    22  	bs := slice.CopyReverse(data)
    23  	return new(big.Int).SetBytes(bs)
    24  }
    25  
    26  // FromBytes converts data in little-endian format to
    27  // an integer.
    28  func FromBytes(data []byte) *big.Int {
    29  	n := new(big.Int)
    30  	size := len(data)
    31  	if size == 0 {
    32  		if data == nil {
    33  			panic("nil slice provided to `FromBytes`")
    34  		}
    35  		return big.NewInt(0)
    36  	}
    37  
    38  	isNeg := data[size-1]&0x80 != 0
    39  
    40  	size = getEffectiveSize(data, isNeg)
    41  	if size == 0 {
    42  		if isNeg {
    43  			return big.NewInt(-1)
    44  		}
    45  
    46  		return big.NewInt(0)
    47  	}
    48  
    49  	lw := size / wordSizeBytes
    50  	ws := make([]big.Word, lw+1)
    51  	for i := 0; i < lw; i++ {
    52  		base := i * wordSizeBytes
    53  		for j := base + 7; j >= base; j-- {
    54  			ws[i] <<= 8
    55  			ws[i] ^= big.Word(data[j])
    56  		}
    57  	}
    58  
    59  	for i := size - 1; i >= lw*wordSizeBytes; i-- {
    60  		ws[lw] <<= 8
    61  		ws[lw] ^= big.Word(data[i])
    62  	}
    63  
    64  	if isNeg {
    65  		for i := 0; i <= lw; i++ {
    66  			ws[i] = ^ws[i]
    67  		}
    68  
    69  		shift := byte(wordSizeBytes-size%wordSizeBytes) * 8
    70  		ws[lw] = ws[lw] & (^big.Word(0) >> shift)
    71  
    72  		n.SetBits(ws)
    73  		n.Neg(n)
    74  
    75  		return n.Sub(n, bigOne)
    76  	}
    77  
    78  	return n.SetBits(ws)
    79  }
    80  
    81  // getEffectiveSize returns the minimal number of bytes required
    82  // to represent a number (two's complement for negatives).
    83  func getEffectiveSize(buf []byte, isNeg bool) int {
    84  	var b byte
    85  	if isNeg {
    86  		b = 0xFF
    87  	}
    88  
    89  	size := len(buf)
    90  	for ; size > 0; size-- {
    91  		if buf[size-1] != b {
    92  			break
    93  		}
    94  	}
    95  
    96  	return size
    97  }
    98  
    99  // ToBytes converts an integer to a slice in little-endian format.
   100  // Note: NEO3 serialization differs from default C# BigInteger.ToByteArray()
   101  // when n == 0. For zero is equal to empty slice in NEO3.
   102  //
   103  // https://github.com/neo-project/neo-vm/blob/master/src/neo-vm/Types/Integer.cs#L16
   104  func ToBytes(n *big.Int) []byte {
   105  	return ToPreallocatedBytes(n, []byte{})
   106  }
   107  
   108  // ToPreallocatedBytes converts an integer to a slice in little-endian format using the given
   109  // byte array for conversion result.
   110  func ToPreallocatedBytes(n *big.Int, data []byte) []byte {
   111  	sign := n.Sign()
   112  	if sign == 0 {
   113  		return data[:0]
   114  	}
   115  
   116  	if sign < 0 {
   117  		bits := n.Bits()
   118  		carry := true
   119  		nonZero := false
   120  		for i := range bits {
   121  			if carry {
   122  				bits[i]--
   123  				carry = (bits[i] == math.MaxUint)
   124  			}
   125  			nonZero = nonZero || (bits[i] != 0)
   126  		}
   127  		defer func() {
   128  			var carry = true
   129  			for i := range bits {
   130  				if carry {
   131  					bits[i]++
   132  					carry = (bits[i] == 0)
   133  				} else {
   134  					break
   135  				}
   136  			}
   137  		}()
   138  		if !nonZero { // n == -1
   139  			return append(data[:0], 0xFF)
   140  		}
   141  	}
   142  
   143  	lb := n.BitLen()/8 + 1
   144  
   145  	if c := cap(data); c < lb {
   146  		data = make([]byte, lb)
   147  	} else {
   148  		data = data[:lb]
   149  	}
   150  	_ = n.FillBytes(data)
   151  	slice.Reverse(data)
   152  
   153  	if sign == -1 {
   154  		for i := range data {
   155  			data[i] = ^data[i]
   156  		}
   157  	}
   158  
   159  	return data
   160  }