]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/ed25519: implement ed25519 on s390x using KDSA instruction
authorRuixin(Peter) Bao <ruixin.bao@ibm.com>
Mon, 21 Oct 2019 17:18:31 +0000 (13:18 -0400)
committerMichael Munday <mike.munday@ibm.com>
Mon, 27 Apr 2020 19:49:42 +0000 (19:49 +0000)
This CL allows the usage of KDSA instruction when it is available.  The
instruction is designed to be resistant to side channel attacks and
offers performance improvement for ed25519.

Benchmarks:
name              old time/op    new time/op    delta
Signing-8            120µs ±20%      62µs ±12%   -48.40%  (p=0.000 n=10+10)
Verification-8       325µs ±17%      69µs ±10%   -78.80%  (p=0.000 n=10+10)

name              old alloc/op   new alloc/op   delta
Signing-8             448B ± 0%        0B       -100.00%  (p=0.000 n=10+10)
Verification-8        288B ± 0%        0B       -100.00%  (p=0.000 n=10+10)

name              old allocs/op  new allocs/op  delta
Signing-8             5.00 ± 0%      0.00       -100.00%  (p=0.000 n=10+10)
Verification-8        2.00 ± 0%      0.00       -100.00%  (p=0.000 n=10+10)

Change-Id: I0330ce83d807370b419ce638bc2cae4cb3c250dc
Reviewed-on: https://go-review.googlesource.com/c/go/+/202578
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/ed25519/ed25519.go
src/crypto/ed25519/ed25519_noasm.go [new file with mode: 0644]
src/crypto/ed25519/ed25519_s390x.go [new file with mode: 0644]
src/crypto/ed25519/ed25519_s390x.s [new file with mode: 0644]
src/crypto/ed25519/ed25519_test.go

index b4f69564203f369e6dee23bd52aaf5b5eb77d21d..748c039dce9876e91f5ad2224c4e760623fa64c8 100644 (file)
@@ -142,7 +142,7 @@ func Sign(privateKey PrivateKey, message []byte) []byte {
        return signature
 }
 
