]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/ecdsa: implement ecdsa on s390x for P256/P384/P521 using KDSA instruction
authorRuixin Bao <ruixin.bao@ibm.com>
Wed, 15 Apr 2020 20:50:50 +0000 (16:50 -0400)
committerMichael Munday <mike.munday@ibm.com>
Mon, 27 Apr 2020 19:49:49 +0000 (19:49 +0000)
This CL revives CL 174437(also IBM CLA) and adds benchmarks and some simplifications.

The original commit message is as follows:

Utilize KDSA when available. This guarantees constant time operation on all three curves mentioned,
and is faster than conventional assembly.

Benchmarks:
name             old time/op    new time/op    delta
SignP256-8         15.2µs ±14%    14.1µs ±18%     ~     (p=0.356 n=9+10)
SignP384-8         4.28ms ±26%    0.02ms ±30%  -99.43%  (p=0.000 n=10+10)
VerifyP256-8       33.6µs ±13%    13.3µs ±38%  -60.32%  (p=0.000 n=9+10)

name             old alloc/op   new alloc/op   delta
SignP256-8         2.16kB ± 0%    1.60kB ± 0%  -25.63%  (p=0.000 n=9+10)
SignP384-8         1.75MB ± 0%    0.00MB ± 0%  -99.90%  (p=0.000 n=9+10)
VerifyP256-8       1.08kB ± 0%    0.18kB ± 0%  -83.70%  (p=0.000 n=9+10)

name             old allocs/op  new allocs/op  delta
SignP256-8           29.0 ± 0%      22.0 ± 0%  -24.14%  (p=0.000 n=10+10)
SignP384-8          14.4k ± 0%      0.0k ± 0%  -99.85%  (p=0.000 n=9+10)
VerifyP256-8         23.0 ± 0%       7.0 ± 0%  -69.57%  (p=0.000 n=10+10)

Change-Id: Ifa1fc5917fa7592dd592affa7549147dbc9b4169
Reviewed-on: https://go-review.googlesource.com/c/go/+/228580
Run-TryBot: Michael Munday <mike.munday@ibm.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Michael Munday <mike.munday@ibm.com>
src/crypto/ecdsa/ecdsa.go
src/crypto/ecdsa/ecdsa_noasm.go [new file with mode: 0644]
src/crypto/ecdsa/ecdsa_s390x.go [new file with mode: 0644]
src/crypto/ecdsa/ecdsa_s390x.s [new file with mode: 0644]
src/crypto/ecdsa/ecdsa_s390x_test.go [new file with mode: 0644]

index 189399d12646d350517de6d293a9148021842259..786b8a988487f0560e90c86e09af994a4cb805fb 100644 (file)
@@ -220,6 +220,10 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err
 
        // See [NSA] 3.4.1
        c := priv.PublicKey.Curve
