From 53bb283f2498c96aa0a46692a0bd28267a127245 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 29 May 2020 14:59:36 +0300 Subject: [PATCH] core: implement key recover interops Part of #1003 --- pkg/core/interop_neo.go | 30 ++++++++ pkg/core/interops.go | 2 + pkg/crypto/keys/publickey.go | 121 +++++++++++++++++++++++++++--- pkg/crypto/keys/publickey_test.go | 33 ++++++++ pkg/crypto/keys/secp256k1.go | 37 +++++++++ 5 files changed, 212 insertions(+), 11 deletions(-) create mode 100644 pkg/crypto/keys/secp256k1.go diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index cd3c0d8fdd..36d081056b 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -1,9 +1,11 @@ package core import ( + "crypto/elliptic" "errors" "fmt" "math" + "math/big" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/storage" @@ -600,6 +602,34 @@ func (ic *interopContext) contractMigrate(v *vm.VM) error { return ic.contractDestroy(v) } +// secp256k1Recover recovers speck256k1 public key. +func (ic *interopContext) secp256k1Recover(v *vm.VM) error { + return ic.eccRecover(keys.Secp256k1(), v) +} + +// secp256r1Recover recovers speck256r1 public key. +func (ic *interopContext) secp256r1Recover(v *vm.VM) error { + return ic.eccRecover(elliptic.P256(), v) +} + +// eccRecover recovers public key using ECCurve set +func (ic *interopContext) eccRecover(curve elliptic.Curve, v *vm.VM) error { + // TODO: perhaps, we don't need to reverse here, depends on the way how we store them + rBytes := append(util.ArrayReverse(v.Estack().Pop().Bytes()), 1) + sBytes := append(util.ArrayReverse(v.Estack().Pop().Bytes()), 1) + r := new(big.Int).SetBytes(rBytes) + s := new(big.Int).SetBytes(sBytes) + isEven := v.Estack().Pop().Bool() + messageHash := v.Estack().Pop().Bytes() + pKey, err := keys.KeyRecover(curve, r, s, messageHash, isEven) + if err != nil { + v.Estack().PushVal(vm.NewByteArrayItem([]byte{})) + return nil + } + v.Estack().PushVal(vm.NewByteArrayItem(pKey.Bytes()[1:])) + return nil +} + // assetCreate creates an asset. func (ic *interopContext) assetCreate(v *vm.VM) error { if ic.trigger != trigger.Application { diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 25040bce65..551c4c8cf2 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -166,6 +166,8 @@ var neoInterops = []interopedFunction{ {Name: "Neo.Contract.GetStorageContext", Func: (*interopContext).contractGetStorageContext, Price: 1}, {Name: "Neo.Contract.IsPayable", Func: (*interopContext).contractIsPayable, Price: 1}, {Name: "Neo.Contract.Migrate", Func: (*interopContext).contractMigrate, Price: 0}, + {Name: "Neo.Cryptography.Secp256k1Recover", Func: (*interopContext).secp256k1Recover, Price: 100}, + {Name: "Neo.Cryptography.Secp256r1Recover", Func: (*interopContext).secp256r1Recover, Price: 100}, {Name: "Neo.Enumerator.Concat", Func: (*interopContext).enumeratorConcat, Price: 1}, {Name: "Neo.Enumerator.Create", Func: (*interopContext).enumeratorCreate, Price: 1}, {Name: "Neo.Enumerator.Next", Func: (*interopContext).enumeratorNext, Price: 1}, diff --git a/pkg/crypto/keys/publickey.go b/pkg/crypto/keys/publickey.go index d10d9c9f41..f20f96573c 100644 --- a/pkg/crypto/keys/publickey.go +++ b/pkg/crypto/keys/publickey.go @@ -134,15 +134,29 @@ func NewPublicKeyFromASN1(data []byte) (*PublicKey, error) { } // decodeCompressedY performs decompression of Y coordinate for given X and Y's least significant bit. -func decodeCompressedY(x *big.Int, ylsb uint) (*big.Int, error) { - c := elliptic.P256() - 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) +// 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) { + var a *big.Int + switch curve.Params().Name { + case Secp256k1Curve: + a = big.NewInt(0) + case elliptic.P256().Params().Name: + a = big.NewInt(3) + default: + // actually, we do support all curves from standard elliptic package as far as + // these curves have a=-3 which is hard-coded in the package + return nil, errors.New("Secp256k1 and Secp256r1 are only supported") + } + 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) @@ -196,7 +210,7 @@ func (p *PublicKey) DecodeBinary(r *io.BinReader) { } x = new(big.Int).SetBytes(xbytes) ylsb := uint(prefix & 0x1) - y, err = decodeCompressedY(x, ylsb) + y, err = decodeCompressedY(x, ylsb, p256) if err != nil { r.Err = err return @@ -254,7 +268,6 @@ func (p *PublicKey) Address() string { // Verify returns true if the signature is valid and corresponds // to the hash and public key. func (p *PublicKey) Verify(signature []byte, hash []byte) bool { - publicKey := &ecdsa.PublicKey{} publicKey.Curve = elliptic.P256() publicKey.X = p.X @@ -306,3 +319,89 @@ func (p *PublicKey) UnmarshalJSON(data []byte) error { return nil } + +// KeyRecover recovers public key from the given signature (r, s) on the given message hash using given elliptic curve. +// Algorithm source: SEC 1 Ver 2.0, section 4.1.6, pages 47-48 (https://www.secg.org/sec1-v2.pdf). +// Flag isEven denotes Y's least significant bit in decompression algorithm. +func KeyRecover(curve elliptic.Curve, r, s *big.Int, messageHash []byte, isEven bool) (PublicKey, error) { + var ( + res PublicKey + err error + ) + if r.Cmp(big.NewInt(1)) == -1 || s.Cmp(big.NewInt(1)) == -1 { + return res, errors.New("invalid signature") + } + params := curve.Params() + // calculate h = (Q + 1 + 2 * Sqrt(Q)) / N + // actually, we can skip this step for secp256k1 and secp256r1 as far as we know cofactor of these curves (h=1) + // (see section 2.4 of http://www.secg.org/sec2-v2.pdf) + num := new(big.Int).Add(new(big.Int).Add(params.P, big.NewInt(1)), new(big.Int).Mul(big.NewInt(2), new(big.Int).Sqrt(params.P))) + h := new(big.Int).Div(num, params.N) + for i := 0; i <= int(h.Int64()); i++ { + // step 1.1: x = (n * i) + r + Rx := new(big.Int).Mul(params.N, big.NewInt(int64(i))) + Rx.Add(Rx, r) + if Rx.Cmp(params.P) == 1 { + break + } + + // steps 1.2 and 1.3: get point R (Ry) + var R *big.Int + if isEven { + R, err = decodeCompressedY(Rx, 0, curve) + } else { + R, err = decodeCompressedY(Rx, 1, curve) + } + if err != nil { + return res, err + } + + // step 1.4: check n*R is point at infinity + // TODO: curve.ScalarMult suppose we're dealing with a=-3 in the elliptic curve equation. + // This line will give wrong results for secp256k1 unless we define our own implementation + // of ScalarMult for secp256k1 curve with a=0. + nRx, nR := curve.ScalarMult(Rx, R, params.N.Bytes()) + if nRx.Sign() != 0 || nR.Sign() != 0 { + continue + } + + // step 1.5: compute e + e := hashToInt(messageHash, curve) + + // step 1.6: Q = r^-1 (sR-eG) + invr := new(big.Int).ModInverse(r, params.N) + // first term. + invrS := new(big.Int).Mul(invr, s) + invrS.Mod(invrS, params.N) + // TODO: implement ScalarMult for secp256k1 + sRx, sR := curve.ScalarMult(Rx, R, invrS.Bytes()) + // second term. + e.Neg(e) + e.Mod(e, params.N) + e.Mul(e, invr) + e.Mod(e, params.N) + // TODO: it won't be difficult to implement ScalarBaseMult having ScalarMult for secp256k1. + minuseGx, minuseGy := curve.ScalarBaseMult(e.Bytes()) + // TODO: the same thing for secp256k1. + Qx, Qy := curve.Add(sRx, sR, minuseGx, minuseGy) + res.X = Qx + res.Y = Qy + } + return res, nil +} + +// copied from crypto/ecdsa +func hashToInt(hash []byte, c elliptic.Curve) *big.Int { + orderBits := c.Params().N.BitLen() + orderBytes := (orderBits + 7) / 8 + if len(hash) > orderBytes { + hash = hash[:orderBytes] + } + + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - orderBits + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} diff --git a/pkg/crypto/keys/publickey_test.go b/pkg/crypto/keys/publickey_test.go index a9c265e4bc..65b9fa1de7 100644 --- a/pkg/crypto/keys/publickey_test.go +++ b/pkg/crypto/keys/publickey_test.go @@ -1,12 +1,15 @@ package keys import ( + "crypto/elliptic" "encoding/hex" "encoding/json" + "math/big" "math/rand" "sort" "testing" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" "github.com/stretchr/testify/require" ) @@ -179,3 +182,33 @@ func TestUnmarshallJSONBadFormat(t *testing.T) { err := json.Unmarshal([]byte(str), actual) require.Error(t, err) } + +func TestRecoverSecp256r1Static(t *testing.T) { + //these data were taken from the reference testcase + b := []byte{123, 245, 126, 56, 3, 123, 197, 199, 26, 31, 212, 186, 120, 195, 168, 153, 57, 108, 234, 49, 107, 203, 44, 207, 185, 212, 187, 129, 74, 43, 225, 69} + privateKey, err := NewPrivateKeyFromBytes(b) + require.NoError(t, err) + message := []byte{72, 101, 108, 108, 111, 87, 111, 114, 108, 100} + r := new(big.Int).SetBytes([]byte{1, 85, 226, 63, 133, 113, 217, 188, 249, 22, 213, 203, 225, 199, 32, 131, 118, 23, 28, 101, 139, 211, 13, 111, 242, 158, 193, 227, 196, 106, 3, 4}) + s := new(big.Int).SetBytes([]byte{65, 174, 206, 164, 81, 34, 76, 104, 5, 49, 51, 20, 221, 183, 157, 199, 199, 47, 78, 137, 172, 99, 212, 110, 129, 72, 236, 59, 250, 81, 200, 13}) + // just ensure it's a valid signature + require.True(t, privateKey.PublicKey().Verify(append(r.Bytes(), s.Bytes()...), message)) + recoveredKey, err := KeyRecover(elliptic.P256(), r, s, message, false) + require.NoError(t, err) + require.True(t, privateKey.PublicKey().Equal(&recoveredKey)) +} + +func TestRecoverSecp256r1(t *testing.T) { + privateKey, err := NewPrivateKey() + require.NoError(t, err) + message := []byte{72, 101, 108, 108, 111, 87, 111, 114, 108, 100} + signature := privateKey.Sign(message) + r := new(big.Int).SetBytes(signature[0:32]) + s := new(big.Int).SetBytes(signature[32:64]) + require.True(t, privateKey.PublicKey().Verify(append(r.Bytes(), s.Bytes()...), hash.Sha256(message).BytesBE())) + // TODO: to test this properly, we should provide correct isEven flag. This flag denotes which one of + // the two recovered R points in decodeCompressedY method should be chosen. + recoveredKey, err := KeyRecover(elliptic.P256(), r, s, hash.Sha256(message).BytesBE(), false) + require.NoError(t, err) + require.True(t, privateKey.PublicKey().Equal(&recoveredKey)) +} diff --git a/pkg/crypto/keys/secp256k1.go b/pkg/crypto/keys/secp256k1.go new file mode 100644 index 0000000000..ab0f22f9c7 --- /dev/null +++ b/pkg/crypto/keys/secp256k1.go @@ -0,0 +1,37 @@ +package keys + +import ( + "crypto/elliptic" + "math/big" + "sync" +) + +// Secp256k1Curve represents the name of Secp256k1 elliptic curve, which is set to be "Secp256k1". +const Secp256k1Curve string = "Secp256k1" + +type secp256k1Curve struct { + *elliptic.CurveParams +} + +var ( + initonce sync.Once + secp256k1 secp256k1Curve +) + +// parameters' values are taken from section 2.4.1 of http://www.secg.org/sec2-v2.pdf +func initSecp256k1() { + secp256k1.CurveParams = &elliptic.CurveParams{Name: Secp256k1Curve} + secp256k1.P, _ = new(big.Int).SetString("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", 16) + secp256k1.N, _ = new(big.Int).SetString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16) + secp256k1.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000007", 16) + secp256k1.Gx, _ = new(big.Int).SetString("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", 16) + secp256k1.Gy, _ = new(big.Int).SetString("483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", 16) + secp256k1.BitSize = 256 +} + +// Secp256k1 returns a Curve which implements secp256k1 elliptic curve. +// The CurveParams.Name of this Curve is "Secp256k1". +func Secp256k1() elliptic.Curve { + initonce.Do(initSecp256k1) + return secp256k1 +}