Skip to content

Commit

Permalink
core: implement Secp256k1 Verify and CheckMultisig interops
Browse files Browse the repository at this point in the history
Closes #918.
  • Loading branch information
AnnaShaleva committed Jul 14, 2020
1 parent 02b423a commit 6429ead
Show file tree
Hide file tree
Showing 12 changed files with 146 additions and 45 deletions.
1 change: 1 addition & 0 deletions pkg/compiler/syscall.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
29 changes: 27 additions & 2 deletions pkg/core/interop/crypto/ecdsa.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
}
Expand All @@ -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()
Expand All @@ -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
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/core/interop/runtime/witness.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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")
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/core/interop_system.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package core

import (
"crypto/elliptic"
"errors"
"fmt"
"math"
Expand Down Expand Up @@ -483,7 +484,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
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/core/interops.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
3 changes: 2 additions & 1 deletion pkg/core/native/native_neo.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package native

import (
"crypto/elliptic"
"math/big"
"sort"
"strings"
Expand Down Expand Up @@ -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
}
Expand Down
35 changes: 24 additions & 11 deletions pkg/crypto/keys/publickey.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ 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"
Expand Down Expand Up @@ -102,12 +104,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
}
Expand Down Expand Up @@ -177,15 +180,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)
Expand Down
3 changes: 2 additions & 1 deletion pkg/crypto/keys/publickey_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keys

import (
"crypto/elliptic"
"encoding/hex"
"encoding/json"
"math/rand"
Expand Down Expand Up @@ -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)
}
Expand Down
79 changes: 63 additions & 16 deletions pkg/crypto/keys/sign_verify_test.go
Original file line number Diff line number Diff line change
@@ -1,38 +1,85 @@
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)

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,
},
}

actual := wrongPubKey.Verify(signedData, hashedData.BytesBE())
expcted := false
assert.Equal(t, expcted, actual)
assert.False(t, wrongPubKey.Verify(signedData, hashedData.BytesBE()))
})
}
6 changes: 6 additions & 0 deletions pkg/interop/crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
19 changes: 10 additions & 9 deletions pkg/vm/vm.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package vm

import (
"crypto/elliptic"
"encoding/binary"
"encoding/json"
"fmt"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1541,17 +1542,17 @@ 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)

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
}
Expand Down Expand Up @@ -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())
}
Expand Down
Loading

0 comments on commit 6429ead

Please sign in to comment.