From d646c035f9749d647b2a3fdab48011d04e0bc2c1 Mon Sep 17 00:00:00 2001 From: "Ruixin(Peter) Bao" Date: Mon, 21 Oct 2019 13:18:31 -0400 Subject: [PATCH] crypto/ed25519: implement ed25519 on s390x using KDSA instruction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 TryBot-Result: Gobot Gobot Reviewed-by: Michael Munday --- src/crypto/ed25519/ed25519.go | 6 +- src/crypto/ed25519/ed25519_noasm.go | 15 +++ src/crypto/ed25519/ed25519_s390x.go | 53 +++++++++ src/crypto/ed25519/ed25519_s390x.s | 163 ++++++++++++++++++++++++++++ src/crypto/ed25519/ed25519_test.go | 44 ++++++-- 5 files changed, 273 insertions(+), 8 deletions(-) create mode 100644 src/crypto/ed25519/ed25519_noasm.go create mode 100644 src/crypto/ed25519/ed25519_s390x.go create mode 100644 src/crypto/ed25519/ed25519_s390x.s diff --git a/src/crypto/ed25519/ed25519.go b/src/crypto/ed25519/ed25519.go index b4f6956420..748c039dce 100644 --- a/src/crypto/ed25519/ed25519.go +++ b/src/crypto/ed25519/ed25519.go @@ -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 index 0000000000..afcc6fca68 --- /dev/null +++ b/src/crypto/ed25519/ed25519_noasm.go @@ -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 index 0000000000..3884c49ef7 --- /dev/null +++ b/src/crypto/ed25519/ed25519_s390x.go @@ -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 index 0000000000..a2e2c9abc7 --- /dev/null +++ b/src/crypto/ed25519/ed25519_s390x.s @@ -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 diff --git a/src/crypto/ed25519/ed25519_test.go b/src/crypto/ed25519/ed25519_test.go index 98e22a719e..6b5cb9d201 100644 --- a/src/crypto/ed25519/ed25519_test.go +++ b/src/crypto/ed25519/ed25519_test.go @@ -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") } }