forked from TrueCloudLab/neoneo-go
core: implement Secp256k1 Verify and CheckMultisig interops
Closes #918.
This commit is contained in:
parent
5326fc587a
commit
a3e306ff78
12 changed files with 141 additions and 45 deletions
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
pkg/vm/vm.go
19
pkg/vm/vm.go
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue