From: Ruixin Bao Date: Wed, 15 Apr 2020 20:50:50 +0000 (-0400) Subject: crypto/ecdsa: implement ecdsa on s390x for P256/P384/P521 using KDSA instruction X-Git-Tag: go1.15beta1~343 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=a637ee1970ac9f005094fd46eadd64c4433168e7;p=gostls13.git crypto/ecdsa: implement ecdsa on s390x for P256/P384/P521 using KDSA instruction 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 TryBot-Result: Gobot Gobot Reviewed-by: Michael Munday --- diff --git a/src/crypto/ecdsa/ecdsa.go b/src/crypto/ecdsa/ecdsa.go index 189399d126..786b8a9884 100644 --- a/src/crypto/ecdsa/ecdsa.go +++ b/src/crypto/ecdsa/ecdsa.go @@ -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 index 0000000000..72196211e5 --- /dev/null +++ b/src/crypto/ecdsa/ecdsa_noasm.go @@ -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 index 0000000000..d8d2c716db --- /dev/null +++ b/src/crypto/ecdsa/ecdsa_s390x.go @@ -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, ¶ms) { + 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, ¶ms) == 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 index 0000000000..ba5b3bf9fa --- /dev/null +++ b/src/crypto/ecdsa/ecdsa_s390x.s @@ -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 index 0000000000..a434575dbc --- /dev/null +++ b/src/crypto/ecdsa/ecdsa_s390x_test.go @@ -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, ¶ms, name) + testSignAndVerify(t, ¶ms, name) + testNonceSafety(t, ¶ms, name) + testINDCCA(t, ¶ms, name) + testNegativeInputs(t, ¶ms, name) + } +}