+       return sign(priv, &csprng, c, hash)
+}
+
+func signGeneric(priv *PrivateKey, csprng *cipher.StreamReader, c elliptic.Curve, hash []byte) (r, s *big.Int, err error) {
        N := c.Params().N
        if N.Sign() == 0 {
                return nil, nil, errZeroParam
@@ -227,7 +231,7 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err
        var k, kInv *big.Int
        for {
                for {
-                       k, err = randFieldElement(c, csprng)
+                       k, err = randFieldElement(c, *csprng)
                        if err != nil {
                                r = nil
                                return
@@ -281,9 +285,13 @@ func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
        if r.Cmp(N) >= 0 || s.Cmp(N) >= 0 {
                return false
        }
-       e := hashToInt(hash, c)
+       return verify(pub, c, hash, r, s)
+}
 
+func verifyGeneric(pub *PublicKey, c elliptic.Curve, hash []byte, r, s *big.Int) bool {
+       e := hashToInt(hash, c)
        var w *big.Int
+       N := c.Params().N
        if in, ok := c.(invertible); ok {
                w = in.Inverse(s)
        } else {
diff --git a/src/crypto/ecdsa/ecdsa_noasm.go b/src/crypto/ecdsa/ecdsa_noasm.go
new file mode 100644 (file)
index 0000000..7219621
--- /dev/null
@@ -0,0 +1,21 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !s390x
+
+package ecdsa
+
+import (
+       "crypto/cipher"
+       "crypto/elliptic"
+       "math/big"
+)
+
+func sign(priv *PrivateKey, csprng *cipher.StreamReader, c elliptic.Curve, hash []byte) (r, s *big.Int, err error) {
+       return signGeneric(priv, csprng, c, hash)
+}
+
+func verify(pub *PublicKey, c elliptic.Curve, hash []byte, r, s *big.Int) bool {
+       return verifyGeneric(pub, c, hash, r, s)
+}
diff --git a/src/crypto/ecdsa/ecdsa_s390x.go b/src/crypto/ecdsa/ecdsa_s390x.go
new file mode 100644 (file)
index 0000000..d8d2c71
--- /dev/null
@@ -0,0 +1,162 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ecdsa
+
+import (
+       "crypto/cipher"
+       "crypto/elliptic"
+       "internal/cpu"
+       "math/big"
+)
+
+// kdsa invokes the "compute digital signature authentication"
+// instruction with the given function code and 4096 byte
+// parameter block.
+//
+// The return value corresponds to the condition code set by the
+// instruction. Interrupted invocations are handled by the
+// function.
+//go:noescape
+func kdsa(fc uint64, params *[4096]byte) (errn uint64)
+
+// canUseKDSA checks if KDSA instruction is available, and if it is, it checks
+// the name of the curve to see if it matches the curves supported(P-256, P-384, P-521).
+// Then, based on the curve name, a function code and a block size will be assigned.
+// If KDSA instruction is not available or if the curve is not supported, canUseKDSA
+// will set ok to false.
+func canUseKDSA(c elliptic.Curve) (functionCode uint64, blockSize int, ok bool) {
+       if !cpu.S390X.HasECDSA {
+               return 0, 0, false
+       }
+       switch c.Params().Name {
+       case "P-256":
+               return 1, 32, true
+       case "P-384":
+               return 2, 48, true
+       case "P-521":
+               return 3, 80, true
+       }
+       return 0, 0, false // A mismatch
+}
+
+// zeroExtendAndCopy pads src with leading zeros until it has the size given.
+// It then copies the padded src into the dst. Bytes beyond size in dst are
+// not modified.
+func zeroExtendAndCopy(dst, src []byte, size int) {
+       nz := size - len(src)
+       if nz < 0 {
+               panic("src is too long")
+       }
+       // the compiler should replace this loop with a memclr call
+       z := dst[:nz]
+       for i := range z {
+               z[i] = 0
+       }
+       copy(dst[nz:size], src[:size-nz])
+       return
+}
+
+func sign(priv *PrivateKey, csprng *cipher.StreamReader, c elliptic.Curve, hash []byte) (r, s *big.Int, err error) {
+       if functionCode, blockSize, ok := canUseKDSA(c); ok {
+               e := hashToInt(hash, c)
+               for {
+                       var k *big.Int
+                       k, err = randFieldElement(c, *csprng)
+                       if err != nil {
+                               return nil, nil, err
+                       }
+
+                       // The parameter block looks like the following for sign.
+                       //      +---------------------+
+                       //      |   Signature(R)      |
+                       //      +---------------------+
+                       //      |   Signature(S)      |
+                       //      +---------------------+
+                       //      |   Hashed Message    |
+                       //      +---------------------+
+                       //      |   Private Key       |
+                       //      +---------------------+
+                       //      |   Random Number     |
+                       //      +---------------------+
+                       //      |                     |
+                       //      |        ...          |
+                       //      |                     |
+                       //      +---------------------+
+                       // The common components(signatureR, signatureS, hashedMessage, privateKey and
+                       // random number) each takes block size of bytes. The block size is different for
+                       // different curves and is set by canUseKDSA function.
+                       var params [4096]byte
+
+                       startingOffset := 2 * blockSize // Set the starting location for copying
+                       // Copy content into the parameter block. In the sign case,
+                       // we copy hashed message, private key and random number into
+                       // the parameter block. Since those are consecutive components in the parameter
+                       // block, we use a for loop here.
+                       for i, v := range []*big.Int{e, priv.D, k} {
+                               startPosition := startingOffset + i*blockSize
+                               endPosition := startPosition + blockSize
+                               zeroExtendAndCopy(params[startPosition:endPosition], v.Bytes(), blockSize)
+                       }
+
+                       // Convert verify function code into a sign function code by adding 8.
+                       // We also need to set the 'deterministic' bit in the function code, by
+                       // adding 128, in order to stop the instruction using its own random number
+                       // generator in addition to the random number we supply.
+                       switch kdsa(functionCode+136, &params) {
+                       case 0: // success
+                               r = new(big.Int)
+                               r.SetBytes(params[:blockSize])
+                               s = new(big.Int)
+                               s.SetBytes(params[blockSize : 2*blockSize])
+                               return
+                       case 1: // error
+                               return nil, nil, errZeroParam
+                       case 2: // retry
+                               continue
+                       }
+                       panic("unreachable")
+               }
+       }
+       return signGeneric(priv, csprng, c, hash)
+}
+
+func verify(pub *PublicKey, c elliptic.Curve, hash []byte, r, s *big.Int) bool {
+       if functionCode, blockSize, ok := canUseKDSA(c); ok {
+               e := hashToInt(hash, c)
+               // The parameter block looks like the following for verify:
+               //      +---------------------+
+               //      |   Signature(R)      |
+               //      +---------------------+
+               //      |   Signature(S)      |
+               //      +---------------------+
+               //      |   Hashed Message    |
+               //      +---------------------+
+               //      |   Public Key X      |
+               //      +---------------------+
+               //      |   Public Key Y      |
+               //      +---------------------+
+               //      |                     |
+               //      |        ...          |
+               //      |                     |
+               //      +---------------------+
+               // The common components(signatureR, signatureS, hashed message, public key X,
+               // and public key Y) each takes block size of bytes. The block size is different for
+               // different curves and is set by canUseKDSA function.
+               var params [4096]byte
+
+               // Copy content into the parameter block. In the verify case,
+               // we copy signature (r), signature(s), hashed message, public key x component,
+               // and public key y component into the parameter block.
+               // Since those are consecutive components in the parameter block, we use a for loop here.
+               for i, v := range []*big.Int{r, s, e, pub.X, pub.Y} {
+                       startPosition := i * blockSize
+                       endPosition := startPosition + blockSize
+                       zeroExtendAndCopy(params[startPosition:endPosition], v.Bytes(), blockSize)
+               }
+
+               return kdsa(functionCode, &params) == 0
+       }
+       return verifyGeneric(pub, c, hash, r, s)
+}
diff --git a/src/crypto/ecdsa/ecdsa_s390x.s b/src/crypto/ecdsa/ecdsa_s390x.s
new file mode 100644 (file)
index 0000000..ba5b3bf
--- /dev/null
@@ -0,0 +1,28 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "textflag.h"
+
+// func kdsa(fc uint64, params *[4096]byte) (errn uint64)
+TEXT ·kdsa(SB), NOSPLIT|NOFRAME, $0-24
+       MOVD fc+0(FP), R0     // function code
+       MOVD params+8(FP), R1 // address parameter block
+
+loop:
+       WORD $0xB93A0008 // compute digital signature authentication
+       BVS  loop        // branch back if interrupted
+       BGT  retry       // signing unsuccessful, but retry with new CSPRN
+       BLT  error       // condition code of 1 indicates a failure
+
+success:
+       MOVD $0, errn+16(FP) // return 0 - sign/verify was successful
+       RET
+
+error:
+       MOVD $1, errn+16(FP) // return 1 - sign/verify failed
+       RET
+
+retry:
+       MOVD $2, errn+16(FP) // return 2 - sign/verify was unsuccessful -- if sign, retry with new RN
+       RET
diff --git a/src/crypto/ecdsa/ecdsa_s390x_test.go b/src/crypto/ecdsa/ecdsa_s390x_test.go
new file mode 100644 (file)
index 0000000..a434575
--- /dev/null
@@ -0,0 +1,33 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build s390x
+
+package ecdsa
+
+import (
+       "crypto/elliptic"
+       "testing"
+)
+
+func TestNoAsm(t *testing.T) {
+       curves := [...]elliptic.Curve{
+               elliptic.P256(),
+               elliptic.P384(),
+               elliptic.P521(),
+       }
+
+       for _, curve := range curves {
+               // override the name of the curve to stop the assembly path being taken
+               params := *curve.Params()
+               name := params.Name
+               params.Name = name + "_GENERIC_OVERRIDE"
+
+               testKeyGeneration(t, &params, name)
+               testSignAndVerify(t, &params, name)
+               testNonceSafety(t, &params, name)
+               testINDCCA(t, &params, name)
+               testNegativeInputs(t, &params, name)
+       }
+}