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 }