native: implement CryptoLib contract

This commit is contained in:
Evgeniy Stratonikov 2021-02-15 18:43:10 +03:00 committed by Anna Shaleva
parent 19a23a36e4
commit 100f2db3fb
22 changed files with 240 additions and 172 deletions

View file

@ -1,39 +0,0 @@
package crypto
import (
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/crypto"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// Sha256 returns sha256 hash of the data.
func Sha256(ic *interop.Context) error {
h, err := getMessageHash(ic, ic.VM.Estack().Pop().Item())
if err != nil {
return err
}
ic.VM.Estack().PushVal(h.BytesBE())
return nil
}
// RipeMD160 returns RipeMD160 hash of the data.
func RipeMD160(ic *interop.Context) error {
var msg []byte
item := ic.VM.Estack().Pop().Item()
switch val := item.(type) {
case *stackitem.Interop:
msg = val.Value().(crypto.Verifiable).GetSignedPart()
case stackitem.Null:
msg = ic.Container.GetSignedPart()
default:
var err error
if msg, err = val.TryBytes(); err != nil {
return err
}
}
h := hash.RipeMD160(msg).BytesBE()
ic.VM.Estack().PushVal(h)
return nil
}

View file

@ -1,69 +0,0 @@
package crypto
import (
"encoding/hex"
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/crypto"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"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"
"github.com/stretchr/testify/require"
)
type testVerifiable []byte
var _ crypto.Verifiable = testVerifiable{}
func (v testVerifiable) GetSignedPart() []byte {
return v
}
func (v testVerifiable) GetSignedHash() util.Uint256 {
return hash.Sha256(v)
}
func testHash0100(t *testing.T, result string, interopFunc func(*interop.Context) error) {
t.Run("good", func(t *testing.T) {
bs := []byte{1, 0}
checkGood := func(t *testing.T, ic *interop.Context) {
require.NoError(t, interopFunc(ic))
require.Equal(t, 1, ic.VM.Estack().Len())
require.Equal(t, result, hex.EncodeToString(ic.VM.Estack().Pop().Bytes()))
}
t.Run("raw bytes", func(t *testing.T) {
ic := &interop.Context{VM: vm.New()}
ic.VM.Estack().PushVal(bs)
checkGood(t, ic)
})
t.Run("interop", func(t *testing.T) {
ic := &interop.Context{VM: vm.New()}
ic.VM.Estack().PushVal(stackitem.NewInterop(testVerifiable(bs)))
checkGood(t, ic)
})
t.Run("container", func(t *testing.T) {
ic := &interop.Context{VM: vm.New(), Container: testVerifiable(bs)}
ic.VM.Estack().PushVal(stackitem.Null{})
checkGood(t, ic)
})
})
t.Run("bad message", func(t *testing.T) {
ic := &interop.Context{VM: vm.New()}
ic.VM.Estack().PushVal(stackitem.NewArray(nil))
require.Error(t, interopFunc(ic))
})
}
func TestSHA256(t *testing.T) {
// 0x0100 hashes to 47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254
res := "47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254"
testHash0100(t, res, Sha256)
}
func TestRIPEMD160(t *testing.T) {
// 0x0100 hashes to 213492c0c6fc5d61497cf17249dd31cd9964b8a3
res := "213492c0c6fc5d61497cf17249dd31cd9964b8a3"
testHash0100(t, res, RipeMD160)
}

View file

@ -10,8 +10,6 @@ var (
ecdsaSecp256k1VerifyID = interopnames.ToID([]byte(interopnames.NeoCryptoVerifyWithECDsaSecp256k1))
ecdsaSecp256r1CheckMultisigID = interopnames.ToID([]byte(interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1))
ecdsaSecp256k1CheckMultisigID = interopnames.ToID([]byte(interopnames.NeoCryptoCheckMultisigWithECDsaSecp256k1))
sha256ID = interopnames.ToID([]byte(interopnames.NeoCryptoSHA256))
ripemd160ID = interopnames.ToID([]byte(interopnames.NeoCryptoRIPEMD160))
)
var cryptoInterops = []interop.Function{
@ -19,8 +17,6 @@ var cryptoInterops = []interop.Function{
{ID: ecdsaSecp256k1VerifyID, Func: ECDSASecp256k1Verify},
{ID: ecdsaSecp256r1CheckMultisigID, Func: ECDSASecp256r1CheckMultisig},
{ID: ecdsaSecp256k1CheckMultisigID, Func: ECDSASecp256k1CheckMultisig},
{ID: sha256ID, Func: Sha256},
{ID: ripemd160ID, Func: RipeMD160},
}
func init() {

View file

@ -51,8 +51,6 @@ const (
NeoCryptoVerifyWithECDsaSecp256k1 = "Neo.Crypto.VerifyWithECDsaSecp256k1"
NeoCryptoCheckMultisigWithECDsaSecp256r1 = "Neo.Crypto.CheckMultisigWithECDsaSecp256r1"
NeoCryptoCheckMultisigWithECDsaSecp256k1 = "Neo.Crypto.CheckMultisigWithECDsaSecp256k1"
NeoCryptoSHA256 = "Neo.Crypto.SHA256"
NeoCryptoRIPEMD160 = "Neo.Crypto.RIPEMD160"
)
var names = []string{
@ -105,6 +103,4 @@ var names = []string{
NeoCryptoVerifyWithECDsaSecp256k1,
NeoCryptoCheckMultisigWithECDsaSecp256r1,
NeoCryptoCheckMultisigWithECDsaSecp256k1,
NeoCryptoSHA256,
NeoCryptoRIPEMD160,
}

View file

@ -93,8 +93,6 @@ var neoInterops = []interop.Function{
Price: fee.ECDSAVerifyPrice, ParamCount: 3},
{Name: interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1, Func: crypto.ECDSASecp256r1CheckMultisig, Price: 0, ParamCount: 3},
{Name: interopnames.NeoCryptoCheckMultisigWithECDsaSecp256k1, Func: crypto.ECDSASecp256k1CheckMultisig, Price: 0, ParamCount: 3},
{Name: interopnames.NeoCryptoSHA256, Func: crypto.Sha256, Price: 1 << 15, ParamCount: 1},
{Name: interopnames.NeoCryptoRIPEMD160, Func: crypto.RipeMD160, Price: 1 << 15, ParamCount: 1},
}
// initIDinInteropsSlice initializes IDs from names in one given

View file

@ -24,6 +24,7 @@ type Contracts struct {
Designate *Designate
NameService *NameService
Notary *Notary
Crypto *Crypto
Contracts []interop.Contract
// persistScript is vm script which executes "onPersist" method of every native contract.
persistScript []byte
@ -61,6 +62,10 @@ func NewContracts(p2pSigExtensionsEnabled bool) *Contracts {
cs.Management = mgmt
cs.Contracts = append(cs.Contracts, mgmt)
c := newCrypto()
cs.Crypto = c
cs.Contracts = append(cs.Contracts, c)
ledger := newLedger()
cs.Ledger = ledger
cs.Contracts = append(cs.Contracts, ledger)

138
pkg/core/native/crypto.go Normal file
View file

@ -0,0 +1,138 @@
package native
import (
"crypto/elliptic"
"errors"
"fmt"
"github.com/btcsuite/btcd/btcec"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"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/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// Crypto represents CryptoLib contract.
type Crypto struct {
interop.ContractMD
}
// NamedCurve identifies named elliptic curves.
type NamedCurve byte
// Various named elliptic curves.
const (
Secp256k1 NamedCurve = 22
Secp256r1 NamedCurve = 23
)
const cryptoContractID = -3
func newCrypto() *Crypto {
c := &Crypto{ContractMD: *interop.NewContractMD(nativenames.CryptoLib, cryptoContractID)}
defer c.UpdateHash()
desc := newDescriptor("sha256", smartcontract.ByteArrayType,
manifest.NewParameter("data", smartcontract.ByteArrayType))
md := newMethodAndPrice(c.sha256, 1<<15, callflag.NoneFlag)
c.AddMethod(md, desc)
desc = newDescriptor("ripemd160", smartcontract.ByteArrayType,
manifest.NewParameter("data", smartcontract.ByteArrayType))
md = newMethodAndPrice(c.ripemd160, 1<<15, callflag.NoneFlag)
c.AddMethod(md, desc)
desc = newDescriptor("verifyWithECDsa", smartcontract.BoolType,
manifest.NewParameter("message", smartcontract.ByteArrayType),
manifest.NewParameter("pubkey", smartcontract.ByteArrayType),
manifest.NewParameter("signature", smartcontract.ByteArrayType),
manifest.NewParameter("curve", smartcontract.IntegerType))
md = newMethodAndPrice(c.verifyWithECDsa, 1<<15, callflag.NoneFlag)
c.AddMethod(md, desc)
return c
}
func (c *Crypto) sha256(_ *interop.Context, args []stackitem.Item) stackitem.Item {
bs, err := args[0].TryBytes()
if err != nil {
panic(err)
}
return stackitem.NewByteArray(hash.Sha256(bs).BytesBE())
}
func (c *Crypto) ripemd160(_ *interop.Context, args []stackitem.Item) stackitem.Item {
bs, err := args[0].TryBytes()
if err != nil {
panic(err)
}
return stackitem.NewByteArray(hash.RipeMD160(bs).BytesBE())
}
func (c *Crypto) verifyWithECDsa(_ *interop.Context, args []stackitem.Item) stackitem.Item {
msg, err := args[0].TryBytes()
if err != nil {
panic(fmt.Errorf("invalid message stackitem: %w", err))
}
hashToCheck := hash.Sha256(msg)
pubkey, err := args[1].TryBytes()
if err != nil {
panic(fmt.Errorf("invalid pubkey stackitem: %w", err))
}
signature, err := args[2].TryBytes()
if err != nil {
panic(fmt.Errorf("invalid signature stackitem: %w", err))
}
curve, err := curveFromStackitem(args[3])
if err != nil {
panic(fmt.Errorf("invalid curve stackitem: %w", err))
}
pkey, err := keys.NewPublicKeyFromBytes(pubkey, curve)
if err != nil {
panic(fmt.Errorf("failed to decode pubkey: %w", err))
}
res := pkey.Verify(signature, hashToCheck.BytesBE())
return stackitem.NewBool(res)
}
func curveFromStackitem(si stackitem.Item) (elliptic.Curve, error) {
curve, err := si.TryInteger()
if err != nil {
return nil, err
}
if !curve.IsInt64() {
return nil, errors.New("not an int64")
}
c := curve.Int64()
switch c {
case int64(Secp256k1):
return btcec.S256(), nil
case int64(Secp256r1):
return elliptic.P256(), nil
default:
return nil, errors.New("unsupported curve type")
}
}
// Metadata implements Contract interface.
func (c *Crypto) Metadata() *interop.ContractMD {
return &c.ContractMD
}
// Initialize implements Contract interface.
func (c *Crypto) Initialize(ic *interop.Context) error {
return nil
}
// OnPersist implements Contract interface.
func (c *Crypto) OnPersist(ic *interop.Context) error {
return nil
}
// PostPersist implements Contract interface.
func (c *Crypto) PostPersist(ic *interop.Context) error {
return nil
}

View file

@ -0,0 +1,41 @@
package native
import (
"encoding/hex"
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
func TestSha256(t *testing.T) {
c := newCrypto()
ic := &interop.Context{VM: vm.New()}
t.Run("bad arg type", func(t *testing.T) {
require.Panics(t, func() {
c.sha256(ic, []stackitem.Item{stackitem.NewInterop(nil)})
})
})
t.Run("good", func(t *testing.T) {
// 0x0100 hashes to 47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254
require.Equal(t, "47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254", hex.EncodeToString(c.sha256(ic, []stackitem.Item{stackitem.NewByteArray([]byte{1, 0})}).Value().([]byte)))
})
}
func TestRIPEMD160(t *testing.T) {
c := newCrypto()
ic := &interop.Context{VM: vm.New()}
t.Run("bad arg type", func(t *testing.T) {
require.Panics(t, func() {
c.ripemd160(ic, []stackitem.Item{stackitem.NewInterop(nil)})
})
})
t.Run("good", func(t *testing.T) {
// 0x0100 hashes to 213492c0c6fc5d61497cf17249dd31cd9964b8a3
require.Equal(t, "213492c0c6fc5d61497cf17249dd31cd9964b8a3", hex.EncodeToString(c.ripemd160(ic, []stackitem.Item{stackitem.NewByteArray([]byte{1, 0})}).Value().([]byte)))
})
}

View file

@ -52,7 +52,7 @@ type roleData struct {
}
const (
designateContractID = -6
designateContractID = -8
// maxNodeCount is the maximum number of nodes to set the role for.
maxNodeCount = 32

View file

@ -26,7 +26,7 @@ type Ledger struct {
}
const (
ledgerContractID = -2
ledgerContractID = -4
prefixBlockHash = 9
prefixCurrentBlock = 12

View file

@ -54,7 +54,7 @@ const (
)
const (
nameServiceID = -8
nameServiceID = -10
prefixRoots = 10
prefixDomainPrice = 22

View file

@ -18,7 +18,7 @@ type GAS struct {
NEO *NEO
}
const gasContractID = -4
const gasContractID = -6
// GASFactor is a divisor for finding GAS integral value.
const GASFactor = NEOTotalSupply

View file

@ -50,7 +50,7 @@ type NEO struct {
}
const (
neoContractID = -3
neoContractID = -5
// NEOTotalSupply is the total amount of NEO in the system.
NEOTotalSupply = 100000000
// prefixCandidate is a prefix used to store validator's data.

View file

@ -11,4 +11,5 @@ const (
Designation = "RoleManagement"
Notary = "Notary"
NameService = "NameService"
CryptoLib = "CryptoLib"
)

View file

@ -47,7 +47,7 @@ type Oracle struct {
}
const (
oracleContractID = -7
oracleContractID = -9
maxURLLength = 256
maxFilterLength = 128
maxCallbackLength = 32

View file

@ -19,7 +19,7 @@ import (
)
const (
policyContractID = -5
policyContractID = -7
defaultExecFeeFactor = interop.DefaultBaseExecFee
defaultFeePerByte = 1000