The new code is meant to be readable without external references for Poly1305, and explains the field logic. The generic code is now 30-50% faster on a Intel(R) Xeon(R) CPU E5-2690 v3 @ 2.60GHz, and even better on a 3.1 GHz i7 MacBook. name old time/op new time/op delta 64-48 126ns ± 0% 80ns ± 1% -36.24% (p=0.000 n=16+20) 1K-48 1.07µs ± 0% 0.81µs ± 2% -23.63% (p=0.000 n=19+20) 2M-48 2.07ms ± 0% 1.61ms ± 1% -22.31% (p=0.000 n=20+20) Write64-48 79.3ns ± 0% 58.0ns ± 1% -26.89% (p=0.000 n=20+19) Write1K-48 1.02µs ± 0% 0.79µs ± 1% -22.91% (p=0.000 n=19+19) Write2M-48 2.07ms ± 0% 1.61ms ± 2% -22.33% (p=0.000 n=17+20) name old speed new speed delta 64-48 508MB/s ± 0% 797MB/s ± 1% +56.95% (p=0.000 n=16+20) 1K-48 960MB/s ± 0% 1257MB/s ± 2% +30.94% (p=0.000 n=18+20) 2M-48 1.01GB/s ± 0% 1.30GB/s ± 1% +28.73% (p=0.000 n=20+20) Write64-48 807MB/s ± 0% 1104MB/s ± 1% +36.78% (p=0.000 n=18+19) Write1K-48 1.00GB/s ± 0% 1.30GB/s ± 1% +29.71% (p=0.000 n=18+19) Write2M-48 1.01GB/s ± 0% 1.31GB/s ± 2% +28.77% (p=0.000 n=17+20) The assembly is still 50-90% faster on the Xeon, 30-60% on the MacBook. The Go code does not use all the arithmetic tricks the assembly does, and it does not have access to the three operand wide shift instruction. name old time/op new time/op delta 64-48 80.3ns ± 1% 54.2ns ± 0% -32.50% (p=0.000 n=20+17) 1K-48 815ns ± 2% 446ns ± 1% -45.27% (p=0.000 n=20+20) 2M-48 1.61ms ± 1% 0.86ms ± 0% -46.54% (p=0.000 n=20+17) Write64-48 58.0ns ± 1% 34.0ns ± 0% -41.34% (p=0.000 n=19+20) Write1K-48 790ns ± 1% 427ns ± 0% -45.92% (p=0.000 n=19+17) Write2M-48 1.61ms ± 2% 0.86ms ± 0% -46.51% (p=0.000 n=20+20) name old speed new speed delta 64-48 797MB/s ± 1% 1180MB/s ± 0% +48.09% (p=0.000 n=20+19) 1K-48 1.26GB/s ± 2% 2.30GB/s ± 1% +82.71% (p=0.000 n=20+20) 2M-48 1.30GB/s ± 1% 2.44GB/s ± 0% +87.04% (p=0.000 n=20+17) Write64-48 1.10GB/s ± 1% 1.88GB/s ± 0% +70.52% (p=0.000 n=19+18) Write1K-48 1.30GB/s ± 1% 2.40GB/s ± 0% +84.84% (p=0.000 n=19+18) Write2M-48 1.31GB/s ± 2% 2.44GB/s ± 0% +86.93% (p=0.000 n=20+20) Hopefully this will also avoid the need for an arm64 implementation. Since now the Go and the amd64/ppc64le assembly use the same limb schedule, drop the assembly initialize and finalize implementations, and make the wrapper code match. It comes with a minor slowdown. name old time/op new time/op delta 64-48 50.3ns ± 0% 54.2ns ± 0% +7.73% (p=0.000 n=20+17) 1K-48 441ns ± 0% 446ns ± 1% +1.10% (p=0.000 n=19+20) 2M-48 860µs ± 0% 859µs ± 0% ~ (p=0.178 n=19+17) Write64-48 34.0ns ± 0% 34.0ns ± 0% ~ (all equal) Write1K-48 424ns ± 0% 427ns ± 0% +0.71% (p=0.000 n=17+17) Write2M-48 860µs ± 0% 859µs ± 0% -0.04% (p=0.000 n=19+20) name old speed new speed delta 64-48 1.27GB/s ± 0% 1.18GB/s ± 0% -7.20% (p=0.000 n=20+19) 1K-48 2.32GB/s ± 0% 2.30GB/s ± 1% -1.07% (p=0.000 n=18+20) 2M-48 2.44GB/s ± 0% 2.44GB/s ± 0% ~ (p=0.173 n=19+17) Write64-48 1.88GB/s ± 0% 1.88GB/s ± 0% +0.04% (p=0.000 n=19+18) Write1K-48 2.41GB/s ± 0% 2.40GB/s ± 0% -0.67% (p=0.000 n=19+18) Write2M-48 2.44GB/s ± 0% 2.44GB/s ± 0% +0.04% (p=0.000 n=19+20) Since poly1305/sum_generic.go was almost entirely rewritten, it's probably best reviewed on gitiles. This is the implementation published at https://blog.filippo.io/a-literate-go-implementation-of-poly1305/ Updates #31470 Change-Id: I74f9011d3ee317a43b05ae7f05d96081d08bffd3 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/169037 Reviewed-by: Katie Hockman <katie@golang.org>
213 lines
5.4 KiB
Go
213 lines
5.4 KiB
Go
// Copyright 2012 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 poly1305
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"flag"
|
|
"testing"
|
|
"unsafe"
|
|
)
|
|
|
|
var stressFlag = flag.Bool("stress", false, "run slow stress tests")
|
|
|
|
type test struct {
|
|
in string
|
|
key string
|
|
tag string
|
|
}
|
|
|
|
func (t *test) Input() []byte {
|
|
in, err := hex.DecodeString(t.in)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return in
|
|
}
|
|
|
|
func (t *test) Key() [32]byte {
|
|
buf, err := hex.DecodeString(t.key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
var key [32]byte
|
|
copy(key[:], buf[:32])
|
|
return key
|
|
}
|
|
|
|
func (t *test) Tag() [16]byte {
|
|
buf, err := hex.DecodeString(t.tag)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
var tag [16]byte
|
|
copy(tag[:], buf[:16])
|
|
return tag
|
|
}
|
|
|
|
func testSum(t *testing.T, unaligned bool, sumImpl func(tag *[TagSize]byte, msg []byte, key *[32]byte)) {
|
|
var tag [16]byte
|
|
for i, v := range testData {
|
|
in := v.Input()
|
|
if unaligned {
|
|
in = unalignBytes(in)
|
|
}
|
|
key := v.Key()
|
|
sumImpl(&tag, in, &key)
|
|
if tag != v.Tag() {
|
|
t.Errorf("%d: expected %x, got %x", i, v.Tag(), tag[:])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBurnin(t *testing.T) {
|
|
// This test can be used to sanity-check significant changes. It can
|
|
// take about many minutes to run, even on fast machines. It's disabled
|
|
// by default.
|
|
if !*stressFlag {
|
|
t.Skip("skipping without -stress")
|
|
}
|
|
|
|
var key [32]byte
|
|
var input [25]byte
|
|
var output [16]byte
|
|
|
|
for i := range key {
|
|
key[i] = 1
|
|
}
|
|
for i := range input {
|
|
input[i] = 2
|
|
}
|
|
|
|
for i := uint64(0); i < 1e10; i++ {
|
|
Sum(&output, input[:], &key)
|
|
copy(key[0:], output[:])
|
|
copy(key[16:], output[:])
|
|
copy(input[:], output[:])
|
|
copy(input[16:], output[:])
|
|
}
|
|
|
|
const expected = "5e3b866aea0b636d240c83c428f84bfa"
|
|
if got := hex.EncodeToString(output[:]); got != expected {
|
|
t.Errorf("expected %s, got %s", expected, got)
|
|
}
|
|
}
|
|
|
|
func TestSum(t *testing.T) { testSum(t, false, Sum) }
|
|
func TestSumUnaligned(t *testing.T) { testSum(t, true, Sum) }
|
|
func TestSumGeneric(t *testing.T) { testSum(t, false, sumGeneric) }
|
|
func TestSumGenericUnaligned(t *testing.T) { testSum(t, true, sumGeneric) }
|
|
|
|
func TestWriteGeneric(t *testing.T) { testWriteGeneric(t, false) }
|
|
func TestWriteGenericUnaligned(t *testing.T) { testWriteGeneric(t, true) }
|
|
func TestWrite(t *testing.T) { testWrite(t, false) }
|
|
func TestWriteUnaligned(t *testing.T) { testWrite(t, true) }
|
|
|
|
func testWriteGeneric(t *testing.T, unaligned bool) {
|
|
for i, v := range testData {
|
|
key := v.Key()
|
|
input := v.Input()
|
|
var out [16]byte
|
|
|
|
if unaligned {
|
|
input = unalignBytes(input)
|
|
}
|
|
h := newMACGeneric(&key)
|
|
n, err := h.Write(input[:len(input)/3])
|
|
if err != nil || n != len(input[:len(input)/3]) {
|
|
t.Errorf("#%d: unexpected Write results: n = %d, err = %v", i, n, err)
|
|
}
|
|
n, err = h.Write(input[len(input)/3:])
|
|
if err != nil || n != len(input[len(input)/3:]) {
|
|
t.Errorf("#%d: unexpected Write results: n = %d, err = %v", i, n, err)
|
|
}
|
|
h.Sum(&out)
|
|
if tag := v.Tag(); out != tag {
|
|
t.Errorf("%d: expected %x, got %x", i, tag[:], out[:])
|
|
}
|
|
}
|
|
}
|
|
|
|
func testWrite(t *testing.T, unaligned bool) {
|
|
for i, v := range testData {
|
|
key := v.Key()
|
|
input := v.Input()
|
|
var out [16]byte
|
|
|
|
if unaligned {
|
|
input = unalignBytes(input)
|
|
}
|
|
h := New(&key)
|
|
n, err := h.Write(input[:len(input)/3])
|
|
if err != nil || n != len(input[:len(input)/3]) {
|
|
t.Errorf("#%d: unexpected Write results: n = %d, err = %v", i, n, err)
|
|
}
|
|
n, err = h.Write(input[len(input)/3:])
|
|
if err != nil || n != len(input[len(input)/3:]) {
|
|
t.Errorf("#%d: unexpected Write results: n = %d, err = %v", i, n, err)
|
|
}
|
|
h.Sum(out[:0])
|
|
if tag := v.Tag(); out != tag {
|
|
t.Errorf("%d: expected %x, got %x", i, tag[:], out[:])
|
|
}
|
|
}
|
|
}
|
|
|
|
func benchmarkSum(b *testing.B, size int, unaligned bool) {
|
|
var out [16]byte
|
|
var key [32]byte
|
|
in := make([]byte, size)
|
|
if unaligned {
|
|
in = unalignBytes(in)
|
|
}
|
|
rand.Read(in)
|
|
b.SetBytes(int64(len(in)))
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
Sum(&out, in, &key)
|
|
}
|
|
}
|
|
|
|
func benchmarkWrite(b *testing.B, size int, unaligned bool) {
|
|
var key [32]byte
|
|
h := New(&key)
|
|
in := make([]byte, size)
|
|
if unaligned {
|
|
in = unalignBytes(in)
|
|
}
|
|
rand.Read(in)
|
|
b.SetBytes(int64(len(in)))
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
h.Write(in)
|
|
}
|
|
}
|
|
|
|
func Benchmark64(b *testing.B) { benchmarkSum(b, 64, false) }
|
|
func Benchmark1K(b *testing.B) { benchmarkSum(b, 1024, false) }
|
|
func Benchmark2M(b *testing.B) { benchmarkSum(b, 2*1024*1024, false) }
|
|
func Benchmark64Unaligned(b *testing.B) { benchmarkSum(b, 64, true) }
|
|
func Benchmark1KUnaligned(b *testing.B) { benchmarkSum(b, 1024, true) }
|
|
func Benchmark2MUnaligned(b *testing.B) { benchmarkSum(b, 2*1024*1024, true) }
|
|
|
|
func BenchmarkWrite64(b *testing.B) { benchmarkWrite(b, 64, false) }
|
|
func BenchmarkWrite1K(b *testing.B) { benchmarkWrite(b, 1024, false) }
|
|
func BenchmarkWrite2M(b *testing.B) { benchmarkWrite(b, 2*1024*1024, false) }
|
|
func BenchmarkWrite64Unaligned(b *testing.B) { benchmarkWrite(b, 64, true) }
|
|
func BenchmarkWrite1KUnaligned(b *testing.B) { benchmarkWrite(b, 1024, true) }
|
|
func BenchmarkWrite2MUnaligned(b *testing.B) { benchmarkWrite(b, 2*1024*1024, true) }
|
|
|
|
func unalignBytes(in []byte) []byte {
|
|
out := make([]byte, len(in)+1)
|
|
if uintptr(unsafe.Pointer(&out[0]))&(unsafe.Alignof(uint32(0))-1) == 0 {
|
|
out = out[1:]
|
|
} else {
|
|
out = out[:len(in)]
|
|
}
|
|
copy(out, in)
|
|
return out
|
|
}
|