Skip to content

Commit

Permalink
core: implement key recover interops
Browse files Browse the repository at this point in the history
closes #1003
  • Loading branch information
AnnaShaleva committed Jun 1, 2020
1 parent 0f6d01f commit 5001705
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 9 deletions.
29 changes: 29 additions & 0 deletions pkg/core/interop_neo.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -600,6 +602,33 @@ 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 {
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 {
Expand Down
2 changes: 2 additions & 0 deletions pkg/core/interops.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
117 changes: 108 additions & 9 deletions pkg/crypto/keys/publickey.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,30 @@ 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)
// 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()
/* 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)
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 Expand Up @@ -196,7 +211,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
Expand Down Expand Up @@ -306,3 +321,87 @@ 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.
// 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
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, curve.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
}
36 changes: 36 additions & 0 deletions pkg/crypto/keys/secp256k1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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
)

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
}

0 comments on commit 5001705

Please sign in to comment.