1
0
mirror of https://github.com/golang/go synced 2024-11-18 16:24:42 -07:00

crypto/ed25519: implement ed25519 on s390x using KDSA instruction

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>
This commit is contained in:
Ruixin(Peter) Bao 2019-10-21 13:18:31 -04:00 committed by Michael Munday
parent 6a4441d6fe
commit d646c035f9
5 changed files with 273 additions and 8 deletions

View File

@ -142,7 +142,7 @@ func Sign(privateKey PrivateKey, message []byte) []byte {
return signature return signature
} }
func sign(signature, privateKey, message []byte) { func signGeneric(signature, privateKey, message []byte) {
if l := len(privateKey); l != PrivateKeySize { if l := len(privateKey); l != PrivateKeySize {
panic("ed25519: bad private key length: " + strconv.Itoa(l)) 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 // Verify reports whether sig is a valid signature of message by publicKey. It
// will panic if len(publicKey) is not PublicKeySize. // will panic if len(publicKey) is not PublicKeySize.
func Verify(publicKey PublicKey, message, sig []byte) bool { 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 { if l := len(publicKey); l != PublicKeySize {
panic("ed25519: bad public key length: " + strconv.Itoa(l)) panic("ed25519: bad public key length: " + strconv.Itoa(l))
} }

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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

View File

@ -26,6 +26,14 @@ func (zeroReader) Read(buf []byte) (int, error) {
return len(buf), nil 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) { func TestUnmarshalMarshal(t *testing.T) {
pub, _, _ := GenerateKey(rand.Reader) pub, _, _ := GenerateKey(rand.Reader)
@ -45,22 +53,33 @@ func TestUnmarshalMarshal(t *testing.T) {
} }
func TestSignVerify(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 var zero zeroReader
public, private, _ := GenerateKey(zero) public, private, _ := GenerateKey(zero)
message := []byte("test message") message := []byte("test message")
sig := Sign(private, message) sig := signImpl(private, message)
if !Verify(public, message, sig) { if !verifyImpl(public, message, sig) {
t.Errorf("valid signature rejected") t.Errorf("valid signature rejected")
} }
wrongMessage := []byte("wrong message") wrongMessage := []byte("wrong message")
if Verify(public, wrongMessage, sig) { if verifyImpl(public, wrongMessage, sig) {
t.Errorf("signature of different message accepted") t.Errorf("signature of different message accepted")
} }
} }
func TestCryptoSigner(t *testing.T) { 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 var zero zeroReader
public, private, _ := GenerateKey(zero) public, private, _ := GenerateKey(zero)
@ -83,7 +102,7 @@ func TestCryptoSigner(t *testing.T) {
t.Fatalf("error from Sign(): %s", err) 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()") t.Errorf("Verify failed on signature from Sign()")
} }
} }
@ -105,6 +124,12 @@ func TestEqual(t *testing.T) {
} }
func TestGolden(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 // sign.input.gz is a selection of test cases from
// https://ed25519.cr.yp.to/python/sign.input // https://ed25519.cr.yp.to/python/sign.input
testDataZ, err := os.Open("testdata/sign.input.gz") testDataZ, err := os.Open("testdata/sign.input.gz")
@ -146,12 +171,12 @@ func TestGolden(t *testing.T) {
copy(priv[:], privBytes) copy(priv[:], privBytes)
copy(priv[32:], pubKey) copy(priv[32:], pubKey)
sig2 := Sign(priv[:], msg) sig2 := signImpl(priv[:], msg)
if !bytes.Equal(sig, sig2[:]) { if !bytes.Equal(sig, sig2[:]) {
t.Errorf("different signature result on line %d: %x vs %x", lineNo, 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) t.Errorf("signature failed to verify on line %d", lineNo)
} }
@ -175,6 +200,11 @@ func TestGolden(t *testing.T) {
} }
func TestMalleability(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 // 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 // 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. // 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, 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") t.Fatal("non-canonical signature accepted")
} }
} }