diff --git a/pkg/core/fee/opcode.go b/pkg/core/fee/opcode.go index 5499c9078..19a73cbdd 100644 --- a/pkg/core/fee/opcode.go +++ b/pkg/core/fee/opcode.go @@ -165,6 +165,8 @@ var coefficients = [256]uint16{ opcode.MOD: 1 << 3, opcode.POW: 1 << 6, opcode.SQRT: 1 << 6, + opcode.MODMUL: 1 << 5, + opcode.MODPOW: 1 << 11, opcode.SHL: 1 << 3, opcode.SHR: 1 << 3, opcode.NOT: 1 << 2, diff --git a/pkg/vm/opcode/opcode.go b/pkg/vm/opcode/opcode.go index 27b78e347..08e1c8cdf 100644 --- a/pkg/vm/opcode/opcode.go +++ b/pkg/vm/opcode/opcode.go @@ -179,6 +179,8 @@ const ( MOD Opcode = 0xA2 POW Opcode = 0xA3 SQRT Opcode = 0xA4 + MODMUL Opcode = 0xA5 + MODPOW Opcode = 0xA6 SHL Opcode = 0xA8 SHR Opcode = 0xA9 NOT Opcode = 0xAA diff --git a/pkg/vm/opcode/opcode_string.go b/pkg/vm/opcode/opcode_string.go index b93176610..9c3a3582c 100644 --- a/pkg/vm/opcode/opcode_string.go +++ b/pkg/vm/opcode/opcode_string.go @@ -161,6 +161,8 @@ func _() { _ = x[MOD-162] _ = x[POW-163] _ = x[SQRT-164] + _ = x[MODMUL-165] + _ = x[MODPOW-166] _ = x[SHL-168] _ = x[SHR-169] _ = x[NOT-170] @@ -202,7 +204,7 @@ func _() { _ = x[CONVERT-219] } -const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHAPUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMP_LJMPIFJMPIF_LJMPIFNOTJMPIFNOT_LJMPEQJMPEQ_LJMPNEJMPNE_LJMPGTJMPGT_LJMPGEJMPGE_LJMPLTJMPLT_LJMPLEJMPLE_LCALLCALL_LCALLACALLTABORTASSERTTHROWTRYTRY_LENDTRYENDTRY_LENDFINALLYRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPROTROLLREVERSE3REVERSE4REVERSENINITSSLOTINITSLOTLDSFLD0LDSFLD1LDSFLD2LDSFLD3LDSFLD4LDSFLD5LDSFLD6LDSFLDSTSFLD0STSFLD1STSFLD2STSFLD3STSFLD4STSFLD5STSFLD6STSFLDLDLOC0LDLOC1LDLOC2LDLOC3LDLOC4LDLOC5LDLOC6LDLOCSTLOC0STLOC1STLOC2STLOC3STLOC4STLOC5STLOC6STLOCLDARG0LDARG1LDARG2LDARG3LDARG4LDARG5LDARG6LDARGSTARG0STARG1STARG2STARG3STARG4STARG5STARG6STARGNEWBUFFERMEMCPYCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODPOWSQRTSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLEGTGEMINMAXWITHINPACKMAPPACKSTRUCTPACKUNPACKNEWARRAY0NEWARRAYNEWARRAY_TNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSPOPITEMISNULLISTYPECONVERT" +const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHAPUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMP_LJMPIFJMPIF_LJMPIFNOTJMPIFNOT_LJMPEQJMPEQ_LJMPNEJMPNE_LJMPGTJMPGT_LJMPGEJMPGE_LJMPLTJMPLT_LJMPLEJMPLE_LCALLCALL_LCALLACALLTABORTASSERTTHROWTRYTRY_LENDTRYENDTRY_LENDFINALLYRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPROTROLLREVERSE3REVERSE4REVERSENINITSSLOTINITSLOTLDSFLD0LDSFLD1LDSFLD2LDSFLD3LDSFLD4LDSFLD5LDSFLD6LDSFLDSTSFLD0STSFLD1STSFLD2STSFLD3STSFLD4STSFLD5STSFLD6STSFLDLDLOC0LDLOC1LDLOC2LDLOC3LDLOC4LDLOC5LDLOC6LDLOCSTLOC0STLOC1STLOC2STLOC3STLOC4STLOC5STLOC6STLOCLDARG0LDARG1LDARG2LDARG3LDARG4LDARG5LDARG6LDARGSTARG0STARG1STARG2STARG3STARG4STARG5STARG6STARGNEWBUFFERMEMCPYCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODPOWSQRTMODMULMODPOWSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLEGTGEMINMAXWITHINPACKMAPPACKSTRUCTPACKUNPACKNEWARRAY0NEWARRAYNEWARRAY_TNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSPOPITEMISNULLISTYPECONVERT" var _Opcode_map = map[Opcode]string{ 0: _Opcode_name[0:8], @@ -356,45 +358,47 @@ var _Opcode_map = map[Opcode]string{ 162: _Opcode_name[862:865], 163: _Opcode_name[865:868], 164: _Opcode_name[868:872], - 168: _Opcode_name[872:875], - 169: _Opcode_name[875:878], - 170: _Opcode_name[878:881], - 171: _Opcode_name[881:888], - 172: _Opcode_name[888:894], - 177: _Opcode_name[894:896], - 179: _Opcode_name[896:904], - 180: _Opcode_name[904:915], - 181: _Opcode_name[915:917], - 182: _Opcode_name[917:919], - 183: _Opcode_name[919:921], - 184: _Opcode_name[921:923], - 185: _Opcode_name[923:926], - 186: _Opcode_name[926:929], - 187: _Opcode_name[929:935], - 190: _Opcode_name[935:942], - 191: _Opcode_name[942:952], - 192: _Opcode_name[952:956], - 193: _Opcode_name[956:962], - 194: _Opcode_name[962:971], - 195: _Opcode_name[971:979], - 196: _Opcode_name[979:989], - 197: _Opcode_name[989:999], - 198: _Opcode_name[999:1008], - 200: _Opcode_name[1008:1014], - 202: _Opcode_name[1014:1018], - 203: _Opcode_name[1018:1024], - 204: _Opcode_name[1024:1028], - 205: _Opcode_name[1028:1034], - 206: _Opcode_name[1034:1042], - 207: _Opcode_name[1042:1048], - 208: _Opcode_name[1048:1055], - 209: _Opcode_name[1055:1067], - 210: _Opcode_name[1067:1073], - 211: _Opcode_name[1073:1083], - 212: _Opcode_name[1083:1090], - 216: _Opcode_name[1090:1096], - 217: _Opcode_name[1096:1102], - 219: _Opcode_name[1102:1109], + 165: _Opcode_name[872:878], + 166: _Opcode_name[878:884], + 168: _Opcode_name[884:887], + 169: _Opcode_name[887:890], + 170: _Opcode_name[890:893], + 171: _Opcode_name[893:900], + 172: _Opcode_name[900:906], + 177: _Opcode_name[906:908], + 179: _Opcode_name[908:916], + 180: _Opcode_name[916:927], + 181: _Opcode_name[927:929], + 182: _Opcode_name[929:931], + 183: _Opcode_name[931:933], + 184: _Opcode_name[933:935], + 185: _Opcode_name[935:938], + 186: _Opcode_name[938:941], + 187: _Opcode_name[941:947], + 190: _Opcode_name[947:954], + 191: _Opcode_name[954:964], + 192: _Opcode_name[964:968], + 193: _Opcode_name[968:974], + 194: _Opcode_name[974:983], + 195: _Opcode_name[983:991], + 196: _Opcode_name[991:1001], + 197: _Opcode_name[1001:1011], + 198: _Opcode_name[1011:1020], + 200: _Opcode_name[1020:1026], + 202: _Opcode_name[1026:1030], + 203: _Opcode_name[1030:1036], + 204: _Opcode_name[1036:1040], + 205: _Opcode_name[1040:1046], + 206: _Opcode_name[1046:1054], + 207: _Opcode_name[1054:1060], + 208: _Opcode_name[1060:1067], + 209: _Opcode_name[1067:1079], + 210: _Opcode_name[1079:1085], + 211: _Opcode_name[1085:1095], + 212: _Opcode_name[1095:1102], + 216: _Opcode_name[1102:1108], + 217: _Opcode_name[1108:1114], + 219: _Opcode_name[1114:1121], } func (i Opcode) String() string { diff --git a/pkg/vm/opcode/opcode_test.go b/pkg/vm/opcode/opcode_test.go index 89514532a..269f2f9c2 100644 --- a/pkg/vm/opcode/opcode_test.go +++ b/pkg/vm/opcode/opcode_test.go @@ -33,5 +33,5 @@ func TestIsValid(t *testing.T) { require.True(t, IsValid(ADD)) require.True(t, IsValid(CONVERT)) require.False(t, IsValid(0xff)) - require.False(t, IsValid(0xa5)) + require.False(t, IsValid(0xa7)) } diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 05fd2d1f2..717d5468a 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -89,7 +89,12 @@ type VM struct { invTree *InvocationTree } -var bigOne = big.NewInt(1) +var ( + bigMinusOne = big.NewInt(-1) + bigZero = big.NewInt(0) + bigOne = big.NewInt(1) + bigTwo = big.NewInt(2) +) // New returns a new VM object ready to load AVM bytecode scripts. func New() *VM { @@ -950,6 +955,44 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro v.estack.PushItem(stackitem.NewBigInteger(new(big.Int).Sqrt(a))) + case opcode.MODMUL: + modulus := v.estack.Pop().BigInt() + if modulus.Sign() == 0 { + panic("zero modulus") + } + x2 := v.estack.Pop().BigInt() + x1 := v.estack.Pop().BigInt() + + res := new(big.Int).Mul(x1, x2) + v.estack.PushItem(stackitem.NewBigInteger(res.Mod(res, modulus))) + + case opcode.MODPOW: + modulus := v.estack.Pop().BigInt() + exponent := v.estack.Pop().BigInt() + base := v.estack.Pop().BigInt() + res := new(big.Int) + switch exponent.Cmp(bigMinusOne) { + case -1: + panic("exponent should be >= -1") + case 0: + if base.Cmp(bigZero) <= 0 { + panic("invalid base") + } + if modulus.Cmp(bigTwo) < 0 { + panic("invalid modulus") + } + if res.ModInverse(base, modulus) == nil { + panic("base and modulus are not relatively prime") + } + case 1: + if modulus.Sign() == 0 { + panic("zero modulus") // https://docs.microsoft.com/en-us/dotnet/api/system.numerics.biginteger.modpow?view=net-6.0#exceptions + } + res.Exp(base, exponent, modulus) + } + + v.estack.PushItem(stackitem.NewBigInteger(res)) + case opcode.SHL, opcode.SHR: b := toInt(v.estack.Pop().BigInt()) if b == 0 { diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index ce2d9375e..fcd25ef4a 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -732,6 +732,35 @@ func TestSQRT(t *testing.T) { t.Run("negative value", getTestFuncForVM(prog, nil, -1)) } +func TestMODMUL(t *testing.T) { + prog := makeProgram(opcode.MODMUL) + t.Run("bad, zero mod", getTestFuncForVM(prog, nil, 1, 2, 0)) + t.Run("good, positive base", getTestFuncForVM(prog, 2, 3, 4, 5)) + t.Run("good, zero base", getTestFuncForVM(prog, 0, 0, 4, 5)) + t.Run("good, negative base", getTestFuncForVM(prog, 3, -3, 4, 5)) + t.Run("good, positive base, negative mod", getTestFuncForVM(prog, 2, 3, 4, -5)) + t.Run("good, negative base, negative mod", getTestFuncForVM(prog, 3, -3, 4, -5)) +} + +func TestMODPOW(t *testing.T) { + prog := makeProgram(opcode.MODPOW) + t.Run("good, positive base", getTestFuncForVM(prog, 1, 3, 4, 5)) + t.Run("good, negative base", getTestFuncForVM(prog, 2, -3, 5, 5)) + t.Run("good, positive base, negative mod", getTestFuncForVM(prog, 1, 3, 4, -5)) + t.Run("good, negative base, negative mod", getTestFuncForVM(prog, 2, -3, 5, -5)) + t.Run("bad, big negative exponent", getTestFuncForVM(prog, nil, 3, -2, 5)) + t.Run("bad, zero modulus", getTestFuncForVM(prog, nil, 3, 4, 0)) + + t.Run("inverse compatibility", func(t *testing.T) { // Tests are taken from C# node. + t.Run("bad mod", getTestFuncForVM(prog, nil, 1, -1, 0)) + t.Run("bad mod", getTestFuncForVM(prog, nil, 1, -1, 1)) + t.Run("bad base", getTestFuncForVM(prog, nil, 0, -1, 0)) + t.Run("bad base", getTestFuncForVM(prog, nil, 0, -1, 1)) + t.Run("no inverse exists", getTestFuncForVM(prog, nil, math.MaxUint16, -1, math.MaxUint8)) + t.Run("good", getTestFuncForVM(prog, 52, 19, -1, 141)) + }) +} + func TestSHR(t *testing.T) { prog := makeProgram(opcode.SHR) t.Run("Good", getTestFuncForVM(prog, 1, 4, 2))