Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: add System.Contract.CreateMultisigAccount #1763

Merged
merged 2 commits into from
Feb 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/compiler/syscall.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ var syscalls = map[string]map[string]string{
},
"contract": {
"Call": interopnames.SystemContractCall,
"CreateMultisigAccount": interopnames.SystemContractCreateMultisigAccount,
"CreateStandardAccount": interopnames.SystemContractCreateStandardAccount,
"IsStandard": interopnames.SystemContractIsStandard,
"GetCallFlags": interopnames.SystemContractGetCallFlags,
Expand Down
2 changes: 2 additions & 0 deletions pkg/core/interop/interopnames/names.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -70,6 +71,7 @@ var names = []string{
SystemCallbackInvoke,
SystemContractCall,
SystemContractCallNative,
SystemContractCreateMultisigAccount,
SystemContractCreateStandardAccount,
SystemContractIsStandard,
SystemContractGetCallFlags,
Expand Down
27 changes: 27 additions & 0 deletions pkg/core/interop_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import (
"crypto/elliptic"
"errors"
"fmt"
"math"

"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"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"
Expand Down Expand Up @@ -221,6 +224,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() || m.Int64() > math.MaxInt32 {
return errors.New("m should fit int32")
}
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()
Expand Down
48 changes: 48 additions & 0 deletions pkg/core/interop_system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package core

import (
"errors"
"math"
"math/big"
"testing"

Expand Down Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions pkg/core/interops.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is should be a bit more expensive that Standard equivalent. 1 << 9?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In C# node the price is 1<<8, but I also thought about it. Create an issue?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prices are not that important for us though key decoding is not free and it there can be a lot of keys there, so fair price relative to Standard is probably higher.

{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},
Expand Down
7 changes: 7 additions & 0 deletions pkg/interop/contract/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ func IsStandard(h interop.Hash160) bool {
return false
}

// CreateMultisigAccount calculates script hash of an m out of n multisignature
// script using given m and a set of public keys bytes. This function uses
// `System.Contract.CreateMultisigAccount` syscall.
func CreateMultisigAccount(m int, pubs []interop.PublicKey) []byte {
return nil
}

// CreateStandardAccount calculates script hash of a given public key.
// This function uses `System.Contract.CreateStandardAccount` syscall.
func CreateStandardAccount(pub interop.PublicKey) []byte {
Expand Down