core: implement Secp256k1 Verify and CheckMultisig interops

Closes #918.
This commit is contained in:
Anna Shaleva 2020-07-13 12:59:41 +03:00
parent 5326fc587a
commit a3e306ff78
12 changed files with 141 additions and 45 deletions

View file

@ -7,6 +7,7 @@ var syscalls = map[string]map[string]string{
},
"crypto": {
"ECDsaSecp256r1Verify": "Neo.Crypto.VerifyWithECDsaSecp256r1",
"ECDsaSecp256k1Verify": "Neo.Crypto.VerifyWithECDsaSecp256k1",
},
"enumerator": {
"Concat": "System.Enumerator.Concat",

View file

@ -1,9 +1,11 @@
package crypto
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/crypto"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
@ -17,11 +19,22 @@ const ECDSAVerifyPrice = 1000000
// ECDSASecp256r1Verify checks ECDSA signature using Secp256r1 elliptic curve.
func ECDSASecp256r1Verify(ic *interop.Context, v *vm.VM) error {
return ecdsaVerify(ic, v, elliptic.P256())
}
// ECDSASecp256k1Verify checks ECDSA signature using Secp256k1 elliptic curve
func ECDSASecp256k1Verify(ic *interop.Context, v *vm.VM) error {
return ecdsaVerify(ic, v, btcec.S256())
}
// ecdsaVerify is internal representation of ECDSASecp256k1Verify and
// ECDSASecp256r1Verify.
func ecdsaVerify(ic *interop.Context, v *vm.VM, curve elliptic.Curve) error {
msg := getMessage(ic, v.Estack().Pop().Item())
hashToCheck := hash.Sha256(msg).BytesBE()
keyb := v.Estack().Pop().Bytes()
signature := v.Estack().Pop().Bytes()
pkey, err := keys.NewPublicKeyFromBytes(keyb)
pkey, err := keys.NewPublicKeyFromBytes(keyb, curve)
if err != nil {
return err
}
@ -33,6 +46,18 @@ func ECDSASecp256r1Verify(ic *interop.Context, v *vm.VM) error {
// ECDSASecp256r1CheckMultisig checks multiple ECDSA signatures at once using
// Secp256r1 elliptic curve.
func ECDSASecp256r1CheckMultisig(ic *interop.Context, v *vm.VM) error {
return ecdsaCheckMultisig(ic, v, elliptic.P256())
}
// ECDSASecp256k1CheckMultisig checks multiple ECDSA signatures at once using
// Secp256k1 elliptic curve.
func ECDSASecp256k1CheckMultisig(ic *interop.Context, v *vm.VM) error {
return ecdsaCheckMultisig(ic, v, btcec.S256())
}
// ecdsaCheckMultisig is internal representation of ECDSASecp256r1CheckMultisig and
// ECDSASecp256k1CheckMultisig
func ecdsaCheckMultisig(ic *interop.Context, v *vm.VM, curve elliptic.Curve) error {
msg := getMessage(ic, v.Estack().Pop().Item())
hashToCheck := hash.Sha256(msg).BytesBE()
pkeys, err := v.Estack().PopSigElements()
@ -51,7 +76,7 @@ func ECDSASecp256r1CheckMultisig(ic *interop.Context, v *vm.VM) error {
if len(pkeys) < len(sigs) {
return errors.New("more signatures than there are keys")
}
sigok := vm.CheckMultisigPar(v, hashToCheck, pkeys, sigs)
sigok := vm.CheckMultisigPar(v, curve, hashToCheck, pkeys, sigs)
v.Estack().PushVal(sigok)
return nil
}

View file

@ -1,6 +1,8 @@
package runtime
import (
"crypto/elliptic"
"github.com/nspcc-dev/neo-go/pkg/core/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
@ -90,7 +92,7 @@ func CheckWitness(ic *interop.Context, v *vm.VM) error {
hash, err := util.Uint160DecodeBytesBE(hashOrKey)
if err != nil {
var key *keys.PublicKey
key, err = keys.NewPublicKeyFromBytes(hashOrKey)
key, err = keys.NewPublicKeyFromBytes(hashOrKey, elliptic.P256())
if err != nil {
return errors.New("parameter given is neither a key nor a hash")
}

View file

@ -1,6 +1,7 @@
package core
import (
"crypto/elliptic"
"errors"
"fmt"
"math"
@ -492,7 +493,7 @@ func contractIsStandard(ic *interop.Context, v *vm.VM) error {
// contractCreateStandardAccount calculates contract scripthash for a given public key.
func contractCreateStandardAccount(ic *interop.Context, v *vm.VM) error {
h := v.Estack().Pop().Bytes()
p, err := keys.NewPublicKeyFromBytes(h)
p, err := keys.NewPublicKeyFromBytes(h, elliptic.P256())
if err != nil {
return err
}

View file

@ -139,7 +139,9 @@ var systemInterops = []interop.Function{
var neoInterops = []interop.Function{
{Name: "Neo.Crypto.VerifyWithECDsaSecp256r1", Func: crypto.ECDSASecp256r1Verify, Price: crypto.ECDSAVerifyPrice},
{Name: "Neo.Crypto.VerifyWithECDsaSecp256k1", Func: crypto.ECDSASecp256k1Verify, Price: crypto.ECDSAVerifyPrice},
{Name: "Neo.Crypto.CheckMultisigWithECDsaSecp256r1", Func: crypto.ECDSASecp256r1CheckMultisig, Price: 0},
{Name: "Neo.Crypto.CheckMultisigWithECDsaSecp256k1", Func: crypto.ECDSASecp256k1CheckMultisig, Price: 0},
{Name: "Neo.Crypto.SHA256", Func: crypto.Sha256, Price: 1000000},
{Name: "Neo.Native.Deploy", Func: native.Deploy, Price: 0,
AllowedTriggers: trigger.Application, RequiredFlags: smartcontract.AllowModifyStates},

View file

@ -1,6 +1,7 @@
package native
import (
"crypto/elliptic"
"math/big"
"sort"
"strings"
@ -361,7 +362,7 @@ func (n *NEO) GetRegisteredValidators(d dao.DAO) ([]state.Validator, error) {
}
arr := make([]state.Validator, len(kvs))
for i := range kvs {
arr[i].Key, err = keys.NewPublicKeyFromBytes([]byte(kvs[i].Key))
arr[i].Key, err = keys.NewPublicKeyFromBytes([]byte(kvs[i].Key), elliptic.P256())
if err != nil {
return nil, err
}

View file

@ -9,6 +9,7 @@ import (
"fmt"
"math/big"
"github.com/btcsuite/btcd/btcec"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io"
@ -100,12 +101,13 @@ func NewPublicKeyFromString(s string) (*PublicKey, error) {
if err != nil {
return nil, err
}
return NewPublicKeyFromBytes(b)
return NewPublicKeyFromBytes(b, elliptic.P256())
}
// NewPublicKeyFromBytes returns public key created from b.
func NewPublicKeyFromBytes(b []byte) (*PublicKey, error) {
// NewPublicKeyFromBytes returns public key created from b using given EC.
func NewPublicKeyFromBytes(b []byte, curve elliptic.Curve) (*PublicKey, error) {
pubKey := new(PublicKey)
pubKey.Curve = curve
if err := pubKey.DecodeBytes(b); err != nil {
return nil, err
}
@ -175,15 +177,25 @@ func NewPublicKeyFromASN1(data []byte) (*PublicKey, error) {
}
// decodeCompressedY performs decompression of Y coordinate for given X and Y's least significant bit.
// We use here a short-form Weierstrass curve (https://www.hyperelliptic.org/EFD/g1p/auto-shortw.html)
// y² = x³ + ax + b. Two types of elliptic curves are supported:
// 1. Secp256k1 (Koblitz curve): y² = x³ + b,
// 2. Secp256r1 (Random curve): y² = x³ - 3x + b.
// To decode compressed curve point we perform the following operation: y = sqrt(x³ + ax + b mod p)
// where `p` denotes the order of the underlying curve field
func decodeCompressedY(x *big.Int, ylsb uint, curve elliptic.Curve) (*big.Int, error) {
c := curve
cp := c.Params()
three := big.NewInt(3)
/* y**2 = x**3 + a*x + b % p */
xCubed := new(big.Int).Exp(x, three, cp.P)
threeX := new(big.Int).Mul(x, three)
threeX.Mod(threeX, cp.P)
ySquared := new(big.Int).Sub(xCubed, threeX)
var a *big.Int
switch curve.(type) {
case *btcec.KoblitzCurve:
a = big.NewInt(0)
default:
a = big.NewInt(3)
}
cp := curve.Params()
xCubed := new(big.Int).Exp(x, big.NewInt(3), cp.P)
aX := new(big.Int).Mul(x, a)
aX.Mod(aX, cp.P)
ySquared := new(big.Int).Sub(xCubed, aX)
ySquared.Add(ySquared, cp.B)
ySquared.Mod(ySquared, cp.P)
y := new(big.Int).ModSqrt(ySquared, cp.P)

View file

@ -1,6 +1,7 @@
package keys
import (
"crypto/elliptic"
"encoding/hex"
"encoding/json"
"math/rand"
@ -59,7 +60,7 @@ func TestNewPublicKeyFromBytes(t *testing.T) {
require.NoError(t, err)
b := priv.PublicKey().Bytes()
pub, err := NewPublicKeyFromBytes(b)
pub, err := NewPublicKeyFromBytes(b, elliptic.P256())
require.NoError(t, err)
require.Equal(t, priv.PublicKey(), pub)
}

View file

@ -1,38 +1,81 @@
package keys
import (
"crypto/ecdsa"
"testing"
"github.com/btcsuite/btcd/btcec"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPubKeyVerify(t *testing.T) {
var data = []byte("sample")
hashedData := hash.Sha256(data)
privKey, err := NewPrivateKey()
assert.Nil(t, err)
signedData := privKey.Sign(data)
pubKey := privKey.PublicKey()
result := pubKey.Verify(signedData, hashedData.BytesBE())
expected := true
assert.Equal(t, expected, result)
t.Run("Secp256r1", func(t *testing.T) {
privKey, err := NewPrivateKey()
assert.Nil(t, err)
signedData := privKey.Sign(data)
pubKey := privKey.PublicKey()
result := pubKey.Verify(signedData, hashedData.BytesBE())
expected := true
assert.Equal(t, expected, result)
pubKey = &PublicKey{}
assert.False(t, pubKey.Verify(signedData, hashedData.BytesBE()))
pubKey = &PublicKey{}
assert.False(t, pubKey.Verify(signedData, hashedData.BytesBE()))
})
t.Run("Secp256k1", func(t *testing.T) {
privateKey, err := btcec.NewPrivateKey(btcec.S256())
assert.Nil(t, err)
signature, err := privateKey.Sign(hashedData.BytesBE())
require.NoError(t, err)
signedData := append(signature.R.Bytes(), signature.S.Bytes()...)
pubKey := PublicKey(ecdsa.PublicKey{
Curve: btcec.S256(),
X: privateKey.X,
Y: privateKey.Y,
})
require.True(t, pubKey.Verify(signedData, hashedData.BytesBE()))
pubKey = PublicKey{}
assert.False(t, pubKey.Verify(signedData, hashedData.BytesBE()))
})
}
func TestWrongPubKey(t *testing.T) {
privKey, _ := NewPrivateKey()
sample := []byte("sample")
hashedData := hash.Sha256(sample)
signedData := privKey.Sign(sample)
secondPrivKey, _ := NewPrivateKey()
wrongPubKey := secondPrivKey.PublicKey()
t.Run("Secp256r1", func(t *testing.T) {
privKey, _ := NewPrivateKey()
signedData := privKey.Sign(sample)
actual := wrongPubKey.Verify(signedData, hashedData.BytesBE())
expcted := false
assert.Equal(t, expcted, actual)
secondPrivKey, _ := NewPrivateKey()
wrongPubKey := secondPrivKey.PublicKey()
actual := wrongPubKey.Verify(signedData, hashedData.BytesBE())
expcted := false
assert.Equal(t, expcted, actual)
})
t.Run("Secp256k1", func(t *testing.T) {
privateKey, err := btcec.NewPrivateKey(btcec.S256())
assert.Nil(t, err)
signature, err := privateKey.Sign(hashedData.BytesBE())
assert.Nil(t, err)
signedData := append(signature.R.Bytes(), signature.S.Bytes()...)
secondPrivKey, err := btcec.NewPrivateKey(btcec.S256())
assert.Nil(t, err)
wrongPubKey := PublicKey(ecdsa.PublicKey{
Curve: btcec.S256(),
X: secondPrivKey.X,
Y: secondPrivKey.Y,
})
assert.False(t, wrongPubKey.Verify(signedData, hashedData.BytesBE()))
})
}

View file

@ -13,3 +13,9 @@ func SHA256(b []byte) []byte {
func ECDsaSecp256r1Verify(msg []byte, pub []byte, sig []byte) bool {
return false
}
// ECDsaSecp256k1Verify checks that sig is correct msg's signature for a given pub
// (serialized public key). It uses `Neo.Crypto.VerifyWithECDsaSecp256k1` syscall.
func ECDsaSecp256k1Verify(msg []byte, pub []byte, sig []byte) bool {
return false
}

View file

@ -1,6 +1,7 @@
package vm
import (
"crypto/elliptic"
"encoding/binary"
"encoding/json"
"fmt"
@ -1457,9 +1458,9 @@ func (v *VM) calcJumpOffset(ctx *Context, parameter []byte) (int, int, error) {
}
// CheckMultisigPar checks if sigs contains sufficient valid signatures.
func CheckMultisigPar(v *VM, h []byte, pkeys [][]byte, sigs [][]byte) bool {
func CheckMultisigPar(v *VM, curve elliptic.Curve, h []byte, pkeys [][]byte, sigs [][]byte) bool {
if len(sigs) == 1 {
return checkMultisig1(v, h, pkeys, sigs[0])
return checkMultisig1(v, curve, h, pkeys, sigs[0])
}
k1, k2 := 0, len(pkeys)-1
@ -1496,8 +1497,8 @@ func CheckMultisigPar(v *VM, h []byte, pkeys [][]byte, sigs [][]byte) bool {
go worker(tasks, results)
}
tasks <- task{pub: v.bytesToPublicKey(pkeys[k1]), signum: s1}
tasks <- task{pub: v.bytesToPublicKey(pkeys[k2]), signum: s2}
tasks <- task{pub: v.bytesToPublicKey(pkeys[k1], curve), signum: s1}
tasks <- task{pub: v.bytesToPublicKey(pkeys[k2], curve), signum: s2}
sigok := true
taskCount := 2
@ -1541,7 +1542,7 @@ loop:
nextKey = k2
}
taskCount++
tasks <- task{pub: v.bytesToPublicKey(pkeys[nextKey]), signum: nextSig}
tasks <- task{pub: v.bytesToPublicKey(pkeys[nextKey], curve), signum: nextSig}
}
close(tasks)
@ -1549,9 +1550,9 @@ loop:
return sigok
}
func checkMultisig1(v *VM, h []byte, pkeys [][]byte, sig []byte) bool {
func checkMultisig1(v *VM, curve elliptic.Curve, h []byte, pkeys [][]byte, sig []byte) bool {
for i := range pkeys {
pkey := v.bytesToPublicKey(pkeys[i])
pkey := v.bytesToPublicKey(pkeys[i], curve)
if pkey.Verify(sig, h) {
return true
}
@ -1606,14 +1607,14 @@ func (v *VM) checkInvocationStackSize() {
// bytesToPublicKey is a helper deserializing keys using cache and panicing on
// error.
func (v *VM) bytesToPublicKey(b []byte) *keys.PublicKey {
func (v *VM) bytesToPublicKey(b []byte, curve elliptic.Curve) *keys.PublicKey {
var pkey *keys.PublicKey
s := string(b)
if v.keys[s] != nil {
pkey = v.keys[s]
} else {
var err error
pkey, err = keys.NewPublicKeyFromBytes(b)
pkey, err = keys.NewPublicKeyFromBytes(b, curve)
if err != nil {
panic(err.Error())
}

View file

@ -2,6 +2,7 @@ package vm
import (
"bytes"
"crypto/elliptic"
"encoding/binary"
"encoding/hex"
"fmt"
@ -116,9 +117,9 @@ func TestBytesToPublicKey(t *testing.T) {
assert.Equal(t, 0, len(cache))
keyHex := "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"
keyBytes, _ := hex.DecodeString(keyHex)
key := v.bytesToPublicKey(keyBytes)
key := v.bytesToPublicKey(keyBytes, elliptic.P256())
assert.NotNil(t, key)
key2 := v.bytesToPublicKey(keyBytes)
key2 := v.bytesToPublicKey(keyBytes, elliptic.P256())
assert.Equal(t, key, key2)
cache = v.GetPublicKeys()
@ -126,7 +127,7 @@ func TestBytesToPublicKey(t *testing.T) {
assert.NotNil(t, cache[string(keyBytes)])
keyBytes[0] = 0xff
require.Panics(t, func() { v.bytesToPublicKey(keyBytes) })
require.Panics(t, func() { v.bytesToPublicKey(keyBytes, elliptic.P256()) })
}
func TestPushBytes1to75(t *testing.T) {