-func sign(signature, privateKey, message []byte) {
+func signGeneric(signature, privateKey, message []byte) {
        if l := len(privateKey); l != PrivateKeySize {
                panic("ed25519: bad private key length: " + strconv.Itoa(l))
        }
@@ -189,6 +189,10 @@ func sign(signature, privateKey, message []byte) {
 // Verify reports whether sig is a valid signature of message by publicKey. It
 // will panic if len(publicKey) is not PublicKeySize.
 func Verify(publicKey PublicKey, message, sig []byte) bool {
+       return verify(publicKey, message, sig)
+}
+
+func verifyGeneric(publicKey PublicKey, message, sig []byte) bool {
        if l := len(publicKey); l != PublicKeySize {
                panic("ed25519: bad public key length: " + strconv.Itoa(l))
        }
diff --git a/src/crypto/ed25519/ed25519_noasm.go b/src/crypto/ed25519/ed25519_noasm.go
new file mode 100644 (file)
index 0000000..afcc6fc
--- /dev/null
@@ -0,0 +1,15 @@
+// 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 purego
+
+package ed25519
+
+func sign(signature, privateKey, message []byte) {
+       signGeneric(signature, privateKey, message)
+}
+
+func verify(publicKey PublicKey, message, sig []byte) bool {
+       return verifyGeneric(publicKey, message, sig)
+}
diff --git a/src/crypto/ed25519/ed25519_s390x.go b/src/crypto/ed25519/ed25519_s390x.go
new file mode 100644 (file)
index 0000000..3884c49
--- /dev/null
@@ -0,0 +1,53 @@
+// 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 !purego
+
+package ed25519
+
+import (
+       "internal/cpu"
+       "strconv"
+)
+
+//go:noescape
+func kdsaSign(message, signature, privateKey []byte) bool
+
+//go:noescape
+func kdsaVerify(message, signature, publicKey []byte) bool
+
+// sign does a check to see if hardware has Edwards Curve instruction available.
+// If it does, use the hardware implementation. Otherwise, use the generic version.
+func sign(signature, privateKey, message []byte) {
+       if cpu.S390X.HasEDDSA {
+               if l := len(privateKey); l != PrivateKeySize {
+                       panic("ed25519: bad private key length: " + strconv.Itoa(l))
+               }
+
+               ret := kdsaSign(message, signature, privateKey[:32])
+               if !ret {
+                       panic("ed25519: kdsa sign has a failure")
+               }
+               return
+       }
+       signGeneric(signature, privateKey, message)
+}
+
+// verify does a check to see if hardware has Edwards Curve instruction available.
+// If it does, use the hardware implementation for eddsa verfication. Otherwise, the generic
+// version is used
+func verify(publicKey PublicKey, message, sig []byte) bool {
+       if cpu.S390X.HasEDDSA {
+               if l := len(publicKey); l != PublicKeySize {
+                       panic("ed25519: bad public key length: " + strconv.Itoa(l))
+               }
+
+               if len(sig) != SignatureSize || sig[63]&224 != 0 {
+                       return false
+               }
+
+               return kdsaVerify(message, sig, publicKey)
+       }
+       return verifyGeneric(publicKey, message, sig)
+}
diff --git a/src/crypto/ed25519/ed25519_s390x.s b/src/crypto/ed25519/ed25519_s390x.s
new file mode 100644 (file)
index 0000000..a2e2c9a
--- /dev/null
@@ -0,0 +1,163 @@
+// 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 !purego
+
+#include "textflag.h"
+
+// func kdsaSign(message, signature, privateKey []byte) bool
+TEXT ·kdsaSign(SB), $4096-73
+       // The kdsa instruction takes function code,
+       // buffer's location, message's location and message len
+       // as parameters. Out of those, the function code and buffer's location
+       // should be placed in R0 and R1 respectively. The message's location
+       // and message length should be placed in an even-odd register pair. (e.g: R2 and R3)
+
+       // The content of parameter block(buffer) looks like the following:
+       // Signature R, Signature S and Private Key all take 32 bytes.
+       // In the signing case, the signatures(R and S) will be generated by
+       // the signing instruction and get placed in the locations shown in the parameter block.
+       //    0 +---------------+
+       //      |  Signature(R) |
+       //   32 +---------------+
+       //      |  Signature(S) |
+       //   64 +---------------+
+       //      |  Private Key  |
+       //   96 +---------------+
+       //      |   Reserved    |
+       //   112+---------------+
+       //      |               |
+       //      |     ...       |
+       //      |               |
+       // 4088 +---------------+
+
+       // The following code section setups the buffer from stack:
+       // Get the address of the buffer stack variable.
+       MOVD $buffer-4096(SP), R1
+
+       // Zero the buffer.
+       MOVD R1, R2
+       MOVD $(4096/256), R0 // number of 256 byte chunks to clear
+
+clear:
+       XC    $256, (R2), (R2)
+       MOVD  $256(R2), R2
+       BRCTG R0, clear
+
+       MOVD $40, R0                   // EDDSA-25519 sign has a function code of 40
+       LMG  message+0(FP), R2, R3     // R2=base R3=len
+       LMG  signature+24(FP), R4, R5  // R4=base R5=len
+       LMG  privateKey+48(FP), R6, R7 // R6=base R7=len
+
+       // Checks the length of signature and private key
+       CMPBNE R5, $64, panic
+       CMPBNE R7, $32, panic
+
+       // The instruction uses RFC 8032's private key, which is the first 32 bytes
+       // of the private key in this package. So we copy that into the buffer.
+       MVC $32, (R6), 64(R1)
+
+loop:
+       WORD $0xB93A0002 // The KDSA instruction
+       BVS  loop        // The instruction is exectued by hardware and can be interrupted. This does a retry when that happens.
+       BNE  error
+
+success:
+       // The signatures generated are in big-endian form, so we
+       // need to reverse the bytes of Signature(R) and Signature(S) in the buffers to transform
+       // them from big-endian to little-endian.
+
+       // Transform Signature(R) from big endian to little endian and copy into the signature
+       MVCIN $32, 31(R1), (R4)
+
+       // Transform Signature(S) from big endian to little endian and copy into the signature
+       MVCIN $32, 63(R1), 32(R4)
+
+       MOVB $1, ret+72(FP)
+       RET
+
+error:
+       // return false
+       MOVB $0, ret+72(FP)
+       RET
+
+panic:
+       UNDEF
+
+// func kdsaVerify(message, signature, publicKey []byte) bool
+TEXT ·kdsaVerify(SB), $4096-73
+       // The kdsa instruction takes function code,
+       // buffer's location, message's location and message len
+       // as parameters. Out of those, the function code and buffer's location
+       // should be placed in R0 and R1 respectively. The message's location
+       // and message length should be placed in an even-odd register pair. (e.g: R2 and R3)
+
+       // The parameter block(buffer) is similar to that of signing, except that
+       // we use public key for verification, and Signatures(R and S) are provided
+       // as input parameters to the parameter block.
+       //    0 +---------------+
+       //      |  Signature(R) |
+       //   32 +---------------+
+       //      |  Signature(S) |
+       //   64 +---------------+
+       //      |  Public Key   |
+       //   96 +---------------+
+       //      |   Reserved    |
+       //   112+---------------+
+       //      |               |
+       //      |     ...       |
+       //      |               |
+       // 4088 +---------------+
+
+       // The following code section setups the buffer from stack:
+       // Get the address of the buffer stack variable.
+       MOVD $buffer-4096(SP), R1
+
+       // Zero the buffer.
+       MOVD R1, R2
+       MOVD $(4096/256), R0 // number of 256 byte chunks to clear
+
+clear:
+       XC    $256, (R2), (R2)
+       MOVD  $256(R2), R2
+       BRCTG R0, clear
+
+       MOVD $32, R0                  // EDDSA-25519 verify has a function code of 32
+       LMG  message+0(FP), R2, R3    // R2=base R3=len
+       LMG  signature+24(FP), R4, R5 // R4=base R5=len
+       LMG  publicKey+48(FP), R6, R7 // R6=base R7=len
+
+       // Checks the length of public key and signature
+       CMPBNE R5, $64, panic
+       CMPBNE R7, $32, panic
+
+verify:
+       // The instruction needs Signature(R), Signature(S) and public key
+       // to be in big-endian form during computation. Therefore,
+       // we do the transformation (from little endian to big endian) and copy those into the buffer.
+
+       // Transform Signature(R) from little endian to big endian and copy into the buffer
+       MVCIN $32, 31(R4), (R1)
+
+       // Transform Signature(S) from little endian to big endian and copy into the buffer
+       MVCIN $32, 63(R4), 32(R1)
+
+       // Transform Public Key from little endian to big endian and copy into the buffer
+       MVCIN $32, 31(R6), 64(R1)
+
+verifyLoop:
+       WORD $0xB93A0002 // KDSA instruction
+       BVS  verifyLoop  // Retry upon hardware interrupt
+       BNE  error
+
+success:
+       MOVB $1, ret+72(FP)
+       RET
+
+error:
+       MOVB $0, ret+72(FP)
+       RET
+
+panic:
+       UNDEF
index 98e22a719ea04d33e6f96f08f50751ddfdcd2d00..6b5cb9d20188e7ab472981064515e266ab565adc 100644 (file)
@@ -26,6 +26,14 @@ func (zeroReader) Read(buf []byte) (int, error) {
        return len(buf), nil
 }
 
+// signGenericWrapper is identical to Sign except that it unconditionally calls signGeneric directly
+// rather than going through the sign function that might call assembly code.
+func signGenericWrapper(privateKey PrivateKey, msg []byte) []byte {
+       sig := make([]byte, SignatureSize)
+       signGeneric(sig, privateKey, msg)
+       return sig
+}
+
 func TestUnmarshalMarshal(t *testing.T) {
        pub, _, _ := GenerateKey(rand.Reader)
 
@@ -45,22 +53,33 @@ func TestUnmarshalMarshal(t *testing.T) {
 }
 
 func TestSignVerify(t *testing.T) {
+       t.Run("Generic", func(t *testing.T) { testSignVerify(t, signGenericWrapper, verifyGeneric) })
+       t.Run("Native", func(t *testing.T) { testSignVerify(t, Sign, Verify) })
+}
+
+func testSignVerify(t *testing.T, signImpl func(privateKey PrivateKey, message []byte) []byte,
+       verifyImpl func(publicKey PublicKey, message, sig []byte) bool) {
        var zero zeroReader
        public, private, _ := GenerateKey(zero)
 
        message := []byte("test message")
-       sig := Sign(private, message)
-       if !Verify(public, message, sig) {
+       sig := signImpl(private, message)
+       if !verifyImpl(public, message, sig) {
                t.Errorf("valid signature rejected")
        }
 
        wrongMessage := []byte("wrong message")
-       if Verify(public, wrongMessage, sig) {
+       if verifyImpl(public, wrongMessage, sig) {
                t.Errorf("signature of different message accepted")
        }
 }
 
 func TestCryptoSigner(t *testing.T) {
+       t.Run("Generic", func(t *testing.T) { testCryptoSigner(t, verifyGeneric) })
+       t.Run("Native", func(t *testing.T) { testCryptoSigner(t, Verify) })
+}
+
+func testCryptoSigner(t *testing.T, verifyImpl func(publicKey PublicKey, message, sig []byte) bool) {
        var zero zeroReader
        public, private, _ := GenerateKey(zero)
 
@@ -83,7 +102,7 @@ func TestCryptoSigner(t *testing.T) {
                t.Fatalf("error from Sign(): %s", err)
        }
 
-       if !Verify(public, message, signature) {
+       if !verifyImpl(public, message, signature) {
                t.Errorf("Verify failed on signature from Sign()")
        }
 }
@@ -105,6 +124,12 @@ func TestEqual(t *testing.T) {
 }
 
 func TestGolden(t *testing.T) {
+       t.Run("Generic", func(t *testing.T) { testGolden(t, signGenericWrapper, verifyGeneric) })
+       t.Run("Native", func(t *testing.T) { testGolden(t, Sign, Verify) })
+}
+
+func testGolden(t *testing.T, signImpl func(privateKey PrivateKey, message []byte) []byte,
+       verifyImpl func(publicKey PublicKey, message, sig []byte) bool) {
        // sign.input.gz is a selection of test cases from
        // https://ed25519.cr.yp.to/python/sign.input
        testDataZ, err := os.Open("testdata/sign.input.gz")
@@ -146,12 +171,12 @@ func TestGolden(t *testing.T) {
                copy(priv[:], privBytes)
                copy(priv[32:], pubKey)
 
-               sig2 := Sign(priv[:], msg)
+               sig2 := signImpl(priv[:], msg)
                if !bytes.Equal(sig, sig2[:]) {
                        t.Errorf("different signature result on line %d: %x vs %x", lineNo, sig, sig2)
                }
 
-               if !Verify(pubKey, msg, sig2) {
+               if !verifyImpl(pubKey, msg, sig2) {
                        t.Errorf("signature failed to verify on line %d", lineNo)
                }
 
@@ -175,6 +200,11 @@ func TestGolden(t *testing.T) {
 }
 
 func TestMalleability(t *testing.T) {
+       t.Run("Generic", func(t *testing.T) { testMalleability(t, verifyGeneric) })
+       t.Run("Native", func(t *testing.T) { testMalleability(t, Verify) })
+}
+
+func testMalleability(t *testing.T, verifyImpl func(publicKey PublicKey, message, sig []byte) bool) {
        // https://tools.ietf.org/html/rfc8032#section-5.1.7 adds an additional test
        // that s be in [0, order). This prevents someone from adding a multiple of
        // order to s and obtaining a second valid signature for the same message.
@@ -193,7 +223,7 @@ func TestMalleability(t *testing.T) {
                0xb1, 0x08, 0xc3, 0xbd, 0xae, 0x36, 0x9e, 0xf5, 0x49, 0xfa,
        }
 
-       if Verify(publicKey, msg, sig) {
+       if verifyImpl(publicKey, msg, sig) {
                t.Fatal("non-canonical signature accepted")
        }
 }