diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index b4975e090e..c15dba9581 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -16,6 +16,7 @@ const ( SystemCallbackInvoke = "System.Callback.Invoke" SystemContractCall = "System.Contract.Call" SystemContractCallNative = "System.Contract.CallNative" + SystemContractCreateMultisigAccount = "System.Contract.CreateMultisigAccount" SystemContractCreateStandardAccount = "System.Contract.CreateStandardAccount" SystemContractIsStandard = "System.Contract.IsStandard" SystemContractGetCallFlags = "System.Contract.GetCallFlags" @@ -70,6 +71,7 @@ var names = []string{ SystemCallbackInvoke, SystemContractCall, SystemContractCallNative, + SystemContractCreateMultisigAccount, SystemContractCreateStandardAccount, SystemContractIsStandard, SystemContractGetCallFlags, diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index d6ffc38363..d8cc4d5da1 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -10,7 +10,9 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -221,6 +223,30 @@ func contractIsStandard(ic *interop.Context) error { return nil } +// contractCreateMultisigAccount calculates multisig contract scripthash for a +// given m and a set of public keys. +func contractCreateMultisigAccount(ic *interop.Context) error { + m := ic.VM.Estack().Pop().BigInt() + if !m.IsInt64() { + return errors.New("m should fit int64") + } + arr := ic.VM.Estack().Pop().Array() + pubs := make(keys.PublicKeys, len(arr)) + for i, pk := range arr { + p, err := keys.NewPublicKeyFromBytes(pk.Value().([]byte), elliptic.P256()) + if err != nil { + return err + } + pubs[i] = p + } + script, err := smartcontract.CreateMultiSigRedeemScript(int(m.Int64()), pubs) + if err != nil { + return err + } + ic.VM.Estack().PushVal(hash.Hash160(script).BytesBE()) + return nil +} + // contractCreateStandardAccount calculates contract scripthash for a given public key. func contractCreateStandardAccount(ic *interop.Context) error { h := ic.VM.Estack().Pop().Bytes() diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index 3c03c82fdc..5d3ff67f7b 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -2,6 +2,7 @@ package core import ( "errors" + "math" "math/big" "testing" @@ -110,6 +111,53 @@ func TestContractCreateAccount(t *testing.T) { }) } +func TestContractCreateMultisigAccount(t *testing.T) { + v, ic, chain := createVM(t) + defer chain.Close() + t.Run("Good", func(t *testing.T) { + m, n := 3, 5 + pubs := make(keys.PublicKeys, n) + arr := make([]stackitem.Item, n) + for i := range pubs { + pk, err := keys.NewPrivateKey() + require.NoError(t, err) + pubs[i] = pk.PublicKey() + arr[i] = stackitem.Make(pubs[i].Bytes()) + } + v.Estack().PushVal(stackitem.Make(arr)) + v.Estack().PushVal(m) + require.NoError(t, contractCreateMultisigAccount(ic)) + + expected, err := smartcontract.CreateMultiSigRedeemScript(m, pubs) + require.NoError(t, err) + value := v.Estack().Pop().Bytes() + u, err := util.Uint160DecodeBytesBE(value) + require.NoError(t, err) + require.Equal(t, hash.Hash160(expected), u) + }) + t.Run("InvalidKey", func(t *testing.T) { + v.Estack().PushVal(stackitem.Make([]stackitem.Item{stackitem.Make([]byte{1, 2, 3})})) + v.Estack().PushVal(1) + require.Error(t, contractCreateMultisigAccount(ic)) + }) + t.Run("Invalid m", func(t *testing.T) { + pk, err := keys.NewPrivateKey() + require.NoError(t, err) + v.Estack().PushVal(stackitem.Make([]stackitem.Item{stackitem.Make(pk.PublicKey().Bytes())})) + v.Estack().PushVal(2) + require.Error(t, contractCreateMultisigAccount(ic)) + }) + t.Run("m overflows int64", func(t *testing.T) { + pk, err := keys.NewPrivateKey() + require.NoError(t, err) + v.Estack().PushVal(stackitem.Make([]stackitem.Item{stackitem.Make(pk.PublicKey().Bytes())})) + m := big.NewInt(math.MaxInt64) + m.Add(m, big.NewInt(1)) + v.Estack().PushVal(stackitem.NewBigInteger(m)) + require.Error(t, contractCreateMultisigAccount(ic)) + }) +} + func TestRuntimeGasLeft(t *testing.T) { v, ic, chain := createVM(t) defer chain.Close() diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 170696693e..021dbb725f 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -43,6 +43,7 @@ var systemInterops = []interop.Function{ {Name: interopnames.SystemContractCall, Func: contract.Call, Price: 1 << 15, RequiredFlags: callflag.AllowCall, ParamCount: 4}, {Name: interopnames.SystemContractCallNative, Func: native.Call, Price: 0, ParamCount: 1}, + {Name: interopnames.SystemContractCreateMultisigAccount, Func: contractCreateMultisigAccount, Price: 1 << 8, ParamCount: 1}, {Name: interopnames.SystemContractCreateStandardAccount, Func: contractCreateStandardAccount, Price: 1 << 8, ParamCount: 1}, {Name: interopnames.SystemContractIsStandard, Func: contractIsStandard, Price: 1 << 10, RequiredFlags: callflag.ReadStates, ParamCount: 1}, {Name: interopnames.SystemContractGetCallFlags, Func: contractGetCallFlags, Price: 1 << 10},