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": { "crypto": {
"ECDsaSecp256r1Verify": "Neo.Crypto.VerifyWithECDsaSecp256r1", "ECDsaSecp256r1Verify": "Neo.Crypto.VerifyWithECDsaSecp256r1",
"ECDsaSecp256k1Verify": "Neo.Crypto.VerifyWithECDsaSecp256k1",
}, },
"enumerator": { "enumerator": {
"Concat": "System.Enumerator.Concat", "Concat": "System.Enumerator.Concat",

View file

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

View file

@ -1,6 +1,8 @@
package runtime package runtime
import ( import (
"crypto/elliptic"
"github.com/nspcc-dev/neo-go/pkg/core/dao" "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/interop"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "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) hash, err := util.Uint160DecodeBytesBE(hashOrKey)
if err != nil { if err != nil {
var key *keys.PublicKey var key *keys.PublicKey
key, err = keys.NewPublicKeyFromBytes(hashOrKey) key, err = keys.NewPublicKeyFromBytes(hashOrKey, elliptic.P256())
if err != nil { if err != nil {
return errors.New("parameter given is neither a key nor a hash") return errors.New("parameter given is neither a key nor a hash")
} }

View file

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

View file

@ -139,7 +139,9 @@ var systemInterops = []interop.Function{
var neoInterops = []interop.Function{ var neoInterops = []interop.Function{
{Name: "Neo.Crypto.VerifyWithECDsaSecp256r1", Func: crypto.ECDSASecp256r1Verify, Price: crypto.ECDSAVerifyPrice}, {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.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.Crypto.SHA256", Func: crypto.Sha256, Price: 1000000},
{Name: "Neo.Native.Deploy", Func: native.Deploy, Price: 0, {Name: "Neo.Native.Deploy", Func: native.Deploy, Price: 0,
AllowedTriggers: trigger.Application, RequiredFlags: smartcontract.AllowModifyStates}, AllowedTriggers: trigger.Application, RequiredFlags: smartcontract.AllowModifyStates},

View file

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

View file

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/btcsuite/btcd/btcec"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "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/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
@ -100,12 +101,13 @@ func NewPublicKeyFromString(s string) (*PublicKey, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return NewPublicKeyFromBytes(b) return NewPublicKeyFromBytes(b, elliptic.P256())
} }
// NewPublicKeyFromBytes returns public key created from b. // NewPublicKeyFromBytes returns public key created from b using given EC.
func NewPublicKeyFromBytes(b []byte) (*PublicKey, error) { func NewPublicKeyFromBytes(b []byte, curve elliptic.Curve) (*PublicKey, error) {
pubKey := new(PublicKey) pubKey := new(PublicKey)
pubKey.Curve = curve
if err := pubKey.DecodeBytes(b); err != nil { if err := pubKey.DecodeBytes(b); err != nil {
return nil, err 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. // 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) { func decodeCompressedY(x *big.Int, ylsb uint, curve elliptic.Curve) (*big.Int, error) {
c := curve var a *big.Int
cp := c.Params() switch curve.(type) {
three := big.NewInt(3) case *btcec.KoblitzCurve:
/* y**2 = x**3 + a*x + b % p */ a = big.NewInt(0)
xCubed := new(big.Int).Exp(x, three, cp.P) default:
threeX := new(big.Int).Mul(x, three) a = big.NewInt(3)
threeX.Mod(threeX, cp.P) }
ySquared := new(big.Int).Sub(xCubed, threeX) 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.Add(ySquared, cp.B)
ySquared.Mod(ySquared, cp.P) ySquared.Mod(ySquared, cp.P)
y := new(big.Int).ModSqrt(ySquared, cp.P) y := new(big.Int).ModSqrt(ySquared, cp.P)

View file

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

View file

@ -1,38 +1,81 @@
package keys package keys
import ( import (
"crypto/ecdsa"
"testing" "testing"
"github.com/btcsuite/btcd/btcec"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestPubKeyVerify(t *testing.T) { func TestPubKeyVerify(t *testing.T) {
var data = []byte("sample") var data = []byte("sample")
hashedData := hash.Sha256(data) hashedData := hash.Sha256(data)
privKey, err := NewPrivateKey() t.Run("Secp256r1", func(t *testing.T) {
assert.Nil(t, err) privKey, err := NewPrivateKey()
signedData := privKey.Sign(data) assert.Nil(t, err)
pubKey := privKey.PublicKey() signedData := privKey.Sign(data)
result := pubKey.Verify(signedData, hashedData.BytesBE()) pubKey := privKey.PublicKey()
expected := true result := pubKey.Verify(signedData, hashedData.BytesBE())
assert.Equal(t, expected, result) expected := true
assert.Equal(t, expected, result)
pubKey = &PublicKey{} pubKey = &PublicKey{}
assert.False(t, pubKey.Verify(signedData, hashedData.BytesBE())) 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) { func TestWrongPubKey(t *testing.T) {
privKey, _ := NewPrivateKey()
sample := []byte("sample") sample := []byte("sample")
hashedData := hash.Sha256(sample) hashedData := hash.Sha256(sample)
signedData := privKey.Sign(sample)
secondPrivKey, _ := NewPrivateKey() t.Run("Secp256r1", func(t *testing.T) {
wrongPubKey := secondPrivKey.PublicKey() privKey, _ := NewPrivateKey()
signedData := privKey.Sign(sample)
actual := wrongPubKey.Verify(signedData, hashedData.BytesBE()) secondPrivKey, _ := NewPrivateKey()
expcted := false wrongPubKey := secondPrivKey.PublicKey()
assert.Equal(t, expcted, actual)
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 { func ECDsaSecp256r1Verify(msg []byte, pub []byte, sig []byte) bool {
return false 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 package vm
import ( import (
"crypto/elliptic"
"encoding/binary" "encoding/binary"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -1457,9 +1458,9 @@ func (v *VM) calcJumpOffset(ctx *Context, parameter []byte) (int, int, error) {
} }
// CheckMultisigPar checks if sigs contains sufficient valid signatures. // 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 { 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 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) go worker(tasks, results)
} }
tasks <- task{pub: v.bytesToPublicKey(pkeys[k1]), signum: s1} tasks <- task{pub: v.bytesToPublicKey(pkeys[k1], curve), signum: s1}
tasks <- task{pub: v.bytesToPublicKey(pkeys[k2]), signum: s2} tasks <- task{pub: v.bytesToPublicKey(pkeys[k2], curve), signum: s2}
sigok := true sigok := true
taskCount := 2 taskCount := 2
@ -1541,7 +1542,7 @@ loop:
nextKey = k2 nextKey = k2
} }
taskCount++ taskCount++
tasks <- task{pub: v.bytesToPublicKey(pkeys[nextKey]), signum: nextSig} tasks <- task{pub: v.bytesToPublicKey(pkeys[nextKey], curve), signum: nextSig}
} }
close(tasks) close(tasks)
@ -1549,9 +1550,9 @@ loop:
return sigok 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 { for i := range pkeys {
pkey := v.bytesToPublicKey(pkeys[i]) pkey := v.bytesToPublicKey(pkeys[i], curve)
if pkey.Verify(sig, h) { if pkey.Verify(sig, h) {
return true return true
} }
@ -1606,14 +1607,14 @@ func (v *VM) checkInvocationStackSize() {
// bytesToPublicKey is a helper deserializing keys using cache and panicing on // bytesToPublicKey is a helper deserializing keys using cache and panicing on
// error. // error.
func (v *VM) bytesToPublicKey(b []byte) *keys.PublicKey { func (v *VM) bytesToPublicKey(b []byte, curve elliptic.Curve) *keys.PublicKey {
var pkey *keys.PublicKey var pkey *keys.PublicKey
s := string(b) s := string(b)
if v.keys[s] != nil { if v.keys[s] != nil {
pkey = v.keys[s] pkey = v.keys[s]
} else { } else {
var err error var err error
pkey, err = keys.NewPublicKeyFromBytes(b) pkey, err = keys.NewPublicKeyFromBytes(b, curve)
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())
} }

View file

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