From cd9fedffdb4d710a43fcd289f6fa942a8537578e Mon Sep 17 00:00:00 2001 From: Shang-Yi Yang Date: Wed, 28 Feb 2024 21:33:09 -0600 Subject: [PATCH 1/9] Add MAYO --- sign/mayo/doc.go | 11 + sign/mayo/gen.go | 233 ++++++++ sign/mayo/internal/common/nist/drbg.go | 71 +++ sign/mayo/mode1/internal/matrix.go | 289 ++++++++++ sign/mayo/mode1/internal/mayo.go | 728 ++++++++++++++++++++++++ sign/mayo/mode1/internal/mayo_test.go | 222 ++++++++ sign/mayo/mode1/internal/params.go | 42 ++ sign/mayo/mode1/mayo.go | 151 +++++ sign/mayo/mode2/internal/matrix.go | 291 ++++++++++ sign/mayo/mode2/internal/mayo.go | 730 +++++++++++++++++++++++++ sign/mayo/mode2/internal/mayo_test.go | 224 ++++++++ sign/mayo/mode2/internal/params.go | 42 ++ sign/mayo/mode2/mayo.go | 151 +++++ sign/mayo/mode3/internal/matrix.go | 291 ++++++++++ sign/mayo/mode3/internal/mayo.go | 730 +++++++++++++++++++++++++ sign/mayo/mode3/internal/mayo_test.go | 224 ++++++++ sign/mayo/mode3/internal/params.go | 42 ++ sign/mayo/mode3/mayo.go | 151 +++++ sign/mayo/mode5/internal/matrix.go | 291 ++++++++++ sign/mayo/mode5/internal/mayo.go | 730 +++++++++++++++++++++++++ sign/mayo/mode5/internal/mayo_test.go | 224 ++++++++ sign/mayo/mode5/internal/params.go | 42 ++ sign/mayo/mode5/mayo.go | 151 +++++ sign/mayo/templates/modePkg.templ.go | 155 ++++++ sign/mayo/templates/params.templ.go | 46 ++ 25 files changed, 6262 insertions(+) create mode 100644 sign/mayo/doc.go create mode 100644 sign/mayo/gen.go create mode 100644 sign/mayo/internal/common/nist/drbg.go create mode 100644 sign/mayo/mode1/internal/matrix.go create mode 100644 sign/mayo/mode1/internal/mayo.go create mode 100644 sign/mayo/mode1/internal/mayo_test.go create mode 100644 sign/mayo/mode1/internal/params.go create mode 100644 sign/mayo/mode1/mayo.go create mode 100644 sign/mayo/mode2/internal/matrix.go create mode 100644 sign/mayo/mode2/internal/mayo.go create mode 100644 sign/mayo/mode2/internal/mayo_test.go create mode 100644 sign/mayo/mode2/internal/params.go create mode 100644 sign/mayo/mode2/mayo.go create mode 100644 sign/mayo/mode3/internal/matrix.go create mode 100644 sign/mayo/mode3/internal/mayo.go create mode 100644 sign/mayo/mode3/internal/mayo_test.go create mode 100644 sign/mayo/mode3/internal/params.go create mode 100644 sign/mayo/mode3/mayo.go create mode 100644 sign/mayo/mode5/internal/matrix.go create mode 100644 sign/mayo/mode5/internal/mayo.go create mode 100644 sign/mayo/mode5/internal/mayo_test.go create mode 100644 sign/mayo/mode5/internal/params.go create mode 100644 sign/mayo/mode5/mayo.go create mode 100644 sign/mayo/templates/modePkg.templ.go create mode 100644 sign/mayo/templates/params.templ.go diff --git a/sign/mayo/doc.go b/sign/mayo/doc.go new file mode 100644 index 000000000..4c7b8ec4c --- /dev/null +++ b/sign/mayo/doc.go @@ -0,0 +1,11 @@ +//go:generate go run gen.go + +// Package mayo implements the MAYO signature scheme +// as submitted to round1 of the NIST PQC competition of Additional Signature Scehemes and described in +// +// https://csrc.nist.gov/csrc/media/Projects/pqc-dig-sig/documents/round-1/spec-files/mayo-spec-web.pdf +// +// This implemented the nibble-sliced version as proposed in +// +// https://eprint.iacr.org/2023/1683 +package mayo diff --git a/sign/mayo/gen.go b/sign/mayo/gen.go new file mode 100644 index 000000000..87be9aaff --- /dev/null +++ b/sign/mayo/gen.go @@ -0,0 +1,233 @@ +//go:build ignore +// +build ignore + +// Autogenerates wrappers from templates to prevent too much duplicated code +// between the code for different modes. +package main + +import ( + "bytes" + "fmt" + "go/format" + "os" + "path" + "strings" + "text/template" +) + +type Mode struct { + Name string + N int + M int + O int + K int + KeySeedSize int + DigestSize int + Tail [5]uint8 +} + +func (m Mode) Pkg() string { + return strings.ToLower(m.Mode()) +} + +func (m Mode) Impl() string { + return "impl" + m.Mode() +} + +func (m Mode) Mode() string { + return strings.ReplaceAll(m.Name, "MAYO_", "Mode") +} + +var ( + Modes = []Mode{ + { + Name: "MAYO_1", + N: 66, + M: 64, + O: 8, + K: 9, + KeySeedSize: 24, + DigestSize: 32, + Tail: [5]uint8{8, 0, 2, 8, 0}, + }, + { + Name: "MAYO_2", + N: 78, + M: 64, + O: 18, + K: 4, + KeySeedSize: 24, + DigestSize: 32, + Tail: [5]uint8{8, 0, 2, 8, 0}, + }, + { + Name: "MAYO_3", + N: 99, + M: 96, + O: 10, + K: 11, + KeySeedSize: 32, + DigestSize: 48, + Tail: [5]uint8{2, 2, 0, 2, 0}, + }, + { + Name: "MAYO_5", + N: 133, + M: 128, + O: 12, + K: 12, + KeySeedSize: 40, + DigestSize: 64, + Tail: [5]uint8{4, 8, 0, 4, 2}, + }, + } + TemplateWarning = "// Code generated from" +) + +func main() { + generateModePackageFiles() + generateParamsFiles() + generateSourceFiles() +} + +// Generates modeX/internal/params.go from templates/params.templ.go +func generateParamsFiles() { + tl, err := template.ParseFiles("templates/params.templ.go") + if err != nil { + panic(err) + } + + for _, mode := range Modes { + buf := new(bytes.Buffer) + err := tl.Execute(buf, mode) + if err != nil { + panic(err) + } + + // Formating output code + code, err := format.Source(buf.Bytes()) + if err != nil { + fmt.Println(buf.String()) + panic("error formating code") + } + + res := string(code) + offset := strings.Index(res, TemplateWarning) + if offset == -1 { + panic("Missing template warning in params.templ.go") + } + err = os.WriteFile(mode.Pkg()+"/internal/params.go", + []byte(res[offset:]), 0o644) + if err != nil { + panic(err) + } + } +} + +// Generates modeX/mayo.go from templates/modePkg.templ.go +func generateModePackageFiles() { + tl, err := template.ParseFiles("templates/modePkg.templ.go") + if err != nil { + panic(err) + } + + for _, mode := range Modes { + buf := new(bytes.Buffer) + err := tl.Execute(buf, mode) + if err != nil { + panic(err) + } + + res := buf.String() + offset := strings.Index(res, TemplateWarning) + if offset == -1 { + panic("Missing template warning in modePkg.templ.go") + } + err = os.WriteFile(mode.Pkg()+"/mayo.go", []byte(res[offset:]), 0o644) + if err != nil { + panic(err) + } + } +} + +// Copies mode1 source files to other modes +func generateSourceFiles() { + files := make(map[string][]byte) + + // Ignore mode specific files. + ignored := func(x string) bool { + return x == "params.go" || x == "params_test.go" || + strings.HasSuffix(x, ".swp") + } + + fs, err := os.ReadDir("mode1/internal") + if err != nil { + panic(err) + } + + // Read files + for _, f := range fs { + name := f.Name() + if ignored(name) { + continue + } + files[name], err = os.ReadFile(path.Join("mode1/internal", name)) + if err != nil { + panic(err) + } + } + + // Go over modes + for _, mode := range Modes { + if mode.Name == "MAYO_1" { + continue + } + + fs, _ = os.ReadDir(path.Join(mode.Pkg(), "internal")) + for _, f := range fs { + name := f.Name() + fn := path.Join(mode.Pkg(), "internal", name) + if ignored(name) { + continue + } + _, ok := files[name] + if !ok { + fmt.Printf("Removing superfluous file: %s\n", fn) + err = os.Remove(fn) + if err != nil { + panic(err) + } + } + if f.IsDir() { + panic(fmt.Sprintf("%s: is a directory", fn)) + } + if f.Type()&os.ModeSymlink != 0 { + fmt.Printf("Removing symlink: %s\n", fn) + err = os.Remove(fn) + if err != nil { + panic(err) + } + } + } + for name, expected := range files { + fn := path.Join(mode.Pkg(), "internal", name) + expected = []byte(fmt.Sprintf( + "%s mode1/internal/%s by gen.go\n\n%s", + TemplateWarning, + name, + string(expected), + )) + got, err := os.ReadFile(fn) + if err == nil { + if bytes.Equal(got, expected) { + continue + } + } + fmt.Printf("Updating %s\n", fn) + err = os.WriteFile(fn, expected, 0o644) + if err != nil { + panic(err) + } + } + } +} diff --git a/sign/mayo/internal/common/nist/drbg.go b/sign/mayo/internal/common/nist/drbg.go new file mode 100644 index 000000000..be9c8ec03 --- /dev/null +++ b/sign/mayo/internal/common/nist/drbg.go @@ -0,0 +1,71 @@ +// Package nist implements helpers to generate NIST's Known Answer Tests (KATs). +package nist + +import ( + "crypto/aes" + "io" +) + +// See NIST's PQCgenKAT.c. +type DRBG struct { + key [32]byte + v [16]byte + + io.Reader +} + +func (g *DRBG) incV() { + for j := 15; j >= 0; j-- { + if g.v[j] == 255 { + g.v[j] = 0 + } else { + g.v[j]++ + break + } + } +} + +// AES256_CTR_DRBG_Update(pd, &g.key, &g.v). +func (g *DRBG) update(pd *[48]byte) { + var buf [48]byte + b, _ := aes.NewCipher(g.key[:]) + for i := 0; i < 3; i++ { + g.incV() + b.Encrypt(buf[i*16:(i+1)*16], g.v[:]) + } + if pd != nil { + for i := 0; i < 48; i++ { + buf[i] ^= pd[i] + } + } + copy(g.key[:], buf[:32]) + copy(g.v[:], buf[32:]) +} + +// randombyte_init(seed, NULL, 256). +func NewDRBG(seed *[48]byte) (g DRBG) { + g.update(seed) + return +} + +// randombytes. +func (g *DRBG) Read(x []byte) (n int, err error) { + var block [16]byte + + n, err = len(x), nil + + b, _ := aes.NewCipher(g.key[:]) + for len(x) > 0 { + g.incV() + b.Encrypt(block[:], g.v[:]) + if len(x) < 16 { + copy(x[:], block[:len(x)]) + break + } + copy(x[:], block[:]) + x = x[16:] + } + g.update(nil) + + return +} diff --git a/sign/mayo/mode1/internal/matrix.go b/sign/mayo/mode1/internal/matrix.go new file mode 100644 index 000000000..dc5fdc1de --- /dev/null +++ b/sign/mayo/mode1/internal/matrix.go @@ -0,0 +1,289 @@ +package internal + +// Given b in GF(16), packs the 32-bit result of (b*x^3, b*x^2, b*x, b) into the returned multiplication table. +func mulTable(b uint8) uint32 { + x := uint32(b) * 0x08040201 + highNibble := x & uint32(0xf0f0f0f0) + + // mod x^4+x+1 + return (x ^ (highNibble >> 4) ^ (highNibble >> 3)) +} + +func vecMulAddPackedTab(p int, in []uint64, tab uint32, acc []uint64) { + lsbMask := uint64(0x1111111111111111) + for i := 0; i < p; i++ { + acc[i] ^= (in[i]&lsbMask)*uint64(tab&0xff) ^ + ((in[i]>>1)&lsbMask)*uint64((tab>>8)&0xf) ^ + ((in[i]>>2)&lsbMask)*uint64((tab>>16)&0xf) ^ + ((in[i]>>3)&lsbMask)*uint64((tab>>24)&0xf) + } +} + +func vecMulAddPacked(p int, in []uint64, a byte, acc []uint64) { + tab := mulTable(a) + vecMulAddPackedTab(p, in, tab, acc) +} + +// Multiplies each nibble in a by b. +func mulAddPacked(a uint64, b uint8) uint64 { + msb := uint64(0x8888888888888888) + a64 := a + r64 := a64 * uint64(b&1) + + for i := 1; i < 4; i++ { + b >>= 1 + aMsb := a64 & msb + a64 ^= aMsb + a64 = (a64 << 1) ^ ((aMsb >> 3) * 3) + r64 ^= (a64) * uint64(b&1) + } + + return r64 +} + +// acc += M1*M2 +// acc and M2 are multiple matrices, M1 is a single matrix +func mulAddMatXMMat(acc []uint64, m1 []uint8, m2 []uint64, rows int, cols int, cols2 int) { + for r := 0; r < rows; r++ { + for c := 0; c < cols; c++ { + tab := mulTable(m1[r*cols+c]) + for k := 0; k < cols2; k++ { + // The following multiplication table way is equivalent to: + // for p := 0; p < P; p++ { + // acc[P*(r*cols2+k)+p] ^= gf16v_mul_u64(m2[P*(c*cols2+k)+p], m1[r*cols+c]) + // } + vecMulAddPackedTab(P, m2[P*(c*cols2+k):], tab, acc[P*(r*cols2+k):]) + } + } + } +} + +// acc += M1*M2, where M1 is upper triangular, acc and M2 is not +// acc and M1 are multiple matrices, M2 is a single matrix +func mulAddMUpperTriangularMatXMat(acc []uint64, m1 []uint64, m2 []uint8, rows int, cols2 int) { + // The ordinary summation order is r -> c -> k, but here it is interchanged to make use of multiplication table + cols := rows + for k := 0; k < cols2; k++ { + for c := 0; c < cols; c++ { + tab := mulTable(m2[c*cols2+k]) + for r := 0; r <= c; r++ { + pos := r*(cols*2-r+1)/2 + (c - r) + vecMulAddPackedTab(P, m1[P*pos:], tab, acc[P*(r*cols2+k):]) + } + } + } +} + +// acc += M1^T*M2, +// acc and M2 are multiple matrices, M1 is a single matrix +// M1, before ^T, is of rows x cols +func mulAddMatTransXMMat(acc []uint64, m1 []uint8, m2 []uint64, rows int, cols int, cols2 int) { + for r := 0; r < cols; r++ { + for c := 0; c < rows; c++ { + tab := mulTable(m1[c*cols+r]) + for k := 0; k < cols2; k++ { + vecMulAddPackedTab(P, m2[P*(c*cols2+k):], tab, acc[P*(r*cols2+k):]) + } + } + } +} + +// acc += M1*M2^T, +// acc and M1 are multiple matrices, M2 is a single matrix +// M2, before ^T, is of cols2 x cols, but cols strides at colsStride +// M1 optionally upper triangular +func mulAddMMatXMatTrans(acc []uint64, m1 []uint64, m2 []uint8, rows int, cols int, cols2 int, colsStride int, isM1Triangular bool) { + for k := 0; k < cols2; k++ { + for c := 0; c < cols; c++ { + rBound := rows - 1 + if isM1Triangular { + rBound = c + } + for r := 0; r <= rBound; r++ { + tab := mulTable(m2[k*colsStride+c]) + pos := r*cols + c + if isM1Triangular { + pos = r*(cols*2-r+1)/2 + (c - r) + } + vecMulAddPackedTab(P, m1[P*pos:], tab, acc[P*(r*cols2+k):]) + } + } + } +} + +// acc += (M1+M1^T)*M2 +// M1 of rows x rows is upper triangular; M2 is of rows x cols2 +// acc and M1 are multiple matrices, M2 is a single matrix +func mulAddMUpperTriangularWithTransposeMatXMat(acc []uint64, m1 []uint64, m2 []uint8, rows int, cols2 int) { + m1pos := 0 + for r := 0; r < rows; r++ { + for c := r; c < rows; c++ { + if c == r { + m1pos += 1 + continue + } + for k := 0; k < cols2; k++ { + vecMulAddPacked(P, m1[P*m1pos:], m2[c*cols2+k], acc[P*(r*cols2+k):]) + vecMulAddPacked(P, m1[P*m1pos:], m2[r*cols2+k], acc[P*(c*cols2+k):]) + } + m1pos++ + } + } +} + +func mulAddMatVec(acc []byte, m []byte, v []byte, rows, cols int) { + for i := 0; i < rows; i++ { + for j := 0; j < cols; j++ { + acc[i] ^= byte(mulAddPacked(uint64(m[i*cols+j]), v[j])) + } + } +} + +func upper(in []uint64, out []uint64, size int) { + pos := 0 + for r := 0; r < size; r++ { + for c := r; c < size; c++ { + copy(out[P*pos:][:P], in[P*(r*size+c):][:P]) + if r != c { + for p := 0; p < P; p++ { + out[P*pos+p] ^= in[P*(c*size+r)+p] + } + } + pos++ + } + } +} + +func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8) { + var accumulator [K * N][P * 16]uint64 + + // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] + // [ 0 P3 ] [S2] [ P3*S2] + + // Note that S = S1||S2 is strided at N=V+O + + // P1 * S1^t : VxV * V*K, where P1 is triangular + pos := 0 + for r := 0; r < V; r++ { + for c := r; c < V; c++ { + for k := 0; k < K; k++ { + vecAddPacked(P, p1[P*pos:], accumulator[r*K+k][P*int(s[k*N+c]):]) + } + pos++ + } + } + + // P2 * S2^t : V*O * O*K + pos = 0 + for r := 0; r < V; r++ { + for c := 0; c < O; c++ { + for k := 0; k < K; k++ { + vecAddPacked(P, p2[P*pos:], accumulator[r*K+k][P*int(s[k*N+V+c]):]) + } + pos++ + } + } + + // P3 * S2^t : O*O * O*K, where P3 is triangular + pos = 0 + for r := 0; r < O; r++ { + for c := r; c < O; c++ { + for k := 0; k < K; k++ { + vecAddPacked(P, p3[P*pos:], accumulator[(r+V)*K+k][P*int(s[k*N+V+c]):]) + } + pos++ + } + } + + for i := 0; i < K*N; i++ { + aggregate(P, accumulator[i], sps[P*i:]) + } +} + +func variableTime2(sps []uint64, s []uint8, pst []uint64) { + var accumulator [K * K][P * 16]uint64 + + // S * PST : KxN * N*K + for r := 0; r < K; r++ { + for c := 0; c < N; c++ { + for k := 0; k < K; k++ { + vecAddPacked(P, pst[P*(c*K+k):], accumulator[r*K+k][P*int(s[r*N+c]):]) + } + } + } + + for i := 0; i < K*K; i++ { + aggregate(P, accumulator[i], sps[P*i:]) + } +} + +// p is always P, but is still kept to be consistent with other functions +// +//nolint:unparam +func vecAddPacked(p int, in []uint64, acc []uint64) { + for i := 0; i < p; i++ { + acc[i] ^= in[i] + } +} + +func aggregate(p int, bins [P * 16]uint64, out []uint64) { + // The following two methods are mathematically equivalent, but the second one is slightly faster. + + // the powers of x mod x^4+x+1, represented as integers, are 1,2,4,8,3,..,13,9 + // out = bins[9]*x^14 + bins[13]*x^13 + bins[15]*x^12 ... + bin[4]*x^2 + bins[2]*x + bins[1] + // = ((bins[9]x+bins[13])x+bins[15])x + ... bins[4])x+bins[2])x+bins[1] + + // vecMulAddPackedByX(p, bins[P*9:], bins[P*13:]) + // vecMulAddPackedByX(p, bins[P*13:], bins[P*15:]) + // vecMulAddPackedByX(p, bins[P*15:], bins[P*14:]) + // vecMulAddPackedByX(p, bins[P*14:], bins[P*7:]) + // vecMulAddPackedByX(p, bins[P*7:], bins[P*10:]) + // vecMulAddPackedByX(p, bins[P*10:], bins[P*5:]) + // vecMulAddPackedByX(p, bins[P*5:], bins[P*11:]) + // vecMulAddPackedByX(p, bins[P*11:], bins[P*12:]) + // vecMulAddPackedByX(p, bins[P*12:], bins[P*6:]) + // vecMulAddPackedByX(p, bins[P*6:], bins[P*3:]) + // vecMulAddPackedByX(p, bins[P*3:], bins[P*8:]) + // vecMulAddPackedByX(p, bins[P*8:], bins[P*4:]) + // vecMulAddPackedByX(p, bins[P*4:], bins[P*2:]) + // vecMulAddPackedByX(p, bins[P*2:], bins[P*1:]) + // copy(out[:P], bins[P*1:]) + + // In the reversed order of the above, because /x turns out to be slightly faster than *x. + // out = ((bins[2]x^-1+bins[4])x^-1+bins[8])x^-1 + ... bins[13])x^-1+bins[9])x^-1+bins[1] + vecMulAddPackedByInvX(p, bins[P*2:], bins[P*4:]) + vecMulAddPackedByInvX(p, bins[P*4:], bins[P*8:]) + vecMulAddPackedByInvX(p, bins[P*8:], bins[P*3:]) + vecMulAddPackedByInvX(p, bins[P*3:], bins[P*6:]) + vecMulAddPackedByInvX(p, bins[P*6:], bins[P*12:]) + vecMulAddPackedByInvX(p, bins[P*12:], bins[P*11:]) + vecMulAddPackedByInvX(p, bins[P*11:], bins[P*5:]) + vecMulAddPackedByInvX(p, bins[P*5:], bins[P*10:]) + vecMulAddPackedByInvX(p, bins[P*10:], bins[P*7:]) + vecMulAddPackedByInvX(p, bins[P*7:], bins[P*14:]) + vecMulAddPackedByInvX(p, bins[P*14:], bins[P*15:]) + vecMulAddPackedByInvX(p, bins[P*15:], bins[P*13:]) + vecMulAddPackedByInvX(p, bins[P*13:], bins[P*9:]) + vecMulAddPackedByInvX(p, bins[P*9:], bins[P*1:]) + copy(out[:P], bins[P*1:]) +} + +// func vecMulAddPackedByX(p int, in []uint64, acc []uint64) { +// // vecMulAddPacked(p, in, 2, acc) + +// msb := uint64(0x8888888888888888) +// for i := 0; i < p; i++ { +// t := in[i] & msb +// acc[i] ^= ((in[i] ^ t) << 1) ^ ((t >> 3) * 3) +// } +// } + +func vecMulAddPackedByInvX(p int, in []uint64, acc []uint64) { + // vecMulAddPacked(p, in, 9, acc) + + lsb := uint64(0x1111111111111111) + for i := 0; i < p; i++ { + t := in[i] & lsb + acc[i] ^= ((in[i] ^ t) >> 1) ^ (t * 9) + } +} diff --git a/sign/mayo/mode1/internal/mayo.go b/sign/mayo/mode1/internal/mayo.go new file mode 100644 index 000000000..c54e181cd --- /dev/null +++ b/sign/mayo/mode1/internal/mayo.go @@ -0,0 +1,728 @@ +package internal + +import ( + "crypto/aes" + "crypto/cipher" + cryptoRand "crypto/rand" + "crypto/subtle" + "io" + "unsafe" + + "github.com/cloudflare/circl/internal/sha3" +) + +type ( + PrivateKey [PrivateKeySize]byte + PublicKey [PublicKeySize]byte +) + +func (pk *PublicKey) Equal(other *PublicKey) bool { + return *pk == *other +} + +func (sk *PrivateKey) Equal(other *PrivateKey) bool { + return subtle.ConstantTimeCompare((*sk)[:], (*other)[:]) == 1 +} + +type ExpandedPublicKey struct { + p1 [P1Size]byte + p2 [P2Size]byte + p3 [P3Size]byte +} + +type ExpandedPrivateKey struct { + seed [KeySeedSize]byte + o [V * O]byte + p1 [M * V * V / 16]uint64 + l [M * V * O / 16]uint64 +} + +func (pk *PublicKey) Expand() *ExpandedPublicKey { + seedPk := pk[:PublicKeySeedSize] + + var nonce [16]byte // zero-initialized + block, _ := aes.NewCipher(seedPk[:]) + ctr := cipher.NewCTR(block, nonce[:]) + + epk := ExpandedPublicKey{} + ctr.XORKeyStream(epk.p1[:], epk.p1[:]) + ctr.XORKeyStream(epk.p2[:], epk.p2[:]) + + copy(epk.p3[:], pk[PublicKeySeedSize:]) + + return &epk +} + +func (sk *PrivateKey) Expand() *ExpandedPrivateKey { + var epk ExpandedPrivateKey + + seed := (*sk)[:KeySeedSize] + copy(epk.seed[:], seed) + + var seedPk [PublicKeySeedSize]byte + var o [OSize]byte + + h := sha3.NewShake256() + _, _ = h.Write(seed[:]) + _, _ = h.Read(seedPk[:]) + _, _ = h.Read(o[:]) + + var nonce [16]byte // zero-initialized + block, _ := aes.NewCipher(seedPk[:]) + ctr := cipher.NewCTR(block, nonce[:]) + + var p12 [P1Size + P2Size]byte + ctr.XORKeyStream(p12[:], p12[:]) + + decode(o[:], epk.o[:]) + + p1Tri := viewBytesAsUint64Slice(p12[:P1Size]) + p2 := viewBytesAsUint64Slice(p12[P1Size:]) + + // TODO: this copy can be saved by reusing buffers but ugly + copy(epk.p1[:], p1Tri) + copy(epk.l[:], p2) + + // compute L_i = (P1 + P1^t)*O + P2 + mulAddMUpperTriangularWithTransposeMatXMat(epk.l[:], p1Tri, epk.o[:], V, O) + + return &epk +} + +// decode unpacks N bytes from src to N*2 nibbles to dst. +// The length is determined by len(dst) +func decode(src []byte, dst []byte) { + i := 0 + for ; i < len(dst)/2; i++ { + dst[i*2] = src[i] & 0xf + dst[i*2+1] = src[i] >> 4 + } + + // Account for odd length + if len(dst)%2 == 1 { + dst[i*2] = src[i] & 0xf + } +} + +// encode packs N=length low nibbles from src to (N+1)/2 bytes in dst. +func encode(src []byte, dst []byte, length int) { + var i int + for i = 0; i+1 < length; i += 2 { + dst[i/2] = (src[i+0] << 0) | (src[i+1] << 4) + } + if length%2 == 1 { + dst[i/2] = (src[i+0] << 0) + } +} + +func viewBytesAsUint64Slice(data []byte) []uint64 { + numUint64 := len(data) / 8 + return unsafe.Slice((*uint64)(unsafe.Pointer(&data[0])), numUint64) +} + +func viewUint64SliceAsBytes(data []uint64) []byte { + numByte := len(data) * 8 + return unsafe.Slice((*uint8)(unsafe.Pointer(&data[0])), numByte) +} + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { + var seed [KeySeedSize]byte + if rand == nil { + rand = cryptoRand.Reader + } + _, err := io.ReadFull(rand, seed[:]) + if err != nil { + return nil, nil, err + } + pk, sk := NewKeyFromSeed(seed) + return pk, sk, nil +} + +func NewKeyFromSeed(seed [KeySeedSize]byte) (*PublicKey, *PrivateKey) { + var sk PrivateKey + copy(sk[:], seed[:]) + + return sk.Public(), &sk +} + +func (sk *PrivateKey) Public() *PublicKey { + var pk PublicKey + seedPk := pk[:PublicKeySeedSize] + var o [OSize]byte + + h := sha3.NewShake256() + _, _ = h.Write(sk[:]) + _, _ = h.Read(seedPk[:]) + _, _ = h.Read(o[:]) + + var nonce [16]byte // zero-initialized + block, _ := aes.NewCipher(seedPk[:]) + ctr := cipher.NewCTR(block, nonce[:]) + + var p12 [P1Size + P2Size]byte + ctr.XORKeyStream(p12[:], p12[:]) + + var oo [V * O]byte + decode(o[:], oo[:]) + + p1Tri := viewBytesAsUint64Slice(p12[:P1Size]) + p1OP2 := viewBytesAsUint64Slice(p12[P1Size:]) + + var p3full [M * O * O / 16]uint64 + var p3 [P3Size / 8]uint64 + + mulAddMUpperTriangularMatXMat(p1OP2, p1Tri, oo[:], V, O) + mulAddMatTransXMMat(p3full[:], oo[:], p1OP2, V, O, O) + + upper(p3full[:], p3[:], O) + + xx := viewUint64SliceAsBytes(p3[:]) + copy(pk[PublicKeySeedSize:], xx[:]) + + return &pk +} + +func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { + if rand == nil { + rand = cryptoRand.Reader + } + + var digest [DigestSize]byte + + h := sha3.NewShake256() + _, _ = h.Write(msg[:]) + _, _ = h.Read(digest[:]) + + var salt [SaltSize]byte + + // R <- $ + if _, err := io.ReadFull(rand, salt[:]); err != nil { + return nil, err + } + + h.Reset() + _, _ = h.Write(digest[:]) + _, _ = h.Write(salt[:]) + _, _ = h.Write(sk.seed[:]) + _, _ = h.Read(salt[:]) + + h.Reset() + _, _ = h.Write(digest[:]) + _, _ = h.Write(salt[:]) + + var tenc [M / 2]byte + _, _ = h.Read(tenc[:]) + + var t [M]byte + decode(tenc[:], t[:]) + + var v [K * V]byte + var x [K*O + 1]byte // + 1 for buffer + for ctr := 0; ctr <= 255; ctr++ { + ctrByte := []byte{byte(ctr)} + h.Reset() + _, _ = h.Write(digest[:]) + _, _ = h.Write(salt[:]) + _, _ = h.Write(sk.seed[:]) + _, _ = h.Write(ctrByte[:]) + + var venc [K * VSize]byte + var renc [K * O / 2]byte + _, _ = h.Read(venc[:]) + _, _ = h.Read(renc[:]) + + var r [K * O]byte + + for i := 0; i < K; i++ { + decode(venc[i*VSize:], v[i*V:(i+1)*V]) + } + decode(renc[:], r[:]) + + // M = vL + var m [M * K * O / 16]uint64 + mulAddMatXMMat(m[:], v[:], sk.l[:], K, V, O) + + // pv = P1 * V^T + var pv [M * V * K / 16]uint64 + mulAddMMatXMatTrans(pv[:], sk.p1[:], v[:], V, V, K, V, true) + + // V * pv + var vpv [M * K * K / 16]uint64 + mulAddMatXMMat(vpv[:], v[:], pv[:], K, V, K) + + var y [M]byte + copy(y[:], t[:]) + emulsifyInto(vpv[:], y[:]) + + var A [M * (K*O + 1)]byte + _ = A + + computeA(m[:], A[:]) + + if sampleSolution(A[:], y[:], r[:], x[:]) { + break + } + } + + var s [K * N]byte + for i := 0; i <= K-1; i++ { + copy(s[i*N:][:V], v[i*V:]) + mulAddMatVec(s[i*N:], sk.o[:], x[i*O:], V, O) + copy(s[i*N+V:][:O], x[i*O:]) + } + + var sig [(K*N+1)/2 + SaltSize]byte + encode(s[:], sig[:], K*N) + copy(sig[(K*N+1)/2:], salt[:]) + + return sig[:], nil +} + +// assume last (KO+1-th) column of a is zero +func sampleSolution(a []byte, y []byte, r []byte, x []byte) bool { + const aCols = K*O + 1 + + copy(x[:], r[:]) + + var ar [M]byte + mulAddMatVec(ar[:], a[:], x[:], M, aCols) + + // move y - Ar to last column of matrix A + for i := 0; i < M; i++ { + a[K*O+i*(aCols)] = y[i] ^ ar[i] + } + + ef(a[:], M, aCols) + + fullRank := byte(0) + for i := 0; i < aCols-1; i++ { + fullRank |= a[(M-1)*(aCols)+i] + } + + if fullRank == 0 { + return false + } + + // back substitution in constant time + // the index of the first nonzero entry in each row is secret, which makes + // things less efficient + + for row := M - 1; row >= 0; row-- { + finished := byte(0) + colUpperBound := min(row+(32/(M-row)), K*O) + // the first nonzero entry in row r is between r and col_upper_bound with probability at least ~1-q^{-32} + + for col := row; col <= colUpperBound; col++ { + // Compare two chars in constant time. + // Returns 0x00 if the byte arrays are equal, 0xff otherwise. + correctColumn := ctCompare8((a[row*aCols+col]), 0) & ^finished + + u := correctColumn & a[row*aCols+aCols-1] + x[col] ^= u + + for i := 0; i < row; i += 8 { + tmp := (uint64(a[i*aCols+col]) << 0) ^ (uint64(a[(i+1)*aCols+col]) << 8) ^ + (uint64(a[(i+2)*aCols+col]) << 16) ^ (uint64(a[(i+3)*aCols+col]) << 24) ^ + (uint64(a[(i+4)*aCols+col]) << 32) ^ (uint64(a[(i+5)*aCols+col]) << 40) ^ + (uint64(a[(i+6)*aCols+col]) << 48) ^ (uint64(a[(i+7)*aCols+col]) << 56) + + tmp = mulx8(u, tmp) + + a[i*aCols+aCols-1] ^= byte((tmp) & 0xf) + a[(i+1)*aCols+aCols-1] ^= byte((tmp >> 8) & 0xf) + a[(i+2)*aCols+aCols-1] ^= byte((tmp >> 16) & 0xf) + a[(i+3)*aCols+aCols-1] ^= byte((tmp >> 24) & 0xf) + a[(i+4)*aCols+aCols-1] ^= byte((tmp >> 32) & 0xf) + a[(i+5)*aCols+aCols-1] ^= byte((tmp >> 40) & 0xf) + a[(i+6)*aCols+aCols-1] ^= byte((tmp >> 48) & 0xf) + a[(i+7)*aCols+aCols-1] ^= byte((tmp >> 56) & 0xf) + } + + finished = finished | correctColumn + } + } + + return true +} + +// if a == b -> 0x0000000000000000, else 0xFFFFFFFFFFFFFFFF +func ctCompare64(a, b int) uint64 { + return uint64((-(int64)(a ^ b)) >> 63) +} + +// a > b -> b - a is negative +// returns 0xFFFFFFFF if true, 0x00000000 if false +func ct64IsGreaterThan(a, b int) uint64 { + diff := int64(b) - int64(a) + return uint64(diff >> 63) +} + +// if a == b -> 0x00, else 0xFF +func ctCompare8(a, b byte) byte { + return byte((-int32(a ^ b)) >> (31)) +} + +func extract(in []uint64, index int) byte { + leg := index / 16 + offset := index % 16 + + return byte((in[leg] >> (offset * 4)) & 0xF) +} + +// put matrix in row echelon form with ones on first nonzero entries *in constant time* +func ef(A []byte, nrows, ncols int) { + // ncols is actually always K*O + 1 + + // we operate each row by packing nibbles to uint64s. + rowLen := (ncols + 15) / 16 + + var pivotRowData [(K*O + 1 + 15) / 16]uint64 // rounds up + var pivotRowData2 [(K*O + 1 + 15) / 16]uint64 + + // nibbleslice the matrix A + var packedAbyte [((K*O + 1 + 15) / 16) * M * 8]byte + for i := 0; i < nrows; i++ { + encode(A[i*ncols:], packedAbyte[i*rowLen*8:], ncols) + } + + packedA := viewBytesAsUint64Slice(packedAbyte[:]) + + // pivot row is secret, pivot col is not + pivotRow := 0 + for pivotCol := 0; pivotCol < ncols; pivotCol++ { + pivotRowLowerBound := max(0, pivotCol+nrows-ncols) + pivotRowUpperBound := min(nrows-1, pivotCol) + // the pivot row is guaranteed to be between these lower and upper bounds if + // A has full rank + + // zero out pivot row + for i := 0; i < rowLen; i++ { + pivotRowData[i] = 0 + pivotRowData2[i] = 0 + } + + // try to get a pivot row in constant time + var pivot byte = 0 + var pivotIsZero uint64 = 0xffffffffffffffff + for row := pivotRowLowerBound; row <= min(nrows-1, pivotRowUpperBound+32); row++ { + isPivotRow := ^ctCompare64(row, pivotRow) + belowPivotRow := ct64IsGreaterThan(row, pivotRow) + + for j := 0; j < rowLen; j++ { + mask := isPivotRow | (belowPivotRow & pivotIsZero) + pivotRowData[j] ^= mask & packedA[row*rowLen+j] + } + pivot = extract(pivotRowData[:], pivotCol) + pivotIsZero = ^ctCompare64(int(pivot), 0) + } + + // multiply pivot row by inverse of pivot + inverse := inverse(pivot) + vecMulAddPacked(rowLen, pivotRowData[:], inverse, pivotRowData2[:]) + + // conditionally write pivot row to the correct row, if there is a nonzero + // pivot + for row := pivotRowLowerBound; row <= pivotRowUpperBound; row++ { + doCopy := ^ctCompare64(row, pivotRow) & ^pivotIsZero + doNotCopy := ^doCopy + for col := 0; col < rowLen; col++ { + packedA[row*rowLen+col] = (doNotCopy & packedA[row*rowLen+col]) + + (doCopy & pivotRowData2[col]) + } + } + + // eliminate entries below pivot + for row := pivotRowLowerBound; row < nrows; row++ { + belowPivot := byte(0) + if row > pivotRow { + belowPivot = 1 + } + eltToElim := extract(packedA[row*rowLen:], pivotCol) + + vecMulAddPacked(rowLen, pivotRowData2[:], belowPivot*eltToElim, + packedA[row*rowLen:]) + } + + pivotRow += -int(^pivotIsZero) + } + + var temp [(O*K + 1 + 15)]byte + + // unnibbleslice the matrix A + for i := 0; i < nrows; i++ { + decode(packedAbyte[i*rowLen*8:], temp[:rowLen*16]) + for j := 0; j < ncols; j++ { + A[i*ncols+j] = temp[j] + } + } +} + +func computeA(m []uint64, _a []byte) { + // M is of K * O * (M / 16) + + // intermediate state of A, which is just accumulation of Mj*x^_ without reduction mod f + // M/8 * K*O + // uint64 + // some idx ko @ K*O idx = ko + 1 + // [ ... [m0 m1 ... m15] [m0 m1 ... m15] .... ] + // [ ... [m16 m17 ... m31] [m16 m17 ... m31] .... ] + // ... + // [ ... [m48 m49 ... m63] [m48 m49 ... m63] .... ] <--- for M=64, this is where reduction is not needed + // ... + // [ ... [m112 m113 ... m127] [m112 m113 ... m127] .... ] <--- here are for reductions later + // = sum of M_k @ ko + // + // later we will somehow transform it to the actual matrix form of A + // for this, we need to group 16 uint64 words together as a chunk, hence OKpadded + // ? why M/8, not something like ~ m+k*(K+1)/2 ? + + const OKpadded = (O*K + 15) / 16 * 16 + var a [(M / 8) * OKpadded]uint64 + + // Emulsify, without reduction, by accumulating M + bitsToShift, wordsToShift := 0, 0 + for i := 0; i < K; i++ { + for j := K - 1; j >= i; j-- { + // always maintain such that l = (bitsToShift + wordsToShift*64) / 4 + + mj := m[j*O*M/16:] + for c := 0; c < O; c++ { + for k := 0; k < M/16; k++ { // currently 4 + a[(O*i+c)+(k+wordsToShift)*OKpadded] ^= mj[k+c*M/16] << bitsToShift + if bitsToShift > 0 { + a[(O*i+c)+(k+wordsToShift+1)*OKpadded] ^= mj[k+c*M/16] >> (64 - bitsToShift) + } + } + } + + if i != j { + mi := m[i*O*M/16:] + for c := 0; c < O; c++ { + for k := 0; k < M/16; k++ { + a[(O*j)+c+(k+wordsToShift)*OKpadded] ^= mi[k+c*M/16] << bitsToShift + if bitsToShift > 0 { + a[(O*j)+c+(k+wordsToShift+1)*OKpadded] ^= mi[k+c*M/16] >> (64 - bitsToShift) + } + } + } + } + + bitsToShift += 4 + if bitsToShift == 64 { + bitsToShift = 0 + wordsToShift++ + } + } + } + + // transpose among groups of 16 uint64s in each row, so that above matrix becomes + // uint64 + // [ ... { [m0 m0 ... m0 ] [m1 m1 ... m1 ] .... [m15 m15 ... m15] } ] + // [ ... { [m16 m16 ... m16] [m17 m7 ... m17] .... [m31 m31 ... m31] } ] + // + // where {} indicates a group of 16 uint64s + + for c := 0; c < OKpadded*((M+(K+1)*K/2+15)/16); c += 16 { + transpose16x16Nibbles(a[c:]) + } + + // reduction mod f by folding rows >= M back around, using 4-bit multiplication table + var tab [len(Tail) * 4]byte + for i := 0; i < len(Tail); i++ { + tab[4*i] = mul(Tail[i], 1) + tab[4*i+1] = mul(Tail[i], 2) + tab[4*i+2] = mul(Tail[i], 4) + tab[4*i+3] = mul(Tail[i], 8) + } + + const lsb = 0x1111111111111111 + + for c := 0; c < OKpadded; c += 16 { + for r := M; r < M+(K+1)*K/2; r++ { + pos := (r/16)*OKpadded + c + (r % 16) + t0 := a[pos] & lsb + t1 := (a[pos] >> 1) & lsb + t2 := (a[pos] >> 2) & lsb + t3 := (a[pos] >> 3) & lsb + for t := 0; t < len(Tail); t++ { + a[((r+t-M)/16)*OKpadded+c+((r+t)%16)] ^= t0*uint64(tab[4*t+0]) ^ t1*uint64(tab[4*t+1]) ^ t2*uint64(tab[4*t+2]) ^ t3*uint64(tab[4*t+3]) + } + } + } + + // transform the temporary matrix into the desired form of A matrix + KO1 := K*O + 1 + for r := 0; r < M; r += 16 { + for c := 0; c < KO1-1; c += 16 { + for i := 0; i < 16; i++ { + src := viewUint64SliceAsBytes(a[r/16*OKpadded+c+i:]) + offset := KO1*(r+i) + c + decode(src, _a[offset:offset+min(16, KO1-1-c)]) + } + } + } +} + +func transpose16x16Nibbles(m []uint64) { + const evenNibbles = 0x0f0f0f0f0f0f0f0f + const evenBytes = 0x00ff00ff00ff00ff + const even2Bytes = 0x0000ffff0000ffff + const evenHalf = 0x00000000ffffffff + + for i := 0; i < 16; i += 2 { + t := ((m[i] >> 4) ^ m[i+1]) & evenNibbles + m[i] ^= t << 4 + m[i+1] ^= t + } + + for i := 0; i < 16; i += 4 { + t0 := ((m[i] >> 8) ^ m[i+2]) & evenBytes + t1 := ((m[i+1] >> 8) ^ m[i+3]) & evenBytes + m[i] ^= (t0 << 8) + m[i+1] ^= (t1 << 8) + m[i+2] ^= t0 + m[i+3] ^= t1 + } + + for i := 0; i < 4; i++ { + t0 := ((m[i] >> 16) ^ m[i+4]) & even2Bytes + t1 := ((m[i+8] >> 16) ^ m[i+12]) & even2Bytes + + m[i] ^= t0 << 16 + m[i+8] ^= t1 << 16 + m[i+4] ^= t0 + m[i+12] ^= t1 + } + + for i := 0; i < 8; i++ { + t := ((m[i] >> 32) ^ m[i+8]) & evenHalf + m[i] ^= t << 32 + m[i+8] ^= t + } +} + +func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { + if len(sig) != SignatureSize { + panic("sig must be of length SignatureSize") + } + + var digest [DigestSize]byte + + h := sha3.NewShake256() + _, _ = h.Write(msg[:]) + _, _ = h.Read(digest[:]) + + h.Reset() + _, _ = h.Write(digest[:]) + _, _ = h.Write(sig[SignatureSize-SaltSize : SignatureSize]) + + var tenc [M / 2]byte + _, _ = h.Read(tenc[:]) + + var t [M]byte + decode(tenc[:], t[:]) + + var s [K * N]byte + decode(sig[:(K*N+1)/2], s[:]) + + P1 := viewBytesAsUint64Slice(epk.p1[:]) + P2 := viewBytesAsUint64Slice(epk.p2[:]) + P3 := viewBytesAsUint64Slice(epk.p3[:]) + + // Note: the variable time approach is overall about 30% faster + // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] + // [ 0 P3 ] [S2] [ P3*S2] + var pst [M * N * K / 16]uint64 + // mulAddMMatXMatTrans(pst[:], P1, s[:], V, V, K, N, true) + // mulAddMMatXMatTrans(pst[:], P2, s[V:], V, O, K, N, false) + // mulAddMMatXMatTrans(pst[M*V*K/16:], P3, s[V:], O, O, K, N, true) + variableTime(pst[:], P1, P2, P3, s[:]) + + // compute S * PST + var sps [M * K * K / 16]uint64 + // mulAddMatXMMat(sps[:], s[:], pst[:], K, N, K) + variableTime2(sps[:], s[:], pst[:]) + + emulsifyInto(sps[:], t[:]) + + var zeros [M]byte + return subtle.ConstantTimeCompare(t[:], zeros[:]) == 1 +} + +// GF(16) multiplication mod x^4 + x + 1 +func mul(a, b uint8) uint8 { + // carryless multiply + p := (a & 1) * b + p ^= (a & 2) * b + p ^= (a & 4) * b + p ^= (a & 8) * b + + // reduce mod x^4 + x + 1 + top := p & 0xf0 + return (p ^ (top >> 4) ^ (top >> 3)) & 0x0f +} + +func mulx8(a byte, b uint64) uint64 { + // carryless multiply + p := uint64(a&1) * b + p ^= uint64(a&2) * b + p ^= uint64(a&4) * b + p ^= uint64(a&8) * b + + // reduce mod x^4 + x + 1 + top := p & 0xf0f0f0f0f0f0f0f0 + return (p ^ (top >> 4) ^ (top >> 3)) & 0x0f0f0f0f0f0f0f0f +} + +func inverse(a byte) byte { + // static unsigned char table[16] = {0, 1, 9, 14, 13, 11, 7, 6, 15, 2, 12, 5, + // 10, 4, 3, 8}; return table[a & 15]; + + a2 := mul(a, a) + a4 := mul(a2, a2) + a8 := mul(a4, a4) + a6 := mul(a2, a4) + a14 := mul(a8, a6) + + return a14 +} + +func emulsifyInto(sps []uint64, y []uint8) { + var acc [M / 16]uint64 + accBytes := viewUint64SliceAsBytes(acc[:]) + + for i := K - 1; i >= 0; i-- { + for j := i; j < K; j++ { + top := uint8(acc[M/16-1] >> 60) + + acc[M/16-1] <<= 4 + for k := M/16 - 2; k >= 0; k-- { + acc[k+1] ^= acc[k] >> 60 + acc[k] <<= 4 + } + + for k := 0; k < len(Tail); k++ { + if k%2 == 0 { + accBytes[k/2] ^= mul(top, Tail[k]) + } else { + accBytes[k/2] ^= mul(top, Tail[k]) << 4 + } + } + + for k := 0; k < M/16; k++ { + acc[k] ^= sps[(i*K+j)*(M/16)+k] + if i != j { + acc[k] ^= sps[(j*K+i)*(M/16)+k] + } + } + } + } + + // add to y + for i := 0; i < M; i += 2 { + y[i] ^= accBytes[i/2] & 0xF + y[i+1] ^= accBytes[i/2] >> 4 + } +} diff --git a/sign/mayo/mode1/internal/mayo_test.go b/sign/mayo/mode1/internal/mayo_test.go new file mode 100644 index 000000000..b840400de --- /dev/null +++ b/sign/mayo/mode1/internal/mayo_test.go @@ -0,0 +1,222 @@ +package internal + +import ( + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "fmt" + "testing" + + "github.com/cloudflare/circl/sign/mayo/internal/common/nist" +) + +func TestNewKey(t *testing.T) { + for _, tc := range []struct { + name string + seed string + expectedPk string + }{ + {"MAYO_1", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb14803", "5b61421edc1c90efaf6075560f0206173555c55eb845ef3a70ee5ef18618361883a0ade490e9e5bc51cd2b25fb4ec9147a2fa6735d8749ed7e149330a30b3ab62b7991c2494cb21d0bc011ceb357597b8afef939a1fe398421c88f43fb4fe380c10caa3a95e507ebae5a571fd958e0505d994566263be8db31ac833aa20ae7ec97d1a865bb292e89d583975009046053bf91ce7d73e9c23c4244071e17a1095f3918330559f323ce557d66b8771f0003cccaeea82314088d56455d72f258e4c35fc6ce2d195cdae99f359a8307ec15f27da4d0634531cd9a1a719121f7c9b99a0d0c24252316f1ea7c16d73f5ecf7125ad0c8d2e02574131504875af5ab6dea1c328e71122f0cdd09e826cc515b4ec160f31d63253dd8798b2d841e80a28479bcca320853a459d4d659df695eea4b5aeb8f45f6c5fce283c64dd6fe928d452be3723f8b448650ebb72acf231e3c27683afcdec22037f1af29c479db1a97448ce570fef082052c9ef45179ba62abd7dd156f17f4da51557383d40a9d544cdf5cac4f4cc1ce69326a3ccc611fb7a6d04126dc54c55c2286a796be18958846d53e1e3f55f3ac0f4e09d8f302878f4993e25c2164970b337d0364aa61eff5d85e41fe784fd57420a5481b9a15f02a47e3e49c0709a37352a50fd0feb7e7f9938173b3e3c570a79c2424f5dc5bf95363b6d5d764e1a38a1aa05a31bbb9858b51fe3cbb19eaade5d4f482260838f5273b042f6340236395f6c347d4bfb922752ad0b17f1bae8645a9a6b1d72e1e91b4828faa313dd85ec5795252dcd95aa33519a56d740cbab7b9ee13e1b8add763bd07a25455e44e60f664846e32eec329c46e0741b6649c34358905d9d01124c3c8ae14e3fb47cc6477ba8e63bd4a3937588251952f5232bb9999aef509414e11a36d110ea24bedc235c55ab6f80116b036845e291148135a32b0d44fe5a4e9ea9f3ae150e11c62ea91318129f79318d3506ba50db3dbe235427d405a5baa9e6192a17014dd19863087445eb4cc9b6164aab3d5af4e73bc3edd97c76dabba0bc26c43d70f7880a0f7c042465da77ae3f8a31fa65903d90797fc0b5deddf578d52940040276c9b415d880e3782e03a2965a6d3b265628c690bd1b096a355362eb751d528fbc6fa365d1cf3d39cf49d8296c28fe8eb52a31a07e877d1e57e91971cbbd14216db76a08da45258a1a801d0cb232a87280df4522329ecd9312e5489d90a39547735cbd11493d0d86389af40c1b9ec5a993d0cbc508da424392695e1cdf205a294122412fac5a2b4d6e0ed5eed7b4de18f0d4b81f746753fa8744ab845c18800befc926d3290531053bf2cecf23d1b6d7fa9fb8d4827e87abf574afe39a636dbb9fce48854c641981f236da01bd8a5901871891aac7fa1f02300101540e7b05665e5f19912ee898666b64bb3cef688577c7b5482e123c0e8fcc466302eca8fc02f35260569cc1d4b00662be924f188226dbc6086d4aaed1ff9c7604748a556d00f87823f9fafaccf752d645b76a5851723274737e4dd7d5d76fdb256e3f1ef733914eb36932eb1c4c666df436351fb817e7055fbfca2b3103fcec84233feaf3fefd600721d3ca1514ee26db6a608dd4333377a9e36dd2dbadb1651eb99d8e3b4fe6"}, + {"MAYO_3", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d", "1c0ee1111b08003f28e65e8b3bdeb0378c8800a9a87f38ca101b55cbe065f76cd8be17feef5b448fbdc40c759729dd609f2f3e0a70f719bd04bdec59158c1d885127c91066d31b4e53848db9566d8d25393dd3e665c8f4b5fd5d5b836fc2e012a059a5550646dfaf9db63eb4c928198a4dc3e8631fa743e11004a2fe3454a9d537697af4bf5d6bee82c2fe40d5c74c29be0cd7e56c8dff8dacebe18320a455a72265559a287385643668d2a20058d927c87a41b8d94ac1c68b6a07c7b055604c7491f11ac30974ab0d76ff2034b6c73d3b4cfb13e8f341f9f3f445bb6a4e47aa381282f83af4c46fd38207da843135dceac04af08096915318e464a8a1fda2a99efd41c61197a498abc875ce302f94eeb8ec32e56f24e2c6c419250e08e2f69148fe083747340af17e634a98afbdd6902211c767b750365f6200fd92e8431160f1e293058cf2cd57be92816a712d796e6fc0c672026c162d93eb79a19c7d4853df371234fec9c9ce76b6edfe082007ff69c64c9980f2c7b7b6009e3db0990cffad1f4119844967572e1122e5c7ce460ccf732c82d3f18457111ae8e1a5381c5b30e9d76028ca26839455d44aac728a24731a1d69c8c856b3d611dfe5336fd5a42f62ee607219c654aa117a874ca33fbb89ecdb6310cb4e0c4a5e2554be2d4bfe6abb062eef513d726c258dafa690324302d8fb108376f991e50e9037e31ed3ce8d8cd785fe9eb419d33c7ac381f91c463a8a06629db910f5fed1d18f2cf30afcfc968ca5617c6cc6fd9080f53cbd05b727c04bf75dd93f08a0ac1a7bc46e8b5a08e6d3c3b1e1232126d4e65b1a3877689dca9b1e53a73d45ad9eeb38faf942a62a804b088226f4ecf15526e24936f9c28c951f5c101c5c44bf91b72fecbb3a9d28cf77e63343ad760089e0739017cf829297204041acf5b4c10fbca19219db233fabb3a564c8cddb281f290b9a8536c9a1d39a35d8eb124182d9cdc51044067647ee861b8c0c6fbbe1aec13076b8f3d8982b52d1243a9448729361619ba34edce7bbc1b3a1545f791a5f2f6d04b5204e073bf66e7b009c7d4a1660355d066fd0d358623fcee946e69d20bf6fb291906e700004aa001494381382c5e1bcc4fb2b7cfc4bba6866548ecc797fec670abc2e8a8dea2c4219ed32102a0a1f4fa67775512768e57851571fd383a5a684ed1e10b62f3bbbb65d58e08151695008ef6a7cf4b7cdd265d9cf1c158f64c607aaf37e51140518724c39f3ad8a759a7ec0af43778c00832088270934cd08a582196953b283fa047f6365585b6078027be9d9c915569b78c65702561ef5de7953fb6f5e3df3ae8b85d3fb2161cc3af62ed83d027b733507fee131da9d228c79277630c3efcdc9ee1e559bcaee4c818d6d29ea6c8cf9eb46ad58a4ab6a4fb2f6d6f4380efa4d60843ca4eac32f8772073f1cb70021547050c30095fc7c884e31a2f91994985eab00e7565da2ece39e47a6c8cfae012687dea9d65b8745d4d760e656a55243fa9ba8fbbd31b7f2bb2629a0ec9265aa883c8775ba173e5ff08a9a3ae4e76f9925418f4e2dce1d00e4f9377245b8939dc58b7d15c172baf984781afd318b264709bde5f0859a95923ecc42108f2fa7c600e9ff69f3f65751d8a457dde89a1ad900314f90e9b712580eff5e4a73efa3633c5d5e34fa8f2528075fa92dce66506f5e947f1fdef820296cc294ecdc059390b644a1407b13d24541d275a49d9bd1a6f68dab296f52965c7f72d50cdc0ec19456001516c10030086d2efa109f45fb6d1f7550de534d0d4ce856e72763463e6d0d11cd237037fea14abb32465f632ff785f1917322e4574bbfa8f74682da17f2bf7bffa0ff030297d5c999d6a7c387ef2a5e168225a60ebb01c6dfc72ba1fdec8f74651f3e64750f2dc98d72099fc85f43fb28ee0c4efe8b7d76bb432453a848310f6e96d833a27ccc98046a210f568c075dc71545ff0d37ae5138406d569e964bf8e6903de7651b33c05e22747074b730cb6f3e94b891e8105ad6921cf5efd40da88ec3d0765756b7bece012f59f1f68ab80082fda5fa37b026d0b6cb5fab9a0ecab6980a179815bc9e56cbdc0d0682c29c5f9fc8cbfbf5a6a9679de92adb5b4778ecdc70f819549087bef738037de613cbec897e016672ce9eaf00382ce4b60c563cf3d23cfa178403f270215cef465ae9b60f007c75c1fcb85ab4ae9ead303057dbeedcaf55f13ea586f55a2eee9da8dd92bc210d1e2d4fb7d219c54517fe5a4ddf5c8c6a776703ec8407d6b393fb331d7aa62423cbbf7a062bc210e9c6565cacfef1e4a5d448f2cbf5c8861857dc870bc37c2070ce1f1147dfdc285d9df8296d6620e1430884e29571ea95ce1deb9c2ae7198f10732b2b2d5ddabfabda6e77acfa2b10c75842e07de69cb2b34abe41018c9a765e45ac85b63a85ca612b1e9682d7c8f512297bdfb780e21506956fcb528bb1916fc1a8356667033e99e16c223139efef6790b8745eafe30df12d900301643f8bd6717d0b5d1de5e75f25bdc950ce507ce7b3489e72d5abdf5a28105a8063d0a9662672489de16a1628548382277fcfb24bc878f071b13d6e120af00c1cdcacf5f1ef4823a2b5da0e7c8487550d80ac9f6891e50be3dca0096042c97cdea3553db702bbcfcf64f1b64719bc89ba51a09057fd9285c4aa7d95532cdc04ee185beb1318132c836278e368ff8e2653e4aafa829a2d40d91bda7b4af3f16442931b7da2b500f7e3abf80219b8a245fc4748d20d549cd12c5af7ca3a8543bd14d0ddfb4fff8a43c661d0a8163fe80bc0f77b762d010b64b831e7c29f9982e5b7338a6411a37c4f1f46419b610415104f154a009cf3bf1805ff279145477a0d5ff0f3c9584874e8e8376101dec2c8c69592a8c46add07a6c0100cfab2bba03771749b3028d287805a6859a1e3a82f30231b01a7e3453059638e0d7a2430348ff4af26d00481cb3de085d269015fb6043a0346281a05448a81d04ad196d916503c35d31e513bb133d49f9646c191f872cf4d6798c18c87e52695d1adf59c97ba8bff5e826ea17ab2fcb8b559e871e6d62d8a93522554bb5d48de674596761595bf28d0114142a542c2f9cab2be451eb37546ce3eb60bb0cc6050e5d38ac96752a8d2575f67ce28452b00b486e2efc8aa80e94a0a442fd35f5fdeb48e52a82c43ef2c0a02d3bf71da67f0131af8aa90e159056ebf4e5dbbe176c9cd8c07d234135c50b524a4efd7b72ca4f413b29809e7a1a53442a1919bcce952203d17e3c5600dcc3160dcbf7f18b9352289ce78fd337c36416b8342635c3d542bd1a357b8e39b04098fe7b60248fd6ea4a2778ec576e4c34323c81e56386d40c79260e3d914db7cabbf269441dcfa216ef7aa85fae99a89f2c1f56838c30fc180135efb98bcd01e2a88d144ecf7063613d3ba62af5fa8cf7d63465273ac0abb3f94e87130310f4b841db13ad7fab1444a02af5649ef9a29392a4fc15b747799cd8a8d735094cb03a606735ac94ee244a8c5a3504f31ae62d3acf7f1586390efc6e8da967f3b174d9e805554603c4fd5cd875e70352d2c6576e3baf0420d6f3aa161f605219bffb93abd51628014072e7c2e533aa86117f02ccd67b623af4ca911eebe981595fd393e75d5b0d0da9440e3aaf9249110f5e8c22a68f9cfe0b30e8665270763390fb8618668a1a56b71065a85b91ff8dbb8e4ab9c3e6c079a2b0276ba475d388262f36a76c3e230bed8c47faae910"}, + {"MAYO_5", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2db505d7cfad1b4974", "708141534c09f5f604225ef8c3472f3749ffbce6c071fa7bb1f4b50b78ecd32e7f35efb6bfcead3313156f9995fca2d2db6d0f0ec50a66bd3d5b469698ab131f21cbae3785a0c838e2e3316fa73e9670adb76fa9e1ec8a0bb7ddae82c0ca7c1aa5e61c31b84180c3aae027223430e988e28c48ce0d5b2ad2750203707f2e284cf1807f008ae2570092a662b73a9a728f81a79338f3452d6af920ee59037f2c8a20b495420c5bb3fa7518a34ae1e64ffa742db0c3c009504ca98882e4abadaf8396cc242d9a7c1d4d71b17d6c65ed26c4e459d8bef86326ecc156209299424cc015c52f97862998f871ebacba29877e040b7adfecc69081f55162d190f374b704662c138c39782e7a8b74b7867e92c5bd8a7833b4242f04712ef4384ef452fbc3078550f15e563c9238d72088275d02d94cf8c163a2bd73810938a1208883874e1b7d8a9ced0ce6a7177da260ec1a35d2e0f45bbdf18f27cd3f0619d134aa47e4df364fbea1dfb852c225b06be4707824d9d2f8b25d6e15f81079511778cca8b82554591823616a7b7ea029c3c3670dba35bf2f11b6493ba5044c159c368f7e4d3ddb215247ac0fa17f227298b9b1d6af4632fec18ab98c9218f583c75e9b8434cb0b68105e44be85da80a65fd67839b4981e2e89feed17f41b60e75122b5328fc0a5772b8a0ec5e4646e1fc1f98a44a31d516be5cf6c187850d7343078304f8a2a02929dc2aaf268ee590507b325a0d744e413d58dd61e62decd0d4e846f08f9fae80cdb7e33d693c57e46cdbc7e1ebd06fc44133748064db8b77f1f87df1f21891af721e840d768be637ebd41cf8be5de36739045b9bae72cd9192a1ff0addbddaf6a0b6cb0d0923ce5b3d5c261c20b1bac348eed6d1cebcdda642103f3a4246e9ee3a1f1950be11796a2db39c54299a6973bdbc4a9446ff27a85fbce46e4c94c25d6fcf1d8bfc773df36591862c0447a400312969c056399b2ddc92703a4d14f1b5862752257103a300381f41f47d7baaaaef9bccbc1a3ce8c94edd1d7c43ba63c961918c1c9cffdd03659158d0827384f796466449a0bc72e94badcbf567e9f6812e320b772b7d330ca8d58996fb9cb9a4ee7772ef4e77f67e2875070249026c1287ca6b43af591a647c33efc965921c9225cb195898f0c0185d3142cd1d924119b003b0b51ccb42caced8baa8060a1750b53f7c039f857dbb248a2520bac9117da7a12ba07483bb751afb7fa1f3d9e9eecea70a9fd233adbad40fd3ff7ca1267a4a8d2375ecfe35d7984ce860725f68f19a19c0e00af83f13d452491720370effe877ec6ad51e702c9ae828b0ad3a845519d3267f3908c808498ea73bc96b70cf4d9cbaf1b1bcc9b12a093936cdf56cbb703799307d80b3755ad72dcc0b667cc14f6dd47d99d936ae3afd5be34264ddc8eaa7026e6391780444ac43c01cddba3c50548d7a2c03a59b1db7d798043351b51d4fc32f6d5f8ceeb8654977e7ba147936a6d172cfb023308730adaa3973fb4d883b95cdc5aea24a83e70561aaec784023e28dc385aae7076d271ca0551b83a60d7388b9a2c804ed66ff4fa6284fd57045d03b6c31d0d32165222efc6876b14a01c18024b0d6fec28361de82a337fb087ba25daed484b3e17fae66ce2bf2bc88614ed5a2e1db9f485ccbf7e029009c164ae3bb9b7f3b7de396970cda0943036e18d2cf96b9979e8d874980a844d7628b5fae66f00ecc4366a96c814785516d056e44b906ef6dc09e1a976384ccaf1b3f40d79a5f6ad426bb70035e84961c188ad24a8122088fb2a4b5abcb5fdd0a26cd7d44dd341c5e5ad65478269cbb5e30f4c23d63043de9c65fdd57fbae9f7a68c7bcf4e4e6b0eefdeaa3bf376c912b15f00e1a6c1b4c80269cdf9bdf25373fd55a7f51ae9aa0c0de6faf001a2016eef56d82eb696472546ec8bd29f1e76f0666b4c8d4c4cf6e633b30f323110d7b0fef38a2ec90f9359278a23f276da7984e87880105d12051937b037d86688b0aab4a11ceccd776263b8a82aa6d5a5490d33e734c651aafea394fb24d85ef3a0620686e1d96d198509e5f04f406986ff201e0b2d3d7584216e8568c68c2d8ebe1947209dff588969c3c088877b8c1dc0b0fadb7295fc44e3bef5d81789329675ac5c3142fec702f749c6e3aed13071c02680f7cb82e91432f6d6fd7c10ca132e61f2d47d6e2008ac2d5157445c1a3cfa52e03c5ad575fe836cdf4d8c9624e097d969eec08dcb4540be5b4a9f3ca03201457a2dae4a966d0b35abc65559484fe0afecb774e9437eba24d22e4da5927d00ca3a9cd49a1f3843b1f3f7b2872f0b1790788bb216d53b9b61f3f394d140a200ed30c3229761af6d3ecff8fc463ef63ae13f92d3d555261f04a287dac6653b7d493cc9026f5be29c8c550bfc513cfdeaf83ec0280fe2786ca9c07b222f6ccd9c31a8848a4e79be4b590fe4e239ad9239df96539b48cdad99fd0249d50204649e26838a076adcd6800221fff817681a6a1ccf807c1ae1794aa7cbe383cef23610436d51cdeb631df8bc1f7f7207b9972c60dfba18cd5378eab1501c6f8d4e0971862cff6b1cf3a327e1afa6371de15fefbb1c80c658d2b372f0bed979929e9fe3c3769ad0766edb634949c29f9bffd879c961f626d10617c161319957c28519ef29142236ccdbe214f7e3e9ab77edd5fa814f5b599513a46f79cac9fd9ab62d7859ed57a5e6a0af7f5e74ea4f9fbef3c9943f2c69cb66ef4497c92b25059980bd55568e8a5c50c0853fe02f9eafe068ebe710cf284976fe4b3c12d5990951775b891cebaf38ba4c9dbf70423210f723a6c0961f00766f0acf1ae75d3f1298b378cdd7fed999a8b45c923c3c8a9a48f007b88f6fc9e0d37458e441247074e9c89e7429bc76a7bef55e702420c1bc65d66bb28d46ad7e0c5be42de7b89a44dbe7ee416c795c0b7e91ca9cf2f2762816ef4bbed139137a8ad3fa7a5a96c917e8af660e736ebe9d70e4b90fa18b560ad173617f8dff65804a2bae40a4d27e86e97e02e485cb529fad4a5c7f72a2b758a693826bb964eeb28842f5aee51db712d68354d7f1ec129b7785091b1cbc3fb389263e064eb03df94183e0c1bd1824b8f5c232d7970c11e53caaae90ebba5ea862f5aa26aba25b5f627823182444efa64f21125de36afdd615518bb5dfdd117b5485910319b458236dad01de33b8605352517ce3aeee706de917ca76ecdc4ef53a733e5f898c2dd6c4d641d141ebcdca2c5c7cc176eac1b493df81702083bfc124ad9ed3511452a71f831c9a2f8d2d771cd3c1b1c440bdc46e87fb5ddc0d45c3e28c10aa1af313c08bb554590dcd296e0916a9b9df3d527a5db9ec1694083bb99c480231c87444998dc7f922f7159b019460d39be64882528f4df1e3e332f7386cfa34310b666d369ed7fff15ce6db939e448beeb74e221862a901c37275cec3e3718b146d0376c0b44e64d393c231050bf166658c333509aadca601619ba6e473705b5be1c9c290a390ee24062265298ad5bb30cc53571b4e8046856a10da99823e0a33c85fbfe785ba709a3cb9b97dd6d56db92febf8a833941655294134be7d37de581caf4e8fe278d9ad5e31d046c7c4c6e3f162c8b5bdb27f1bb5c772b8e7838f67f97cc3c090510fcb5e2f96191a77eeb7febfdcbff81a58860bd4fa87cb41b1fd51ac670828fd6efdcace5ceb85339db706286e860bacb1667da894ab79d32a67563f9a1935953679b687b558324e801c62b9eff69888a51cb8c79d6eb4aa9651108b53d1b66afb1fcbed7dce98403c64885bef7e45a0689ce783cf7741c2c787c1643e08a109b7f5813c11a16bc9269faf0c74e6e0d08428d3de2cd00e18615e157488b2a4095ade2e101dc3fd48da57bbd1c79e9c5cf0992014f80317fa35ee71560c27500761e8a60449546ffe1a232806a8ec6a5694941485f3e0af3f4cb557a42219435bf38fa10e11f3a243c9c584cde736a02bcc37b8f624931710c159f167cdb41deec3db28535731dcefb4926c8580d290ae818eb3a09738ca5d88ed1909ca95f1c71d9305f89b773cb840e3f481aa2f51dd65082da3afca358bf88d2781b46d82d7685f4ccd0983c75f9f1d6382493692604da6c8e8effb27129b1776509b98efc15c75ea1f11c04d7374742ec8cf28341a1be36deba666dabf6bcade7bc3670c03bf3bd42441edfe79716a7ccd32dc3008693a42800944bea936484bed7b6174718d31ada938690ad1efb0442fcdc7a8967f96fc4fa96821ff9862c3b1495ca55afe8ed5aa8097e20fe54f3ed2f2be1cd241a494bccfd3c75d7acd180ab6eb192402947c273abf6fac2e957c8fcc7757d79361071b2067349823cc9db3d531aaf0561acbb918df2924276ea64eb2bb095680d0be8da8d38d53f97d6cc68d88bc4f22c6865cffbd59a6fa015d2f0a12a985fd97a34e4d7f9504cc5a86acd4c791f105b9cb944ae4d0b0f747a1dd089a7903dda5528752132d2387e4854234a8a3ce95fd8afd4bfe8e55f4c026e80348ece14da917b3a696ad797e70d06356cfd2463f5ccb96549e1a037f938624754c57401cddcc948cbd088a0f276329a87db91308ece6360c743e4f8f360fed28b77f7a60745aea0b89721f4cb342b75ee1e51625292db27a97c2eb18186278fd826fb49cfe87fc9b45bd26a02266da90a7ca7d6c5d9eaaa036a0c7a12171832923facc0aae7554eb365e312e6b7b3e8be167b8e580fd5cdd609b5308864600b98f9b69d71eef1d4765f4ec07a584d9bae8a407777d7334bbb285ddb15a07dd5e3a4e8c8072710a4ac204162987f2b669524d88afb77e5f4a540f9985d560dae55945abcdeb3f90a498f76cf44d0a9e3977cdadc11a8613d50e4fb19577a6f6a16dd1b4003e56268a1134d774b9cc4e94ced0943a0199ea9ec944e891f02deb85c1b870e535f97599f843e8467c771784796c3c8712f3681b475a4eab150d18928aef90f293bdc505784441b6ffec7e525b15b5d1e2203f8ec95db5eddc8c6eb21e82118c75d6dcf2c58eb4487eed220b1eb6f3a3a7e92b0ce13ad5e87c4e4edd2c8bcab83d7622d20f3f5cd74a930515808e500563848ab0893cef9323ebdeeac9716270f3550c1d92ffb4d650c8442300cb7a4c61de2b5876f6563271ab7abdacc962be387556a6452dfdedf99148b4a2fe635dc730cfe9cf07a1ad5e148e2eb639e25838a8b5f6271a1ac8330847da496f042237e9f38ace9c37f096da0d76157825c2c8f578919faa984f4be65f7055e517b8f78589e1ff361d769e5d387795b79bf476959815d9b6ff22361f76e60cab6986e0d164a0984a013101e3cadd3eee5199030b45e8c6b9ea63f80c7e6e2909b39d96d6f62776820281eac9fb55fb5c124b7dd9e9b114d476d2fbc676df79f9e9b214ea69013681e7cd8d379bba254382ae4678986f67eba92cd978ac6353e6207101a3e1d18008c3d5924eef4991e0b576fd9f582980faf24b898796c18388f40ae96403d45b31785958d37ca09b76a5a7320776ca4d328e95ea7b6703f4ab0947070f5c20dfd154a9476099d193dccd487f589759754c43fb81c1941439b4d63eeb6b24c294c70ac0a71ff437a6abb795aed2fa4e2483f2a09ac6d1a85b91155cb368219d17883106568c27c924070ad23ed0193dacc353dbb95a4c1de118c1a710f96db6eb9c146c2d0e07873d495d1ad9e3717e09c067422fe5a2da8d9406e1395aa39cf0c82f9c290d1e31a4a4d9b4c381c95079327c296e5db5f8e3c92ef62c03f1c2ac7c4b0174528b5dc0ff37c8c7d0533e4248fb86ca1cb5df91e05e1cc582482e7f12136e8628a48085ff27d15815506c5013f6f65e3c89286616ffd5d08f3fc2194ae51fc4a706e2cd83b6629ea8cce9ee3f968db84c85e3dc0c4b3e4cd6f105a196260f5873c8590401719ce3afd8de96d465b5258ce6b6e53db24fdc8fea3a9062fd2f10ddfb0adcd355724e366afb6b401118d73873c8e682dacbb4be47452eeb98ce51607903126f83ab6b27723818f816f9ede8ef344b3e7bdafdbe8292c6af60835f572ee68629386575541f1b5d5bae84bd85cb65e1ec46a70abf6365f75f975c3b87944e45d0da01f267b569d9ce55cdc90fb3ba1b5f7337f87a914eb5f6f7dd66a56b052430b148e3e295eb82fed70c6a38f738ac63b820db7183d18156abaf638ae366ce6491f0f0e326e1c9c3c8e7df4d62632f7599805f4a8ef06ddee3191e35f20cb7d03e7afbae7cc71a914aa9c1744b420ab5f406771e944ee7e3aec062bec33696df2c5ac2e09648902ea629aac071346ef87f5e54aaf650785f5ade96e0919ec00b99926594cdbdd1e0482e56fc0b78cb6142d2a4be8aed181918b11a841fbb8b442a3e2af1ef1c29528d6447c72b8c2e514aabd0595ebdc698b90ef61a6ee4b400f01fa5c71a2dead6822f33c3341d8c019bd100aa9028b57e517ac6880112335390a8349b90235486eddee01685a7cf1ea158cf9cc1f348a6b113a9d82f2dae95c7ae56f9183d97cbc35a25baea3552b8c2a7de779f6b74db1cfc75d07d79aae1857b601e73cc3686c175f135233989e6d56bdbe611f96a2b51804467e47b474479ccfabddbb4629a0fdc7f73e172da3c168c6c40461371e05a9a332ee3aeb0c9a5985425e102e421f497ec978de92bf73c98fd7d1bbd068e9d8ae82cbc7ef3923bcfe97bd68fe9563195d26a1afdc3bd91b32044fbe68b4bccf29a8d52d13c625d89f73611e371dfe6b73b50d6388cfd87ba92074533cfb34a4d543126f096002d9ce5ecf89cc11b0449f696e1661a36884ac1bf58f397d993c82bcc03c1db156ffa5148da7a73b3be2b6a3b2436c26c24ac0154bee5837d079dfa65d04a208de8b79691518f9056ccdb382e61c8d7606be73322641c71719a23ad6a571f907aa0bd1d8f3170296f3cc88bacd9b3fe944548808e80d00c07bfddf271ace893f56e01f95329aa944d32d9623180142b32e60ce0b908cf2748a29c42a3a59b378e47ebbc4ada9b43133f51bb4d28b821bec1788cc94ac1c8e9e4499aae9e17fcabff0a824637f125e9b358e0"}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.name != Mode { + t.Skip() + } + + var seed [KeySeedSize]byte + _, _ = hex.Decode(seed[:], []byte(tc.seed)) + + pk, sk := NewKeyFromSeed(seed) + + pk2 := [PublicKeySize]byte(*pk) + sk2 := [PrivateKeySize]byte(*sk) + + if hex.EncodeToString(pk2[:]) != tc.expectedPk { + t.Fatal() + } + + if hex.EncodeToString(sk2[:]) != tc.seed { + t.Fatal() + } + }) + } +} + +func TestVerify(t *testing.T) { + for _, tc := range []struct { + name string + seed string + message string + signature string + }{ + {"MAYO_1", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb14803", "d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8", "165e6139a87a4a4966326ba83e6d1ef68d1d7bec62c9d1d267ba09df6815783980f0a6ca0923aee6e5b9495b7ed6fe6c49cc41f794a80d297b19d3f13b0bfe3e8865378f5d24a210dae0982a1036ec9cbe87c29ce9a05a8290d0d1e8603abd163a27b119878663c06745cee03f417300402bf198ee4ac2be99dfe19700f169efe6a365ae0fe9c1bc22028c864ec55072da4f98f878759de68399a2c8d2a2656996927265ce2908e05913f8057707d39dfd3ef58a79cad49f0bd010ca9e8ebde500057bffd0ff28015a60b3061cee55dfa1d59e1a72a8ce639777e6190542733639fbba575aae945583bf804ea129e1a80e12fb628accdcc3f43da37c7194bb641bb8b6404aee481b4ffb13be31941d30c1f9d818182545738ce2644dc9c9b0a1289c6294d3225cc23ee03cc8258176e1a958e90dfed58ad3a859d1b06dee53af6a"}, + {"MAYO_3", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d", "d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8", "01de18cb2cfce7ba2d15677a57e7d86b20a96c7b93daf27232ddc7a0249796ef9321ace5792a4af1ca49908780398a25f9e4b1293ad8209301c769f123cfa430e5bbff1812dd4a8efd5621c109444cb4a1bcf4f1d85abc6fbe493ae0370aba2b2251de011129be10c25f452c987ddb1c499faf1dc8da2e60e7913ab7ade65844a92ed3a5c04c2245c67d342e5067be4a8c024753a8fd5962481b9c52051723f50165ec2b0ec8f73b4b337361c12014607f9fe41c3591eae8653d18b95616b18a84a1a5951ab5082ceebd5d2cfd202fe3ae29b60a790d04295edfbce886eb98d63cce694c9517f5f6832b7cef38efc9c8f4c14e01429f5487cb752f675f4e4212058db63fc29b9c00701810144ac48e7b007f4dfaa80a2ec9899e60f704ac0d0b58c8a716433c91a27d966d2b7b2685da350c9f6c59233988cbb3f8f2383f5eb10e496e64017a0789ca8f702254e1b839382d42b8399488bd74517e280f1719392621596730862678dec519c50cd6bdc07f118217ff68004401fbccf8606c1af2ae6ce0dde06c1bd06a460bace8dc9408f1d5cf76dd063f63db9a0969098ec45fcde2e878d464f4de4bfe1fe710f2132d8e486f64e7d309d9faed999ce368e821a05d79f781fb895a767a09256541cd54242b5df5e262220fe232c637934380b137df86ab4fa07b11f07f914835c87d1547fe526001a61b8ce00721d90d8a4b54b60000ef75d9bdb94fc7f861ee46908ade105ea098cef938f49dae729817633003c87a0a6c0cbfd10185f7657804a2330a7def349da1df84a1a61dc857d2458425"}, + {"MAYO_5", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2db505d7cfad1b4974", "d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8", "7bc32600e7fe4e0d8caf187997624ba57867ec136d7112aceb161cf813ad98aa9709f0e42bc6e86c4c1a32e5b929df9976a4c0d77886d9e5005fad28c3b0e20dd7bb8d49aaaa77d969c72c9d37d78c3ff556ae777f4bac4d505194b6026fb3e17ad631980dfc373c7510c024817c1900d3428984e4e952b49b4c46c928717ea34f417b5f5d6067caf5edc15c1fcbf74d9f7a158958daf27acf3bcb611195278550d06bd83441083e2a64f0e70a8b668453b7f638a64c6d2424cf2ae31fb6efedf951215d14648d2e3282526c0504e4a0b56ac0336a28b98297d16ca2087c3c46ab10275ea0f5611bbde1b44beb420977aa8ec3693ac097901a8e3aef2d377e19ab6d14a42a5c5d7a13ec1df5be83657b7ee84e7671828aff72265bc7fa90829f92df63a159e51e222da67a5201db70597814ac4d793caeda47d7fdd2c59402a2e8a67beb6f9f43b31feaec24854f429e2aec6fe9e7412584f08719c0c5c90e6bc5371d65eebefcc956ada0659b9b0b5f4d4733873fb4daa0e837fda9062d9a79766ef66686f9b355a219d74728ccad98b04391427a30d8b95391b9e54b9f902853d0086fb1bfd136df4dc1ef6eed4c75f449612c76a506ec6c2d7e9676c776f615300230aae0353afc02d80ff377d79a923d2348eec166e0d1b40aa2428a2bf2f87dd89dffd6f1969b39cd84b3d2813078cd4a7753379a79ab6b33f9e8b54a73dde0c46f4fa9afb8a9d0445ae59d202a3df70143baf1de61de9d55681467e343a025b1e0f67af5b1e3e22568d449af192b3e241b6b635e668e851b39e9909ed77ec007a8fba5aec76551e6bb08f454ccf4d48867456c50fdeeaa80f4557d375bda789dc0a5622ccd20d0e2f32e4c7876464f8c8b5393f45eba04085f356927955fe89c475aba7862590656717b5c77f1f79d2a430a2dfb6c779f1794f5f6d374e6e566706f9bfcf33e2070b10af8bc9b853d38215bcb4ad02123445d91b485952514c63a21fac93146a15962fad262c2cdc8ce18a592e4420f60d18ae4ad558b5229b372ed06f021827e932bdb7c43ed6db84ab9c2e408251619bd8b3ce28b9c55c3a21ae2f300758c80086f0ed4a316403195512c2880239f50afa996ce24374ceb25637183b96cf246d204a04018f4344c81aad9cfa98c2ee4f5f6ce3a6f352a86e9a1b07b"}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.name != Mode { + t.Skip() + } + + var seed [KeySeedSize]byte + _, _ = hex.Decode(seed[:], []byte(tc.seed)) + + m, _ := hex.DecodeString(tc.message) + s, _ := hex.DecodeString(tc.signature) + + pk, _ := NewKeyFromSeed(seed) + epk := pk.Expand() + + if !Verify(m, s, epk) { + t.Fatal("should verify") + } + + epk.p1[0] ^= 1 + if Verify(m, s, epk) { + t.Fatal("should not verify") + } + }) + } +} + +func TestPQCgenKATSign(t *testing.T) { + for _, tc := range []struct { + name string + want string + }{ + {"MAYO_1", "d0da809f52866507b6354588cd44b712bac138a8363fde768adb92285b6e9865"}, + {"MAYO_2", "e7382b9b0fd985023a53f292b5d5caf444541a5bd531cf6e1e4d35b8bd864123"}, + {"MAYO_3", "0a53ad1ea675f6be7364d37b552cfa7ca254ac315724fb2871c2fe0567f509b9"}, + {"MAYO_5", "26a4bf3204c41fcb9911f761066668da34554efdd0684346b348ccd669f16b56"}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.name != Mode { + t.Skip() + } + + var seed [48]byte + var eseed [KeySeedSize]byte + for i := 0; i < 48; i++ { + seed[i] = byte(i) + } + f := sha256.New() + g := nist.NewDRBG(&seed) + fmt.Fprintf(f, "# %s\n\n", tc.name) + for i := 0; i < 100; i++ { + mlen := 33 * (i + 1) + _, _ = g.Read(seed[:]) + msg := make([]byte, mlen) + _, _ = g.Read(msg[:]) + + fmt.Fprintf(f, "count = %d\n", i) + fmt.Fprintf(f, "seed = %X\n", seed) + fmt.Fprintf(f, "mlen = %d\n", mlen) + fmt.Fprintf(f, "msg = %X\n", msg) + + g2 := nist.NewDRBG(&seed) + _, _ = g2.Read(eseed[:]) + pk, sk := NewKeyFromSeed(eseed) + + pk2 := [PublicKeySize]byte(*pk) + sk2 := [PrivateKeySize]byte(*sk) + + fmt.Fprintf(f, "pk = %X\n", pk2) + fmt.Fprintf(f, "sk = %X\n", sk2) + fmt.Fprintf(f, "smlen = %d\n", mlen+SignatureSize) + + sig, err := Sign(msg[:], sk.Expand(), &g2) + if err != nil { + t.Fatal() + } + + fmt.Fprintf(f, "sm = %X%X\n\n", sig, msg) + + if !Verify(msg[:], sig, pk.Expand()) { + t.Fatal() + } + } + + if fmt.Sprintf("%x", f.Sum(nil)) != tc.want { + t.Fatal() + } + }) + } +} + +func BenchmarkKeyGen(b *testing.B) { + var seed [KeySeedSize]byte + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + _, _ = NewKeyFromSeed(seed) + } +} + +func BenchmarkMatMul(b *testing.B) { + var acc [V * O * P]uint64 + var m1 [V * (V + 1) / 2 * P]uint64 + var m2 [V * O]byte + + for i := 0; i < b.N; i++ { + m1[i%6844] = uint64(i) + binary.LittleEndian.PutUint64(m2[:], uint64(i)) + + mulAddMUpperTriangularMatXMat(acc[:], m1[:], m2[:], V, O) + } +} + +func BenchmarkVerify(b *testing.B) { + var seed [KeySeedSize]byte + var msg [8]byte + pk, sk := NewKeyFromSeed(seed) + sig, _ := Sign(msg[:], sk.Expand(), nil) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Verify(msg[:], sig[:], pk.Expand()) + } +} + +func BenchmarkVerifyExpandedKey(b *testing.B) { + var seed [KeySeedSize]byte + var msg [8]byte + pk, sk := NewKeyFromSeed(seed) + sig, _ := Sign(msg[:], sk.Expand(), nil) + epk := pk.Expand() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Verify(msg[:], sig[:], epk) + } +} + +type zeroReader struct{} + +func (zeroReader) Read(buf []byte) (int, error) { + for i := range buf { + buf[i] = 0 + } + return len(buf), nil +} + +func BenchmarkSign(b *testing.B) { + var seed [KeySeedSize]byte + var msg [8]byte + _, sk := NewKeyFromSeed(seed) + b.ResetTimer() + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(msg[:], uint64(i)) + _, _ = Sign(msg[:], sk.Expand(), zeroReader{}) + } +} + +func BenchmarkSignExpandedKey(b *testing.B) { + var seed [KeySeedSize]byte + var msg [8]byte + _, sk := NewKeyFromSeed(seed) + esk := sk.Expand() + b.ResetTimer() + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(msg[:], uint64(i)) + _, _ = Sign(msg[:], esk, zeroReader{}) + } +} diff --git a/sign/mayo/mode1/internal/params.go b/sign/mayo/mode1/internal/params.go new file mode 100644 index 000000000..c3c0596f7 --- /dev/null +++ b/sign/mayo/mode1/internal/params.go @@ -0,0 +1,42 @@ +// Code generated from params.templ.go. DO NOT EDIT. + +package internal + +const ( + Mode = "MAYO_1" + + N = 66 + M = 64 + O = 8 + K = 9 + + KeySeedSize = 24 + DigestSize = 32 +) + +var Tail = [5]uint8{8, 0, 2, 8, 0} + +const ( + V = N - O + + // The division by 2 converts the number of nibbles to bytes (when packed together). + // We don't explicitly round up beause given parameters ensure this will not happen. + OSize = V * O / 2 // O is a V*O matrix of GF(16) + P1Size = (V * (V + 1) / 2) * M / 2 // P1 consists of M V*V triangular matrices + P2Size = V * O * M / 2 + P3Size = (O * (O + 1) / 2) * M / 2 // P3 consists of M O*O triangular matrices + + VSize = (V + 1) / 2 // +1 to round up + + SignatureSize = (K*N+1)/2 + SaltSize + + PublicKeySeedSize = 16 + + PrivateKeySize = KeySeedSize + PublicKeySize = PublicKeySeedSize + P3Size + + SaltSize = KeySeedSize + + // P denotes the number of uint64 words required to fit M GF16 elements + P = M / 16 +) diff --git a/sign/mayo/mode1/mayo.go b/sign/mayo/mode1/mayo.go new file mode 100644 index 000000000..e8d115228 --- /dev/null +++ b/sign/mayo/mode1/mayo.go @@ -0,0 +1,151 @@ +// Code generated from modePkg.templ.go. DO NOT EDIT. + +// mode1 implements the MAYO signature scheme MAYO_1 +// as submitted to round1 of the NIST PQC competition of Additional Signature Scehemes and described in +// +// https://csrc.nist.gov/csrc/media/Projects/pqc-dig-sig/documents/round-1/spec-files/mayo-spec-web.pdf +// +// This implemented the nibble-sliced version as proposed in +// +// https://eprint.iacr.org/2023/1683 +package mode1 + +import ( + "crypto" + "errors" + "io" + + "github.com/cloudflare/circl/sign/mayo/mode1/internal" +) + +const ( + // Size of seed for NewKeyFromSeed + SeedSize = internal.KeySeedSize + + // Size of a packed PublicKey + PublicKeySize = internal.PublicKeySize + + // Size of a packed PrivateKey + PrivateKeySize = internal.PrivateKeySize + + // Size of a signature + SignatureSize = internal.SignatureSize +) + +// PublicKey is the type of Mayo1 public key +type PublicKey internal.PublicKey + +// PrivateKey is the type of Mayo1 private key +type PrivateKey internal.PrivateKey + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { + pk, sk, err := internal.GenerateKey(rand) + return (*PublicKey)(pk), (*PrivateKey)(sk), err +} + +// NewKeyFromSeed derives a public/private key pair using the given seed. +func NewKeyFromSeed(seed [SeedSize]byte) (*PublicKey, *PrivateKey) { + pk, sk := internal.NewKeyFromSeed(seed) + return (*PublicKey)(pk), (*PrivateKey)(sk) +} + +// Sign signs the given message using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { + return internal.Sign( + msg, + (*internal.PrivateKey)(sk).Expand(), + rand, + ) +} + +// Verify checks whether the given signature by pk on msg is valid. +func Verify(pk *PublicKey, msg []byte, signature []byte) bool { + return internal.Verify( + msg, + signature, + (*internal.PublicKey)(pk).Expand(), + ) +} + +// Packs the public key. +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var buf [PublicKeySize]byte + b := [PublicKeySize]byte(*pk) + copy(buf[:], b[:]) + return buf[:], nil +} + +// Packs the private key. +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var buf [PrivateKeySize]byte + b := [PrivateKeySize]byte(*sk) + copy(buf[:], b[:]) + return buf[:], nil +} + +// Unpacks the public key from data. +func (pk *PublicKey) UnmarshalBinary(data []byte) error { + if len(data) != PublicKeySize { + return errors.New("packed public key must be of mode1.PublicKeySize bytes") + } + self := (*(*[PublicKeySize]byte)(pk)) + copy(self[:], data[:]) + return nil +} + +// Unpacks the private key from data. +func (sk *PrivateKey) UnmarshalBinary(data []byte) error { + if len(data) != PublicKeySize { + return errors.New("packed private key must be of mode1.PrivateKeySize bytes") + } + self := (*(*[PrivateKeySize]byte)(sk)) + copy(self[:], data[:]) + return nil +} + +// Sign signs the given message. +// +// opts.HashFunc() must return zero, which can be achieved by passing +// crypto.Hash(0) for opts. Will only return an error +// if opts.HashFunc() is non-zero. +// +// This function is used to make PrivateKey implement the crypto.Signer +// interface. The package-level Sign function might be more convenient +// to use. +func (sk *PrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ( + signature []byte, err error) { + if opts.HashFunc() != crypto.Hash(0) { + return nil, errors.New("mayo: cannot sign hashed message") + } + + return Sign(sk, msg, rand) +} + +// Computes the public key corresponding to this private key. +// +// Returns a *PublicKey. The type crypto.PublicKey is used to make +// PrivateKey implement the crypto.Signer interface. +func (sk *PrivateKey) Public() crypto.PublicKey { + return (*internal.PrivateKey)(sk).Public +} + +// Equal returns whether the two private keys equal. +func (sk *PrivateKey) Equal(other crypto.PrivateKey) bool { + castOther, ok := other.(*PrivateKey) + if !ok { + return false + } + return (*internal.PrivateKey)(sk).Equal((*internal.PrivateKey)(castOther)) +} + +// Equal returns whether the two public keys equal. +func (pk *PublicKey) Equal(other crypto.PublicKey) bool { + castOther, ok := other.(*PublicKey) + if !ok { + return false + } + return (*internal.PublicKey)(pk).Equal((*internal.PublicKey)(castOther)) +} diff --git a/sign/mayo/mode2/internal/matrix.go b/sign/mayo/mode2/internal/matrix.go new file mode 100644 index 000000000..5ddf0d842 --- /dev/null +++ b/sign/mayo/mode2/internal/matrix.go @@ -0,0 +1,291 @@ +// Code generated from mode1/internal/matrix.go by gen.go + +package internal + +// Given b in GF(16), packs the 32-bit result of (b*x^3, b*x^2, b*x, b) into the returned multiplication table. +func mulTable(b uint8) uint32 { + x := uint32(b) * 0x08040201 + highNibble := x & uint32(0xf0f0f0f0) + + // mod x^4+x+1 + return (x ^ (highNibble >> 4) ^ (highNibble >> 3)) +} + +func vecMulAddPackedTab(p int, in []uint64, tab uint32, acc []uint64) { + lsbMask := uint64(0x1111111111111111) + for i := 0; i < p; i++ { + acc[i] ^= (in[i]&lsbMask)*uint64(tab&0xff) ^ + ((in[i]>>1)&lsbMask)*uint64((tab>>8)&0xf) ^ + ((in[i]>>2)&lsbMask)*uint64((tab>>16)&0xf) ^ + ((in[i]>>3)&lsbMask)*uint64((tab>>24)&0xf) + } +} + +func vecMulAddPacked(p int, in []uint64, a byte, acc []uint64) { + tab := mulTable(a) + vecMulAddPackedTab(p, in, tab, acc) +} + +// Multiplies each nibble in a by b. +func mulAddPacked(a uint64, b uint8) uint64 { + msb := uint64(0x8888888888888888) + a64 := a + r64 := a64 * uint64(b&1) + + for i := 1; i < 4; i++ { + b >>= 1 + aMsb := a64 & msb + a64 ^= aMsb + a64 = (a64 << 1) ^ ((aMsb >> 3) * 3) + r64 ^= (a64) * uint64(b&1) + } + + return r64 +} + +// acc += M1*M2 +// acc and M2 are multiple matrices, M1 is a single matrix +func mulAddMatXMMat(acc []uint64, m1 []uint8, m2 []uint64, rows int, cols int, cols2 int) { + for r := 0; r < rows; r++ { + for c := 0; c < cols; c++ { + tab := mulTable(m1[r*cols+c]) + for k := 0; k < cols2; k++ { + // The following multiplication table way is equivalent to: + // for p := 0; p < P; p++ { + // acc[P*(r*cols2+k)+p] ^= gf16v_mul_u64(m2[P*(c*cols2+k)+p], m1[r*cols+c]) + // } + vecMulAddPackedTab(P, m2[P*(c*cols2+k):], tab, acc[P*(r*cols2+k):]) + } + } + } +} + +// acc += M1*M2, where M1 is upper triangular, acc and M2 is not +// acc and M1 are multiple matrices, M2 is a single matrix +func mulAddMUpperTriangularMatXMat(acc []uint64, m1 []uint64, m2 []uint8, rows int, cols2 int) { + // The ordinary summation order is r -> c -> k, but here it is interchanged to make use of multiplication table + cols := rows + for k := 0; k < cols2; k++ { + for c := 0; c < cols; c++ { + tab := mulTable(m2[c*cols2+k]) + for r := 0; r <= c; r++ { + pos := r*(cols*2-r+1)/2 + (c - r) + vecMulAddPackedTab(P, m1[P*pos:], tab, acc[P*(r*cols2+k):]) + } + } + } +} + +// acc += M1^T*M2, +// acc and M2 are multiple matrices, M1 is a single matrix +// M1, before ^T, is of rows x cols +func mulAddMatTransXMMat(acc []uint64, m1 []uint8, m2 []uint64, rows int, cols int, cols2 int) { + for r := 0; r < cols; r++ { + for c := 0; c < rows; c++ { + tab := mulTable(m1[c*cols+r]) + for k := 0; k < cols2; k++ { + vecMulAddPackedTab(P, m2[P*(c*cols2+k):], tab, acc[P*(r*cols2+k):]) + } + } + } +} + +// acc += M1*M2^T, +// acc and M1 are multiple matrices, M2 is a single matrix +// M2, before ^T, is of cols2 x cols, but cols strides at colsStride +// M1 optionally upper triangular +func mulAddMMatXMatTrans(acc []uint64, m1 []uint64, m2 []uint8, rows int, cols int, cols2 int, colsStride int, isM1Triangular bool) { + for k := 0; k < cols2; k++ { + for c := 0; c < cols; c++ { + rBound := rows - 1 + if isM1Triangular { + rBound = c + } + for r := 0; r <= rBound; r++ { + tab := mulTable(m2[k*colsStride+c]) + pos := r*cols + c + if isM1Triangular { + pos = r*(cols*2-r+1)/2 + (c - r) + } + vecMulAddPackedTab(P, m1[P*pos:], tab, acc[P*(r*cols2+k):]) + } + } + } +} + +// acc += (M1+M1^T)*M2 +// M1 of rows x rows is upper triangular; M2 is of rows x cols2 +// acc and M1 are multiple matrices, M2 is a single matrix +func mulAddMUpperTriangularWithTransposeMatXMat(acc []uint64, m1 []uint64, m2 []uint8, rows int, cols2 int) { + m1pos := 0 + for r := 0; r < rows; r++ { + for c := r; c < rows; c++ { + if c == r { + m1pos += 1 + continue + } + for k := 0; k < cols2; k++ { + vecMulAddPacked(P, m1[P*m1pos:], m2[c*cols2+k], acc[P*(r*cols2+k):]) + vecMulAddPacked(P, m1[P*m1pos:], m2[r*cols2+k], acc[P*(c*cols2+k):]) + } + m1pos++ + } + } +} + +func mulAddMatVec(acc []byte, m []byte, v []byte, rows, cols int) { + for i := 0; i < rows; i++ { + for j := 0; j < cols; j++ { + acc[i] ^= byte(mulAddPacked(uint64(m[i*cols+j]), v[j])) + } + } +} + +func upper(in []uint64, out []uint64, size int) { + pos := 0 + for r := 0; r < size; r++ { + for c := r; c < size; c++ { + copy(out[P*pos:][:P], in[P*(r*size+c):][:P]) + if r != c { + for p := 0; p < P; p++ { + out[P*pos+p] ^= in[P*(c*size+r)+p] + } + } + pos++ + } + } +} + +func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8) { + var accumulator [K * N][P * 16]uint64 + + // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] + // [ 0 P3 ] [S2] [ P3*S2] + + // Note that S = S1||S2 is strided at N=V+O + + // P1 * S1^t : VxV * V*K, where P1 is triangular + pos := 0 + for r := 0; r < V; r++ { + for c := r; c < V; c++ { + for k := 0; k < K; k++ { + vecAddPacked(P, p1[P*pos:], accumulator[r*K+k][P*int(s[k*N+c]):]) + } + pos++ + } + } + + // P2 * S2^t : V*O * O*K + pos = 0 + for r := 0; r < V; r++ { + for c := 0; c < O; c++ { + for k := 0; k < K; k++ { + vecAddPacked(P, p2[P*pos:], accumulator[r*K+k][P*int(s[k*N+V+c]):]) + } + pos++ + } + } + + // P3 * S2^t : O*O * O*K, where P3 is triangular + pos = 0 + for r := 0; r < O; r++ { + for c := r; c < O; c++ { + for k := 0; k < K; k++ { + vecAddPacked(P, p3[P*pos:], accumulator[(r+V)*K+k][P*int(s[k*N+V+c]):]) + } + pos++ + } + } + + for i := 0; i < K*N; i++ { + aggregate(P, accumulator[i], sps[P*i:]) + } +} + +func variableTime2(sps []uint64, s []uint8, pst []uint64) { + var accumulator [K * K][P * 16]uint64 + + // S * PST : KxN * N*K + for r := 0; r < K; r++ { + for c := 0; c < N; c++ { + for k := 0; k < K; k++ { + vecAddPacked(P, pst[P*(c*K+k):], accumulator[r*K+k][P*int(s[r*N+c]):]) + } + } + } + + for i := 0; i < K*K; i++ { + aggregate(P, accumulator[i], sps[P*i:]) + } +} + +// p is always P, but is still kept to be consistent with other functions +// +//nolint:unparam +func vecAddPacked(p int, in []uint64, acc []uint64) { + for i := 0; i < p; i++ { + acc[i] ^= in[i] + } +} + +func aggregate(p int, bins [P * 16]uint64, out []uint64) { + // The following two methods are mathematically equivalent, but the second one is slightly faster. + + // the powers of x mod x^4+x+1, represented as integers, are 1,2,4,8,3,..,13,9 + // out = bins[9]*x^14 + bins[13]*x^13 + bins[15]*x^12 ... + bin[4]*x^2 + bins[2]*x + bins[1] + // = ((bins[9]x+bins[13])x+bins[15])x + ... bins[4])x+bins[2])x+bins[1] + + // vecMulAddPackedByX(p, bins[P*9:], bins[P*13:]) + // vecMulAddPackedByX(p, bins[P*13:], bins[P*15:]) + // vecMulAddPackedByX(p, bins[P*15:], bins[P*14:]) + // vecMulAddPackedByX(p, bins[P*14:], bins[P*7:]) + // vecMulAddPackedByX(p, bins[P*7:], bins[P*10:]) + // vecMulAddPackedByX(p, bins[P*10:], bins[P*5:]) + // vecMulAddPackedByX(p, bins[P*5:], bins[P*11:]) + // vecMulAddPackedByX(p, bins[P*11:], bins[P*12:]) + // vecMulAddPackedByX(p, bins[P*12:], bins[P*6:]) + // vecMulAddPackedByX(p, bins[P*6:], bins[P*3:]) + // vecMulAddPackedByX(p, bins[P*3:], bins[P*8:]) + // vecMulAddPackedByX(p, bins[P*8:], bins[P*4:]) + // vecMulAddPackedByX(p, bins[P*4:], bins[P*2:]) + // vecMulAddPackedByX(p, bins[P*2:], bins[P*1:]) + // copy(out[:P], bins[P*1:]) + + // In the reversed order of the above, because /x turns out to be slightly faster than *x. + // out = ((bins[2]x^-1+bins[4])x^-1+bins[8])x^-1 + ... bins[13])x^-1+bins[9])x^-1+bins[1] + vecMulAddPackedByInvX(p, bins[P*2:], bins[P*4:]) + vecMulAddPackedByInvX(p, bins[P*4:], bins[P*8:]) + vecMulAddPackedByInvX(p, bins[P*8:], bins[P*3:]) + vecMulAddPackedByInvX(p, bins[P*3:], bins[P*6:]) + vecMulAddPackedByInvX(p, bins[P*6:], bins[P*12:]) + vecMulAddPackedByInvX(p, bins[P*12:], bins[P*11:]) + vecMulAddPackedByInvX(p, bins[P*11:], bins[P*5:]) + vecMulAddPackedByInvX(p, bins[P*5:], bins[P*10:]) + vecMulAddPackedByInvX(p, bins[P*10:], bins[P*7:]) + vecMulAddPackedByInvX(p, bins[P*7:], bins[P*14:]) + vecMulAddPackedByInvX(p, bins[P*14:], bins[P*15:]) + vecMulAddPackedByInvX(p, bins[P*15:], bins[P*13:]) + vecMulAddPackedByInvX(p, bins[P*13:], bins[P*9:]) + vecMulAddPackedByInvX(p, bins[P*9:], bins[P*1:]) + copy(out[:P], bins[P*1:]) +} + +// func vecMulAddPackedByX(p int, in []uint64, acc []uint64) { +// // vecMulAddPacked(p, in, 2, acc) + +// msb := uint64(0x8888888888888888) +// for i := 0; i < p; i++ { +// t := in[i] & msb +// acc[i] ^= ((in[i] ^ t) << 1) ^ ((t >> 3) * 3) +// } +// } + +func vecMulAddPackedByInvX(p int, in []uint64, acc []uint64) { + // vecMulAddPacked(p, in, 9, acc) + + lsb := uint64(0x1111111111111111) + for i := 0; i < p; i++ { + t := in[i] & lsb + acc[i] ^= ((in[i] ^ t) >> 1) ^ (t * 9) + } +} diff --git a/sign/mayo/mode2/internal/mayo.go b/sign/mayo/mode2/internal/mayo.go new file mode 100644 index 000000000..a06568d0e --- /dev/null +++ b/sign/mayo/mode2/internal/mayo.go @@ -0,0 +1,730 @@ +// Code generated from mode1/internal/mayo.go by gen.go + +package internal + +import ( + "crypto/aes" + "crypto/cipher" + cryptoRand "crypto/rand" + "crypto/subtle" + "io" + "unsafe" + + "github.com/cloudflare/circl/internal/sha3" +) + +type ( + PrivateKey [PrivateKeySize]byte + PublicKey [PublicKeySize]byte +) + +func (pk *PublicKey) Equal(other *PublicKey) bool { + return *pk == *other +} + +func (sk *PrivateKey) Equal(other *PrivateKey) bool { + return subtle.ConstantTimeCompare((*sk)[:], (*other)[:]) == 1 +} + +type ExpandedPublicKey struct { + p1 [P1Size]byte + p2 [P2Size]byte + p3 [P3Size]byte +} + +type ExpandedPrivateKey struct { + seed [KeySeedSize]byte + o [V * O]byte + p1 [M * V * V / 16]uint64 + l [M * V * O / 16]uint64 +} + +func (pk *PublicKey) Expand() *ExpandedPublicKey { + seedPk := pk[:PublicKeySeedSize] + + var nonce [16]byte // zero-initialized + block, _ := aes.NewCipher(seedPk[:]) + ctr := cipher.NewCTR(block, nonce[:]) + + epk := ExpandedPublicKey{} + ctr.XORKeyStream(epk.p1[:], epk.p1[:]) + ctr.XORKeyStream(epk.p2[:], epk.p2[:]) + + copy(epk.p3[:], pk[PublicKeySeedSize:]) + + return &epk +} + +func (sk *PrivateKey) Expand() *ExpandedPrivateKey { + var epk ExpandedPrivateKey + + seed := (*sk)[:KeySeedSize] + copy(epk.seed[:], seed) + + var seedPk [PublicKeySeedSize]byte + var o [OSize]byte + + h := sha3.NewShake256() + _, _ = h.Write(seed[:]) + _, _ = h.Read(seedPk[:]) + _, _ = h.Read(o[:]) + + var nonce [16]byte // zero-initialized + block, _ := aes.NewCipher(seedPk[:]) + ctr := cipher.NewCTR(block, nonce[:]) + + var p12 [P1Size + P2Size]byte + ctr.XORKeyStream(p12[:], p12[:]) + + decode(o[:], epk.o[:]) + + p1Tri := viewBytesAsUint64Slice(p12[:P1Size]) + p2 := viewBytesAsUint64Slice(p12[P1Size:]) + + // TODO: this copy can be saved by reusing buffers but ugly + copy(epk.p1[:], p1Tri) + copy(epk.l[:], p2) + + // compute L_i = (P1 + P1^t)*O + P2 + mulAddMUpperTriangularWithTransposeMatXMat(epk.l[:], p1Tri, epk.o[:], V, O) + + return &epk +} + +// decode unpacks N bytes from src to N*2 nibbles to dst. +// The length is determined by len(dst) +func decode(src []byte, dst []byte) { + i := 0 + for ; i < len(dst)/2; i++ { + dst[i*2] = src[i] & 0xf + dst[i*2+1] = src[i] >> 4 + } + + // Account for odd length + if len(dst)%2 == 1 { + dst[i*2] = src[i] & 0xf + } +} + +// encode packs N=length low nibbles from src to (N+1)/2 bytes in dst. +func encode(src []byte, dst []byte, length int) { + var i int + for i = 0; i+1 < length; i += 2 { + dst[i/2] = (src[i+0] << 0) | (src[i+1] << 4) + } + if length%2 == 1 { + dst[i/2] = (src[i+0] << 0) + } +} + +func viewBytesAsUint64Slice(data []byte) []uint64 { + numUint64 := len(data) / 8 + return unsafe.Slice((*uint64)(unsafe.Pointer(&data[0])), numUint64) +} + +func viewUint64SliceAsBytes(data []uint64) []byte { + numByte := len(data) * 8 + return unsafe.Slice((*uint8)(unsafe.Pointer(&data[0])), numByte) +} + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { + var seed [KeySeedSize]byte + if rand == nil { + rand = cryptoRand.Reader + } + _, err := io.ReadFull(rand, seed[:]) + if err != nil { + return nil, nil, err + } + pk, sk := NewKeyFromSeed(seed) + return pk, sk, nil +} + +func NewKeyFromSeed(seed [KeySeedSize]byte) (*PublicKey, *PrivateKey) { + var sk PrivateKey + copy(sk[:], seed[:]) + + return sk.Public(), &sk +} + +func (sk *PrivateKey) Public() *PublicKey { + var pk PublicKey + seedPk := pk[:PublicKeySeedSize] + var o [OSize]byte + + h := sha3.NewShake256() + _, _ = h.Write(sk[:]) + _, _ = h.Read(seedPk[:]) + _, _ = h.Read(o[:]) + + var nonce [16]byte // zero-initialized + block, _ := aes.NewCipher(seedPk[:]) + ctr := cipher.NewCTR(block, nonce[:]) + + var p12 [P1Size + P2Size]byte + ctr.XORKeyStream(p12[:], p12[:]) + + var oo [V * O]byte + decode(o[:], oo[:]) + + p1Tri := viewBytesAsUint64Slice(p12[:P1Size]) + p1OP2 := viewBytesAsUint64Slice(p12[P1Size:]) + + var p3full [M * O * O / 16]uint64 + var p3 [P3Size / 8]uint64 + + mulAddMUpperTriangularMatXMat(p1OP2, p1Tri, oo[:], V, O) + mulAddMatTransXMMat(p3full[:], oo[:], p1OP2, V, O, O) + + upper(p3full[:], p3[:], O) + + xx := viewUint64SliceAsBytes(p3[:]) + copy(pk[PublicKeySeedSize:], xx[:]) + + return &pk +} + +func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { + if rand == nil { + rand = cryptoRand.Reader + } + + var digest [DigestSize]byte + + h := sha3.NewShake256() + _, _ = h.Write(msg[:]) + _, _ = h.Read(digest[:]) + + var salt [SaltSize]byte + + // R <- $ + if _, err := io.ReadFull(rand, salt[:]); err != nil { + return nil, err + } + + h.Reset() + _, _ = h.Write(digest[:]) + _, _ = h.Write(salt[:]) + _, _ = h.Write(sk.seed[:]) + _, _ = h.Read(salt[:]) + + h.Reset() + _, _ = h.Write(digest[:]) + _, _ = h.Write(salt[:]) + + var tenc [M / 2]byte + _, _ = h.Read(tenc[:]) + + var t [M]byte + decode(tenc[:], t[:]) + + var v [K * V]byte + var x [K*O + 1]byte // + 1 for buffer + for ctr := 0; ctr <= 255; ctr++ { + ctrByte := []byte{byte(ctr)} + h.Reset() + _, _ = h.Write(digest[:]) + _, _ = h.Write(salt[:]) + _, _ = h.Write(sk.seed[:]) + _, _ = h.Write(ctrByte[:]) + + var venc [K * VSize]byte + var renc [K * O / 2]byte + _, _ = h.Read(venc[:]) + _, _ = h.Read(renc[:]) + + var r [K * O]byte + + for i := 0; i < K; i++ { + decode(venc[i*VSize:], v[i*V:(i+1)*V]) + } + decode(renc[:], r[:]) + + // M = vL + var m [M * K * O / 16]uint64 + mulAddMatXMMat(m[:], v[:], sk.l[:], K, V, O) + + // pv = P1 * V^T + var pv [M * V * K / 16]uint64 + mulAddMMatXMatTrans(pv[:], sk.p1[:], v[:], V, V, K, V, true) + + // V * pv + var vpv [M * K * K / 16]uint64 + mulAddMatXMMat(vpv[:], v[:], pv[:], K, V, K) + + var y [M]byte + copy(y[:], t[:]) + emulsifyInto(vpv[:], y[:]) + + var A [M * (K*O + 1)]byte + _ = A + + computeA(m[:], A[:]) + + if sampleSolution(A[:], y[:], r[:], x[:]) { + break + } + } + + var s [K * N]byte + for i := 0; i <= K-1; i++ { + copy(s[i*N:][:V], v[i*V:]) + mulAddMatVec(s[i*N:], sk.o[:], x[i*O:], V, O) + copy(s[i*N+V:][:O], x[i*O:]) + } + + var sig [(K*N+1)/2 + SaltSize]byte + encode(s[:], sig[:], K*N) + copy(sig[(K*N+1)/2:], salt[:]) + + return sig[:], nil +} + +// assume last (KO+1-th) column of a is zero +func sampleSolution(a []byte, y []byte, r []byte, x []byte) bool { + const aCols = K*O + 1 + + copy(x[:], r[:]) + + var ar [M]byte + mulAddMatVec(ar[:], a[:], x[:], M, aCols) + + // move y - Ar to last column of matrix A + for i := 0; i < M; i++ { + a[K*O+i*(aCols)] = y[i] ^ ar[i] + } + + ef(a[:], M, aCols) + + fullRank := byte(0) + for i := 0; i < aCols-1; i++ { + fullRank |= a[(M-1)*(aCols)+i] + } + + if fullRank == 0 { + return false + } + + // back substitution in constant time + // the index of the first nonzero entry in each row is secret, which makes + // things less efficient + + for row := M - 1; row >= 0; row-- { + finished := byte(0) + colUpperBound := min(row+(32/(M-row)), K*O) + // the first nonzero entry in row r is between r and col_upper_bound with probability at least ~1-q^{-32} + + for col := row; col <= colUpperBound; col++ { + // Compare two chars in constant time. + // Returns 0x00 if the byte arrays are equal, 0xff otherwise. + correctColumn := ctCompare8((a[row*aCols+col]), 0) & ^finished + + u := correctColumn & a[row*aCols+aCols-1] + x[col] ^= u + + for i := 0; i < row; i += 8 { + tmp := (uint64(a[i*aCols+col]) << 0) ^ (uint64(a[(i+1)*aCols+col]) << 8) ^ + (uint64(a[(i+2)*aCols+col]) << 16) ^ (uint64(a[(i+3)*aCols+col]) << 24) ^ + (uint64(a[(i+4)*aCols+col]) << 32) ^ (uint64(a[(i+5)*aCols+col]) << 40) ^ + (uint64(a[(i+6)*aCols+col]) << 48) ^ (uint64(a[(i+7)*aCols+col]) << 56) + + tmp = mulx8(u, tmp) + + a[i*aCols+aCols-1] ^= byte((tmp) & 0xf) + a[(i+1)*aCols+aCols-1] ^= byte((tmp >> 8) & 0xf) + a[(i+2)*aCols+aCols-1] ^= byte((tmp >> 16) & 0xf) + a[(i+3)*aCols+aCols-1] ^= byte((tmp >> 24) & 0xf) + a[(i+4)*aCols+aCols-1] ^= byte((tmp >> 32) & 0xf) + a[(i+5)*aCols+aCols-1] ^= byte((tmp >> 40) & 0xf) + a[(i+6)*aCols+aCols-1] ^= byte((tmp >> 48) & 0xf) + a[(i+7)*aCols+aCols-1] ^= byte((tmp >> 56) & 0xf) + } + + finished = finished | correctColumn + } + } + + return true +} + +// if a == b -> 0x0000000000000000, else 0xFFFFFFFFFFFFFFFF +func ctCompare64(a, b int) uint64 { + return uint64((-(int64)(a ^ b)) >> 63) +} + +// a > b -> b - a is negative +// returns 0xFFFFFFFF if true, 0x00000000 if false +func ct64IsGreaterThan(a, b int) uint64 { + diff := int64(b) - int64(a) + return uint64(diff >> 63) +} + +// if a == b -> 0x00, else 0xFF +func ctCompare8(a, b byte) byte { + return byte((-int32(a ^ b)) >> (31)) +} + +func extract(in []uint64, index int) byte { + leg := index / 16 + offset := index % 16 + + return byte((in[leg] >> (offset * 4)) & 0xF) +} + +// put matrix in row echelon form with ones on first nonzero entries *in constant time* +func ef(A []byte, nrows, ncols int) { + // ncols is actually always K*O + 1 + + // we operate each row by packing nibbles to uint64s. + rowLen := (ncols + 15) / 16 + + var pivotRowData [(K*O + 1 + 15) / 16]uint64 // rounds up + var pivotRowData2 [(K*O + 1 + 15) / 16]uint64 + + // nibbleslice the matrix A + var packedAbyte [((K*O + 1 + 15) / 16) * M * 8]byte + for i := 0; i < nrows; i++ { + encode(A[i*ncols:], packedAbyte[i*rowLen*8:], ncols) + } + + packedA := viewBytesAsUint64Slice(packedAbyte[:]) + + // pivot row is secret, pivot col is not + pivotRow := 0 + for pivotCol := 0; pivotCol < ncols; pivotCol++ { + pivotRowLowerBound := max(0, pivotCol+nrows-ncols) + pivotRowUpperBound := min(nrows-1, pivotCol) + // the pivot row is guaranteed to be between these lower and upper bounds if + // A has full rank + + // zero out pivot row + for i := 0; i < rowLen; i++ { + pivotRowData[i] = 0 + pivotRowData2[i] = 0 + } + + // try to get a pivot row in constant time + var pivot byte = 0 + var pivotIsZero uint64 = 0xffffffffffffffff + for row := pivotRowLowerBound; row <= min(nrows-1, pivotRowUpperBound+32); row++ { + isPivotRow := ^ctCompare64(row, pivotRow) + belowPivotRow := ct64IsGreaterThan(row, pivotRow) + + for j := 0; j < rowLen; j++ { + mask := isPivotRow | (belowPivotRow & pivotIsZero) + pivotRowData[j] ^= mask & packedA[row*rowLen+j] + } + pivot = extract(pivotRowData[:], pivotCol) + pivotIsZero = ^ctCompare64(int(pivot), 0) + } + + // multiply pivot row by inverse of pivot + inverse := inverse(pivot) + vecMulAddPacked(rowLen, pivotRowData[:], inverse, pivotRowData2[:]) + + // conditionally write pivot row to the correct row, if there is a nonzero + // pivot + for row := pivotRowLowerBound; row <= pivotRowUpperBound; row++ { + doCopy := ^ctCompare64(row, pivotRow) & ^pivotIsZero + doNotCopy := ^doCopy + for col := 0; col < rowLen; col++ { + packedA[row*rowLen+col] = (doNotCopy & packedA[row*rowLen+col]) + + (doCopy & pivotRowData2[col]) + } + } + + // eliminate entries below pivot + for row := pivotRowLowerBound; row < nrows; row++ { + belowPivot := byte(0) + if row > pivotRow { + belowPivot = 1 + } + eltToElim := extract(packedA[row*rowLen:], pivotCol) + + vecMulAddPacked(rowLen, pivotRowData2[:], belowPivot*eltToElim, + packedA[row*rowLen:]) + } + + pivotRow += -int(^pivotIsZero) + } + + var temp [(O*K + 1 + 15)]byte + + // unnibbleslice the matrix A + for i := 0; i < nrows; i++ { + decode(packedAbyte[i*rowLen*8:], temp[:rowLen*16]) + for j := 0; j < ncols; j++ { + A[i*ncols+j] = temp[j] + } + } +} + +func computeA(m []uint64, _a []byte) { + // M is of K * O * (M / 16) + + // intermediate state of A, which is just accumulation of Mj*x^_ without reduction mod f + // M/8 * K*O + // uint64 + // some idx ko @ K*O idx = ko + 1 + // [ ... [m0 m1 ... m15] [m0 m1 ... m15] .... ] + // [ ... [m16 m17 ... m31] [m16 m17 ... m31] .... ] + // ... + // [ ... [m48 m49 ... m63] [m48 m49 ... m63] .... ] <--- for M=64, this is where reduction is not needed + // ... + // [ ... [m112 m113 ... m127] [m112 m113 ... m127] .... ] <--- here are for reductions later + // = sum of M_k @ ko + // + // later we will somehow transform it to the actual matrix form of A + // for this, we need to group 16 uint64 words together as a chunk, hence OKpadded + // ? why M/8, not something like ~ m+k*(K+1)/2 ? + + const OKpadded = (O*K + 15) / 16 * 16 + var a [(M / 8) * OKpadded]uint64 + + // Emulsify, without reduction, by accumulating M + bitsToShift, wordsToShift := 0, 0 + for i := 0; i < K; i++ { + for j := K - 1; j >= i; j-- { + // always maintain such that l = (bitsToShift + wordsToShift*64) / 4 + + mj := m[j*O*M/16:] + for c := 0; c < O; c++ { + for k := 0; k < M/16; k++ { // currently 4 + a[(O*i+c)+(k+wordsToShift)*OKpadded] ^= mj[k+c*M/16] << bitsToShift + if bitsToShift > 0 { + a[(O*i+c)+(k+wordsToShift+1)*OKpadded] ^= mj[k+c*M/16] >> (64 - bitsToShift) + } + } + } + + if i != j { + mi := m[i*O*M/16:] + for c := 0; c < O; c++ { + for k := 0; k < M/16; k++ { + a[(O*j)+c+(k+wordsToShift)*OKpadded] ^= mi[k+c*M/16] << bitsToShift + if bitsToShift > 0 { + a[(O*j)+c+(k+wordsToShift+1)*OKpadded] ^= mi[k+c*M/16] >> (64 - bitsToShift) + } + } + } + } + + bitsToShift += 4 + if bitsToShift == 64 { + bitsToShift = 0 + wordsToShift++ + } + } + } + + // transpose among groups of 16 uint64s in each row, so that above matrix becomes + // uint64 + // [ ... { [m0 m0 ... m0 ] [m1 m1 ... m1 ] .... [m15 m15 ... m15] } ] + // [ ... { [m16 m16 ... m16] [m17 m7 ... m17] .... [m31 m31 ... m31] } ] + // + // where {} indicates a group of 16 uint64s + + for c := 0; c < OKpadded*((M+(K+1)*K/2+15)/16); c += 16 { + transpose16x16Nibbles(a[c:]) + } + + // reduction mod f by folding rows >= M back around, using 4-bit multiplication table + var tab [len(Tail) * 4]byte + for i := 0; i < len(Tail); i++ { + tab[4*i] = mul(Tail[i], 1) + tab[4*i+1] = mul(Tail[i], 2) + tab[4*i+2] = mul(Tail[i], 4) + tab[4*i+3] = mul(Tail[i], 8) + } + + const lsb = 0x1111111111111111 + + for c := 0; c < OKpadded; c += 16 { + for r := M; r < M+(K+1)*K/2; r++ { + pos := (r/16)*OKpadded + c + (r % 16) + t0 := a[pos] & lsb + t1 := (a[pos] >> 1) & lsb + t2 := (a[pos] >> 2) & lsb + t3 := (a[pos] >> 3) & lsb + for t := 0; t < len(Tail); t++ { + a[((r+t-M)/16)*OKpadded+c+((r+t)%16)] ^= t0*uint64(tab[4*t+0]) ^ t1*uint64(tab[4*t+1]) ^ t2*uint64(tab[4*t+2]) ^ t3*uint64(tab[4*t+3]) + } + } + } + + // transform the temporary matrix into the desired form of A matrix + KO1 := K*O + 1 + for r := 0; r < M; r += 16 { + for c := 0; c < KO1-1; c += 16 { + for i := 0; i < 16; i++ { + src := viewUint64SliceAsBytes(a[r/16*OKpadded+c+i:]) + offset := KO1*(r+i) + c + decode(src, _a[offset:offset+min(16, KO1-1-c)]) + } + } + } +} + +func transpose16x16Nibbles(m []uint64) { + const evenNibbles = 0x0f0f0f0f0f0f0f0f + const evenBytes = 0x00ff00ff00ff00ff + const even2Bytes = 0x0000ffff0000ffff + const evenHalf = 0x00000000ffffffff + + for i := 0; i < 16; i += 2 { + t := ((m[i] >> 4) ^ m[i+1]) & evenNibbles + m[i] ^= t << 4 + m[i+1] ^= t + } + + for i := 0; i < 16; i += 4 { + t0 := ((m[i] >> 8) ^ m[i+2]) & evenBytes + t1 := ((m[i+1] >> 8) ^ m[i+3]) & evenBytes + m[i] ^= (t0 << 8) + m[i+1] ^= (t1 << 8) + m[i+2] ^= t0 + m[i+3] ^= t1 + } + + for i := 0; i < 4; i++ { + t0 := ((m[i] >> 16) ^ m[i+4]) & even2Bytes + t1 := ((m[i+8] >> 16) ^ m[i+12]) & even2Bytes + + m[i] ^= t0 << 16 + m[i+8] ^= t1 << 16 + m[i+4] ^= t0 + m[i+12] ^= t1 + } + + for i := 0; i < 8; i++ { + t := ((m[i] >> 32) ^ m[i+8]) & evenHalf + m[i] ^= t << 32 + m[i+8] ^= t + } +} + +func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { + if len(sig) != SignatureSize { + panic("sig must be of length SignatureSize") + } + + var digest [DigestSize]byte + + h := sha3.NewShake256() + _, _ = h.Write(msg[:]) + _, _ = h.Read(digest[:]) + + h.Reset() + _, _ = h.Write(digest[:]) + _, _ = h.Write(sig[SignatureSize-SaltSize : SignatureSize]) + + var tenc [M / 2]byte + _, _ = h.Read(tenc[:]) + + var t [M]byte + decode(tenc[:], t[:]) + + var s [K * N]byte + decode(sig[:(K*N+1)/2], s[:]) + + P1 := viewBytesAsUint64Slice(epk.p1[:]) + P2 := viewBytesAsUint64Slice(epk.p2[:]) + P3 := viewBytesAsUint64Slice(epk.p3[:]) + + // Note: the variable time approach is overall about 30% faster + // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] + // [ 0 P3 ] [S2] [ P3*S2] + var pst [M * N * K / 16]uint64 + // mulAddMMatXMatTrans(pst[:], P1, s[:], V, V, K, N, true) + // mulAddMMatXMatTrans(pst[:], P2, s[V:], V, O, K, N, false) + // mulAddMMatXMatTrans(pst[M*V*K/16:], P3, s[V:], O, O, K, N, true) + variableTime(pst[:], P1, P2, P3, s[:]) + + // compute S * PST + var sps [M * K * K / 16]uint64 + // mulAddMatXMMat(sps[:], s[:], pst[:], K, N, K) + variableTime2(sps[:], s[:], pst[:]) + + emulsifyInto(sps[:], t[:]) + + var zeros [M]byte + return subtle.ConstantTimeCompare(t[:], zeros[:]) == 1 +} + +// GF(16) multiplication mod x^4 + x + 1 +func mul(a, b uint8) uint8 { + // carryless multiply + p := (a & 1) * b + p ^= (a & 2) * b + p ^= (a & 4) * b + p ^= (a & 8) * b + + // reduce mod x^4 + x + 1 + top := p & 0xf0 + return (p ^ (top >> 4) ^ (top >> 3)) & 0x0f +} + +func mulx8(a byte, b uint64) uint64 { + // carryless multiply + p := uint64(a&1) * b + p ^= uint64(a&2) * b + p ^= uint64(a&4) * b + p ^= uint64(a&8) * b + + // reduce mod x^4 + x + 1 + top := p & 0xf0f0f0f0f0f0f0f0 + return (p ^ (top >> 4) ^ (top >> 3)) & 0x0f0f0f0f0f0f0f0f +} + +func inverse(a byte) byte { + // static unsigned char table[16] = {0, 1, 9, 14, 13, 11, 7, 6, 15, 2, 12, 5, + // 10, 4, 3, 8}; return table[a & 15]; + + a2 := mul(a, a) + a4 := mul(a2, a2) + a8 := mul(a4, a4) + a6 := mul(a2, a4) + a14 := mul(a8, a6) + + return a14 +} + +func emulsifyInto(sps []uint64, y []uint8) { + var acc [M / 16]uint64 + accBytes := viewUint64SliceAsBytes(acc[:]) + + for i := K - 1; i >= 0; i-- { + for j := i; j < K; j++ { + top := uint8(acc[M/16-1] >> 60) + + acc[M/16-1] <<= 4 + for k := M/16 - 2; k >= 0; k-- { + acc[k+1] ^= acc[k] >> 60 + acc[k] <<= 4 + } + + for k := 0; k < len(Tail); k++ { + if k%2 == 0 { + accBytes[k/2] ^= mul(top, Tail[k]) + } else { + accBytes[k/2] ^= mul(top, Tail[k]) << 4 + } + } + + for k := 0; k < M/16; k++ { + acc[k] ^= sps[(i*K+j)*(M/16)+k] + if i != j { + acc[k] ^= sps[(j*K+i)*(M/16)+k] + } + } + } + } + + // add to y + for i := 0; i < M; i += 2 { + y[i] ^= accBytes[i/2] & 0xF + y[i+1] ^= accBytes[i/2] >> 4 + } +} diff --git a/sign/mayo/mode2/internal/mayo_test.go b/sign/mayo/mode2/internal/mayo_test.go new file mode 100644 index 000000000..1695f70f3 --- /dev/null +++ b/sign/mayo/mode2/internal/mayo_test.go @@ -0,0 +1,224 @@ +// Code generated from mode1/internal/mayo_test.go by gen.go + +package internal + +import ( + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "fmt" + "testing" + + "github.com/cloudflare/circl/sign/mayo/internal/common/nist" +) + +func TestNewKey(t *testing.T) { + for _, tc := range []struct { + name string + seed string + expectedPk string + }{ + {"MAYO_1", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb14803", "5b61421edc1c90efaf6075560f0206173555c55eb845ef3a70ee5ef18618361883a0ade490e9e5bc51cd2b25fb4ec9147a2fa6735d8749ed7e149330a30b3ab62b7991c2494cb21d0bc011ceb357597b8afef939a1fe398421c88f43fb4fe380c10caa3a95e507ebae5a571fd958e0505d994566263be8db31ac833aa20ae7ec97d1a865bb292e89d583975009046053bf91ce7d73e9c23c4244071e17a1095f3918330559f323ce557d66b8771f0003cccaeea82314088d56455d72f258e4c35fc6ce2d195cdae99f359a8307ec15f27da4d0634531cd9a1a719121f7c9b99a0d0c24252316f1ea7c16d73f5ecf7125ad0c8d2e02574131504875af5ab6dea1c328e71122f0cdd09e826cc515b4ec160f31d63253dd8798b2d841e80a28479bcca320853a459d4d659df695eea4b5aeb8f45f6c5fce283c64dd6fe928d452be3723f8b448650ebb72acf231e3c27683afcdec22037f1af29c479db1a97448ce570fef082052c9ef45179ba62abd7dd156f17f4da51557383d40a9d544cdf5cac4f4cc1ce69326a3ccc611fb7a6d04126dc54c55c2286a796be18958846d53e1e3f55f3ac0f4e09d8f302878f4993e25c2164970b337d0364aa61eff5d85e41fe784fd57420a5481b9a15f02a47e3e49c0709a37352a50fd0feb7e7f9938173b3e3c570a79c2424f5dc5bf95363b6d5d764e1a38a1aa05a31bbb9858b51fe3cbb19eaade5d4f482260838f5273b042f6340236395f6c347d4bfb922752ad0b17f1bae8645a9a6b1d72e1e91b4828faa313dd85ec5795252dcd95aa33519a56d740cbab7b9ee13e1b8add763bd07a25455e44e60f664846e32eec329c46e0741b6649c34358905d9d01124c3c8ae14e3fb47cc6477ba8e63bd4a3937588251952f5232bb9999aef509414e11a36d110ea24bedc235c55ab6f80116b036845e291148135a32b0d44fe5a4e9ea9f3ae150e11c62ea91318129f79318d3506ba50db3dbe235427d405a5baa9e6192a17014dd19863087445eb4cc9b6164aab3d5af4e73bc3edd97c76dabba0bc26c43d70f7880a0f7c042465da77ae3f8a31fa65903d90797fc0b5deddf578d52940040276c9b415d880e3782e03a2965a6d3b265628c690bd1b096a355362eb751d528fbc6fa365d1cf3d39cf49d8296c28fe8eb52a31a07e877d1e57e91971cbbd14216db76a08da45258a1a801d0cb232a87280df4522329ecd9312e5489d90a39547735cbd11493d0d86389af40c1b9ec5a993d0cbc508da424392695e1cdf205a294122412fac5a2b4d6e0ed5eed7b4de18f0d4b81f746753fa8744ab845c18800befc926d3290531053bf2cecf23d1b6d7fa9fb8d4827e87abf574afe39a636dbb9fce48854c641981f236da01bd8a5901871891aac7fa1f02300101540e7b05665e5f19912ee898666b64bb3cef688577c7b5482e123c0e8fcc466302eca8fc02f35260569cc1d4b00662be924f188226dbc6086d4aaed1ff9c7604748a556d00f87823f9fafaccf752d645b76a5851723274737e4dd7d5d76fdb256e3f1ef733914eb36932eb1c4c666df436351fb817e7055fbfca2b3103fcec84233feaf3fefd600721d3ca1514ee26db6a608dd4333377a9e36dd2dbadb1651eb99d8e3b4fe6"}, + {"MAYO_3", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d", "1c0ee1111b08003f28e65e8b3bdeb0378c8800a9a87f38ca101b55cbe065f76cd8be17feef5b448fbdc40c759729dd609f2f3e0a70f719bd04bdec59158c1d885127c91066d31b4e53848db9566d8d25393dd3e665c8f4b5fd5d5b836fc2e012a059a5550646dfaf9db63eb4c928198a4dc3e8631fa743e11004a2fe3454a9d537697af4bf5d6bee82c2fe40d5c74c29be0cd7e56c8dff8dacebe18320a455a72265559a287385643668d2a20058d927c87a41b8d94ac1c68b6a07c7b055604c7491f11ac30974ab0d76ff2034b6c73d3b4cfb13e8f341f9f3f445bb6a4e47aa381282f83af4c46fd38207da843135dceac04af08096915318e464a8a1fda2a99efd41c61197a498abc875ce302f94eeb8ec32e56f24e2c6c419250e08e2f69148fe083747340af17e634a98afbdd6902211c767b750365f6200fd92e8431160f1e293058cf2cd57be92816a712d796e6fc0c672026c162d93eb79a19c7d4853df371234fec9c9ce76b6edfe082007ff69c64c9980f2c7b7b6009e3db0990cffad1f4119844967572e1122e5c7ce460ccf732c82d3f18457111ae8e1a5381c5b30e9d76028ca26839455d44aac728a24731a1d69c8c856b3d611dfe5336fd5a42f62ee607219c654aa117a874ca33fbb89ecdb6310cb4e0c4a5e2554be2d4bfe6abb062eef513d726c258dafa690324302d8fb108376f991e50e9037e31ed3ce8d8cd785fe9eb419d33c7ac381f91c463a8a06629db910f5fed1d18f2cf30afcfc968ca5617c6cc6fd9080f53cbd05b727c04bf75dd93f08a0ac1a7bc46e8b5a08e6d3c3b1e1232126d4e65b1a3877689dca9b1e53a73d45ad9eeb38faf942a62a804b088226f4ecf15526e24936f9c28c951f5c101c5c44bf91b72fecbb3a9d28cf77e63343ad760089e0739017cf829297204041acf5b4c10fbca19219db233fabb3a564c8cddb281f290b9a8536c9a1d39a35d8eb124182d9cdc51044067647ee861b8c0c6fbbe1aec13076b8f3d8982b52d1243a9448729361619ba34edce7bbc1b3a1545f791a5f2f6d04b5204e073bf66e7b009c7d4a1660355d066fd0d358623fcee946e69d20bf6fb291906e700004aa001494381382c5e1bcc4fb2b7cfc4bba6866548ecc797fec670abc2e8a8dea2c4219ed32102a0a1f4fa67775512768e57851571fd383a5a684ed1e10b62f3bbbb65d58e08151695008ef6a7cf4b7cdd265d9cf1c158f64c607aaf37e51140518724c39f3ad8a759a7ec0af43778c00832088270934cd08a582196953b283fa047f6365585b6078027be9d9c915569b78c65702561ef5de7953fb6f5e3df3ae8b85d3fb2161cc3af62ed83d027b733507fee131da9d228c79277630c3efcdc9ee1e559bcaee4c818d6d29ea6c8cf9eb46ad58a4ab6a4fb2f6d6f4380efa4d60843ca4eac32f8772073f1cb70021547050c30095fc7c884e31a2f91994985eab00e7565da2ece39e47a6c8cfae012687dea9d65b8745d4d760e656a55243fa9ba8fbbd31b7f2bb2629a0ec9265aa883c8775ba173e5ff08a9a3ae4e76f9925418f4e2dce1d00e4f9377245b8939dc58b7d15c172baf984781afd318b264709bde5f0859a95923ecc42108f2fa7c600e9ff69f3f65751d8a457dde89a1ad900314f90e9b712580eff5e4a73efa3633c5d5e34fa8f2528075fa92dce66506f5e947f1fdef820296cc294ecdc059390b644a1407b13d24541d275a49d9bd1a6f68dab296f52965c7f72d50cdc0ec19456001516c10030086d2efa109f45fb6d1f7550de534d0d4ce856e72763463e6d0d11cd237037fea14abb32465f632ff785f1917322e4574bbfa8f74682da17f2bf7bffa0ff030297d5c999d6a7c387ef2a5e168225a60ebb01c6dfc72ba1fdec8f74651f3e64750f2dc98d72099fc85f43fb28ee0c4efe8b7d76bb432453a848310f6e96d833a27ccc98046a210f568c075dc71545ff0d37ae5138406d569e964bf8e6903de7651b33c05e22747074b730cb6f3e94b891e8105ad6921cf5efd40da88ec3d0765756b7bece012f59f1f68ab80082fda5fa37b026d0b6cb5fab9a0ecab6980a179815bc9e56cbdc0d0682c29c5f9fc8cbfbf5a6a9679de92adb5b4778ecdc70f819549087bef738037de613cbec897e016672ce9eaf00382ce4b60c563cf3d23cfa178403f270215cef465ae9b60f007c75c1fcb85ab4ae9ead303057dbeedcaf55f13ea586f55a2eee9da8dd92bc210d1e2d4fb7d219c54517fe5a4ddf5c8c6a776703ec8407d6b393fb331d7aa62423cbbf7a062bc210e9c6565cacfef1e4a5d448f2cbf5c8861857dc870bc37c2070ce1f1147dfdc285d9df8296d6620e1430884e29571ea95ce1deb9c2ae7198f10732b2b2d5ddabfabda6e77acfa2b10c75842e07de69cb2b34abe41018c9a765e45ac85b63a85ca612b1e9682d7c8f512297bdfb780e21506956fcb528bb1916fc1a8356667033e99e16c223139efef6790b8745eafe30df12d900301643f8bd6717d0b5d1de5e75f25bdc950ce507ce7b3489e72d5abdf5a28105a8063d0a9662672489de16a1628548382277fcfb24bc878f071b13d6e120af00c1cdcacf5f1ef4823a2b5da0e7c8487550d80ac9f6891e50be3dca0096042c97cdea3553db702bbcfcf64f1b64719bc89ba51a09057fd9285c4aa7d95532cdc04ee185beb1318132c836278e368ff8e2653e4aafa829a2d40d91bda7b4af3f16442931b7da2b500f7e3abf80219b8a245fc4748d20d549cd12c5af7ca3a8543bd14d0ddfb4fff8a43c661d0a8163fe80bc0f77b762d010b64b831e7c29f9982e5b7338a6411a37c4f1f46419b610415104f154a009cf3bf1805ff279145477a0d5ff0f3c9584874e8e8376101dec2c8c69592a8c46add07a6c0100cfab2bba03771749b3028d287805a6859a1e3a82f30231b01a7e3453059638e0d7a2430348ff4af26d00481cb3de085d269015fb6043a0346281a05448a81d04ad196d916503c35d31e513bb133d49f9646c191f872cf4d6798c18c87e52695d1adf59c97ba8bff5e826ea17ab2fcb8b559e871e6d62d8a93522554bb5d48de674596761595bf28d0114142a542c2f9cab2be451eb37546ce3eb60bb0cc6050e5d38ac96752a8d2575f67ce28452b00b486e2efc8aa80e94a0a442fd35f5fdeb48e52a82c43ef2c0a02d3bf71da67f0131af8aa90e159056ebf4e5dbbe176c9cd8c07d234135c50b524a4efd7b72ca4f413b29809e7a1a53442a1919bcce952203d17e3c5600dcc3160dcbf7f18b9352289ce78fd337c36416b8342635c3d542bd1a357b8e39b04098fe7b60248fd6ea4a2778ec576e4c34323c81e56386d40c79260e3d914db7cabbf269441dcfa216ef7aa85fae99a89f2c1f56838c30fc180135efb98bcd01e2a88d144ecf7063613d3ba62af5fa8cf7d63465273ac0abb3f94e87130310f4b841db13ad7fab1444a02af5649ef9a29392a4fc15b747799cd8a8d735094cb03a606735ac94ee244a8c5a3504f31ae62d3acf7f1586390efc6e8da967f3b174d9e805554603c4fd5cd875e70352d2c6576e3baf0420d6f3aa161f605219bffb93abd51628014072e7c2e533aa86117f02ccd67b623af4ca911eebe981595fd393e75d5b0d0da9440e3aaf9249110f5e8c22a68f9cfe0b30e8665270763390fb8618668a1a56b71065a85b91ff8dbb8e4ab9c3e6c079a2b0276ba475d388262f36a76c3e230bed8c47faae910"}, + {"MAYO_5", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2db505d7cfad1b4974", "708141534c09f5f604225ef8c3472f3749ffbce6c071fa7bb1f4b50b78ecd32e7f35efb6bfcead3313156f9995fca2d2db6d0f0ec50a66bd3d5b469698ab131f21cbae3785a0c838e2e3316fa73e9670adb76fa9e1ec8a0bb7ddae82c0ca7c1aa5e61c31b84180c3aae027223430e988e28c48ce0d5b2ad2750203707f2e284cf1807f008ae2570092a662b73a9a728f81a79338f3452d6af920ee59037f2c8a20b495420c5bb3fa7518a34ae1e64ffa742db0c3c009504ca98882e4abadaf8396cc242d9a7c1d4d71b17d6c65ed26c4e459d8bef86326ecc156209299424cc015c52f97862998f871ebacba29877e040b7adfecc69081f55162d190f374b704662c138c39782e7a8b74b7867e92c5bd8a7833b4242f04712ef4384ef452fbc3078550f15e563c9238d72088275d02d94cf8c163a2bd73810938a1208883874e1b7d8a9ced0ce6a7177da260ec1a35d2e0f45bbdf18f27cd3f0619d134aa47e4df364fbea1dfb852c225b06be4707824d9d2f8b25d6e15f81079511778cca8b82554591823616a7b7ea029c3c3670dba35bf2f11b6493ba5044c159c368f7e4d3ddb215247ac0fa17f227298b9b1d6af4632fec18ab98c9218f583c75e9b8434cb0b68105e44be85da80a65fd67839b4981e2e89feed17f41b60e75122b5328fc0a5772b8a0ec5e4646e1fc1f98a44a31d516be5cf6c187850d7343078304f8a2a02929dc2aaf268ee590507b325a0d744e413d58dd61e62decd0d4e846f08f9fae80cdb7e33d693c57e46cdbc7e1ebd06fc44133748064db8b77f1f87df1f21891af721e840d768be637ebd41cf8be5de36739045b9bae72cd9192a1ff0addbddaf6a0b6cb0d0923ce5b3d5c261c20b1bac348eed6d1cebcdda642103f3a4246e9ee3a1f1950be11796a2db39c54299a6973bdbc4a9446ff27a85fbce46e4c94c25d6fcf1d8bfc773df36591862c0447a400312969c056399b2ddc92703a4d14f1b5862752257103a300381f41f47d7baaaaef9bccbc1a3ce8c94edd1d7c43ba63c961918c1c9cffdd03659158d0827384f796466449a0bc72e94badcbf567e9f6812e320b772b7d330ca8d58996fb9cb9a4ee7772ef4e77f67e2875070249026c1287ca6b43af591a647c33efc965921c9225cb195898f0c0185d3142cd1d924119b003b0b51ccb42caced8baa8060a1750b53f7c039f857dbb248a2520bac9117da7a12ba07483bb751afb7fa1f3d9e9eecea70a9fd233adbad40fd3ff7ca1267a4a8d2375ecfe35d7984ce860725f68f19a19c0e00af83f13d452491720370effe877ec6ad51e702c9ae828b0ad3a845519d3267f3908c808498ea73bc96b70cf4d9cbaf1b1bcc9b12a093936cdf56cbb703799307d80b3755ad72dcc0b667cc14f6dd47d99d936ae3afd5be34264ddc8eaa7026e6391780444ac43c01cddba3c50548d7a2c03a59b1db7d798043351b51d4fc32f6d5f8ceeb8654977e7ba147936a6d172cfb023308730adaa3973fb4d883b95cdc5aea24a83e70561aaec784023e28dc385aae7076d271ca0551b83a60d7388b9a2c804ed66ff4fa6284fd57045d03b6c31d0d32165222efc6876b14a01c18024b0d6fec28361de82a337fb087ba25daed484b3e17fae66ce2bf2bc88614ed5a2e1db9f485ccbf7e029009c164ae3bb9b7f3b7de396970cda0943036e18d2cf96b9979e8d874980a844d7628b5fae66f00ecc4366a96c814785516d056e44b906ef6dc09e1a976384ccaf1b3f40d79a5f6ad426bb70035e84961c188ad24a8122088fb2a4b5abcb5fdd0a26cd7d44dd341c5e5ad65478269cbb5e30f4c23d63043de9c65fdd57fbae9f7a68c7bcf4e4e6b0eefdeaa3bf376c912b15f00e1a6c1b4c80269cdf9bdf25373fd55a7f51ae9aa0c0de6faf001a2016eef56d82eb696472546ec8bd29f1e76f0666b4c8d4c4cf6e633b30f323110d7b0fef38a2ec90f9359278a23f276da7984e87880105d12051937b037d86688b0aab4a11ceccd776263b8a82aa6d5a5490d33e734c651aafea394fb24d85ef3a0620686e1d96d198509e5f04f406986ff201e0b2d3d7584216e8568c68c2d8ebe1947209dff588969c3c088877b8c1dc0b0fadb7295fc44e3bef5d81789329675ac5c3142fec702f749c6e3aed13071c02680f7cb82e91432f6d6fd7c10ca132e61f2d47d6e2008ac2d5157445c1a3cfa52e03c5ad575fe836cdf4d8c9624e097d969eec08dcb4540be5b4a9f3ca03201457a2dae4a966d0b35abc65559484fe0afecb774e9437eba24d22e4da5927d00ca3a9cd49a1f3843b1f3f7b2872f0b1790788bb216d53b9b61f3f394d140a200ed30c3229761af6d3ecff8fc463ef63ae13f92d3d555261f04a287dac6653b7d493cc9026f5be29c8c550bfc513cfdeaf83ec0280fe2786ca9c07b222f6ccd9c31a8848a4e79be4b590fe4e239ad9239df96539b48cdad99fd0249d50204649e26838a076adcd6800221fff817681a6a1ccf807c1ae1794aa7cbe383cef23610436d51cdeb631df8bc1f7f7207b9972c60dfba18cd5378eab1501c6f8d4e0971862cff6b1cf3a327e1afa6371de15fefbb1c80c658d2b372f0bed979929e9fe3c3769ad0766edb634949c29f9bffd879c961f626d10617c161319957c28519ef29142236ccdbe214f7e3e9ab77edd5fa814f5b599513a46f79cac9fd9ab62d7859ed57a5e6a0af7f5e74ea4f9fbef3c9943f2c69cb66ef4497c92b25059980bd55568e8a5c50c0853fe02f9eafe068ebe710cf284976fe4b3c12d5990951775b891cebaf38ba4c9dbf70423210f723a6c0961f00766f0acf1ae75d3f1298b378cdd7fed999a8b45c923c3c8a9a48f007b88f6fc9e0d37458e441247074e9c89e7429bc76a7bef55e702420c1bc65d66bb28d46ad7e0c5be42de7b89a44dbe7ee416c795c0b7e91ca9cf2f2762816ef4bbed139137a8ad3fa7a5a96c917e8af660e736ebe9d70e4b90fa18b560ad173617f8dff65804a2bae40a4d27e86e97e02e485cb529fad4a5c7f72a2b758a693826bb964eeb28842f5aee51db712d68354d7f1ec129b7785091b1cbc3fb389263e064eb03df94183e0c1bd1824b8f5c232d7970c11e53caaae90ebba5ea862f5aa26aba25b5f627823182444efa64f21125de36afdd615518bb5dfdd117b5485910319b458236dad01de33b8605352517ce3aeee706de917ca76ecdc4ef53a733e5f898c2dd6c4d641d141ebcdca2c5c7cc176eac1b493df81702083bfc124ad9ed3511452a71f831c9a2f8d2d771cd3c1b1c440bdc46e87fb5ddc0d45c3e28c10aa1af313c08bb554590dcd296e0916a9b9df3d527a5db9ec1694083bb99c480231c87444998dc7f922f7159b019460d39be64882528f4df1e3e332f7386cfa34310b666d369ed7fff15ce6db939e448beeb74e221862a901c37275cec3e3718b146d0376c0b44e64d393c231050bf166658c333509aadca601619ba6e473705b5be1c9c290a390ee24062265298ad5bb30cc53571b4e8046856a10da99823e0a33c85fbfe785ba709a3cb9b97dd6d56db92febf8a833941655294134be7d37de581caf4e8fe278d9ad5e31d046c7c4c6e3f162c8b5bdb27f1bb5c772b8e7838f67f97cc3c090510fcb5e2f96191a77eeb7febfdcbff81a58860bd4fa87cb41b1fd51ac670828fd6efdcace5ceb85339db706286e860bacb1667da894ab79d32a67563f9a1935953679b687b558324e801c62b9eff69888a51cb8c79d6eb4aa9651108b53d1b66afb1fcbed7dce98403c64885bef7e45a0689ce783cf7741c2c787c1643e08a109b7f5813c11a16bc9269faf0c74e6e0d08428d3de2cd00e18615e157488b2a4095ade2e101dc3fd48da57bbd1c79e9c5cf0992014f80317fa35ee71560c27500761e8a60449546ffe1a232806a8ec6a5694941485f3e0af3f4cb557a42219435bf38fa10e11f3a243c9c584cde736a02bcc37b8f624931710c159f167cdb41deec3db28535731dcefb4926c8580d290ae818eb3a09738ca5d88ed1909ca95f1c71d9305f89b773cb840e3f481aa2f51dd65082da3afca358bf88d2781b46d82d7685f4ccd0983c75f9f1d6382493692604da6c8e8effb27129b1776509b98efc15c75ea1f11c04d7374742ec8cf28341a1be36deba666dabf6bcade7bc3670c03bf3bd42441edfe79716a7ccd32dc3008693a42800944bea936484bed7b6174718d31ada938690ad1efb0442fcdc7a8967f96fc4fa96821ff9862c3b1495ca55afe8ed5aa8097e20fe54f3ed2f2be1cd241a494bccfd3c75d7acd180ab6eb192402947c273abf6fac2e957c8fcc7757d79361071b2067349823cc9db3d531aaf0561acbb918df2924276ea64eb2bb095680d0be8da8d38d53f97d6cc68d88bc4f22c6865cffbd59a6fa015d2f0a12a985fd97a34e4d7f9504cc5a86acd4c791f105b9cb944ae4d0b0f747a1dd089a7903dda5528752132d2387e4854234a8a3ce95fd8afd4bfe8e55f4c026e80348ece14da917b3a696ad797e70d06356cfd2463f5ccb96549e1a037f938624754c57401cddcc948cbd088a0f276329a87db91308ece6360c743e4f8f360fed28b77f7a60745aea0b89721f4cb342b75ee1e51625292db27a97c2eb18186278fd826fb49cfe87fc9b45bd26a02266da90a7ca7d6c5d9eaaa036a0c7a12171832923facc0aae7554eb365e312e6b7b3e8be167b8e580fd5cdd609b5308864600b98f9b69d71eef1d4765f4ec07a584d9bae8a407777d7334bbb285ddb15a07dd5e3a4e8c8072710a4ac204162987f2b669524d88afb77e5f4a540f9985d560dae55945abcdeb3f90a498f76cf44d0a9e3977cdadc11a8613d50e4fb19577a6f6a16dd1b4003e56268a1134d774b9cc4e94ced0943a0199ea9ec944e891f02deb85c1b870e535f97599f843e8467c771784796c3c8712f3681b475a4eab150d18928aef90f293bdc505784441b6ffec7e525b15b5d1e2203f8ec95db5eddc8c6eb21e82118c75d6dcf2c58eb4487eed220b1eb6f3a3a7e92b0ce13ad5e87c4e4edd2c8bcab83d7622d20f3f5cd74a930515808e500563848ab0893cef9323ebdeeac9716270f3550c1d92ffb4d650c8442300cb7a4c61de2b5876f6563271ab7abdacc962be387556a6452dfdedf99148b4a2fe635dc730cfe9cf07a1ad5e148e2eb639e25838a8b5f6271a1ac8330847da496f042237e9f38ace9c37f096da0d76157825c2c8f578919faa984f4be65f7055e517b8f78589e1ff361d769e5d387795b79bf476959815d9b6ff22361f76e60cab6986e0d164a0984a013101e3cadd3eee5199030b45e8c6b9ea63f80c7e6e2909b39d96d6f62776820281eac9fb55fb5c124b7dd9e9b114d476d2fbc676df79f9e9b214ea69013681e7cd8d379bba254382ae4678986f67eba92cd978ac6353e6207101a3e1d18008c3d5924eef4991e0b576fd9f582980faf24b898796c18388f40ae96403d45b31785958d37ca09b76a5a7320776ca4d328e95ea7b6703f4ab0947070f5c20dfd154a9476099d193dccd487f589759754c43fb81c1941439b4d63eeb6b24c294c70ac0a71ff437a6abb795aed2fa4e2483f2a09ac6d1a85b91155cb368219d17883106568c27c924070ad23ed0193dacc353dbb95a4c1de118c1a710f96db6eb9c146c2d0e07873d495d1ad9e3717e09c067422fe5a2da8d9406e1395aa39cf0c82f9c290d1e31a4a4d9b4c381c95079327c296e5db5f8e3c92ef62c03f1c2ac7c4b0174528b5dc0ff37c8c7d0533e4248fb86ca1cb5df91e05e1cc582482e7f12136e8628a48085ff27d15815506c5013f6f65e3c89286616ffd5d08f3fc2194ae51fc4a706e2cd83b6629ea8cce9ee3f968db84c85e3dc0c4b3e4cd6f105a196260f5873c8590401719ce3afd8de96d465b5258ce6b6e53db24fdc8fea3a9062fd2f10ddfb0adcd355724e366afb6b401118d73873c8e682dacbb4be47452eeb98ce51607903126f83ab6b27723818f816f9ede8ef344b3e7bdafdbe8292c6af60835f572ee68629386575541f1b5d5bae84bd85cb65e1ec46a70abf6365f75f975c3b87944e45d0da01f267b569d9ce55cdc90fb3ba1b5f7337f87a914eb5f6f7dd66a56b052430b148e3e295eb82fed70c6a38f738ac63b820db7183d18156abaf638ae366ce6491f0f0e326e1c9c3c8e7df4d62632f7599805f4a8ef06ddee3191e35f20cb7d03e7afbae7cc71a914aa9c1744b420ab5f406771e944ee7e3aec062bec33696df2c5ac2e09648902ea629aac071346ef87f5e54aaf650785f5ade96e0919ec00b99926594cdbdd1e0482e56fc0b78cb6142d2a4be8aed181918b11a841fbb8b442a3e2af1ef1c29528d6447c72b8c2e514aabd0595ebdc698b90ef61a6ee4b400f01fa5c71a2dead6822f33c3341d8c019bd100aa9028b57e517ac6880112335390a8349b90235486eddee01685a7cf1ea158cf9cc1f348a6b113a9d82f2dae95c7ae56f9183d97cbc35a25baea3552b8c2a7de779f6b74db1cfc75d07d79aae1857b601e73cc3686c175f135233989e6d56bdbe611f96a2b51804467e47b474479ccfabddbb4629a0fdc7f73e172da3c168c6c40461371e05a9a332ee3aeb0c9a5985425e102e421f497ec978de92bf73c98fd7d1bbd068e9d8ae82cbc7ef3923bcfe97bd68fe9563195d26a1afdc3bd91b32044fbe68b4bccf29a8d52d13c625d89f73611e371dfe6b73b50d6388cfd87ba92074533cfb34a4d543126f096002d9ce5ecf89cc11b0449f696e1661a36884ac1bf58f397d993c82bcc03c1db156ffa5148da7a73b3be2b6a3b2436c26c24ac0154bee5837d079dfa65d04a208de8b79691518f9056ccdb382e61c8d7606be73322641c71719a23ad6a571f907aa0bd1d8f3170296f3cc88bacd9b3fe944548808e80d00c07bfddf271ace893f56e01f95329aa944d32d9623180142b32e60ce0b908cf2748a29c42a3a59b378e47ebbc4ada9b43133f51bb4d28b821bec1788cc94ac1c8e9e4499aae9e17fcabff0a824637f125e9b358e0"}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.name != Mode { + t.Skip() + } + + var seed [KeySeedSize]byte + _, _ = hex.Decode(seed[:], []byte(tc.seed)) + + pk, sk := NewKeyFromSeed(seed) + + pk2 := [PublicKeySize]byte(*pk) + sk2 := [PrivateKeySize]byte(*sk) + + if hex.EncodeToString(pk2[:]) != tc.expectedPk { + t.Fatal() + } + + if hex.EncodeToString(sk2[:]) != tc.seed { + t.Fatal() + } + }) + } +} + +func TestVerify(t *testing.T) { + for _, tc := range []struct { + name string + seed string + message string + signature string + }{ + {"MAYO_1", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb14803", "d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8", "165e6139a87a4a4966326ba83e6d1ef68d1d7bec62c9d1d267ba09df6815783980f0a6ca0923aee6e5b9495b7ed6fe6c49cc41f794a80d297b19d3f13b0bfe3e8865378f5d24a210dae0982a1036ec9cbe87c29ce9a05a8290d0d1e8603abd163a27b119878663c06745cee03f417300402bf198ee4ac2be99dfe19700f169efe6a365ae0fe9c1bc22028c864ec55072da4f98f878759de68399a2c8d2a2656996927265ce2908e05913f8057707d39dfd3ef58a79cad49f0bd010ca9e8ebde500057bffd0ff28015a60b3061cee55dfa1d59e1a72a8ce639777e6190542733639fbba575aae945583bf804ea129e1a80e12fb628accdcc3f43da37c7194bb641bb8b6404aee481b4ffb13be31941d30c1f9d818182545738ce2644dc9c9b0a1289c6294d3225cc23ee03cc8258176e1a958e90dfed58ad3a859d1b06dee53af6a"}, + {"MAYO_3", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d", "d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8", "01de18cb2cfce7ba2d15677a57e7d86b20a96c7b93daf27232ddc7a0249796ef9321ace5792a4af1ca49908780398a25f9e4b1293ad8209301c769f123cfa430e5bbff1812dd4a8efd5621c109444cb4a1bcf4f1d85abc6fbe493ae0370aba2b2251de011129be10c25f452c987ddb1c499faf1dc8da2e60e7913ab7ade65844a92ed3a5c04c2245c67d342e5067be4a8c024753a8fd5962481b9c52051723f50165ec2b0ec8f73b4b337361c12014607f9fe41c3591eae8653d18b95616b18a84a1a5951ab5082ceebd5d2cfd202fe3ae29b60a790d04295edfbce886eb98d63cce694c9517f5f6832b7cef38efc9c8f4c14e01429f5487cb752f675f4e4212058db63fc29b9c00701810144ac48e7b007f4dfaa80a2ec9899e60f704ac0d0b58c8a716433c91a27d966d2b7b2685da350c9f6c59233988cbb3f8f2383f5eb10e496e64017a0789ca8f702254e1b839382d42b8399488bd74517e280f1719392621596730862678dec519c50cd6bdc07f118217ff68004401fbccf8606c1af2ae6ce0dde06c1bd06a460bace8dc9408f1d5cf76dd063f63db9a0969098ec45fcde2e878d464f4de4bfe1fe710f2132d8e486f64e7d309d9faed999ce368e821a05d79f781fb895a767a09256541cd54242b5df5e262220fe232c637934380b137df86ab4fa07b11f07f914835c87d1547fe526001a61b8ce00721d90d8a4b54b60000ef75d9bdb94fc7f861ee46908ade105ea098cef938f49dae729817633003c87a0a6c0cbfd10185f7657804a2330a7def349da1df84a1a61dc857d2458425"}, + {"MAYO_5", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2db505d7cfad1b4974", "d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8", "7bc32600e7fe4e0d8caf187997624ba57867ec136d7112aceb161cf813ad98aa9709f0e42bc6e86c4c1a32e5b929df9976a4c0d77886d9e5005fad28c3b0e20dd7bb8d49aaaa77d969c72c9d37d78c3ff556ae777f4bac4d505194b6026fb3e17ad631980dfc373c7510c024817c1900d3428984e4e952b49b4c46c928717ea34f417b5f5d6067caf5edc15c1fcbf74d9f7a158958daf27acf3bcb611195278550d06bd83441083e2a64f0e70a8b668453b7f638a64c6d2424cf2ae31fb6efedf951215d14648d2e3282526c0504e4a0b56ac0336a28b98297d16ca2087c3c46ab10275ea0f5611bbde1b44beb420977aa8ec3693ac097901a8e3aef2d377e19ab6d14a42a5c5d7a13ec1df5be83657b7ee84e7671828aff72265bc7fa90829f92df63a159e51e222da67a5201db70597814ac4d793caeda47d7fdd2c59402a2e8a67beb6f9f43b31feaec24854f429e2aec6fe9e7412584f08719c0c5c90e6bc5371d65eebefcc956ada0659b9b0b5f4d4733873fb4daa0e837fda9062d9a79766ef66686f9b355a219d74728ccad98b04391427a30d8b95391b9e54b9f902853d0086fb1bfd136df4dc1ef6eed4c75f449612c76a506ec6c2d7e9676c776f615300230aae0353afc02d80ff377d79a923d2348eec166e0d1b40aa2428a2bf2f87dd89dffd6f1969b39cd84b3d2813078cd4a7753379a79ab6b33f9e8b54a73dde0c46f4fa9afb8a9d0445ae59d202a3df70143baf1de61de9d55681467e343a025b1e0f67af5b1e3e22568d449af192b3e241b6b635e668e851b39e9909ed77ec007a8fba5aec76551e6bb08f454ccf4d48867456c50fdeeaa80f4557d375bda789dc0a5622ccd20d0e2f32e4c7876464f8c8b5393f45eba04085f356927955fe89c475aba7862590656717b5c77f1f79d2a430a2dfb6c779f1794f5f6d374e6e566706f9bfcf33e2070b10af8bc9b853d38215bcb4ad02123445d91b485952514c63a21fac93146a15962fad262c2cdc8ce18a592e4420f60d18ae4ad558b5229b372ed06f021827e932bdb7c43ed6db84ab9c2e408251619bd8b3ce28b9c55c3a21ae2f300758c80086f0ed4a316403195512c2880239f50afa996ce24374ceb25637183b96cf246d204a04018f4344c81aad9cfa98c2ee4f5f6ce3a6f352a86e9a1b07b"}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.name != Mode { + t.Skip() + } + + var seed [KeySeedSize]byte + _, _ = hex.Decode(seed[:], []byte(tc.seed)) + + m, _ := hex.DecodeString(tc.message) + s, _ := hex.DecodeString(tc.signature) + + pk, _ := NewKeyFromSeed(seed) + epk := pk.Expand() + + if !Verify(m, s, epk) { + t.Fatal("should verify") + } + + epk.p1[0] ^= 1 + if Verify(m, s, epk) { + t.Fatal("should not verify") + } + }) + } +} + +func TestPQCgenKATSign(t *testing.T) { + for _, tc := range []struct { + name string + want string + }{ + {"MAYO_1", "d0da809f52866507b6354588cd44b712bac138a8363fde768adb92285b6e9865"}, + {"MAYO_2", "e7382b9b0fd985023a53f292b5d5caf444541a5bd531cf6e1e4d35b8bd864123"}, + {"MAYO_3", "0a53ad1ea675f6be7364d37b552cfa7ca254ac315724fb2871c2fe0567f509b9"}, + {"MAYO_5", "26a4bf3204c41fcb9911f761066668da34554efdd0684346b348ccd669f16b56"}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.name != Mode { + t.Skip() + } + + var seed [48]byte + var eseed [KeySeedSize]byte + for i := 0; i < 48; i++ { + seed[i] = byte(i) + } + f := sha256.New() + g := nist.NewDRBG(&seed) + fmt.Fprintf(f, "# %s\n\n", tc.name) + for i := 0; i < 100; i++ { + mlen := 33 * (i + 1) + _, _ = g.Read(seed[:]) + msg := make([]byte, mlen) + _, _ = g.Read(msg[:]) + + fmt.Fprintf(f, "count = %d\n", i) + fmt.Fprintf(f, "seed = %X\n", seed) + fmt.Fprintf(f, "mlen = %d\n", mlen) + fmt.Fprintf(f, "msg = %X\n", msg) + + g2 := nist.NewDRBG(&seed) + _, _ = g2.Read(eseed[:]) + pk, sk := NewKeyFromSeed(eseed) + + pk2 := [PublicKeySize]byte(*pk) + sk2 := [PrivateKeySize]byte(*sk) + + fmt.Fprintf(f, "pk = %X\n", pk2) + fmt.Fprintf(f, "sk = %X\n", sk2) + fmt.Fprintf(f, "smlen = %d\n", mlen+SignatureSize) + + sig, err := Sign(msg[:], sk.Expand(), &g2) + if err != nil { + t.Fatal() + } + + fmt.Fprintf(f, "sm = %X%X\n\n", sig, msg) + + if !Verify(msg[:], sig, pk.Expand()) { + t.Fatal() + } + } + + if fmt.Sprintf("%x", f.Sum(nil)) != tc.want { + t.Fatal() + } + }) + } +} + +func BenchmarkKeyGen(b *testing.B) { + var seed [KeySeedSize]byte + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + _, _ = NewKeyFromSeed(seed) + } +} + +func BenchmarkMatMul(b *testing.B) { + var acc [V * O * P]uint64 + var m1 [V * (V + 1) / 2 * P]uint64 + var m2 [V * O]byte + + for i := 0; i < b.N; i++ { + m1[i%6844] = uint64(i) + binary.LittleEndian.PutUint64(m2[:], uint64(i)) + + mulAddMUpperTriangularMatXMat(acc[:], m1[:], m2[:], V, O) + } +} + +func BenchmarkVerify(b *testing.B) { + var seed [KeySeedSize]byte + var msg [8]byte + pk, sk := NewKeyFromSeed(seed) + sig, _ := Sign(msg[:], sk.Expand(), nil) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Verify(msg[:], sig[:], pk.Expand()) + } +} + +func BenchmarkVerifyExpandedKey(b *testing.B) { + var seed [KeySeedSize]byte + var msg [8]byte + pk, sk := NewKeyFromSeed(seed) + sig, _ := Sign(msg[:], sk.Expand(), nil) + epk := pk.Expand() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Verify(msg[:], sig[:], epk) + } +} + +type zeroReader struct{} + +func (zeroReader) Read(buf []byte) (int, error) { + for i := range buf { + buf[i] = 0 + } + return len(buf), nil +} + +func BenchmarkSign(b *testing.B) { + var seed [KeySeedSize]byte + var msg [8]byte + _, sk := NewKeyFromSeed(seed) + b.ResetTimer() + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(msg[:], uint64(i)) + _, _ = Sign(msg[:], sk.Expand(), zeroReader{}) + } +} + +func BenchmarkSignExpandedKey(b *testing.B) { + var seed [KeySeedSize]byte + var msg [8]byte + _, sk := NewKeyFromSeed(seed) + esk := sk.Expand() + b.ResetTimer() + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(msg[:], uint64(i)) + _, _ = Sign(msg[:], esk, zeroReader{}) + } +} diff --git a/sign/mayo/mode2/internal/params.go b/sign/mayo/mode2/internal/params.go new file mode 100644 index 000000000..348fce62e --- /dev/null +++ b/sign/mayo/mode2/internal/params.go @@ -0,0 +1,42 @@ +// Code generated from params.templ.go. DO NOT EDIT. + +package internal + +const ( + Mode = "MAYO_2" + + N = 78 + M = 64 + O = 18 + K = 4 + + KeySeedSize = 24 + DigestSize = 32 +) + +var Tail = [5]uint8{8, 0, 2, 8, 0} + +const ( + V = N - O + + // The division by 2 converts the number of nibbles to bytes (when packed together). + // We don't explicitly round up beause given parameters ensure this will not happen. + OSize = V * O / 2 // O is a V*O matrix of GF(16) + P1Size = (V * (V + 1) / 2) * M / 2 // P1 consists of M V*V triangular matrices + P2Size = V * O * M / 2 + P3Size = (O * (O + 1) / 2) * M / 2 // P3 consists of M O*O triangular matrices + + VSize = (V + 1) / 2 // +1 to round up + + SignatureSize = (K*N+1)/2 + SaltSize + + PublicKeySeedSize = 16 + + PrivateKeySize = KeySeedSize + PublicKeySize = PublicKeySeedSize + P3Size + + SaltSize = KeySeedSize + + // P denotes the number of uint64 words required to fit M GF16 elements + P = M / 16 +) diff --git a/sign/mayo/mode2/mayo.go b/sign/mayo/mode2/mayo.go new file mode 100644 index 000000000..5905c4992 --- /dev/null +++ b/sign/mayo/mode2/mayo.go @@ -0,0 +1,151 @@ +// Code generated from modePkg.templ.go. DO NOT EDIT. + +// mode2 implements the MAYO signature scheme MAYO_2 +// as submitted to round1 of the NIST PQC competition of Additional Signature Scehemes and described in +// +// https://csrc.nist.gov/csrc/media/Projects/pqc-dig-sig/documents/round-1/spec-files/mayo-spec-web.pdf +// +// This implemented the nibble-sliced version as proposed in +// +// https://eprint.iacr.org/2023/1683 +package mode2 + +import ( + "crypto" + "errors" + "io" + + "github.com/cloudflare/circl/sign/mayo/mode2/internal" +) + +const ( + // Size of seed for NewKeyFromSeed + SeedSize = internal.KeySeedSize + + // Size of a packed PublicKey + PublicKeySize = internal.PublicKeySize + + // Size of a packed PrivateKey + PrivateKeySize = internal.PrivateKeySize + + // Size of a signature + SignatureSize = internal.SignatureSize +) + +// PublicKey is the type of Mayo1 public key +type PublicKey internal.PublicKey + +// PrivateKey is the type of Mayo1 private key +type PrivateKey internal.PrivateKey + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { + pk, sk, err := internal.GenerateKey(rand) + return (*PublicKey)(pk), (*PrivateKey)(sk), err +} + +// NewKeyFromSeed derives a public/private key pair using the given seed. +func NewKeyFromSeed(seed [SeedSize]byte) (*PublicKey, *PrivateKey) { + pk, sk := internal.NewKeyFromSeed(seed) + return (*PublicKey)(pk), (*PrivateKey)(sk) +} + +// Sign signs the given message using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { + return internal.Sign( + msg, + (*internal.PrivateKey)(sk).Expand(), + rand, + ) +} + +// Verify checks whether the given signature by pk on msg is valid. +func Verify(pk *PublicKey, msg []byte, signature []byte) bool { + return internal.Verify( + msg, + signature, + (*internal.PublicKey)(pk).Expand(), + ) +} + +// Packs the public key. +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var buf [PublicKeySize]byte + b := [PublicKeySize]byte(*pk) + copy(buf[:], b[:]) + return buf[:], nil +} + +// Packs the private key. +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var buf [PrivateKeySize]byte + b := [PrivateKeySize]byte(*sk) + copy(buf[:], b[:]) + return buf[:], nil +} + +// Unpacks the public key from data. +func (pk *PublicKey) UnmarshalBinary(data []byte) error { + if len(data) != PublicKeySize { + return errors.New("packed public key must be of mode2.PublicKeySize bytes") + } + self := (*(*[PublicKeySize]byte)(pk)) + copy(self[:], data[:]) + return nil +} + +// Unpacks the private key from data. +func (sk *PrivateKey) UnmarshalBinary(data []byte) error { + if len(data) != PublicKeySize { + return errors.New("packed private key must be of mode2.PrivateKeySize bytes") + } + self := (*(*[PrivateKeySize]byte)(sk)) + copy(self[:], data[:]) + return nil +} + +// Sign signs the given message. +// +// opts.HashFunc() must return zero, which can be achieved by passing +// crypto.Hash(0) for opts. Will only return an error +// if opts.HashFunc() is non-zero. +// +// This function is used to make PrivateKey implement the crypto.Signer +// interface. The package-level Sign function might be more convenient +// to use. +func (sk *PrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ( + signature []byte, err error) { + if opts.HashFunc() != crypto.Hash(0) { + return nil, errors.New("mayo: cannot sign hashed message") + } + + return Sign(sk, msg, rand) +} + +// Computes the public key corresponding to this private key. +// +// Returns a *PublicKey. The type crypto.PublicKey is used to make +// PrivateKey implement the crypto.Signer interface. +func (sk *PrivateKey) Public() crypto.PublicKey { + return (*internal.PrivateKey)(sk).Public +} + +// Equal returns whether the two private keys equal. +func (sk *PrivateKey) Equal(other crypto.PrivateKey) bool { + castOther, ok := other.(*PrivateKey) + if !ok { + return false + } + return (*internal.PrivateKey)(sk).Equal((*internal.PrivateKey)(castOther)) +} + +// Equal returns whether the two public keys equal. +func (pk *PublicKey) Equal(other crypto.PublicKey) bool { + castOther, ok := other.(*PublicKey) + if !ok { + return false + } + return (*internal.PublicKey)(pk).Equal((*internal.PublicKey)(castOther)) +} diff --git a/sign/mayo/mode3/internal/matrix.go b/sign/mayo/mode3/internal/matrix.go new file mode 100644 index 000000000..5ddf0d842 --- /dev/null +++ b/sign/mayo/mode3/internal/matrix.go @@ -0,0 +1,291 @@ +// Code generated from mode1/internal/matrix.go by gen.go + +package internal + +// Given b in GF(16), packs the 32-bit result of (b*x^3, b*x^2, b*x, b) into the returned multiplication table. +func mulTable(b uint8) uint32 { + x := uint32(b) * 0x08040201 + highNibble := x & uint32(0xf0f0f0f0) + + // mod x^4+x+1 + return (x ^ (highNibble >> 4) ^ (highNibble >> 3)) +} + +func vecMulAddPackedTab(p int, in []uint64, tab uint32, acc []uint64) { + lsbMask := uint64(0x1111111111111111) + for i := 0; i < p; i++ { + acc[i] ^= (in[i]&lsbMask)*uint64(tab&0xff) ^ + ((in[i]>>1)&lsbMask)*uint64((tab>>8)&0xf) ^ + ((in[i]>>2)&lsbMask)*uint64((tab>>16)&0xf) ^ + ((in[i]>>3)&lsbMask)*uint64((tab>>24)&0xf) + } +} + +func vecMulAddPacked(p int, in []uint64, a byte, acc []uint64) { + tab := mulTable(a) + vecMulAddPackedTab(p, in, tab, acc) +} + +// Multiplies each nibble in a by b. +func mulAddPacked(a uint64, b uint8) uint64 { + msb := uint64(0x8888888888888888) + a64 := a + r64 := a64 * uint64(b&1) + + for i := 1; i < 4; i++ { + b >>= 1 + aMsb := a64 & msb + a64 ^= aMsb + a64 = (a64 << 1) ^ ((aMsb >> 3) * 3) + r64 ^= (a64) * uint64(b&1) + } + + return r64 +} + +// acc += M1*M2 +// acc and M2 are multiple matrices, M1 is a single matrix +func mulAddMatXMMat(acc []uint64, m1 []uint8, m2 []uint64, rows int, cols int, cols2 int) { + for r := 0; r < rows; r++ { + for c := 0; c < cols; c++ { + tab := mulTable(m1[r*cols+c]) + for k := 0; k < cols2; k++ { + // The following multiplication table way is equivalent to: + // for p := 0; p < P; p++ { + // acc[P*(r*cols2+k)+p] ^= gf16v_mul_u64(m2[P*(c*cols2+k)+p], m1[r*cols+c]) + // } + vecMulAddPackedTab(P, m2[P*(c*cols2+k):], tab, acc[P*(r*cols2+k):]) + } + } + } +} + +// acc += M1*M2, where M1 is upper triangular, acc and M2 is not +// acc and M1 are multiple matrices, M2 is a single matrix +func mulAddMUpperTriangularMatXMat(acc []uint64, m1 []uint64, m2 []uint8, rows int, cols2 int) { + // The ordinary summation order is r -> c -> k, but here it is interchanged to make use of multiplication table + cols := rows + for k := 0; k < cols2; k++ { + for c := 0; c < cols; c++ { + tab := mulTable(m2[c*cols2+k]) + for r := 0; r <= c; r++ { + pos := r*(cols*2-r+1)/2 + (c - r) + vecMulAddPackedTab(P, m1[P*pos:], tab, acc[P*(r*cols2+k):]) + } + } + } +} + +// acc += M1^T*M2, +// acc and M2 are multiple matrices, M1 is a single matrix +// M1, before ^T, is of rows x cols +func mulAddMatTransXMMat(acc []uint64, m1 []uint8, m2 []uint64, rows int, cols int, cols2 int) { + for r := 0; r < cols; r++ { + for c := 0; c < rows; c++ { + tab := mulTable(m1[c*cols+r]) + for k := 0; k < cols2; k++ { + vecMulAddPackedTab(P, m2[P*(c*cols2+k):], tab, acc[P*(r*cols2+k):]) + } + } + } +} + +// acc += M1*M2^T, +// acc and M1 are multiple matrices, M2 is a single matrix +// M2, before ^T, is of cols2 x cols, but cols strides at colsStride +// M1 optionally upper triangular +func mulAddMMatXMatTrans(acc []uint64, m1 []uint64, m2 []uint8, rows int, cols int, cols2 int, colsStride int, isM1Triangular bool) { + for k := 0; k < cols2; k++ { + for c := 0; c < cols; c++ { + rBound := rows - 1 + if isM1Triangular { + rBound = c + } + for r := 0; r <= rBound; r++ { + tab := mulTable(m2[k*colsStride+c]) + pos := r*cols + c + if isM1Triangular { + pos = r*(cols*2-r+1)/2 + (c - r) + } + vecMulAddPackedTab(P, m1[P*pos:], tab, acc[P*(r*cols2+k):]) + } + } + } +} + +// acc += (M1+M1^T)*M2 +// M1 of rows x rows is upper triangular; M2 is of rows x cols2 +// acc and M1 are multiple matrices, M2 is a single matrix +func mulAddMUpperTriangularWithTransposeMatXMat(acc []uint64, m1 []uint64, m2 []uint8, rows int, cols2 int) { + m1pos := 0 + for r := 0; r < rows; r++ { + for c := r; c < rows; c++ { + if c == r { + m1pos += 1 + continue + } + for k := 0; k < cols2; k++ { + vecMulAddPacked(P, m1[P*m1pos:], m2[c*cols2+k], acc[P*(r*cols2+k):]) + vecMulAddPacked(P, m1[P*m1pos:], m2[r*cols2+k], acc[P*(c*cols2+k):]) + } + m1pos++ + } + } +} + +func mulAddMatVec(acc []byte, m []byte, v []byte, rows, cols int) { + for i := 0; i < rows; i++ { + for j := 0; j < cols; j++ { + acc[i] ^= byte(mulAddPacked(uint64(m[i*cols+j]), v[j])) + } + } +} + +func upper(in []uint64, out []uint64, size int) { + pos := 0 + for r := 0; r < size; r++ { + for c := r; c < size; c++ { + copy(out[P*pos:][:P], in[P*(r*size+c):][:P]) + if r != c { + for p := 0; p < P; p++ { + out[P*pos+p] ^= in[P*(c*size+r)+p] + } + } + pos++ + } + } +} + +func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8) { + var accumulator [K * N][P * 16]uint64 + + // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] + // [ 0 P3 ] [S2] [ P3*S2] + + // Note that S = S1||S2 is strided at N=V+O + + // P1 * S1^t : VxV * V*K, where P1 is triangular + pos := 0 + for r := 0; r < V; r++ { + for c := r; c < V; c++ { + for k := 0; k < K; k++ { + vecAddPacked(P, p1[P*pos:], accumulator[r*K+k][P*int(s[k*N+c]):]) + } + pos++ + } + } + + // P2 * S2^t : V*O * O*K + pos = 0 + for r := 0; r < V; r++ { + for c := 0; c < O; c++ { + for k := 0; k < K; k++ { + vecAddPacked(P, p2[P*pos:], accumulator[r*K+k][P*int(s[k*N+V+c]):]) + } + pos++ + } + } + + // P3 * S2^t : O*O * O*K, where P3 is triangular + pos = 0 + for r := 0; r < O; r++ { + for c := r; c < O; c++ { + for k := 0; k < K; k++ { + vecAddPacked(P, p3[P*pos:], accumulator[(r+V)*K+k][P*int(s[k*N+V+c]):]) + } + pos++ + } + } + + for i := 0; i < K*N; i++ { + aggregate(P, accumulator[i], sps[P*i:]) + } +} + +func variableTime2(sps []uint64, s []uint8, pst []uint64) { + var accumulator [K * K][P * 16]uint64 + + // S * PST : KxN * N*K + for r := 0; r < K; r++ { + for c := 0; c < N; c++ { + for k := 0; k < K; k++ { + vecAddPacked(P, pst[P*(c*K+k):], accumulator[r*K+k][P*int(s[r*N+c]):]) + } + } + } + + for i := 0; i < K*K; i++ { + aggregate(P, accumulator[i], sps[P*i:]) + } +} + +// p is always P, but is still kept to be consistent with other functions +// +//nolint:unparam +func vecAddPacked(p int, in []uint64, acc []uint64) { + for i := 0; i < p; i++ { + acc[i] ^= in[i] + } +} + +func aggregate(p int, bins [P * 16]uint64, out []uint64) { + // The following two methods are mathematically equivalent, but the second one is slightly faster. + + // the powers of x mod x^4+x+1, represented as integers, are 1,2,4,8,3,..,13,9 + // out = bins[9]*x^14 + bins[13]*x^13 + bins[15]*x^12 ... + bin[4]*x^2 + bins[2]*x + bins[1] + // = ((bins[9]x+bins[13])x+bins[15])x + ... bins[4])x+bins[2])x+bins[1] + + // vecMulAddPackedByX(p, bins[P*9:], bins[P*13:]) + // vecMulAddPackedByX(p, bins[P*13:], bins[P*15:]) + // vecMulAddPackedByX(p, bins[P*15:], bins[P*14:]) + // vecMulAddPackedByX(p, bins[P*14:], bins[P*7:]) + // vecMulAddPackedByX(p, bins[P*7:], bins[P*10:]) + // vecMulAddPackedByX(p, bins[P*10:], bins[P*5:]) + // vecMulAddPackedByX(p, bins[P*5:], bins[P*11:]) + // vecMulAddPackedByX(p, bins[P*11:], bins[P*12:]) + // vecMulAddPackedByX(p, bins[P*12:], bins[P*6:]) + // vecMulAddPackedByX(p, bins[P*6:], bins[P*3:]) + // vecMulAddPackedByX(p, bins[P*3:], bins[P*8:]) + // vecMulAddPackedByX(p, bins[P*8:], bins[P*4:]) + // vecMulAddPackedByX(p, bins[P*4:], bins[P*2:]) + // vecMulAddPackedByX(p, bins[P*2:], bins[P*1:]) + // copy(out[:P], bins[P*1:]) + + // In the reversed order of the above, because /x turns out to be slightly faster than *x. + // out = ((bins[2]x^-1+bins[4])x^-1+bins[8])x^-1 + ... bins[13])x^-1+bins[9])x^-1+bins[1] + vecMulAddPackedByInvX(p, bins[P*2:], bins[P*4:]) + vecMulAddPackedByInvX(p, bins[P*4:], bins[P*8:]) + vecMulAddPackedByInvX(p, bins[P*8:], bins[P*3:]) + vecMulAddPackedByInvX(p, bins[P*3:], bins[P*6:]) + vecMulAddPackedByInvX(p, bins[P*6:], bins[P*12:]) + vecMulAddPackedByInvX(p, bins[P*12:], bins[P*11:]) + vecMulAddPackedByInvX(p, bins[P*11:], bins[P*5:]) + vecMulAddPackedByInvX(p, bins[P*5:], bins[P*10:]) + vecMulAddPackedByInvX(p, bins[P*10:], bins[P*7:]) + vecMulAddPackedByInvX(p, bins[P*7:], bins[P*14:]) + vecMulAddPackedByInvX(p, bins[P*14:], bins[P*15:]) + vecMulAddPackedByInvX(p, bins[P*15:], bins[P*13:]) + vecMulAddPackedByInvX(p, bins[P*13:], bins[P*9:]) + vecMulAddPackedByInvX(p, bins[P*9:], bins[P*1:]) + copy(out[:P], bins[P*1:]) +} + +// func vecMulAddPackedByX(p int, in []uint64, acc []uint64) { +// // vecMulAddPacked(p, in, 2, acc) + +// msb := uint64(0x8888888888888888) +// for i := 0; i < p; i++ { +// t := in[i] & msb +// acc[i] ^= ((in[i] ^ t) << 1) ^ ((t >> 3) * 3) +// } +// } + +func vecMulAddPackedByInvX(p int, in []uint64, acc []uint64) { + // vecMulAddPacked(p, in, 9, acc) + + lsb := uint64(0x1111111111111111) + for i := 0; i < p; i++ { + t := in[i] & lsb + acc[i] ^= ((in[i] ^ t) >> 1) ^ (t * 9) + } +} diff --git a/sign/mayo/mode3/internal/mayo.go b/sign/mayo/mode3/internal/mayo.go new file mode 100644 index 000000000..a06568d0e --- /dev/null +++ b/sign/mayo/mode3/internal/mayo.go @@ -0,0 +1,730 @@ +// Code generated from mode1/internal/mayo.go by gen.go + +package internal + +import ( + "crypto/aes" + "crypto/cipher" + cryptoRand "crypto/rand" + "crypto/subtle" + "io" + "unsafe" + + "github.com/cloudflare/circl/internal/sha3" +) + +type ( + PrivateKey [PrivateKeySize]byte + PublicKey [PublicKeySize]byte +) + +func (pk *PublicKey) Equal(other *PublicKey) bool { + return *pk == *other +} + +func (sk *PrivateKey) Equal(other *PrivateKey) bool { + return subtle.ConstantTimeCompare((*sk)[:], (*other)[:]) == 1 +} + +type ExpandedPublicKey struct { + p1 [P1Size]byte + p2 [P2Size]byte + p3 [P3Size]byte +} + +type ExpandedPrivateKey struct { + seed [KeySeedSize]byte + o [V * O]byte + p1 [M * V * V / 16]uint64 + l [M * V * O / 16]uint64 +} + +func (pk *PublicKey) Expand() *ExpandedPublicKey { + seedPk := pk[:PublicKeySeedSize] + + var nonce [16]byte // zero-initialized + block, _ := aes.NewCipher(seedPk[:]) + ctr := cipher.NewCTR(block, nonce[:]) + + epk := ExpandedPublicKey{} + ctr.XORKeyStream(epk.p1[:], epk.p1[:]) + ctr.XORKeyStream(epk.p2[:], epk.p2[:]) + + copy(epk.p3[:], pk[PublicKeySeedSize:]) + + return &epk +} + +func (sk *PrivateKey) Expand() *ExpandedPrivateKey { + var epk ExpandedPrivateKey + + seed := (*sk)[:KeySeedSize] + copy(epk.seed[:], seed) + + var seedPk [PublicKeySeedSize]byte + var o [OSize]byte + + h := sha3.NewShake256() + _, _ = h.Write(seed[:]) + _, _ = h.Read(seedPk[:]) + _, _ = h.Read(o[:]) + + var nonce [16]byte // zero-initialized + block, _ := aes.NewCipher(seedPk[:]) + ctr := cipher.NewCTR(block, nonce[:]) + + var p12 [P1Size + P2Size]byte + ctr.XORKeyStream(p12[:], p12[:]) + + decode(o[:], epk.o[:]) + + p1Tri := viewBytesAsUint64Slice(p12[:P1Size]) + p2 := viewBytesAsUint64Slice(p12[P1Size:]) + + // TODO: this copy can be saved by reusing buffers but ugly + copy(epk.p1[:], p1Tri) + copy(epk.l[:], p2) + + // compute L_i = (P1 + P1^t)*O + P2 + mulAddMUpperTriangularWithTransposeMatXMat(epk.l[:], p1Tri, epk.o[:], V, O) + + return &epk +} + +// decode unpacks N bytes from src to N*2 nibbles to dst. +// The length is determined by len(dst) +func decode(src []byte, dst []byte) { + i := 0 + for ; i < len(dst)/2; i++ { + dst[i*2] = src[i] & 0xf + dst[i*2+1] = src[i] >> 4 + } + + // Account for odd length + if len(dst)%2 == 1 { + dst[i*2] = src[i] & 0xf + } +} + +// encode packs N=length low nibbles from src to (N+1)/2 bytes in dst. +func encode(src []byte, dst []byte, length int) { + var i int + for i = 0; i+1 < length; i += 2 { + dst[i/2] = (src[i+0] << 0) | (src[i+1] << 4) + } + if length%2 == 1 { + dst[i/2] = (src[i+0] << 0) + } +} + +func viewBytesAsUint64Slice(data []byte) []uint64 { + numUint64 := len(data) / 8 + return unsafe.Slice((*uint64)(unsafe.Pointer(&data[0])), numUint64) +} + +func viewUint64SliceAsBytes(data []uint64) []byte { + numByte := len(data) * 8 + return unsafe.Slice((*uint8)(unsafe.Pointer(&data[0])), numByte) +} + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { + var seed [KeySeedSize]byte + if rand == nil { + rand = cryptoRand.Reader + } + _, err := io.ReadFull(rand, seed[:]) + if err != nil { + return nil, nil, err + } + pk, sk := NewKeyFromSeed(seed) + return pk, sk, nil +} + +func NewKeyFromSeed(seed [KeySeedSize]byte) (*PublicKey, *PrivateKey) { + var sk PrivateKey + copy(sk[:], seed[:]) + + return sk.Public(), &sk +} + +func (sk *PrivateKey) Public() *PublicKey { + var pk PublicKey + seedPk := pk[:PublicKeySeedSize] + var o [OSize]byte + + h := sha3.NewShake256() + _, _ = h.Write(sk[:]) + _, _ = h.Read(seedPk[:]) + _, _ = h.Read(o[:]) + + var nonce [16]byte // zero-initialized + block, _ := aes.NewCipher(seedPk[:]) + ctr := cipher.NewCTR(block, nonce[:]) + + var p12 [P1Size + P2Size]byte + ctr.XORKeyStream(p12[:], p12[:]) + + var oo [V * O]byte + decode(o[:], oo[:]) + + p1Tri := viewBytesAsUint64Slice(p12[:P1Size]) + p1OP2 := viewBytesAsUint64Slice(p12[P1Size:]) + + var p3full [M * O * O / 16]uint64 + var p3 [P3Size / 8]uint64 + + mulAddMUpperTriangularMatXMat(p1OP2, p1Tri, oo[:], V, O) + mulAddMatTransXMMat(p3full[:], oo[:], p1OP2, V, O, O) + + upper(p3full[:], p3[:], O) + + xx := viewUint64SliceAsBytes(p3[:]) + copy(pk[PublicKeySeedSize:], xx[:]) + + return &pk +} + +func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { + if rand == nil { + rand = cryptoRand.Reader + } + + var digest [DigestSize]byte + + h := sha3.NewShake256() + _, _ = h.Write(msg[:]) + _, _ = h.Read(digest[:]) + + var salt [SaltSize]byte + + // R <- $ + if _, err := io.ReadFull(rand, salt[:]); err != nil { + return nil, err + } + + h.Reset() + _, _ = h.Write(digest[:]) + _, _ = h.Write(salt[:]) + _, _ = h.Write(sk.seed[:]) + _, _ = h.Read(salt[:]) + + h.Reset() + _, _ = h.Write(digest[:]) + _, _ = h.Write(salt[:]) + + var tenc [M / 2]byte + _, _ = h.Read(tenc[:]) + + var t [M]byte + decode(tenc[:], t[:]) + + var v [K * V]byte + var x [K*O + 1]byte // + 1 for buffer + for ctr := 0; ctr <= 255; ctr++ { + ctrByte := []byte{byte(ctr)} + h.Reset() + _, _ = h.Write(digest[:]) + _, _ = h.Write(salt[:]) + _, _ = h.Write(sk.seed[:]) + _, _ = h.Write(ctrByte[:]) + + var venc [K * VSize]byte + var renc [K * O / 2]byte + _, _ = h.Read(venc[:]) + _, _ = h.Read(renc[:]) + + var r [K * O]byte + + for i := 0; i < K; i++ { + decode(venc[i*VSize:], v[i*V:(i+1)*V]) + } + decode(renc[:], r[:]) + + // M = vL + var m [M * K * O / 16]uint64 + mulAddMatXMMat(m[:], v[:], sk.l[:], K, V, O) + + // pv = P1 * V^T + var pv [M * V * K / 16]uint64 + mulAddMMatXMatTrans(pv[:], sk.p1[:], v[:], V, V, K, V, true) + + // V * pv + var vpv [M * K * K / 16]uint64 + mulAddMatXMMat(vpv[:], v[:], pv[:], K, V, K) + + var y [M]byte + copy(y[:], t[:]) + emulsifyInto(vpv[:], y[:]) + + var A [M * (K*O + 1)]byte + _ = A + + computeA(m[:], A[:]) + + if sampleSolution(A[:], y[:], r[:], x[:]) { + break + } + } + + var s [K * N]byte + for i := 0; i <= K-1; i++ { + copy(s[i*N:][:V], v[i*V:]) + mulAddMatVec(s[i*N:], sk.o[:], x[i*O:], V, O) + copy(s[i*N+V:][:O], x[i*O:]) + } + + var sig [(K*N+1)/2 + SaltSize]byte + encode(s[:], sig[:], K*N) + copy(sig[(K*N+1)/2:], salt[:]) + + return sig[:], nil +} + +// assume last (KO+1-th) column of a is zero +func sampleSolution(a []byte, y []byte, r []byte, x []byte) bool { + const aCols = K*O + 1 + + copy(x[:], r[:]) + + var ar [M]byte + mulAddMatVec(ar[:], a[:], x[:], M, aCols) + + // move y - Ar to last column of matrix A + for i := 0; i < M; i++ { + a[K*O+i*(aCols)] = y[i] ^ ar[i] + } + + ef(a[:], M, aCols) + + fullRank := byte(0) + for i := 0; i < aCols-1; i++ { + fullRank |= a[(M-1)*(aCols)+i] + } + + if fullRank == 0 { + return false + } + + // back substitution in constant time + // the index of the first nonzero entry in each row is secret, which makes + // things less efficient + + for row := M - 1; row >= 0; row-- { + finished := byte(0) + colUpperBound := min(row+(32/(M-row)), K*O) + // the first nonzero entry in row r is between r and col_upper_bound with probability at least ~1-q^{-32} + + for col := row; col <= colUpperBound; col++ { + // Compare two chars in constant time. + // Returns 0x00 if the byte arrays are equal, 0xff otherwise. + correctColumn := ctCompare8((a[row*aCols+col]), 0) & ^finished + + u := correctColumn & a[row*aCols+aCols-1] + x[col] ^= u + + for i := 0; i < row; i += 8 { + tmp := (uint64(a[i*aCols+col]) << 0) ^ (uint64(a[(i+1)*aCols+col]) << 8) ^ + (uint64(a[(i+2)*aCols+col]) << 16) ^ (uint64(a[(i+3)*aCols+col]) << 24) ^ + (uint64(a[(i+4)*aCols+col]) << 32) ^ (uint64(a[(i+5)*aCols+col]) << 40) ^ + (uint64(a[(i+6)*aCols+col]) << 48) ^ (uint64(a[(i+7)*aCols+col]) << 56) + + tmp = mulx8(u, tmp) + + a[i*aCols+aCols-1] ^= byte((tmp) & 0xf) + a[(i+1)*aCols+aCols-1] ^= byte((tmp >> 8) & 0xf) + a[(i+2)*aCols+aCols-1] ^= byte((tmp >> 16) & 0xf) + a[(i+3)*aCols+aCols-1] ^= byte((tmp >> 24) & 0xf) + a[(i+4)*aCols+aCols-1] ^= byte((tmp >> 32) & 0xf) + a[(i+5)*aCols+aCols-1] ^= byte((tmp >> 40) & 0xf) + a[(i+6)*aCols+aCols-1] ^= byte((tmp >> 48) & 0xf) + a[(i+7)*aCols+aCols-1] ^= byte((tmp >> 56) & 0xf) + } + + finished = finished | correctColumn + } + } + + return true +} + +// if a == b -> 0x0000000000000000, else 0xFFFFFFFFFFFFFFFF +func ctCompare64(a, b int) uint64 { + return uint64((-(int64)(a ^ b)) >> 63) +} + +// a > b -> b - a is negative +// returns 0xFFFFFFFF if true, 0x00000000 if false +func ct64IsGreaterThan(a, b int) uint64 { + diff := int64(b) - int64(a) + return uint64(diff >> 63) +} + +// if a == b -> 0x00, else 0xFF +func ctCompare8(a, b byte) byte { + return byte((-int32(a ^ b)) >> (31)) +} + +func extract(in []uint64, index int) byte { + leg := index / 16 + offset := index % 16 + + return byte((in[leg] >> (offset * 4)) & 0xF) +} + +// put matrix in row echelon form with ones on first nonzero entries *in constant time* +func ef(A []byte, nrows, ncols int) { + // ncols is actually always K*O + 1 + + // we operate each row by packing nibbles to uint64s. + rowLen := (ncols + 15) / 16 + + var pivotRowData [(K*O + 1 + 15) / 16]uint64 // rounds up + var pivotRowData2 [(K*O + 1 + 15) / 16]uint64 + + // nibbleslice the matrix A + var packedAbyte [((K*O + 1 + 15) / 16) * M * 8]byte + for i := 0; i < nrows; i++ { + encode(A[i*ncols:], packedAbyte[i*rowLen*8:], ncols) + } + + packedA := viewBytesAsUint64Slice(packedAbyte[:]) + + // pivot row is secret, pivot col is not + pivotRow := 0 + for pivotCol := 0; pivotCol < ncols; pivotCol++ { + pivotRowLowerBound := max(0, pivotCol+nrows-ncols) + pivotRowUpperBound := min(nrows-1, pivotCol) + // the pivot row is guaranteed to be between these lower and upper bounds if + // A has full rank + + // zero out pivot row + for i := 0; i < rowLen; i++ { + pivotRowData[i] = 0 + pivotRowData2[i] = 0 + } + + // try to get a pivot row in constant time + var pivot byte = 0 + var pivotIsZero uint64 = 0xffffffffffffffff + for row := pivotRowLowerBound; row <= min(nrows-1, pivotRowUpperBound+32); row++ { + isPivotRow := ^ctCompare64(row, pivotRow) + belowPivotRow := ct64IsGreaterThan(row, pivotRow) + + for j := 0; j < rowLen; j++ { + mask := isPivotRow | (belowPivotRow & pivotIsZero) + pivotRowData[j] ^= mask & packedA[row*rowLen+j] + } + pivot = extract(pivotRowData[:], pivotCol) + pivotIsZero = ^ctCompare64(int(pivot), 0) + } + + // multiply pivot row by inverse of pivot + inverse := inverse(pivot) + vecMulAddPacked(rowLen, pivotRowData[:], inverse, pivotRowData2[:]) + + // conditionally write pivot row to the correct row, if there is a nonzero + // pivot + for row := pivotRowLowerBound; row <= pivotRowUpperBound; row++ { + doCopy := ^ctCompare64(row, pivotRow) & ^pivotIsZero + doNotCopy := ^doCopy + for col := 0; col < rowLen; col++ { + packedA[row*rowLen+col] = (doNotCopy & packedA[row*rowLen+col]) + + (doCopy & pivotRowData2[col]) + } + } + + // eliminate entries below pivot + for row := pivotRowLowerBound; row < nrows; row++ { + belowPivot := byte(0) + if row > pivotRow { + belowPivot = 1 + } + eltToElim := extract(packedA[row*rowLen:], pivotCol) + + vecMulAddPacked(rowLen, pivotRowData2[:], belowPivot*eltToElim, + packedA[row*rowLen:]) + } + + pivotRow += -int(^pivotIsZero) + } + + var temp [(O*K + 1 + 15)]byte + + // unnibbleslice the matrix A + for i := 0; i < nrows; i++ { + decode(packedAbyte[i*rowLen*8:], temp[:rowLen*16]) + for j := 0; j < ncols; j++ { + A[i*ncols+j] = temp[j] + } + } +} + +func computeA(m []uint64, _a []byte) { + // M is of K * O * (M / 16) + + // intermediate state of A, which is just accumulation of Mj*x^_ without reduction mod f + // M/8 * K*O + // uint64 + // some idx ko @ K*O idx = ko + 1 + // [ ... [m0 m1 ... m15] [m0 m1 ... m15] .... ] + // [ ... [m16 m17 ... m31] [m16 m17 ... m31] .... ] + // ... + // [ ... [m48 m49 ... m63] [m48 m49 ... m63] .... ] <--- for M=64, this is where reduction is not needed + // ... + // [ ... [m112 m113 ... m127] [m112 m113 ... m127] .... ] <--- here are for reductions later + // = sum of M_k @ ko + // + // later we will somehow transform it to the actual matrix form of A + // for this, we need to group 16 uint64 words together as a chunk, hence OKpadded + // ? why M/8, not something like ~ m+k*(K+1)/2 ? + + const OKpadded = (O*K + 15) / 16 * 16 + var a [(M / 8) * OKpadded]uint64 + + // Emulsify, without reduction, by accumulating M + bitsToShift, wordsToShift := 0, 0 + for i := 0; i < K; i++ { + for j := K - 1; j >= i; j-- { + // always maintain such that l = (bitsToShift + wordsToShift*64) / 4 + + mj := m[j*O*M/16:] + for c := 0; c < O; c++ { + for k := 0; k < M/16; k++ { // currently 4 + a[(O*i+c)+(k+wordsToShift)*OKpadded] ^= mj[k+c*M/16] << bitsToShift + if bitsToShift > 0 { + a[(O*i+c)+(k+wordsToShift+1)*OKpadded] ^= mj[k+c*M/16] >> (64 - bitsToShift) + } + } + } + + if i != j { + mi := m[i*O*M/16:] + for c := 0; c < O; c++ { + for k := 0; k < M/16; k++ { + a[(O*j)+c+(k+wordsToShift)*OKpadded] ^= mi[k+c*M/16] << bitsToShift + if bitsToShift > 0 { + a[(O*j)+c+(k+wordsToShift+1)*OKpadded] ^= mi[k+c*M/16] >> (64 - bitsToShift) + } + } + } + } + + bitsToShift += 4 + if bitsToShift == 64 { + bitsToShift = 0 + wordsToShift++ + } + } + } + + // transpose among groups of 16 uint64s in each row, so that above matrix becomes + // uint64 + // [ ... { [m0 m0 ... m0 ] [m1 m1 ... m1 ] .... [m15 m15 ... m15] } ] + // [ ... { [m16 m16 ... m16] [m17 m7 ... m17] .... [m31 m31 ... m31] } ] + // + // where {} indicates a group of 16 uint64s + + for c := 0; c < OKpadded*((M+(K+1)*K/2+15)/16); c += 16 { + transpose16x16Nibbles(a[c:]) + } + + // reduction mod f by folding rows >= M back around, using 4-bit multiplication table + var tab [len(Tail) * 4]byte + for i := 0; i < len(Tail); i++ { + tab[4*i] = mul(Tail[i], 1) + tab[4*i+1] = mul(Tail[i], 2) + tab[4*i+2] = mul(Tail[i], 4) + tab[4*i+3] = mul(Tail[i], 8) + } + + const lsb = 0x1111111111111111 + + for c := 0; c < OKpadded; c += 16 { + for r := M; r < M+(K+1)*K/2; r++ { + pos := (r/16)*OKpadded + c + (r % 16) + t0 := a[pos] & lsb + t1 := (a[pos] >> 1) & lsb + t2 := (a[pos] >> 2) & lsb + t3 := (a[pos] >> 3) & lsb + for t := 0; t < len(Tail); t++ { + a[((r+t-M)/16)*OKpadded+c+((r+t)%16)] ^= t0*uint64(tab[4*t+0]) ^ t1*uint64(tab[4*t+1]) ^ t2*uint64(tab[4*t+2]) ^ t3*uint64(tab[4*t+3]) + } + } + } + + // transform the temporary matrix into the desired form of A matrix + KO1 := K*O + 1 + for r := 0; r < M; r += 16 { + for c := 0; c < KO1-1; c += 16 { + for i := 0; i < 16; i++ { + src := viewUint64SliceAsBytes(a[r/16*OKpadded+c+i:]) + offset := KO1*(r+i) + c + decode(src, _a[offset:offset+min(16, KO1-1-c)]) + } + } + } +} + +func transpose16x16Nibbles(m []uint64) { + const evenNibbles = 0x0f0f0f0f0f0f0f0f + const evenBytes = 0x00ff00ff00ff00ff + const even2Bytes = 0x0000ffff0000ffff + const evenHalf = 0x00000000ffffffff + + for i := 0; i < 16; i += 2 { + t := ((m[i] >> 4) ^ m[i+1]) & evenNibbles + m[i] ^= t << 4 + m[i+1] ^= t + } + + for i := 0; i < 16; i += 4 { + t0 := ((m[i] >> 8) ^ m[i+2]) & evenBytes + t1 := ((m[i+1] >> 8) ^ m[i+3]) & evenBytes + m[i] ^= (t0 << 8) + m[i+1] ^= (t1 << 8) + m[i+2] ^= t0 + m[i+3] ^= t1 + } + + for i := 0; i < 4; i++ { + t0 := ((m[i] >> 16) ^ m[i+4]) & even2Bytes + t1 := ((m[i+8] >> 16) ^ m[i+12]) & even2Bytes + + m[i] ^= t0 << 16 + m[i+8] ^= t1 << 16 + m[i+4] ^= t0 + m[i+12] ^= t1 + } + + for i := 0; i < 8; i++ { + t := ((m[i] >> 32) ^ m[i+8]) & evenHalf + m[i] ^= t << 32 + m[i+8] ^= t + } +} + +func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { + if len(sig) != SignatureSize { + panic("sig must be of length SignatureSize") + } + + var digest [DigestSize]byte + + h := sha3.NewShake256() + _, _ = h.Write(msg[:]) + _, _ = h.Read(digest[:]) + + h.Reset() + _, _ = h.Write(digest[:]) + _, _ = h.Write(sig[SignatureSize-SaltSize : SignatureSize]) + + var tenc [M / 2]byte + _, _ = h.Read(tenc[:]) + + var t [M]byte + decode(tenc[:], t[:]) + + var s [K * N]byte + decode(sig[:(K*N+1)/2], s[:]) + + P1 := viewBytesAsUint64Slice(epk.p1[:]) + P2 := viewBytesAsUint64Slice(epk.p2[:]) + P3 := viewBytesAsUint64Slice(epk.p3[:]) + + // Note: the variable time approach is overall about 30% faster + // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] + // [ 0 P3 ] [S2] [ P3*S2] + var pst [M * N * K / 16]uint64 + // mulAddMMatXMatTrans(pst[:], P1, s[:], V, V, K, N, true) + // mulAddMMatXMatTrans(pst[:], P2, s[V:], V, O, K, N, false) + // mulAddMMatXMatTrans(pst[M*V*K/16:], P3, s[V:], O, O, K, N, true) + variableTime(pst[:], P1, P2, P3, s[:]) + + // compute S * PST + var sps [M * K * K / 16]uint64 + // mulAddMatXMMat(sps[:], s[:], pst[:], K, N, K) + variableTime2(sps[:], s[:], pst[:]) + + emulsifyInto(sps[:], t[:]) + + var zeros [M]byte + return subtle.ConstantTimeCompare(t[:], zeros[:]) == 1 +} + +// GF(16) multiplication mod x^4 + x + 1 +func mul(a, b uint8) uint8 { + // carryless multiply + p := (a & 1) * b + p ^= (a & 2) * b + p ^= (a & 4) * b + p ^= (a & 8) * b + + // reduce mod x^4 + x + 1 + top := p & 0xf0 + return (p ^ (top >> 4) ^ (top >> 3)) & 0x0f +} + +func mulx8(a byte, b uint64) uint64 { + // carryless multiply + p := uint64(a&1) * b + p ^= uint64(a&2) * b + p ^= uint64(a&4) * b + p ^= uint64(a&8) * b + + // reduce mod x^4 + x + 1 + top := p & 0xf0f0f0f0f0f0f0f0 + return (p ^ (top >> 4) ^ (top >> 3)) & 0x0f0f0f0f0f0f0f0f +} + +func inverse(a byte) byte { + // static unsigned char table[16] = {0, 1, 9, 14, 13, 11, 7, 6, 15, 2, 12, 5, + // 10, 4, 3, 8}; return table[a & 15]; + + a2 := mul(a, a) + a4 := mul(a2, a2) + a8 := mul(a4, a4) + a6 := mul(a2, a4) + a14 := mul(a8, a6) + + return a14 +} + +func emulsifyInto(sps []uint64, y []uint8) { + var acc [M / 16]uint64 + accBytes := viewUint64SliceAsBytes(acc[:]) + + for i := K - 1; i >= 0; i-- { + for j := i; j < K; j++ { + top := uint8(acc[M/16-1] >> 60) + + acc[M/16-1] <<= 4 + for k := M/16 - 2; k >= 0; k-- { + acc[k+1] ^= acc[k] >> 60 + acc[k] <<= 4 + } + + for k := 0; k < len(Tail); k++ { + if k%2 == 0 { + accBytes[k/2] ^= mul(top, Tail[k]) + } else { + accBytes[k/2] ^= mul(top, Tail[k]) << 4 + } + } + + for k := 0; k < M/16; k++ { + acc[k] ^= sps[(i*K+j)*(M/16)+k] + if i != j { + acc[k] ^= sps[(j*K+i)*(M/16)+k] + } + } + } + } + + // add to y + for i := 0; i < M; i += 2 { + y[i] ^= accBytes[i/2] & 0xF + y[i+1] ^= accBytes[i/2] >> 4 + } +} diff --git a/sign/mayo/mode3/internal/mayo_test.go b/sign/mayo/mode3/internal/mayo_test.go new file mode 100644 index 000000000..1695f70f3 --- /dev/null +++ b/sign/mayo/mode3/internal/mayo_test.go @@ -0,0 +1,224 @@ +// Code generated from mode1/internal/mayo_test.go by gen.go + +package internal + +import ( + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "fmt" + "testing" + + "github.com/cloudflare/circl/sign/mayo/internal/common/nist" +) + +func TestNewKey(t *testing.T) { + for _, tc := range []struct { + name string + seed string + expectedPk string + }{ + {"MAYO_1", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb14803", "5b61421edc1c90efaf6075560f0206173555c55eb845ef3a70ee5ef18618361883a0ade490e9e5bc51cd2b25fb4ec9147a2fa6735d8749ed7e149330a30b3ab62b7991c2494cb21d0bc011ceb357597b8afef939a1fe398421c88f43fb4fe380c10caa3a95e507ebae5a571fd958e0505d994566263be8db31ac833aa20ae7ec97d1a865bb292e89d583975009046053bf91ce7d73e9c23c4244071e17a1095f3918330559f323ce557d66b8771f0003cccaeea82314088d56455d72f258e4c35fc6ce2d195cdae99f359a8307ec15f27da4d0634531cd9a1a719121f7c9b99a0d0c24252316f1ea7c16d73f5ecf7125ad0c8d2e02574131504875af5ab6dea1c328e71122f0cdd09e826cc515b4ec160f31d63253dd8798b2d841e80a28479bcca320853a459d4d659df695eea4b5aeb8f45f6c5fce283c64dd6fe928d452be3723f8b448650ebb72acf231e3c27683afcdec22037f1af29c479db1a97448ce570fef082052c9ef45179ba62abd7dd156f17f4da51557383d40a9d544cdf5cac4f4cc1ce69326a3ccc611fb7a6d04126dc54c55c2286a796be18958846d53e1e3f55f3ac0f4e09d8f302878f4993e25c2164970b337d0364aa61eff5d85e41fe784fd57420a5481b9a15f02a47e3e49c0709a37352a50fd0feb7e7f9938173b3e3c570a79c2424f5dc5bf95363b6d5d764e1a38a1aa05a31bbb9858b51fe3cbb19eaade5d4f482260838f5273b042f6340236395f6c347d4bfb922752ad0b17f1bae8645a9a6b1d72e1e91b4828faa313dd85ec5795252dcd95aa33519a56d740cbab7b9ee13e1b8add763bd07a25455e44e60f664846e32eec329c46e0741b6649c34358905d9d01124c3c8ae14e3fb47cc6477ba8e63bd4a3937588251952f5232bb9999aef509414e11a36d110ea24bedc235c55ab6f80116b036845e291148135a32b0d44fe5a4e9ea9f3ae150e11c62ea91318129f79318d3506ba50db3dbe235427d405a5baa9e6192a17014dd19863087445eb4cc9b6164aab3d5af4e73bc3edd97c76dabba0bc26c43d70f7880a0f7c042465da77ae3f8a31fa65903d90797fc0b5deddf578d52940040276c9b415d880e3782e03a2965a6d3b265628c690bd1b096a355362eb751d528fbc6fa365d1cf3d39cf49d8296c28fe8eb52a31a07e877d1e57e91971cbbd14216db76a08da45258a1a801d0cb232a87280df4522329ecd9312e5489d90a39547735cbd11493d0d86389af40c1b9ec5a993d0cbc508da424392695e1cdf205a294122412fac5a2b4d6e0ed5eed7b4de18f0d4b81f746753fa8744ab845c18800befc926d3290531053bf2cecf23d1b6d7fa9fb8d4827e87abf574afe39a636dbb9fce48854c641981f236da01bd8a5901871891aac7fa1f02300101540e7b05665e5f19912ee898666b64bb3cef688577c7b5482e123c0e8fcc466302eca8fc02f35260569cc1d4b00662be924f188226dbc6086d4aaed1ff9c7604748a556d00f87823f9fafaccf752d645b76a5851723274737e4dd7d5d76fdb256e3f1ef733914eb36932eb1c4c666df436351fb817e7055fbfca2b3103fcec84233feaf3fefd600721d3ca1514ee26db6a608dd4333377a9e36dd2dbadb1651eb99d8e3b4fe6"}, + {"MAYO_3", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d", "1c0ee1111b08003f28e65e8b3bdeb0378c8800a9a87f38ca101b55cbe065f76cd8be17feef5b448fbdc40c759729dd609f2f3e0a70f719bd04bdec59158c1d885127c91066d31b4e53848db9566d8d25393dd3e665c8f4b5fd5d5b836fc2e012a059a5550646dfaf9db63eb4c928198a4dc3e8631fa743e11004a2fe3454a9d537697af4bf5d6bee82c2fe40d5c74c29be0cd7e56c8dff8dacebe18320a455a72265559a287385643668d2a20058d927c87a41b8d94ac1c68b6a07c7b055604c7491f11ac30974ab0d76ff2034b6c73d3b4cfb13e8f341f9f3f445bb6a4e47aa381282f83af4c46fd38207da843135dceac04af08096915318e464a8a1fda2a99efd41c61197a498abc875ce302f94eeb8ec32e56f24e2c6c419250e08e2f69148fe083747340af17e634a98afbdd6902211c767b750365f6200fd92e8431160f1e293058cf2cd57be92816a712d796e6fc0c672026c162d93eb79a19c7d4853df371234fec9c9ce76b6edfe082007ff69c64c9980f2c7b7b6009e3db0990cffad1f4119844967572e1122e5c7ce460ccf732c82d3f18457111ae8e1a5381c5b30e9d76028ca26839455d44aac728a24731a1d69c8c856b3d611dfe5336fd5a42f62ee607219c654aa117a874ca33fbb89ecdb6310cb4e0c4a5e2554be2d4bfe6abb062eef513d726c258dafa690324302d8fb108376f991e50e9037e31ed3ce8d8cd785fe9eb419d33c7ac381f91c463a8a06629db910f5fed1d18f2cf30afcfc968ca5617c6cc6fd9080f53cbd05b727c04bf75dd93f08a0ac1a7bc46e8b5a08e6d3c3b1e1232126d4e65b1a3877689dca9b1e53a73d45ad9eeb38faf942a62a804b088226f4ecf15526e24936f9c28c951f5c101c5c44bf91b72fecbb3a9d28cf77e63343ad760089e0739017cf829297204041acf5b4c10fbca19219db233fabb3a564c8cddb281f290b9a8536c9a1d39a35d8eb124182d9cdc51044067647ee861b8c0c6fbbe1aec13076b8f3d8982b52d1243a9448729361619ba34edce7bbc1b3a1545f791a5f2f6d04b5204e073bf66e7b009c7d4a1660355d066fd0d358623fcee946e69d20bf6fb291906e700004aa001494381382c5e1bcc4fb2b7cfc4bba6866548ecc797fec670abc2e8a8dea2c4219ed32102a0a1f4fa67775512768e57851571fd383a5a684ed1e10b62f3bbbb65d58e08151695008ef6a7cf4b7cdd265d9cf1c158f64c607aaf37e51140518724c39f3ad8a759a7ec0af43778c00832088270934cd08a582196953b283fa047f6365585b6078027be9d9c915569b78c65702561ef5de7953fb6f5e3df3ae8b85d3fb2161cc3af62ed83d027b733507fee131da9d228c79277630c3efcdc9ee1e559bcaee4c818d6d29ea6c8cf9eb46ad58a4ab6a4fb2f6d6f4380efa4d60843ca4eac32f8772073f1cb70021547050c30095fc7c884e31a2f91994985eab00e7565da2ece39e47a6c8cfae012687dea9d65b8745d4d760e656a55243fa9ba8fbbd31b7f2bb2629a0ec9265aa883c8775ba173e5ff08a9a3ae4e76f9925418f4e2dce1d00e4f9377245b8939dc58b7d15c172baf984781afd318b264709bde5f0859a95923ecc42108f2fa7c600e9ff69f3f65751d8a457dde89a1ad900314f90e9b712580eff5e4a73efa3633c5d5e34fa8f2528075fa92dce66506f5e947f1fdef820296cc294ecdc059390b644a1407b13d24541d275a49d9bd1a6f68dab296f52965c7f72d50cdc0ec19456001516c10030086d2efa109f45fb6d1f7550de534d0d4ce856e72763463e6d0d11cd237037fea14abb32465f632ff785f1917322e4574bbfa8f74682da17f2bf7bffa0ff030297d5c999d6a7c387ef2a5e168225a60ebb01c6dfc72ba1fdec8f74651f3e64750f2dc98d72099fc85f43fb28ee0c4efe8b7d76bb432453a848310f6e96d833a27ccc98046a210f568c075dc71545ff0d37ae5138406d569e964bf8e6903de7651b33c05e22747074b730cb6f3e94b891e8105ad6921cf5efd40da88ec3d0765756b7bece012f59f1f68ab80082fda5fa37b026d0b6cb5fab9a0ecab6980a179815bc9e56cbdc0d0682c29c5f9fc8cbfbf5a6a9679de92adb5b4778ecdc70f819549087bef738037de613cbec897e016672ce9eaf00382ce4b60c563cf3d23cfa178403f270215cef465ae9b60f007c75c1fcb85ab4ae9ead303057dbeedcaf55f13ea586f55a2eee9da8dd92bc210d1e2d4fb7d219c54517fe5a4ddf5c8c6a776703ec8407d6b393fb331d7aa62423cbbf7a062bc210e9c6565cacfef1e4a5d448f2cbf5c8861857dc870bc37c2070ce1f1147dfdc285d9df8296d6620e1430884e29571ea95ce1deb9c2ae7198f10732b2b2d5ddabfabda6e77acfa2b10c75842e07de69cb2b34abe41018c9a765e45ac85b63a85ca612b1e9682d7c8f512297bdfb780e21506956fcb528bb1916fc1a8356667033e99e16c223139efef6790b8745eafe30df12d900301643f8bd6717d0b5d1de5e75f25bdc950ce507ce7b3489e72d5abdf5a28105a8063d0a9662672489de16a1628548382277fcfb24bc878f071b13d6e120af00c1cdcacf5f1ef4823a2b5da0e7c8487550d80ac9f6891e50be3dca0096042c97cdea3553db702bbcfcf64f1b64719bc89ba51a09057fd9285c4aa7d95532cdc04ee185beb1318132c836278e368ff8e2653e4aafa829a2d40d91bda7b4af3f16442931b7da2b500f7e3abf80219b8a245fc4748d20d549cd12c5af7ca3a8543bd14d0ddfb4fff8a43c661d0a8163fe80bc0f77b762d010b64b831e7c29f9982e5b7338a6411a37c4f1f46419b610415104f154a009cf3bf1805ff279145477a0d5ff0f3c9584874e8e8376101dec2c8c69592a8c46add07a6c0100cfab2bba03771749b3028d287805a6859a1e3a82f30231b01a7e3453059638e0d7a2430348ff4af26d00481cb3de085d269015fb6043a0346281a05448a81d04ad196d916503c35d31e513bb133d49f9646c191f872cf4d6798c18c87e52695d1adf59c97ba8bff5e826ea17ab2fcb8b559e871e6d62d8a93522554bb5d48de674596761595bf28d0114142a542c2f9cab2be451eb37546ce3eb60bb0cc6050e5d38ac96752a8d2575f67ce28452b00b486e2efc8aa80e94a0a442fd35f5fdeb48e52a82c43ef2c0a02d3bf71da67f0131af8aa90e159056ebf4e5dbbe176c9cd8c07d234135c50b524a4efd7b72ca4f413b29809e7a1a53442a1919bcce952203d17e3c5600dcc3160dcbf7f18b9352289ce78fd337c36416b8342635c3d542bd1a357b8e39b04098fe7b60248fd6ea4a2778ec576e4c34323c81e56386d40c79260e3d914db7cabbf269441dcfa216ef7aa85fae99a89f2c1f56838c30fc180135efb98bcd01e2a88d144ecf7063613d3ba62af5fa8cf7d63465273ac0abb3f94e87130310f4b841db13ad7fab1444a02af5649ef9a29392a4fc15b747799cd8a8d735094cb03a606735ac94ee244a8c5a3504f31ae62d3acf7f1586390efc6e8da967f3b174d9e805554603c4fd5cd875e70352d2c6576e3baf0420d6f3aa161f605219bffb93abd51628014072e7c2e533aa86117f02ccd67b623af4ca911eebe981595fd393e75d5b0d0da9440e3aaf9249110f5e8c22a68f9cfe0b30e8665270763390fb8618668a1a56b71065a85b91ff8dbb8e4ab9c3e6c079a2b0276ba475d388262f36a76c3e230bed8c47faae910"}, + {"MAYO_5", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2db505d7cfad1b4974", "708141534c09f5f604225ef8c3472f3749ffbce6c071fa7bb1f4b50b78ecd32e7f35efb6bfcead3313156f9995fca2d2db6d0f0ec50a66bd3d5b469698ab131f21cbae3785a0c838e2e3316fa73e9670adb76fa9e1ec8a0bb7ddae82c0ca7c1aa5e61c31b84180c3aae027223430e988e28c48ce0d5b2ad2750203707f2e284cf1807f008ae2570092a662b73a9a728f81a79338f3452d6af920ee59037f2c8a20b495420c5bb3fa7518a34ae1e64ffa742db0c3c009504ca98882e4abadaf8396cc242d9a7c1d4d71b17d6c65ed26c4e459d8bef86326ecc156209299424cc015c52f97862998f871ebacba29877e040b7adfecc69081f55162d190f374b704662c138c39782e7a8b74b7867e92c5bd8a7833b4242f04712ef4384ef452fbc3078550f15e563c9238d72088275d02d94cf8c163a2bd73810938a1208883874e1b7d8a9ced0ce6a7177da260ec1a35d2e0f45bbdf18f27cd3f0619d134aa47e4df364fbea1dfb852c225b06be4707824d9d2f8b25d6e15f81079511778cca8b82554591823616a7b7ea029c3c3670dba35bf2f11b6493ba5044c159c368f7e4d3ddb215247ac0fa17f227298b9b1d6af4632fec18ab98c9218f583c75e9b8434cb0b68105e44be85da80a65fd67839b4981e2e89feed17f41b60e75122b5328fc0a5772b8a0ec5e4646e1fc1f98a44a31d516be5cf6c187850d7343078304f8a2a02929dc2aaf268ee590507b325a0d744e413d58dd61e62decd0d4e846f08f9fae80cdb7e33d693c57e46cdbc7e1ebd06fc44133748064db8b77f1f87df1f21891af721e840d768be637ebd41cf8be5de36739045b9bae72cd9192a1ff0addbddaf6a0b6cb0d0923ce5b3d5c261c20b1bac348eed6d1cebcdda642103f3a4246e9ee3a1f1950be11796a2db39c54299a6973bdbc4a9446ff27a85fbce46e4c94c25d6fcf1d8bfc773df36591862c0447a400312969c056399b2ddc92703a4d14f1b5862752257103a300381f41f47d7baaaaef9bccbc1a3ce8c94edd1d7c43ba63c961918c1c9cffdd03659158d0827384f796466449a0bc72e94badcbf567e9f6812e320b772b7d330ca8d58996fb9cb9a4ee7772ef4e77f67e2875070249026c1287ca6b43af591a647c33efc965921c9225cb195898f0c0185d3142cd1d924119b003b0b51ccb42caced8baa8060a1750b53f7c039f857dbb248a2520bac9117da7a12ba07483bb751afb7fa1f3d9e9eecea70a9fd233adbad40fd3ff7ca1267a4a8d2375ecfe35d7984ce860725f68f19a19c0e00af83f13d452491720370effe877ec6ad51e702c9ae828b0ad3a845519d3267f3908c808498ea73bc96b70cf4d9cbaf1b1bcc9b12a093936cdf56cbb703799307d80b3755ad72dcc0b667cc14f6dd47d99d936ae3afd5be34264ddc8eaa7026e6391780444ac43c01cddba3c50548d7a2c03a59b1db7d798043351b51d4fc32f6d5f8ceeb8654977e7ba147936a6d172cfb023308730adaa3973fb4d883b95cdc5aea24a83e70561aaec784023e28dc385aae7076d271ca0551b83a60d7388b9a2c804ed66ff4fa6284fd57045d03b6c31d0d32165222efc6876b14a01c18024b0d6fec28361de82a337fb087ba25daed484b3e17fae66ce2bf2bc88614ed5a2e1db9f485ccbf7e029009c164ae3bb9b7f3b7de396970cda0943036e18d2cf96b9979e8d874980a844d7628b5fae66f00ecc4366a96c814785516d056e44b906ef6dc09e1a976384ccaf1b3f40d79a5f6ad426bb70035e84961c188ad24a8122088fb2a4b5abcb5fdd0a26cd7d44dd341c5e5ad65478269cbb5e30f4c23d63043de9c65fdd57fbae9f7a68c7bcf4e4e6b0eefdeaa3bf376c912b15f00e1a6c1b4c80269cdf9bdf25373fd55a7f51ae9aa0c0de6faf001a2016eef56d82eb696472546ec8bd29f1e76f0666b4c8d4c4cf6e633b30f323110d7b0fef38a2ec90f9359278a23f276da7984e87880105d12051937b037d86688b0aab4a11ceccd776263b8a82aa6d5a5490d33e734c651aafea394fb24d85ef3a0620686e1d96d198509e5f04f406986ff201e0b2d3d7584216e8568c68c2d8ebe1947209dff588969c3c088877b8c1dc0b0fadb7295fc44e3bef5d81789329675ac5c3142fec702f749c6e3aed13071c02680f7cb82e91432f6d6fd7c10ca132e61f2d47d6e2008ac2d5157445c1a3cfa52e03c5ad575fe836cdf4d8c9624e097d969eec08dcb4540be5b4a9f3ca03201457a2dae4a966d0b35abc65559484fe0afecb774e9437eba24d22e4da5927d00ca3a9cd49a1f3843b1f3f7b2872f0b1790788bb216d53b9b61f3f394d140a200ed30c3229761af6d3ecff8fc463ef63ae13f92d3d555261f04a287dac6653b7d493cc9026f5be29c8c550bfc513cfdeaf83ec0280fe2786ca9c07b222f6ccd9c31a8848a4e79be4b590fe4e239ad9239df96539b48cdad99fd0249d50204649e26838a076adcd6800221fff817681a6a1ccf807c1ae1794aa7cbe383cef23610436d51cdeb631df8bc1f7f7207b9972c60dfba18cd5378eab1501c6f8d4e0971862cff6b1cf3a327e1afa6371de15fefbb1c80c658d2b372f0bed979929e9fe3c3769ad0766edb634949c29f9bffd879c961f626d10617c161319957c28519ef29142236ccdbe214f7e3e9ab77edd5fa814f5b599513a46f79cac9fd9ab62d7859ed57a5e6a0af7f5e74ea4f9fbef3c9943f2c69cb66ef4497c92b25059980bd55568e8a5c50c0853fe02f9eafe068ebe710cf284976fe4b3c12d5990951775b891cebaf38ba4c9dbf70423210f723a6c0961f00766f0acf1ae75d3f1298b378cdd7fed999a8b45c923c3c8a9a48f007b88f6fc9e0d37458e441247074e9c89e7429bc76a7bef55e702420c1bc65d66bb28d46ad7e0c5be42de7b89a44dbe7ee416c795c0b7e91ca9cf2f2762816ef4bbed139137a8ad3fa7a5a96c917e8af660e736ebe9d70e4b90fa18b560ad173617f8dff65804a2bae40a4d27e86e97e02e485cb529fad4a5c7f72a2b758a693826bb964eeb28842f5aee51db712d68354d7f1ec129b7785091b1cbc3fb389263e064eb03df94183e0c1bd1824b8f5c232d7970c11e53caaae90ebba5ea862f5aa26aba25b5f627823182444efa64f21125de36afdd615518bb5dfdd117b5485910319b458236dad01de33b8605352517ce3aeee706de917ca76ecdc4ef53a733e5f898c2dd6c4d641d141ebcdca2c5c7cc176eac1b493df81702083bfc124ad9ed3511452a71f831c9a2f8d2d771cd3c1b1c440bdc46e87fb5ddc0d45c3e28c10aa1af313c08bb554590dcd296e0916a9b9df3d527a5db9ec1694083bb99c480231c87444998dc7f922f7159b019460d39be64882528f4df1e3e332f7386cfa34310b666d369ed7fff15ce6db939e448beeb74e221862a901c37275cec3e3718b146d0376c0b44e64d393c231050bf166658c333509aadca601619ba6e473705b5be1c9c290a390ee24062265298ad5bb30cc53571b4e8046856a10da99823e0a33c85fbfe785ba709a3cb9b97dd6d56db92febf8a833941655294134be7d37de581caf4e8fe278d9ad5e31d046c7c4c6e3f162c8b5bdb27f1bb5c772b8e7838f67f97cc3c090510fcb5e2f96191a77eeb7febfdcbff81a58860bd4fa87cb41b1fd51ac670828fd6efdcace5ceb85339db706286e860bacb1667da894ab79d32a67563f9a1935953679b687b558324e801c62b9eff69888a51cb8c79d6eb4aa9651108b53d1b66afb1fcbed7dce98403c64885bef7e45a0689ce783cf7741c2c787c1643e08a109b7f5813c11a16bc9269faf0c74e6e0d08428d3de2cd00e18615e157488b2a4095ade2e101dc3fd48da57bbd1c79e9c5cf0992014f80317fa35ee71560c27500761e8a60449546ffe1a232806a8ec6a5694941485f3e0af3f4cb557a42219435bf38fa10e11f3a243c9c584cde736a02bcc37b8f624931710c159f167cdb41deec3db28535731dcefb4926c8580d290ae818eb3a09738ca5d88ed1909ca95f1c71d9305f89b773cb840e3f481aa2f51dd65082da3afca358bf88d2781b46d82d7685f4ccd0983c75f9f1d6382493692604da6c8e8effb27129b1776509b98efc15c75ea1f11c04d7374742ec8cf28341a1be36deba666dabf6bcade7bc3670c03bf3bd42441edfe79716a7ccd32dc3008693a42800944bea936484bed7b6174718d31ada938690ad1efb0442fcdc7a8967f96fc4fa96821ff9862c3b1495ca55afe8ed5aa8097e20fe54f3ed2f2be1cd241a494bccfd3c75d7acd180ab6eb192402947c273abf6fac2e957c8fcc7757d79361071b2067349823cc9db3d531aaf0561acbb918df2924276ea64eb2bb095680d0be8da8d38d53f97d6cc68d88bc4f22c6865cffbd59a6fa015d2f0a12a985fd97a34e4d7f9504cc5a86acd4c791f105b9cb944ae4d0b0f747a1dd089a7903dda5528752132d2387e4854234a8a3ce95fd8afd4bfe8e55f4c026e80348ece14da917b3a696ad797e70d06356cfd2463f5ccb96549e1a037f938624754c57401cddcc948cbd088a0f276329a87db91308ece6360c743e4f8f360fed28b77f7a60745aea0b89721f4cb342b75ee1e51625292db27a97c2eb18186278fd826fb49cfe87fc9b45bd26a02266da90a7ca7d6c5d9eaaa036a0c7a12171832923facc0aae7554eb365e312e6b7b3e8be167b8e580fd5cdd609b5308864600b98f9b69d71eef1d4765f4ec07a584d9bae8a407777d7334bbb285ddb15a07dd5e3a4e8c8072710a4ac204162987f2b669524d88afb77e5f4a540f9985d560dae55945abcdeb3f90a498f76cf44d0a9e3977cdadc11a8613d50e4fb19577a6f6a16dd1b4003e56268a1134d774b9cc4e94ced0943a0199ea9ec944e891f02deb85c1b870e535f97599f843e8467c771784796c3c8712f3681b475a4eab150d18928aef90f293bdc505784441b6ffec7e525b15b5d1e2203f8ec95db5eddc8c6eb21e82118c75d6dcf2c58eb4487eed220b1eb6f3a3a7e92b0ce13ad5e87c4e4edd2c8bcab83d7622d20f3f5cd74a930515808e500563848ab0893cef9323ebdeeac9716270f3550c1d92ffb4d650c8442300cb7a4c61de2b5876f6563271ab7abdacc962be387556a6452dfdedf99148b4a2fe635dc730cfe9cf07a1ad5e148e2eb639e25838a8b5f6271a1ac8330847da496f042237e9f38ace9c37f096da0d76157825c2c8f578919faa984f4be65f7055e517b8f78589e1ff361d769e5d387795b79bf476959815d9b6ff22361f76e60cab6986e0d164a0984a013101e3cadd3eee5199030b45e8c6b9ea63f80c7e6e2909b39d96d6f62776820281eac9fb55fb5c124b7dd9e9b114d476d2fbc676df79f9e9b214ea69013681e7cd8d379bba254382ae4678986f67eba92cd978ac6353e6207101a3e1d18008c3d5924eef4991e0b576fd9f582980faf24b898796c18388f40ae96403d45b31785958d37ca09b76a5a7320776ca4d328e95ea7b6703f4ab0947070f5c20dfd154a9476099d193dccd487f589759754c43fb81c1941439b4d63eeb6b24c294c70ac0a71ff437a6abb795aed2fa4e2483f2a09ac6d1a85b91155cb368219d17883106568c27c924070ad23ed0193dacc353dbb95a4c1de118c1a710f96db6eb9c146c2d0e07873d495d1ad9e3717e09c067422fe5a2da8d9406e1395aa39cf0c82f9c290d1e31a4a4d9b4c381c95079327c296e5db5f8e3c92ef62c03f1c2ac7c4b0174528b5dc0ff37c8c7d0533e4248fb86ca1cb5df91e05e1cc582482e7f12136e8628a48085ff27d15815506c5013f6f65e3c89286616ffd5d08f3fc2194ae51fc4a706e2cd83b6629ea8cce9ee3f968db84c85e3dc0c4b3e4cd6f105a196260f5873c8590401719ce3afd8de96d465b5258ce6b6e53db24fdc8fea3a9062fd2f10ddfb0adcd355724e366afb6b401118d73873c8e682dacbb4be47452eeb98ce51607903126f83ab6b27723818f816f9ede8ef344b3e7bdafdbe8292c6af60835f572ee68629386575541f1b5d5bae84bd85cb65e1ec46a70abf6365f75f975c3b87944e45d0da01f267b569d9ce55cdc90fb3ba1b5f7337f87a914eb5f6f7dd66a56b052430b148e3e295eb82fed70c6a38f738ac63b820db7183d18156abaf638ae366ce6491f0f0e326e1c9c3c8e7df4d62632f7599805f4a8ef06ddee3191e35f20cb7d03e7afbae7cc71a914aa9c1744b420ab5f406771e944ee7e3aec062bec33696df2c5ac2e09648902ea629aac071346ef87f5e54aaf650785f5ade96e0919ec00b99926594cdbdd1e0482e56fc0b78cb6142d2a4be8aed181918b11a841fbb8b442a3e2af1ef1c29528d6447c72b8c2e514aabd0595ebdc698b90ef61a6ee4b400f01fa5c71a2dead6822f33c3341d8c019bd100aa9028b57e517ac6880112335390a8349b90235486eddee01685a7cf1ea158cf9cc1f348a6b113a9d82f2dae95c7ae56f9183d97cbc35a25baea3552b8c2a7de779f6b74db1cfc75d07d79aae1857b601e73cc3686c175f135233989e6d56bdbe611f96a2b51804467e47b474479ccfabddbb4629a0fdc7f73e172da3c168c6c40461371e05a9a332ee3aeb0c9a5985425e102e421f497ec978de92bf73c98fd7d1bbd068e9d8ae82cbc7ef3923bcfe97bd68fe9563195d26a1afdc3bd91b32044fbe68b4bccf29a8d52d13c625d89f73611e371dfe6b73b50d6388cfd87ba92074533cfb34a4d543126f096002d9ce5ecf89cc11b0449f696e1661a36884ac1bf58f397d993c82bcc03c1db156ffa5148da7a73b3be2b6a3b2436c26c24ac0154bee5837d079dfa65d04a208de8b79691518f9056ccdb382e61c8d7606be73322641c71719a23ad6a571f907aa0bd1d8f3170296f3cc88bacd9b3fe944548808e80d00c07bfddf271ace893f56e01f95329aa944d32d9623180142b32e60ce0b908cf2748a29c42a3a59b378e47ebbc4ada9b43133f51bb4d28b821bec1788cc94ac1c8e9e4499aae9e17fcabff0a824637f125e9b358e0"}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.name != Mode { + t.Skip() + } + + var seed [KeySeedSize]byte + _, _ = hex.Decode(seed[:], []byte(tc.seed)) + + pk, sk := NewKeyFromSeed(seed) + + pk2 := [PublicKeySize]byte(*pk) + sk2 := [PrivateKeySize]byte(*sk) + + if hex.EncodeToString(pk2[:]) != tc.expectedPk { + t.Fatal() + } + + if hex.EncodeToString(sk2[:]) != tc.seed { + t.Fatal() + } + }) + } +} + +func TestVerify(t *testing.T) { + for _, tc := range []struct { + name string + seed string + message string + signature string + }{ + {"MAYO_1", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb14803", "d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8", "165e6139a87a4a4966326ba83e6d1ef68d1d7bec62c9d1d267ba09df6815783980f0a6ca0923aee6e5b9495b7ed6fe6c49cc41f794a80d297b19d3f13b0bfe3e8865378f5d24a210dae0982a1036ec9cbe87c29ce9a05a8290d0d1e8603abd163a27b119878663c06745cee03f417300402bf198ee4ac2be99dfe19700f169efe6a365ae0fe9c1bc22028c864ec55072da4f98f878759de68399a2c8d2a2656996927265ce2908e05913f8057707d39dfd3ef58a79cad49f0bd010ca9e8ebde500057bffd0ff28015a60b3061cee55dfa1d59e1a72a8ce639777e6190542733639fbba575aae945583bf804ea129e1a80e12fb628accdcc3f43da37c7194bb641bb8b6404aee481b4ffb13be31941d30c1f9d818182545738ce2644dc9c9b0a1289c6294d3225cc23ee03cc8258176e1a958e90dfed58ad3a859d1b06dee53af6a"}, + {"MAYO_3", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d", "d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8", "01de18cb2cfce7ba2d15677a57e7d86b20a96c7b93daf27232ddc7a0249796ef9321ace5792a4af1ca49908780398a25f9e4b1293ad8209301c769f123cfa430e5bbff1812dd4a8efd5621c109444cb4a1bcf4f1d85abc6fbe493ae0370aba2b2251de011129be10c25f452c987ddb1c499faf1dc8da2e60e7913ab7ade65844a92ed3a5c04c2245c67d342e5067be4a8c024753a8fd5962481b9c52051723f50165ec2b0ec8f73b4b337361c12014607f9fe41c3591eae8653d18b95616b18a84a1a5951ab5082ceebd5d2cfd202fe3ae29b60a790d04295edfbce886eb98d63cce694c9517f5f6832b7cef38efc9c8f4c14e01429f5487cb752f675f4e4212058db63fc29b9c00701810144ac48e7b007f4dfaa80a2ec9899e60f704ac0d0b58c8a716433c91a27d966d2b7b2685da350c9f6c59233988cbb3f8f2383f5eb10e496e64017a0789ca8f702254e1b839382d42b8399488bd74517e280f1719392621596730862678dec519c50cd6bdc07f118217ff68004401fbccf8606c1af2ae6ce0dde06c1bd06a460bace8dc9408f1d5cf76dd063f63db9a0969098ec45fcde2e878d464f4de4bfe1fe710f2132d8e486f64e7d309d9faed999ce368e821a05d79f781fb895a767a09256541cd54242b5df5e262220fe232c637934380b137df86ab4fa07b11f07f914835c87d1547fe526001a61b8ce00721d90d8a4b54b60000ef75d9bdb94fc7f861ee46908ade105ea098cef938f49dae729817633003c87a0a6c0cbfd10185f7657804a2330a7def349da1df84a1a61dc857d2458425"}, + {"MAYO_5", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2db505d7cfad1b4974", "d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8", "7bc32600e7fe4e0d8caf187997624ba57867ec136d7112aceb161cf813ad98aa9709f0e42bc6e86c4c1a32e5b929df9976a4c0d77886d9e5005fad28c3b0e20dd7bb8d49aaaa77d969c72c9d37d78c3ff556ae777f4bac4d505194b6026fb3e17ad631980dfc373c7510c024817c1900d3428984e4e952b49b4c46c928717ea34f417b5f5d6067caf5edc15c1fcbf74d9f7a158958daf27acf3bcb611195278550d06bd83441083e2a64f0e70a8b668453b7f638a64c6d2424cf2ae31fb6efedf951215d14648d2e3282526c0504e4a0b56ac0336a28b98297d16ca2087c3c46ab10275ea0f5611bbde1b44beb420977aa8ec3693ac097901a8e3aef2d377e19ab6d14a42a5c5d7a13ec1df5be83657b7ee84e7671828aff72265bc7fa90829f92df63a159e51e222da67a5201db70597814ac4d793caeda47d7fdd2c59402a2e8a67beb6f9f43b31feaec24854f429e2aec6fe9e7412584f08719c0c5c90e6bc5371d65eebefcc956ada0659b9b0b5f4d4733873fb4daa0e837fda9062d9a79766ef66686f9b355a219d74728ccad98b04391427a30d8b95391b9e54b9f902853d0086fb1bfd136df4dc1ef6eed4c75f449612c76a506ec6c2d7e9676c776f615300230aae0353afc02d80ff377d79a923d2348eec166e0d1b40aa2428a2bf2f87dd89dffd6f1969b39cd84b3d2813078cd4a7753379a79ab6b33f9e8b54a73dde0c46f4fa9afb8a9d0445ae59d202a3df70143baf1de61de9d55681467e343a025b1e0f67af5b1e3e22568d449af192b3e241b6b635e668e851b39e9909ed77ec007a8fba5aec76551e6bb08f454ccf4d48867456c50fdeeaa80f4557d375bda789dc0a5622ccd20d0e2f32e4c7876464f8c8b5393f45eba04085f356927955fe89c475aba7862590656717b5c77f1f79d2a430a2dfb6c779f1794f5f6d374e6e566706f9bfcf33e2070b10af8bc9b853d38215bcb4ad02123445d91b485952514c63a21fac93146a15962fad262c2cdc8ce18a592e4420f60d18ae4ad558b5229b372ed06f021827e932bdb7c43ed6db84ab9c2e408251619bd8b3ce28b9c55c3a21ae2f300758c80086f0ed4a316403195512c2880239f50afa996ce24374ceb25637183b96cf246d204a04018f4344c81aad9cfa98c2ee4f5f6ce3a6f352a86e9a1b07b"}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.name != Mode { + t.Skip() + } + + var seed [KeySeedSize]byte + _, _ = hex.Decode(seed[:], []byte(tc.seed)) + + m, _ := hex.DecodeString(tc.message) + s, _ := hex.DecodeString(tc.signature) + + pk, _ := NewKeyFromSeed(seed) + epk := pk.Expand() + + if !Verify(m, s, epk) { + t.Fatal("should verify") + } + + epk.p1[0] ^= 1 + if Verify(m, s, epk) { + t.Fatal("should not verify") + } + }) + } +} + +func TestPQCgenKATSign(t *testing.T) { + for _, tc := range []struct { + name string + want string + }{ + {"MAYO_1", "d0da809f52866507b6354588cd44b712bac138a8363fde768adb92285b6e9865"}, + {"MAYO_2", "e7382b9b0fd985023a53f292b5d5caf444541a5bd531cf6e1e4d35b8bd864123"}, + {"MAYO_3", "0a53ad1ea675f6be7364d37b552cfa7ca254ac315724fb2871c2fe0567f509b9"}, + {"MAYO_5", "26a4bf3204c41fcb9911f761066668da34554efdd0684346b348ccd669f16b56"}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.name != Mode { + t.Skip() + } + + var seed [48]byte + var eseed [KeySeedSize]byte + for i := 0; i < 48; i++ { + seed[i] = byte(i) + } + f := sha256.New() + g := nist.NewDRBG(&seed) + fmt.Fprintf(f, "# %s\n\n", tc.name) + for i := 0; i < 100; i++ { + mlen := 33 * (i + 1) + _, _ = g.Read(seed[:]) + msg := make([]byte, mlen) + _, _ = g.Read(msg[:]) + + fmt.Fprintf(f, "count = %d\n", i) + fmt.Fprintf(f, "seed = %X\n", seed) + fmt.Fprintf(f, "mlen = %d\n", mlen) + fmt.Fprintf(f, "msg = %X\n", msg) + + g2 := nist.NewDRBG(&seed) + _, _ = g2.Read(eseed[:]) + pk, sk := NewKeyFromSeed(eseed) + + pk2 := [PublicKeySize]byte(*pk) + sk2 := [PrivateKeySize]byte(*sk) + + fmt.Fprintf(f, "pk = %X\n", pk2) + fmt.Fprintf(f, "sk = %X\n", sk2) + fmt.Fprintf(f, "smlen = %d\n", mlen+SignatureSize) + + sig, err := Sign(msg[:], sk.Expand(), &g2) + if err != nil { + t.Fatal() + } + + fmt.Fprintf(f, "sm = %X%X\n\n", sig, msg) + + if !Verify(msg[:], sig, pk.Expand()) { + t.Fatal() + } + } + + if fmt.Sprintf("%x", f.Sum(nil)) != tc.want { + t.Fatal() + } + }) + } +} + +func BenchmarkKeyGen(b *testing.B) { + var seed [KeySeedSize]byte + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + _, _ = NewKeyFromSeed(seed) + } +} + +func BenchmarkMatMul(b *testing.B) { + var acc [V * O * P]uint64 + var m1 [V * (V + 1) / 2 * P]uint64 + var m2 [V * O]byte + + for i := 0; i < b.N; i++ { + m1[i%6844] = uint64(i) + binary.LittleEndian.PutUint64(m2[:], uint64(i)) + + mulAddMUpperTriangularMatXMat(acc[:], m1[:], m2[:], V, O) + } +} + +func BenchmarkVerify(b *testing.B) { + var seed [KeySeedSize]byte + var msg [8]byte + pk, sk := NewKeyFromSeed(seed) + sig, _ := Sign(msg[:], sk.Expand(), nil) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Verify(msg[:], sig[:], pk.Expand()) + } +} + +func BenchmarkVerifyExpandedKey(b *testing.B) { + var seed [KeySeedSize]byte + var msg [8]byte + pk, sk := NewKeyFromSeed(seed) + sig, _ := Sign(msg[:], sk.Expand(), nil) + epk := pk.Expand() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Verify(msg[:], sig[:], epk) + } +} + +type zeroReader struct{} + +func (zeroReader) Read(buf []byte) (int, error) { + for i := range buf { + buf[i] = 0 + } + return len(buf), nil +} + +func BenchmarkSign(b *testing.B) { + var seed [KeySeedSize]byte + var msg [8]byte + _, sk := NewKeyFromSeed(seed) + b.ResetTimer() + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(msg[:], uint64(i)) + _, _ = Sign(msg[:], sk.Expand(), zeroReader{}) + } +} + +func BenchmarkSignExpandedKey(b *testing.B) { + var seed [KeySeedSize]byte + var msg [8]byte + _, sk := NewKeyFromSeed(seed) + esk := sk.Expand() + b.ResetTimer() + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(msg[:], uint64(i)) + _, _ = Sign(msg[:], esk, zeroReader{}) + } +} diff --git a/sign/mayo/mode3/internal/params.go b/sign/mayo/mode3/internal/params.go new file mode 100644 index 000000000..314050d49 --- /dev/null +++ b/sign/mayo/mode3/internal/params.go @@ -0,0 +1,42 @@ +// Code generated from params.templ.go. DO NOT EDIT. + +package internal + +const ( + Mode = "MAYO_3" + + N = 99 + M = 96 + O = 10 + K = 11 + + KeySeedSize = 32 + DigestSize = 48 +) + +var Tail = [5]uint8{2, 2, 0, 2, 0} + +const ( + V = N - O + + // The division by 2 converts the number of nibbles to bytes (when packed together). + // We don't explicitly round up beause given parameters ensure this will not happen. + OSize = V * O / 2 // O is a V*O matrix of GF(16) + P1Size = (V * (V + 1) / 2) * M / 2 // P1 consists of M V*V triangular matrices + P2Size = V * O * M / 2 + P3Size = (O * (O + 1) / 2) * M / 2 // P3 consists of M O*O triangular matrices + + VSize = (V + 1) / 2 // +1 to round up + + SignatureSize = (K*N+1)/2 + SaltSize + + PublicKeySeedSize = 16 + + PrivateKeySize = KeySeedSize + PublicKeySize = PublicKeySeedSize + P3Size + + SaltSize = KeySeedSize + + // P denotes the number of uint64 words required to fit M GF16 elements + P = M / 16 +) diff --git a/sign/mayo/mode3/mayo.go b/sign/mayo/mode3/mayo.go new file mode 100644 index 000000000..3ca521fe4 --- /dev/null +++ b/sign/mayo/mode3/mayo.go @@ -0,0 +1,151 @@ +// Code generated from modePkg.templ.go. DO NOT EDIT. + +// mode3 implements the MAYO signature scheme MAYO_3 +// as submitted to round1 of the NIST PQC competition of Additional Signature Scehemes and described in +// +// https://csrc.nist.gov/csrc/media/Projects/pqc-dig-sig/documents/round-1/spec-files/mayo-spec-web.pdf +// +// This implemented the nibble-sliced version as proposed in +// +// https://eprint.iacr.org/2023/1683 +package mode3 + +import ( + "crypto" + "errors" + "io" + + "github.com/cloudflare/circl/sign/mayo/mode3/internal" +) + +const ( + // Size of seed for NewKeyFromSeed + SeedSize = internal.KeySeedSize + + // Size of a packed PublicKey + PublicKeySize = internal.PublicKeySize + + // Size of a packed PrivateKey + PrivateKeySize = internal.PrivateKeySize + + // Size of a signature + SignatureSize = internal.SignatureSize +) + +// PublicKey is the type of Mayo1 public key +type PublicKey internal.PublicKey + +// PrivateKey is the type of Mayo1 private key +type PrivateKey internal.PrivateKey + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { + pk, sk, err := internal.GenerateKey(rand) + return (*PublicKey)(pk), (*PrivateKey)(sk), err +} + +// NewKeyFromSeed derives a public/private key pair using the given seed. +func NewKeyFromSeed(seed [SeedSize]byte) (*PublicKey, *PrivateKey) { + pk, sk := internal.NewKeyFromSeed(seed) + return (*PublicKey)(pk), (*PrivateKey)(sk) +} + +// Sign signs the given message using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { + return internal.Sign( + msg, + (*internal.PrivateKey)(sk).Expand(), + rand, + ) +} + +// Verify checks whether the given signature by pk on msg is valid. +func Verify(pk *PublicKey, msg []byte, signature []byte) bool { + return internal.Verify( + msg, + signature, + (*internal.PublicKey)(pk).Expand(), + ) +} + +// Packs the public key. +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var buf [PublicKeySize]byte + b := [PublicKeySize]byte(*pk) + copy(buf[:], b[:]) + return buf[:], nil +} + +// Packs the private key. +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var buf [PrivateKeySize]byte + b := [PrivateKeySize]byte(*sk) + copy(buf[:], b[:]) + return buf[:], nil +} + +// Unpacks the public key from data. +func (pk *PublicKey) UnmarshalBinary(data []byte) error { + if len(data) != PublicKeySize { + return errors.New("packed public key must be of mode3.PublicKeySize bytes") + } + self := (*(*[PublicKeySize]byte)(pk)) + copy(self[:], data[:]) + return nil +} + +// Unpacks the private key from data. +func (sk *PrivateKey) UnmarshalBinary(data []byte) error { + if len(data) != PublicKeySize { + return errors.New("packed private key must be of mode3.PrivateKeySize bytes") + } + self := (*(*[PrivateKeySize]byte)(sk)) + copy(self[:], data[:]) + return nil +} + +// Sign signs the given message. +// +// opts.HashFunc() must return zero, which can be achieved by passing +// crypto.Hash(0) for opts. Will only return an error +// if opts.HashFunc() is non-zero. +// +// This function is used to make PrivateKey implement the crypto.Signer +// interface. The package-level Sign function might be more convenient +// to use. +func (sk *PrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ( + signature []byte, err error) { + if opts.HashFunc() != crypto.Hash(0) { + return nil, errors.New("mayo: cannot sign hashed message") + } + + return Sign(sk, msg, rand) +} + +// Computes the public key corresponding to this private key. +// +// Returns a *PublicKey. The type crypto.PublicKey is used to make +// PrivateKey implement the crypto.Signer interface. +func (sk *PrivateKey) Public() crypto.PublicKey { + return (*internal.PrivateKey)(sk).Public +} + +// Equal returns whether the two private keys equal. +func (sk *PrivateKey) Equal(other crypto.PrivateKey) bool { + castOther, ok := other.(*PrivateKey) + if !ok { + return false + } + return (*internal.PrivateKey)(sk).Equal((*internal.PrivateKey)(castOther)) +} + +// Equal returns whether the two public keys equal. +func (pk *PublicKey) Equal(other crypto.PublicKey) bool { + castOther, ok := other.(*PublicKey) + if !ok { + return false + } + return (*internal.PublicKey)(pk).Equal((*internal.PublicKey)(castOther)) +} diff --git a/sign/mayo/mode5/internal/matrix.go b/sign/mayo/mode5/internal/matrix.go new file mode 100644 index 000000000..5ddf0d842 --- /dev/null +++ b/sign/mayo/mode5/internal/matrix.go @@ -0,0 +1,291 @@ +// Code generated from mode1/internal/matrix.go by gen.go + +package internal + +// Given b in GF(16), packs the 32-bit result of (b*x^3, b*x^2, b*x, b) into the returned multiplication table. +func mulTable(b uint8) uint32 { + x := uint32(b) * 0x08040201 + highNibble := x & uint32(0xf0f0f0f0) + + // mod x^4+x+1 + return (x ^ (highNibble >> 4) ^ (highNibble >> 3)) +} + +func vecMulAddPackedTab(p int, in []uint64, tab uint32, acc []uint64) { + lsbMask := uint64(0x1111111111111111) + for i := 0; i < p; i++ { + acc[i] ^= (in[i]&lsbMask)*uint64(tab&0xff) ^ + ((in[i]>>1)&lsbMask)*uint64((tab>>8)&0xf) ^ + ((in[i]>>2)&lsbMask)*uint64((tab>>16)&0xf) ^ + ((in[i]>>3)&lsbMask)*uint64((tab>>24)&0xf) + } +} + +func vecMulAddPacked(p int, in []uint64, a byte, acc []uint64) { + tab := mulTable(a) + vecMulAddPackedTab(p, in, tab, acc) +} + +// Multiplies each nibble in a by b. +func mulAddPacked(a uint64, b uint8) uint64 { + msb := uint64(0x8888888888888888) + a64 := a + r64 := a64 * uint64(b&1) + + for i := 1; i < 4; i++ { + b >>= 1 + aMsb := a64 & msb + a64 ^= aMsb + a64 = (a64 << 1) ^ ((aMsb >> 3) * 3) + r64 ^= (a64) * uint64(b&1) + } + + return r64 +} + +// acc += M1*M2 +// acc and M2 are multiple matrices, M1 is a single matrix +func mulAddMatXMMat(acc []uint64, m1 []uint8, m2 []uint64, rows int, cols int, cols2 int) { + for r := 0; r < rows; r++ { + for c := 0; c < cols; c++ { + tab := mulTable(m1[r*cols+c]) + for k := 0; k < cols2; k++ { + // The following multiplication table way is equivalent to: + // for p := 0; p < P; p++ { + // acc[P*(r*cols2+k)+p] ^= gf16v_mul_u64(m2[P*(c*cols2+k)+p], m1[r*cols+c]) + // } + vecMulAddPackedTab(P, m2[P*(c*cols2+k):], tab, acc[P*(r*cols2+k):]) + } + } + } +} + +// acc += M1*M2, where M1 is upper triangular, acc and M2 is not +// acc and M1 are multiple matrices, M2 is a single matrix +func mulAddMUpperTriangularMatXMat(acc []uint64, m1 []uint64, m2 []uint8, rows int, cols2 int) { + // The ordinary summation order is r -> c -> k, but here it is interchanged to make use of multiplication table + cols := rows + for k := 0; k < cols2; k++ { + for c := 0; c < cols; c++ { + tab := mulTable(m2[c*cols2+k]) + for r := 0; r <= c; r++ { + pos := r*(cols*2-r+1)/2 + (c - r) + vecMulAddPackedTab(P, m1[P*pos:], tab, acc[P*(r*cols2+k):]) + } + } + } +} + +// acc += M1^T*M2, +// acc and M2 are multiple matrices, M1 is a single matrix +// M1, before ^T, is of rows x cols +func mulAddMatTransXMMat(acc []uint64, m1 []uint8, m2 []uint64, rows int, cols int, cols2 int) { + for r := 0; r < cols; r++ { + for c := 0; c < rows; c++ { + tab := mulTable(m1[c*cols+r]) + for k := 0; k < cols2; k++ { + vecMulAddPackedTab(P, m2[P*(c*cols2+k):], tab, acc[P*(r*cols2+k):]) + } + } + } +} + +// acc += M1*M2^T, +// acc and M1 are multiple matrices, M2 is a single matrix +// M2, before ^T, is of cols2 x cols, but cols strides at colsStride +// M1 optionally upper triangular +func mulAddMMatXMatTrans(acc []uint64, m1 []uint64, m2 []uint8, rows int, cols int, cols2 int, colsStride int, isM1Triangular bool) { + for k := 0; k < cols2; k++ { + for c := 0; c < cols; c++ { + rBound := rows - 1 + if isM1Triangular { + rBound = c + } + for r := 0; r <= rBound; r++ { + tab := mulTable(m2[k*colsStride+c]) + pos := r*cols + c + if isM1Triangular { + pos = r*(cols*2-r+1)/2 + (c - r) + } + vecMulAddPackedTab(P, m1[P*pos:], tab, acc[P*(r*cols2+k):]) + } + } + } +} + +// acc += (M1+M1^T)*M2 +// M1 of rows x rows is upper triangular; M2 is of rows x cols2 +// acc and M1 are multiple matrices, M2 is a single matrix +func mulAddMUpperTriangularWithTransposeMatXMat(acc []uint64, m1 []uint64, m2 []uint8, rows int, cols2 int) { + m1pos := 0 + for r := 0; r < rows; r++ { + for c := r; c < rows; c++ { + if c == r { + m1pos += 1 + continue + } + for k := 0; k < cols2; k++ { + vecMulAddPacked(P, m1[P*m1pos:], m2[c*cols2+k], acc[P*(r*cols2+k):]) + vecMulAddPacked(P, m1[P*m1pos:], m2[r*cols2+k], acc[P*(c*cols2+k):]) + } + m1pos++ + } + } +} + +func mulAddMatVec(acc []byte, m []byte, v []byte, rows, cols int) { + for i := 0; i < rows; i++ { + for j := 0; j < cols; j++ { + acc[i] ^= byte(mulAddPacked(uint64(m[i*cols+j]), v[j])) + } + } +} + +func upper(in []uint64, out []uint64, size int) { + pos := 0 + for r := 0; r < size; r++ { + for c := r; c < size; c++ { + copy(out[P*pos:][:P], in[P*(r*size+c):][:P]) + if r != c { + for p := 0; p < P; p++ { + out[P*pos+p] ^= in[P*(c*size+r)+p] + } + } + pos++ + } + } +} + +func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8) { + var accumulator [K * N][P * 16]uint64 + + // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] + // [ 0 P3 ] [S2] [ P3*S2] + + // Note that S = S1||S2 is strided at N=V+O + + // P1 * S1^t : VxV * V*K, where P1 is triangular + pos := 0 + for r := 0; r < V; r++ { + for c := r; c < V; c++ { + for k := 0; k < K; k++ { + vecAddPacked(P, p1[P*pos:], accumulator[r*K+k][P*int(s[k*N+c]):]) + } + pos++ + } + } + + // P2 * S2^t : V*O * O*K + pos = 0 + for r := 0; r < V; r++ { + for c := 0; c < O; c++ { + for k := 0; k < K; k++ { + vecAddPacked(P, p2[P*pos:], accumulator[r*K+k][P*int(s[k*N+V+c]):]) + } + pos++ + } + } + + // P3 * S2^t : O*O * O*K, where P3 is triangular + pos = 0 + for r := 0; r < O; r++ { + for c := r; c < O; c++ { + for k := 0; k < K; k++ { + vecAddPacked(P, p3[P*pos:], accumulator[(r+V)*K+k][P*int(s[k*N+V+c]):]) + } + pos++ + } + } + + for i := 0; i < K*N; i++ { + aggregate(P, accumulator[i], sps[P*i:]) + } +} + +func variableTime2(sps []uint64, s []uint8, pst []uint64) { + var accumulator [K * K][P * 16]uint64 + + // S * PST : KxN * N*K + for r := 0; r < K; r++ { + for c := 0; c < N; c++ { + for k := 0; k < K; k++ { + vecAddPacked(P, pst[P*(c*K+k):], accumulator[r*K+k][P*int(s[r*N+c]):]) + } + } + } + + for i := 0; i < K*K; i++ { + aggregate(P, accumulator[i], sps[P*i:]) + } +} + +// p is always P, but is still kept to be consistent with other functions +// +//nolint:unparam +func vecAddPacked(p int, in []uint64, acc []uint64) { + for i := 0; i < p; i++ { + acc[i] ^= in[i] + } +} + +func aggregate(p int, bins [P * 16]uint64, out []uint64) { + // The following two methods are mathematically equivalent, but the second one is slightly faster. + + // the powers of x mod x^4+x+1, represented as integers, are 1,2,4,8,3,..,13,9 + // out = bins[9]*x^14 + bins[13]*x^13 + bins[15]*x^12 ... + bin[4]*x^2 + bins[2]*x + bins[1] + // = ((bins[9]x+bins[13])x+bins[15])x + ... bins[4])x+bins[2])x+bins[1] + + // vecMulAddPackedByX(p, bins[P*9:], bins[P*13:]) + // vecMulAddPackedByX(p, bins[P*13:], bins[P*15:]) + // vecMulAddPackedByX(p, bins[P*15:], bins[P*14:]) + // vecMulAddPackedByX(p, bins[P*14:], bins[P*7:]) + // vecMulAddPackedByX(p, bins[P*7:], bins[P*10:]) + // vecMulAddPackedByX(p, bins[P*10:], bins[P*5:]) + // vecMulAddPackedByX(p, bins[P*5:], bins[P*11:]) + // vecMulAddPackedByX(p, bins[P*11:], bins[P*12:]) + // vecMulAddPackedByX(p, bins[P*12:], bins[P*6:]) + // vecMulAddPackedByX(p, bins[P*6:], bins[P*3:]) + // vecMulAddPackedByX(p, bins[P*3:], bins[P*8:]) + // vecMulAddPackedByX(p, bins[P*8:], bins[P*4:]) + // vecMulAddPackedByX(p, bins[P*4:], bins[P*2:]) + // vecMulAddPackedByX(p, bins[P*2:], bins[P*1:]) + // copy(out[:P], bins[P*1:]) + + // In the reversed order of the above, because /x turns out to be slightly faster than *x. + // out = ((bins[2]x^-1+bins[4])x^-1+bins[8])x^-1 + ... bins[13])x^-1+bins[9])x^-1+bins[1] + vecMulAddPackedByInvX(p, bins[P*2:], bins[P*4:]) + vecMulAddPackedByInvX(p, bins[P*4:], bins[P*8:]) + vecMulAddPackedByInvX(p, bins[P*8:], bins[P*3:]) + vecMulAddPackedByInvX(p, bins[P*3:], bins[P*6:]) + vecMulAddPackedByInvX(p, bins[P*6:], bins[P*12:]) + vecMulAddPackedByInvX(p, bins[P*12:], bins[P*11:]) + vecMulAddPackedByInvX(p, bins[P*11:], bins[P*5:]) + vecMulAddPackedByInvX(p, bins[P*5:], bins[P*10:]) + vecMulAddPackedByInvX(p, bins[P*10:], bins[P*7:]) + vecMulAddPackedByInvX(p, bins[P*7:], bins[P*14:]) + vecMulAddPackedByInvX(p, bins[P*14:], bins[P*15:]) + vecMulAddPackedByInvX(p, bins[P*15:], bins[P*13:]) + vecMulAddPackedByInvX(p, bins[P*13:], bins[P*9:]) + vecMulAddPackedByInvX(p, bins[P*9:], bins[P*1:]) + copy(out[:P], bins[P*1:]) +} + +// func vecMulAddPackedByX(p int, in []uint64, acc []uint64) { +// // vecMulAddPacked(p, in, 2, acc) + +// msb := uint64(0x8888888888888888) +// for i := 0; i < p; i++ { +// t := in[i] & msb +// acc[i] ^= ((in[i] ^ t) << 1) ^ ((t >> 3) * 3) +// } +// } + +func vecMulAddPackedByInvX(p int, in []uint64, acc []uint64) { + // vecMulAddPacked(p, in, 9, acc) + + lsb := uint64(0x1111111111111111) + for i := 0; i < p; i++ { + t := in[i] & lsb + acc[i] ^= ((in[i] ^ t) >> 1) ^ (t * 9) + } +} diff --git a/sign/mayo/mode5/internal/mayo.go b/sign/mayo/mode5/internal/mayo.go new file mode 100644 index 000000000..a06568d0e --- /dev/null +++ b/sign/mayo/mode5/internal/mayo.go @@ -0,0 +1,730 @@ +// Code generated from mode1/internal/mayo.go by gen.go + +package internal + +import ( + "crypto/aes" + "crypto/cipher" + cryptoRand "crypto/rand" + "crypto/subtle" + "io" + "unsafe" + + "github.com/cloudflare/circl/internal/sha3" +) + +type ( + PrivateKey [PrivateKeySize]byte + PublicKey [PublicKeySize]byte +) + +func (pk *PublicKey) Equal(other *PublicKey) bool { + return *pk == *other +} + +func (sk *PrivateKey) Equal(other *PrivateKey) bool { + return subtle.ConstantTimeCompare((*sk)[:], (*other)[:]) == 1 +} + +type ExpandedPublicKey struct { + p1 [P1Size]byte + p2 [P2Size]byte + p3 [P3Size]byte +} + +type ExpandedPrivateKey struct { + seed [KeySeedSize]byte + o [V * O]byte + p1 [M * V * V / 16]uint64 + l [M * V * O / 16]uint64 +} + +func (pk *PublicKey) Expand() *ExpandedPublicKey { + seedPk := pk[:PublicKeySeedSize] + + var nonce [16]byte // zero-initialized + block, _ := aes.NewCipher(seedPk[:]) + ctr := cipher.NewCTR(block, nonce[:]) + + epk := ExpandedPublicKey{} + ctr.XORKeyStream(epk.p1[:], epk.p1[:]) + ctr.XORKeyStream(epk.p2[:], epk.p2[:]) + + copy(epk.p3[:], pk[PublicKeySeedSize:]) + + return &epk +} + +func (sk *PrivateKey) Expand() *ExpandedPrivateKey { + var epk ExpandedPrivateKey + + seed := (*sk)[:KeySeedSize] + copy(epk.seed[:], seed) + + var seedPk [PublicKeySeedSize]byte + var o [OSize]byte + + h := sha3.NewShake256() + _, _ = h.Write(seed[:]) + _, _ = h.Read(seedPk[:]) + _, _ = h.Read(o[:]) + + var nonce [16]byte // zero-initialized + block, _ := aes.NewCipher(seedPk[:]) + ctr := cipher.NewCTR(block, nonce[:]) + + var p12 [P1Size + P2Size]byte + ctr.XORKeyStream(p12[:], p12[:]) + + decode(o[:], epk.o[:]) + + p1Tri := viewBytesAsUint64Slice(p12[:P1Size]) + p2 := viewBytesAsUint64Slice(p12[P1Size:]) + + // TODO: this copy can be saved by reusing buffers but ugly + copy(epk.p1[:], p1Tri) + copy(epk.l[:], p2) + + // compute L_i = (P1 + P1^t)*O + P2 + mulAddMUpperTriangularWithTransposeMatXMat(epk.l[:], p1Tri, epk.o[:], V, O) + + return &epk +} + +// decode unpacks N bytes from src to N*2 nibbles to dst. +// The length is determined by len(dst) +func decode(src []byte, dst []byte) { + i := 0 + for ; i < len(dst)/2; i++ { + dst[i*2] = src[i] & 0xf + dst[i*2+1] = src[i] >> 4 + } + + // Account for odd length + if len(dst)%2 == 1 { + dst[i*2] = src[i] & 0xf + } +} + +// encode packs N=length low nibbles from src to (N+1)/2 bytes in dst. +func encode(src []byte, dst []byte, length int) { + var i int + for i = 0; i+1 < length; i += 2 { + dst[i/2] = (src[i+0] << 0) | (src[i+1] << 4) + } + if length%2 == 1 { + dst[i/2] = (src[i+0] << 0) + } +} + +func viewBytesAsUint64Slice(data []byte) []uint64 { + numUint64 := len(data) / 8 + return unsafe.Slice((*uint64)(unsafe.Pointer(&data[0])), numUint64) +} + +func viewUint64SliceAsBytes(data []uint64) []byte { + numByte := len(data) * 8 + return unsafe.Slice((*uint8)(unsafe.Pointer(&data[0])), numByte) +} + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { + var seed [KeySeedSize]byte + if rand == nil { + rand = cryptoRand.Reader + } + _, err := io.ReadFull(rand, seed[:]) + if err != nil { + return nil, nil, err + } + pk, sk := NewKeyFromSeed(seed) + return pk, sk, nil +} + +func NewKeyFromSeed(seed [KeySeedSize]byte) (*PublicKey, *PrivateKey) { + var sk PrivateKey + copy(sk[:], seed[:]) + + return sk.Public(), &sk +} + +func (sk *PrivateKey) Public() *PublicKey { + var pk PublicKey + seedPk := pk[:PublicKeySeedSize] + var o [OSize]byte + + h := sha3.NewShake256() + _, _ = h.Write(sk[:]) + _, _ = h.Read(seedPk[:]) + _, _ = h.Read(o[:]) + + var nonce [16]byte // zero-initialized + block, _ := aes.NewCipher(seedPk[:]) + ctr := cipher.NewCTR(block, nonce[:]) + + var p12 [P1Size + P2Size]byte + ctr.XORKeyStream(p12[:], p12[:]) + + var oo [V * O]byte + decode(o[:], oo[:]) + + p1Tri := viewBytesAsUint64Slice(p12[:P1Size]) + p1OP2 := viewBytesAsUint64Slice(p12[P1Size:]) + + var p3full [M * O * O / 16]uint64 + var p3 [P3Size / 8]uint64 + + mulAddMUpperTriangularMatXMat(p1OP2, p1Tri, oo[:], V, O) + mulAddMatTransXMMat(p3full[:], oo[:], p1OP2, V, O, O) + + upper(p3full[:], p3[:], O) + + xx := viewUint64SliceAsBytes(p3[:]) + copy(pk[PublicKeySeedSize:], xx[:]) + + return &pk +} + +func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { + if rand == nil { + rand = cryptoRand.Reader + } + + var digest [DigestSize]byte + + h := sha3.NewShake256() + _, _ = h.Write(msg[:]) + _, _ = h.Read(digest[:]) + + var salt [SaltSize]byte + + // R <- $ + if _, err := io.ReadFull(rand, salt[:]); err != nil { + return nil, err + } + + h.Reset() + _, _ = h.Write(digest[:]) + _, _ = h.Write(salt[:]) + _, _ = h.Write(sk.seed[:]) + _, _ = h.Read(salt[:]) + + h.Reset() + _, _ = h.Write(digest[:]) + _, _ = h.Write(salt[:]) + + var tenc [M / 2]byte + _, _ = h.Read(tenc[:]) + + var t [M]byte + decode(tenc[:], t[:]) + + var v [K * V]byte + var x [K*O + 1]byte // + 1 for buffer + for ctr := 0; ctr <= 255; ctr++ { + ctrByte := []byte{byte(ctr)} + h.Reset() + _, _ = h.Write(digest[:]) + _, _ = h.Write(salt[:]) + _, _ = h.Write(sk.seed[:]) + _, _ = h.Write(ctrByte[:]) + + var venc [K * VSize]byte + var renc [K * O / 2]byte + _, _ = h.Read(venc[:]) + _, _ = h.Read(renc[:]) + + var r [K * O]byte + + for i := 0; i < K; i++ { + decode(venc[i*VSize:], v[i*V:(i+1)*V]) + } + decode(renc[:], r[:]) + + // M = vL + var m [M * K * O / 16]uint64 + mulAddMatXMMat(m[:], v[:], sk.l[:], K, V, O) + + // pv = P1 * V^T + var pv [M * V * K / 16]uint64 + mulAddMMatXMatTrans(pv[:], sk.p1[:], v[:], V, V, K, V, true) + + // V * pv + var vpv [M * K * K / 16]uint64 + mulAddMatXMMat(vpv[:], v[:], pv[:], K, V, K) + + var y [M]byte + copy(y[:], t[:]) + emulsifyInto(vpv[:], y[:]) + + var A [M * (K*O + 1)]byte + _ = A + + computeA(m[:], A[:]) + + if sampleSolution(A[:], y[:], r[:], x[:]) { + break + } + } + + var s [K * N]byte + for i := 0; i <= K-1; i++ { + copy(s[i*N:][:V], v[i*V:]) + mulAddMatVec(s[i*N:], sk.o[:], x[i*O:], V, O) + copy(s[i*N+V:][:O], x[i*O:]) + } + + var sig [(K*N+1)/2 + SaltSize]byte + encode(s[:], sig[:], K*N) + copy(sig[(K*N+1)/2:], salt[:]) + + return sig[:], nil +} + +// assume last (KO+1-th) column of a is zero +func sampleSolution(a []byte, y []byte, r []byte, x []byte) bool { + const aCols = K*O + 1 + + copy(x[:], r[:]) + + var ar [M]byte + mulAddMatVec(ar[:], a[:], x[:], M, aCols) + + // move y - Ar to last column of matrix A + for i := 0; i < M; i++ { + a[K*O+i*(aCols)] = y[i] ^ ar[i] + } + + ef(a[:], M, aCols) + + fullRank := byte(0) + for i := 0; i < aCols-1; i++ { + fullRank |= a[(M-1)*(aCols)+i] + } + + if fullRank == 0 { + return false + } + + // back substitution in constant time + // the index of the first nonzero entry in each row is secret, which makes + // things less efficient + + for row := M - 1; row >= 0; row-- { + finished := byte(0) + colUpperBound := min(row+(32/(M-row)), K*O) + // the first nonzero entry in row r is between r and col_upper_bound with probability at least ~1-q^{-32} + + for col := row; col <= colUpperBound; col++ { + // Compare two chars in constant time. + // Returns 0x00 if the byte arrays are equal, 0xff otherwise. + correctColumn := ctCompare8((a[row*aCols+col]), 0) & ^finished + + u := correctColumn & a[row*aCols+aCols-1] + x[col] ^= u + + for i := 0; i < row; i += 8 { + tmp := (uint64(a[i*aCols+col]) << 0) ^ (uint64(a[(i+1)*aCols+col]) << 8) ^ + (uint64(a[(i+2)*aCols+col]) << 16) ^ (uint64(a[(i+3)*aCols+col]) << 24) ^ + (uint64(a[(i+4)*aCols+col]) << 32) ^ (uint64(a[(i+5)*aCols+col]) << 40) ^ + (uint64(a[(i+6)*aCols+col]) << 48) ^ (uint64(a[(i+7)*aCols+col]) << 56) + + tmp = mulx8(u, tmp) + + a[i*aCols+aCols-1] ^= byte((tmp) & 0xf) + a[(i+1)*aCols+aCols-1] ^= byte((tmp >> 8) & 0xf) + a[(i+2)*aCols+aCols-1] ^= byte((tmp >> 16) & 0xf) + a[(i+3)*aCols+aCols-1] ^= byte((tmp >> 24) & 0xf) + a[(i+4)*aCols+aCols-1] ^= byte((tmp >> 32) & 0xf) + a[(i+5)*aCols+aCols-1] ^= byte((tmp >> 40) & 0xf) + a[(i+6)*aCols+aCols-1] ^= byte((tmp >> 48) & 0xf) + a[(i+7)*aCols+aCols-1] ^= byte((tmp >> 56) & 0xf) + } + + finished = finished | correctColumn + } + } + + return true +} + +// if a == b -> 0x0000000000000000, else 0xFFFFFFFFFFFFFFFF +func ctCompare64(a, b int) uint64 { + return uint64((-(int64)(a ^ b)) >> 63) +} + +// a > b -> b - a is negative +// returns 0xFFFFFFFF if true, 0x00000000 if false +func ct64IsGreaterThan(a, b int) uint64 { + diff := int64(b) - int64(a) + return uint64(diff >> 63) +} + +// if a == b -> 0x00, else 0xFF +func ctCompare8(a, b byte) byte { + return byte((-int32(a ^ b)) >> (31)) +} + +func extract(in []uint64, index int) byte { + leg := index / 16 + offset := index % 16 + + return byte((in[leg] >> (offset * 4)) & 0xF) +} + +// put matrix in row echelon form with ones on first nonzero entries *in constant time* +func ef(A []byte, nrows, ncols int) { + // ncols is actually always K*O + 1 + + // we operate each row by packing nibbles to uint64s. + rowLen := (ncols + 15) / 16 + + var pivotRowData [(K*O + 1 + 15) / 16]uint64 // rounds up + var pivotRowData2 [(K*O + 1 + 15) / 16]uint64 + + // nibbleslice the matrix A + var packedAbyte [((K*O + 1 + 15) / 16) * M * 8]byte + for i := 0; i < nrows; i++ { + encode(A[i*ncols:], packedAbyte[i*rowLen*8:], ncols) + } + + packedA := viewBytesAsUint64Slice(packedAbyte[:]) + + // pivot row is secret, pivot col is not + pivotRow := 0 + for pivotCol := 0; pivotCol < ncols; pivotCol++ { + pivotRowLowerBound := max(0, pivotCol+nrows-ncols) + pivotRowUpperBound := min(nrows-1, pivotCol) + // the pivot row is guaranteed to be between these lower and upper bounds if + // A has full rank + + // zero out pivot row + for i := 0; i < rowLen; i++ { + pivotRowData[i] = 0 + pivotRowData2[i] = 0 + } + + // try to get a pivot row in constant time + var pivot byte = 0 + var pivotIsZero uint64 = 0xffffffffffffffff + for row := pivotRowLowerBound; row <= min(nrows-1, pivotRowUpperBound+32); row++ { + isPivotRow := ^ctCompare64(row, pivotRow) + belowPivotRow := ct64IsGreaterThan(row, pivotRow) + + for j := 0; j < rowLen; j++ { + mask := isPivotRow | (belowPivotRow & pivotIsZero) + pivotRowData[j] ^= mask & packedA[row*rowLen+j] + } + pivot = extract(pivotRowData[:], pivotCol) + pivotIsZero = ^ctCompare64(int(pivot), 0) + } + + // multiply pivot row by inverse of pivot + inverse := inverse(pivot) + vecMulAddPacked(rowLen, pivotRowData[:], inverse, pivotRowData2[:]) + + // conditionally write pivot row to the correct row, if there is a nonzero + // pivot + for row := pivotRowLowerBound; row <= pivotRowUpperBound; row++ { + doCopy := ^ctCompare64(row, pivotRow) & ^pivotIsZero + doNotCopy := ^doCopy + for col := 0; col < rowLen; col++ { + packedA[row*rowLen+col] = (doNotCopy & packedA[row*rowLen+col]) + + (doCopy & pivotRowData2[col]) + } + } + + // eliminate entries below pivot + for row := pivotRowLowerBound; row < nrows; row++ { + belowPivot := byte(0) + if row > pivotRow { + belowPivot = 1 + } + eltToElim := extract(packedA[row*rowLen:], pivotCol) + + vecMulAddPacked(rowLen, pivotRowData2[:], belowPivot*eltToElim, + packedA[row*rowLen:]) + } + + pivotRow += -int(^pivotIsZero) + } + + var temp [(O*K + 1 + 15)]byte + + // unnibbleslice the matrix A + for i := 0; i < nrows; i++ { + decode(packedAbyte[i*rowLen*8:], temp[:rowLen*16]) + for j := 0; j < ncols; j++ { + A[i*ncols+j] = temp[j] + } + } +} + +func computeA(m []uint64, _a []byte) { + // M is of K * O * (M / 16) + + // intermediate state of A, which is just accumulation of Mj*x^_ without reduction mod f + // M/8 * K*O + // uint64 + // some idx ko @ K*O idx = ko + 1 + // [ ... [m0 m1 ... m15] [m0 m1 ... m15] .... ] + // [ ... [m16 m17 ... m31] [m16 m17 ... m31] .... ] + // ... + // [ ... [m48 m49 ... m63] [m48 m49 ... m63] .... ] <--- for M=64, this is where reduction is not needed + // ... + // [ ... [m112 m113 ... m127] [m112 m113 ... m127] .... ] <--- here are for reductions later + // = sum of M_k @ ko + // + // later we will somehow transform it to the actual matrix form of A + // for this, we need to group 16 uint64 words together as a chunk, hence OKpadded + // ? why M/8, not something like ~ m+k*(K+1)/2 ? + + const OKpadded = (O*K + 15) / 16 * 16 + var a [(M / 8) * OKpadded]uint64 + + // Emulsify, without reduction, by accumulating M + bitsToShift, wordsToShift := 0, 0 + for i := 0; i < K; i++ { + for j := K - 1; j >= i; j-- { + // always maintain such that l = (bitsToShift + wordsToShift*64) / 4 + + mj := m[j*O*M/16:] + for c := 0; c < O; c++ { + for k := 0; k < M/16; k++ { // currently 4 + a[(O*i+c)+(k+wordsToShift)*OKpadded] ^= mj[k+c*M/16] << bitsToShift + if bitsToShift > 0 { + a[(O*i+c)+(k+wordsToShift+1)*OKpadded] ^= mj[k+c*M/16] >> (64 - bitsToShift) + } + } + } + + if i != j { + mi := m[i*O*M/16:] + for c := 0; c < O; c++ { + for k := 0; k < M/16; k++ { + a[(O*j)+c+(k+wordsToShift)*OKpadded] ^= mi[k+c*M/16] << bitsToShift + if bitsToShift > 0 { + a[(O*j)+c+(k+wordsToShift+1)*OKpadded] ^= mi[k+c*M/16] >> (64 - bitsToShift) + } + } + } + } + + bitsToShift += 4 + if bitsToShift == 64 { + bitsToShift = 0 + wordsToShift++ + } + } + } + + // transpose among groups of 16 uint64s in each row, so that above matrix becomes + // uint64 + // [ ... { [m0 m0 ... m0 ] [m1 m1 ... m1 ] .... [m15 m15 ... m15] } ] + // [ ... { [m16 m16 ... m16] [m17 m7 ... m17] .... [m31 m31 ... m31] } ] + // + // where {} indicates a group of 16 uint64s + + for c := 0; c < OKpadded*((M+(K+1)*K/2+15)/16); c += 16 { + transpose16x16Nibbles(a[c:]) + } + + // reduction mod f by folding rows >= M back around, using 4-bit multiplication table + var tab [len(Tail) * 4]byte + for i := 0; i < len(Tail); i++ { + tab[4*i] = mul(Tail[i], 1) + tab[4*i+1] = mul(Tail[i], 2) + tab[4*i+2] = mul(Tail[i], 4) + tab[4*i+3] = mul(Tail[i], 8) + } + + const lsb = 0x1111111111111111 + + for c := 0; c < OKpadded; c += 16 { + for r := M; r < M+(K+1)*K/2; r++ { + pos := (r/16)*OKpadded + c + (r % 16) + t0 := a[pos] & lsb + t1 := (a[pos] >> 1) & lsb + t2 := (a[pos] >> 2) & lsb + t3 := (a[pos] >> 3) & lsb + for t := 0; t < len(Tail); t++ { + a[((r+t-M)/16)*OKpadded+c+((r+t)%16)] ^= t0*uint64(tab[4*t+0]) ^ t1*uint64(tab[4*t+1]) ^ t2*uint64(tab[4*t+2]) ^ t3*uint64(tab[4*t+3]) + } + } + } + + // transform the temporary matrix into the desired form of A matrix + KO1 := K*O + 1 + for r := 0; r < M; r += 16 { + for c := 0; c < KO1-1; c += 16 { + for i := 0; i < 16; i++ { + src := viewUint64SliceAsBytes(a[r/16*OKpadded+c+i:]) + offset := KO1*(r+i) + c + decode(src, _a[offset:offset+min(16, KO1-1-c)]) + } + } + } +} + +func transpose16x16Nibbles(m []uint64) { + const evenNibbles = 0x0f0f0f0f0f0f0f0f + const evenBytes = 0x00ff00ff00ff00ff + const even2Bytes = 0x0000ffff0000ffff + const evenHalf = 0x00000000ffffffff + + for i := 0; i < 16; i += 2 { + t := ((m[i] >> 4) ^ m[i+1]) & evenNibbles + m[i] ^= t << 4 + m[i+1] ^= t + } + + for i := 0; i < 16; i += 4 { + t0 := ((m[i] >> 8) ^ m[i+2]) & evenBytes + t1 := ((m[i+1] >> 8) ^ m[i+3]) & evenBytes + m[i] ^= (t0 << 8) + m[i+1] ^= (t1 << 8) + m[i+2] ^= t0 + m[i+3] ^= t1 + } + + for i := 0; i < 4; i++ { + t0 := ((m[i] >> 16) ^ m[i+4]) & even2Bytes + t1 := ((m[i+8] >> 16) ^ m[i+12]) & even2Bytes + + m[i] ^= t0 << 16 + m[i+8] ^= t1 << 16 + m[i+4] ^= t0 + m[i+12] ^= t1 + } + + for i := 0; i < 8; i++ { + t := ((m[i] >> 32) ^ m[i+8]) & evenHalf + m[i] ^= t << 32 + m[i+8] ^= t + } +} + +func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { + if len(sig) != SignatureSize { + panic("sig must be of length SignatureSize") + } + + var digest [DigestSize]byte + + h := sha3.NewShake256() + _, _ = h.Write(msg[:]) + _, _ = h.Read(digest[:]) + + h.Reset() + _, _ = h.Write(digest[:]) + _, _ = h.Write(sig[SignatureSize-SaltSize : SignatureSize]) + + var tenc [M / 2]byte + _, _ = h.Read(tenc[:]) + + var t [M]byte + decode(tenc[:], t[:]) + + var s [K * N]byte + decode(sig[:(K*N+1)/2], s[:]) + + P1 := viewBytesAsUint64Slice(epk.p1[:]) + P2 := viewBytesAsUint64Slice(epk.p2[:]) + P3 := viewBytesAsUint64Slice(epk.p3[:]) + + // Note: the variable time approach is overall about 30% faster + // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] + // [ 0 P3 ] [S2] [ P3*S2] + var pst [M * N * K / 16]uint64 + // mulAddMMatXMatTrans(pst[:], P1, s[:], V, V, K, N, true) + // mulAddMMatXMatTrans(pst[:], P2, s[V:], V, O, K, N, false) + // mulAddMMatXMatTrans(pst[M*V*K/16:], P3, s[V:], O, O, K, N, true) + variableTime(pst[:], P1, P2, P3, s[:]) + + // compute S * PST + var sps [M * K * K / 16]uint64 + // mulAddMatXMMat(sps[:], s[:], pst[:], K, N, K) + variableTime2(sps[:], s[:], pst[:]) + + emulsifyInto(sps[:], t[:]) + + var zeros [M]byte + return subtle.ConstantTimeCompare(t[:], zeros[:]) == 1 +} + +// GF(16) multiplication mod x^4 + x + 1 +func mul(a, b uint8) uint8 { + // carryless multiply + p := (a & 1) * b + p ^= (a & 2) * b + p ^= (a & 4) * b + p ^= (a & 8) * b + + // reduce mod x^4 + x + 1 + top := p & 0xf0 + return (p ^ (top >> 4) ^ (top >> 3)) & 0x0f +} + +func mulx8(a byte, b uint64) uint64 { + // carryless multiply + p := uint64(a&1) * b + p ^= uint64(a&2) * b + p ^= uint64(a&4) * b + p ^= uint64(a&8) * b + + // reduce mod x^4 + x + 1 + top := p & 0xf0f0f0f0f0f0f0f0 + return (p ^ (top >> 4) ^ (top >> 3)) & 0x0f0f0f0f0f0f0f0f +} + +func inverse(a byte) byte { + // static unsigned char table[16] = {0, 1, 9, 14, 13, 11, 7, 6, 15, 2, 12, 5, + // 10, 4, 3, 8}; return table[a & 15]; + + a2 := mul(a, a) + a4 := mul(a2, a2) + a8 := mul(a4, a4) + a6 := mul(a2, a4) + a14 := mul(a8, a6) + + return a14 +} + +func emulsifyInto(sps []uint64, y []uint8) { + var acc [M / 16]uint64 + accBytes := viewUint64SliceAsBytes(acc[:]) + + for i := K - 1; i >= 0; i-- { + for j := i; j < K; j++ { + top := uint8(acc[M/16-1] >> 60) + + acc[M/16-1] <<= 4 + for k := M/16 - 2; k >= 0; k-- { + acc[k+1] ^= acc[k] >> 60 + acc[k] <<= 4 + } + + for k := 0; k < len(Tail); k++ { + if k%2 == 0 { + accBytes[k/2] ^= mul(top, Tail[k]) + } else { + accBytes[k/2] ^= mul(top, Tail[k]) << 4 + } + } + + for k := 0; k < M/16; k++ { + acc[k] ^= sps[(i*K+j)*(M/16)+k] + if i != j { + acc[k] ^= sps[(j*K+i)*(M/16)+k] + } + } + } + } + + // add to y + for i := 0; i < M; i += 2 { + y[i] ^= accBytes[i/2] & 0xF + y[i+1] ^= accBytes[i/2] >> 4 + } +} diff --git a/sign/mayo/mode5/internal/mayo_test.go b/sign/mayo/mode5/internal/mayo_test.go new file mode 100644 index 000000000..1695f70f3 --- /dev/null +++ b/sign/mayo/mode5/internal/mayo_test.go @@ -0,0 +1,224 @@ +// Code generated from mode1/internal/mayo_test.go by gen.go + +package internal + +import ( + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "fmt" + "testing" + + "github.com/cloudflare/circl/sign/mayo/internal/common/nist" +) + +func TestNewKey(t *testing.T) { + for _, tc := range []struct { + name string + seed string + expectedPk string + }{ + {"MAYO_1", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb14803", "5b61421edc1c90efaf6075560f0206173555c55eb845ef3a70ee5ef18618361883a0ade490e9e5bc51cd2b25fb4ec9147a2fa6735d8749ed7e149330a30b3ab62b7991c2494cb21d0bc011ceb357597b8afef939a1fe398421c88f43fb4fe380c10caa3a95e507ebae5a571fd958e0505d994566263be8db31ac833aa20ae7ec97d1a865bb292e89d583975009046053bf91ce7d73e9c23c4244071e17a1095f3918330559f323ce557d66b8771f0003cccaeea82314088d56455d72f258e4c35fc6ce2d195cdae99f359a8307ec15f27da4d0634531cd9a1a719121f7c9b99a0d0c24252316f1ea7c16d73f5ecf7125ad0c8d2e02574131504875af5ab6dea1c328e71122f0cdd09e826cc515b4ec160f31d63253dd8798b2d841e80a28479bcca320853a459d4d659df695eea4b5aeb8f45f6c5fce283c64dd6fe928d452be3723f8b448650ebb72acf231e3c27683afcdec22037f1af29c479db1a97448ce570fef082052c9ef45179ba62abd7dd156f17f4da51557383d40a9d544cdf5cac4f4cc1ce69326a3ccc611fb7a6d04126dc54c55c2286a796be18958846d53e1e3f55f3ac0f4e09d8f302878f4993e25c2164970b337d0364aa61eff5d85e41fe784fd57420a5481b9a15f02a47e3e49c0709a37352a50fd0feb7e7f9938173b3e3c570a79c2424f5dc5bf95363b6d5d764e1a38a1aa05a31bbb9858b51fe3cbb19eaade5d4f482260838f5273b042f6340236395f6c347d4bfb922752ad0b17f1bae8645a9a6b1d72e1e91b4828faa313dd85ec5795252dcd95aa33519a56d740cbab7b9ee13e1b8add763bd07a25455e44e60f664846e32eec329c46e0741b6649c34358905d9d01124c3c8ae14e3fb47cc6477ba8e63bd4a3937588251952f5232bb9999aef509414e11a36d110ea24bedc235c55ab6f80116b036845e291148135a32b0d44fe5a4e9ea9f3ae150e11c62ea91318129f79318d3506ba50db3dbe235427d405a5baa9e6192a17014dd19863087445eb4cc9b6164aab3d5af4e73bc3edd97c76dabba0bc26c43d70f7880a0f7c042465da77ae3f8a31fa65903d90797fc0b5deddf578d52940040276c9b415d880e3782e03a2965a6d3b265628c690bd1b096a355362eb751d528fbc6fa365d1cf3d39cf49d8296c28fe8eb52a31a07e877d1e57e91971cbbd14216db76a08da45258a1a801d0cb232a87280df4522329ecd9312e5489d90a39547735cbd11493d0d86389af40c1b9ec5a993d0cbc508da424392695e1cdf205a294122412fac5a2b4d6e0ed5eed7b4de18f0d4b81f746753fa8744ab845c18800befc926d3290531053bf2cecf23d1b6d7fa9fb8d4827e87abf574afe39a636dbb9fce48854c641981f236da01bd8a5901871891aac7fa1f02300101540e7b05665e5f19912ee898666b64bb3cef688577c7b5482e123c0e8fcc466302eca8fc02f35260569cc1d4b00662be924f188226dbc6086d4aaed1ff9c7604748a556d00f87823f9fafaccf752d645b76a5851723274737e4dd7d5d76fdb256e3f1ef733914eb36932eb1c4c666df436351fb817e7055fbfca2b3103fcec84233feaf3fefd600721d3ca1514ee26db6a608dd4333377a9e36dd2dbadb1651eb99d8e3b4fe6"}, + {"MAYO_3", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d", "1c0ee1111b08003f28e65e8b3bdeb0378c8800a9a87f38ca101b55cbe065f76cd8be17feef5b448fbdc40c759729dd609f2f3e0a70f719bd04bdec59158c1d885127c91066d31b4e53848db9566d8d25393dd3e665c8f4b5fd5d5b836fc2e012a059a5550646dfaf9db63eb4c928198a4dc3e8631fa743e11004a2fe3454a9d537697af4bf5d6bee82c2fe40d5c74c29be0cd7e56c8dff8dacebe18320a455a72265559a287385643668d2a20058d927c87a41b8d94ac1c68b6a07c7b055604c7491f11ac30974ab0d76ff2034b6c73d3b4cfb13e8f341f9f3f445bb6a4e47aa381282f83af4c46fd38207da843135dceac04af08096915318e464a8a1fda2a99efd41c61197a498abc875ce302f94eeb8ec32e56f24e2c6c419250e08e2f69148fe083747340af17e634a98afbdd6902211c767b750365f6200fd92e8431160f1e293058cf2cd57be92816a712d796e6fc0c672026c162d93eb79a19c7d4853df371234fec9c9ce76b6edfe082007ff69c64c9980f2c7b7b6009e3db0990cffad1f4119844967572e1122e5c7ce460ccf732c82d3f18457111ae8e1a5381c5b30e9d76028ca26839455d44aac728a24731a1d69c8c856b3d611dfe5336fd5a42f62ee607219c654aa117a874ca33fbb89ecdb6310cb4e0c4a5e2554be2d4bfe6abb062eef513d726c258dafa690324302d8fb108376f991e50e9037e31ed3ce8d8cd785fe9eb419d33c7ac381f91c463a8a06629db910f5fed1d18f2cf30afcfc968ca5617c6cc6fd9080f53cbd05b727c04bf75dd93f08a0ac1a7bc46e8b5a08e6d3c3b1e1232126d4e65b1a3877689dca9b1e53a73d45ad9eeb38faf942a62a804b088226f4ecf15526e24936f9c28c951f5c101c5c44bf91b72fecbb3a9d28cf77e63343ad760089e0739017cf829297204041acf5b4c10fbca19219db233fabb3a564c8cddb281f290b9a8536c9a1d39a35d8eb124182d9cdc51044067647ee861b8c0c6fbbe1aec13076b8f3d8982b52d1243a9448729361619ba34edce7bbc1b3a1545f791a5f2f6d04b5204e073bf66e7b009c7d4a1660355d066fd0d358623fcee946e69d20bf6fb291906e700004aa001494381382c5e1bcc4fb2b7cfc4bba6866548ecc797fec670abc2e8a8dea2c4219ed32102a0a1f4fa67775512768e57851571fd383a5a684ed1e10b62f3bbbb65d58e08151695008ef6a7cf4b7cdd265d9cf1c158f64c607aaf37e51140518724c39f3ad8a759a7ec0af43778c00832088270934cd08a582196953b283fa047f6365585b6078027be9d9c915569b78c65702561ef5de7953fb6f5e3df3ae8b85d3fb2161cc3af62ed83d027b733507fee131da9d228c79277630c3efcdc9ee1e559bcaee4c818d6d29ea6c8cf9eb46ad58a4ab6a4fb2f6d6f4380efa4d60843ca4eac32f8772073f1cb70021547050c30095fc7c884e31a2f91994985eab00e7565da2ece39e47a6c8cfae012687dea9d65b8745d4d760e656a55243fa9ba8fbbd31b7f2bb2629a0ec9265aa883c8775ba173e5ff08a9a3ae4e76f9925418f4e2dce1d00e4f9377245b8939dc58b7d15c172baf984781afd318b264709bde5f0859a95923ecc42108f2fa7c600e9ff69f3f65751d8a457dde89a1ad900314f90e9b712580eff5e4a73efa3633c5d5e34fa8f2528075fa92dce66506f5e947f1fdef820296cc294ecdc059390b644a1407b13d24541d275a49d9bd1a6f68dab296f52965c7f72d50cdc0ec19456001516c10030086d2efa109f45fb6d1f7550de534d0d4ce856e72763463e6d0d11cd237037fea14abb32465f632ff785f1917322e4574bbfa8f74682da17f2bf7bffa0ff030297d5c999d6a7c387ef2a5e168225a60ebb01c6dfc72ba1fdec8f74651f3e64750f2dc98d72099fc85f43fb28ee0c4efe8b7d76bb432453a848310f6e96d833a27ccc98046a210f568c075dc71545ff0d37ae5138406d569e964bf8e6903de7651b33c05e22747074b730cb6f3e94b891e8105ad6921cf5efd40da88ec3d0765756b7bece012f59f1f68ab80082fda5fa37b026d0b6cb5fab9a0ecab6980a179815bc9e56cbdc0d0682c29c5f9fc8cbfbf5a6a9679de92adb5b4778ecdc70f819549087bef738037de613cbec897e016672ce9eaf00382ce4b60c563cf3d23cfa178403f270215cef465ae9b60f007c75c1fcb85ab4ae9ead303057dbeedcaf55f13ea586f55a2eee9da8dd92bc210d1e2d4fb7d219c54517fe5a4ddf5c8c6a776703ec8407d6b393fb331d7aa62423cbbf7a062bc210e9c6565cacfef1e4a5d448f2cbf5c8861857dc870bc37c2070ce1f1147dfdc285d9df8296d6620e1430884e29571ea95ce1deb9c2ae7198f10732b2b2d5ddabfabda6e77acfa2b10c75842e07de69cb2b34abe41018c9a765e45ac85b63a85ca612b1e9682d7c8f512297bdfb780e21506956fcb528bb1916fc1a8356667033e99e16c223139efef6790b8745eafe30df12d900301643f8bd6717d0b5d1de5e75f25bdc950ce507ce7b3489e72d5abdf5a28105a8063d0a9662672489de16a1628548382277fcfb24bc878f071b13d6e120af00c1cdcacf5f1ef4823a2b5da0e7c8487550d80ac9f6891e50be3dca0096042c97cdea3553db702bbcfcf64f1b64719bc89ba51a09057fd9285c4aa7d95532cdc04ee185beb1318132c836278e368ff8e2653e4aafa829a2d40d91bda7b4af3f16442931b7da2b500f7e3abf80219b8a245fc4748d20d549cd12c5af7ca3a8543bd14d0ddfb4fff8a43c661d0a8163fe80bc0f77b762d010b64b831e7c29f9982e5b7338a6411a37c4f1f46419b610415104f154a009cf3bf1805ff279145477a0d5ff0f3c9584874e8e8376101dec2c8c69592a8c46add07a6c0100cfab2bba03771749b3028d287805a6859a1e3a82f30231b01a7e3453059638e0d7a2430348ff4af26d00481cb3de085d269015fb6043a0346281a05448a81d04ad196d916503c35d31e513bb133d49f9646c191f872cf4d6798c18c87e52695d1adf59c97ba8bff5e826ea17ab2fcb8b559e871e6d62d8a93522554bb5d48de674596761595bf28d0114142a542c2f9cab2be451eb37546ce3eb60bb0cc6050e5d38ac96752a8d2575f67ce28452b00b486e2efc8aa80e94a0a442fd35f5fdeb48e52a82c43ef2c0a02d3bf71da67f0131af8aa90e159056ebf4e5dbbe176c9cd8c07d234135c50b524a4efd7b72ca4f413b29809e7a1a53442a1919bcce952203d17e3c5600dcc3160dcbf7f18b9352289ce78fd337c36416b8342635c3d542bd1a357b8e39b04098fe7b60248fd6ea4a2778ec576e4c34323c81e56386d40c79260e3d914db7cabbf269441dcfa216ef7aa85fae99a89f2c1f56838c30fc180135efb98bcd01e2a88d144ecf7063613d3ba62af5fa8cf7d63465273ac0abb3f94e87130310f4b841db13ad7fab1444a02af5649ef9a29392a4fc15b747799cd8a8d735094cb03a606735ac94ee244a8c5a3504f31ae62d3acf7f1586390efc6e8da967f3b174d9e805554603c4fd5cd875e70352d2c6576e3baf0420d6f3aa161f605219bffb93abd51628014072e7c2e533aa86117f02ccd67b623af4ca911eebe981595fd393e75d5b0d0da9440e3aaf9249110f5e8c22a68f9cfe0b30e8665270763390fb8618668a1a56b71065a85b91ff8dbb8e4ab9c3e6c079a2b0276ba475d388262f36a76c3e230bed8c47faae910"}, + {"MAYO_5", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2db505d7cfad1b4974", "708141534c09f5f604225ef8c3472f3749ffbce6c071fa7bb1f4b50b78ecd32e7f35efb6bfcead3313156f9995fca2d2db6d0f0ec50a66bd3d5b469698ab131f21cbae3785a0c838e2e3316fa73e9670adb76fa9e1ec8a0bb7ddae82c0ca7c1aa5e61c31b84180c3aae027223430e988e28c48ce0d5b2ad2750203707f2e284cf1807f008ae2570092a662b73a9a728f81a79338f3452d6af920ee59037f2c8a20b495420c5bb3fa7518a34ae1e64ffa742db0c3c009504ca98882e4abadaf8396cc242d9a7c1d4d71b17d6c65ed26c4e459d8bef86326ecc156209299424cc015c52f97862998f871ebacba29877e040b7adfecc69081f55162d190f374b704662c138c39782e7a8b74b7867e92c5bd8a7833b4242f04712ef4384ef452fbc3078550f15e563c9238d72088275d02d94cf8c163a2bd73810938a1208883874e1b7d8a9ced0ce6a7177da260ec1a35d2e0f45bbdf18f27cd3f0619d134aa47e4df364fbea1dfb852c225b06be4707824d9d2f8b25d6e15f81079511778cca8b82554591823616a7b7ea029c3c3670dba35bf2f11b6493ba5044c159c368f7e4d3ddb215247ac0fa17f227298b9b1d6af4632fec18ab98c9218f583c75e9b8434cb0b68105e44be85da80a65fd67839b4981e2e89feed17f41b60e75122b5328fc0a5772b8a0ec5e4646e1fc1f98a44a31d516be5cf6c187850d7343078304f8a2a02929dc2aaf268ee590507b325a0d744e413d58dd61e62decd0d4e846f08f9fae80cdb7e33d693c57e46cdbc7e1ebd06fc44133748064db8b77f1f87df1f21891af721e840d768be637ebd41cf8be5de36739045b9bae72cd9192a1ff0addbddaf6a0b6cb0d0923ce5b3d5c261c20b1bac348eed6d1cebcdda642103f3a4246e9ee3a1f1950be11796a2db39c54299a6973bdbc4a9446ff27a85fbce46e4c94c25d6fcf1d8bfc773df36591862c0447a400312969c056399b2ddc92703a4d14f1b5862752257103a300381f41f47d7baaaaef9bccbc1a3ce8c94edd1d7c43ba63c961918c1c9cffdd03659158d0827384f796466449a0bc72e94badcbf567e9f6812e320b772b7d330ca8d58996fb9cb9a4ee7772ef4e77f67e2875070249026c1287ca6b43af591a647c33efc965921c9225cb195898f0c0185d3142cd1d924119b003b0b51ccb42caced8baa8060a1750b53f7c039f857dbb248a2520bac9117da7a12ba07483bb751afb7fa1f3d9e9eecea70a9fd233adbad40fd3ff7ca1267a4a8d2375ecfe35d7984ce860725f68f19a19c0e00af83f13d452491720370effe877ec6ad51e702c9ae828b0ad3a845519d3267f3908c808498ea73bc96b70cf4d9cbaf1b1bcc9b12a093936cdf56cbb703799307d80b3755ad72dcc0b667cc14f6dd47d99d936ae3afd5be34264ddc8eaa7026e6391780444ac43c01cddba3c50548d7a2c03a59b1db7d798043351b51d4fc32f6d5f8ceeb8654977e7ba147936a6d172cfb023308730adaa3973fb4d883b95cdc5aea24a83e70561aaec784023e28dc385aae7076d271ca0551b83a60d7388b9a2c804ed66ff4fa6284fd57045d03b6c31d0d32165222efc6876b14a01c18024b0d6fec28361de82a337fb087ba25daed484b3e17fae66ce2bf2bc88614ed5a2e1db9f485ccbf7e029009c164ae3bb9b7f3b7de396970cda0943036e18d2cf96b9979e8d874980a844d7628b5fae66f00ecc4366a96c814785516d056e44b906ef6dc09e1a976384ccaf1b3f40d79a5f6ad426bb70035e84961c188ad24a8122088fb2a4b5abcb5fdd0a26cd7d44dd341c5e5ad65478269cbb5e30f4c23d63043de9c65fdd57fbae9f7a68c7bcf4e4e6b0eefdeaa3bf376c912b15f00e1a6c1b4c80269cdf9bdf25373fd55a7f51ae9aa0c0de6faf001a2016eef56d82eb696472546ec8bd29f1e76f0666b4c8d4c4cf6e633b30f323110d7b0fef38a2ec90f9359278a23f276da7984e87880105d12051937b037d86688b0aab4a11ceccd776263b8a82aa6d5a5490d33e734c651aafea394fb24d85ef3a0620686e1d96d198509e5f04f406986ff201e0b2d3d7584216e8568c68c2d8ebe1947209dff588969c3c088877b8c1dc0b0fadb7295fc44e3bef5d81789329675ac5c3142fec702f749c6e3aed13071c02680f7cb82e91432f6d6fd7c10ca132e61f2d47d6e2008ac2d5157445c1a3cfa52e03c5ad575fe836cdf4d8c9624e097d969eec08dcb4540be5b4a9f3ca03201457a2dae4a966d0b35abc65559484fe0afecb774e9437eba24d22e4da5927d00ca3a9cd49a1f3843b1f3f7b2872f0b1790788bb216d53b9b61f3f394d140a200ed30c3229761af6d3ecff8fc463ef63ae13f92d3d555261f04a287dac6653b7d493cc9026f5be29c8c550bfc513cfdeaf83ec0280fe2786ca9c07b222f6ccd9c31a8848a4e79be4b590fe4e239ad9239df96539b48cdad99fd0249d50204649e26838a076adcd6800221fff817681a6a1ccf807c1ae1794aa7cbe383cef23610436d51cdeb631df8bc1f7f7207b9972c60dfba18cd5378eab1501c6f8d4e0971862cff6b1cf3a327e1afa6371de15fefbb1c80c658d2b372f0bed979929e9fe3c3769ad0766edb634949c29f9bffd879c961f626d10617c161319957c28519ef29142236ccdbe214f7e3e9ab77edd5fa814f5b599513a46f79cac9fd9ab62d7859ed57a5e6a0af7f5e74ea4f9fbef3c9943f2c69cb66ef4497c92b25059980bd55568e8a5c50c0853fe02f9eafe068ebe710cf284976fe4b3c12d5990951775b891cebaf38ba4c9dbf70423210f723a6c0961f00766f0acf1ae75d3f1298b378cdd7fed999a8b45c923c3c8a9a48f007b88f6fc9e0d37458e441247074e9c89e7429bc76a7bef55e702420c1bc65d66bb28d46ad7e0c5be42de7b89a44dbe7ee416c795c0b7e91ca9cf2f2762816ef4bbed139137a8ad3fa7a5a96c917e8af660e736ebe9d70e4b90fa18b560ad173617f8dff65804a2bae40a4d27e86e97e02e485cb529fad4a5c7f72a2b758a693826bb964eeb28842f5aee51db712d68354d7f1ec129b7785091b1cbc3fb389263e064eb03df94183e0c1bd1824b8f5c232d7970c11e53caaae90ebba5ea862f5aa26aba25b5f627823182444efa64f21125de36afdd615518bb5dfdd117b5485910319b458236dad01de33b8605352517ce3aeee706de917ca76ecdc4ef53a733e5f898c2dd6c4d641d141ebcdca2c5c7cc176eac1b493df81702083bfc124ad9ed3511452a71f831c9a2f8d2d771cd3c1b1c440bdc46e87fb5ddc0d45c3e28c10aa1af313c08bb554590dcd296e0916a9b9df3d527a5db9ec1694083bb99c480231c87444998dc7f922f7159b019460d39be64882528f4df1e3e332f7386cfa34310b666d369ed7fff15ce6db939e448beeb74e221862a901c37275cec3e3718b146d0376c0b44e64d393c231050bf166658c333509aadca601619ba6e473705b5be1c9c290a390ee24062265298ad5bb30cc53571b4e8046856a10da99823e0a33c85fbfe785ba709a3cb9b97dd6d56db92febf8a833941655294134be7d37de581caf4e8fe278d9ad5e31d046c7c4c6e3f162c8b5bdb27f1bb5c772b8e7838f67f97cc3c090510fcb5e2f96191a77eeb7febfdcbff81a58860bd4fa87cb41b1fd51ac670828fd6efdcace5ceb85339db706286e860bacb1667da894ab79d32a67563f9a1935953679b687b558324e801c62b9eff69888a51cb8c79d6eb4aa9651108b53d1b66afb1fcbed7dce98403c64885bef7e45a0689ce783cf7741c2c787c1643e08a109b7f5813c11a16bc9269faf0c74e6e0d08428d3de2cd00e18615e157488b2a4095ade2e101dc3fd48da57bbd1c79e9c5cf0992014f80317fa35ee71560c27500761e8a60449546ffe1a232806a8ec6a5694941485f3e0af3f4cb557a42219435bf38fa10e11f3a243c9c584cde736a02bcc37b8f624931710c159f167cdb41deec3db28535731dcefb4926c8580d290ae818eb3a09738ca5d88ed1909ca95f1c71d9305f89b773cb840e3f481aa2f51dd65082da3afca358bf88d2781b46d82d7685f4ccd0983c75f9f1d6382493692604da6c8e8effb27129b1776509b98efc15c75ea1f11c04d7374742ec8cf28341a1be36deba666dabf6bcade7bc3670c03bf3bd42441edfe79716a7ccd32dc3008693a42800944bea936484bed7b6174718d31ada938690ad1efb0442fcdc7a8967f96fc4fa96821ff9862c3b1495ca55afe8ed5aa8097e20fe54f3ed2f2be1cd241a494bccfd3c75d7acd180ab6eb192402947c273abf6fac2e957c8fcc7757d79361071b2067349823cc9db3d531aaf0561acbb918df2924276ea64eb2bb095680d0be8da8d38d53f97d6cc68d88bc4f22c6865cffbd59a6fa015d2f0a12a985fd97a34e4d7f9504cc5a86acd4c791f105b9cb944ae4d0b0f747a1dd089a7903dda5528752132d2387e4854234a8a3ce95fd8afd4bfe8e55f4c026e80348ece14da917b3a696ad797e70d06356cfd2463f5ccb96549e1a037f938624754c57401cddcc948cbd088a0f276329a87db91308ece6360c743e4f8f360fed28b77f7a60745aea0b89721f4cb342b75ee1e51625292db27a97c2eb18186278fd826fb49cfe87fc9b45bd26a02266da90a7ca7d6c5d9eaaa036a0c7a12171832923facc0aae7554eb365e312e6b7b3e8be167b8e580fd5cdd609b5308864600b98f9b69d71eef1d4765f4ec07a584d9bae8a407777d7334bbb285ddb15a07dd5e3a4e8c8072710a4ac204162987f2b669524d88afb77e5f4a540f9985d560dae55945abcdeb3f90a498f76cf44d0a9e3977cdadc11a8613d50e4fb19577a6f6a16dd1b4003e56268a1134d774b9cc4e94ced0943a0199ea9ec944e891f02deb85c1b870e535f97599f843e8467c771784796c3c8712f3681b475a4eab150d18928aef90f293bdc505784441b6ffec7e525b15b5d1e2203f8ec95db5eddc8c6eb21e82118c75d6dcf2c58eb4487eed220b1eb6f3a3a7e92b0ce13ad5e87c4e4edd2c8bcab83d7622d20f3f5cd74a930515808e500563848ab0893cef9323ebdeeac9716270f3550c1d92ffb4d650c8442300cb7a4c61de2b5876f6563271ab7abdacc962be387556a6452dfdedf99148b4a2fe635dc730cfe9cf07a1ad5e148e2eb639e25838a8b5f6271a1ac8330847da496f042237e9f38ace9c37f096da0d76157825c2c8f578919faa984f4be65f7055e517b8f78589e1ff361d769e5d387795b79bf476959815d9b6ff22361f76e60cab6986e0d164a0984a013101e3cadd3eee5199030b45e8c6b9ea63f80c7e6e2909b39d96d6f62776820281eac9fb55fb5c124b7dd9e9b114d476d2fbc676df79f9e9b214ea69013681e7cd8d379bba254382ae4678986f67eba92cd978ac6353e6207101a3e1d18008c3d5924eef4991e0b576fd9f582980faf24b898796c18388f40ae96403d45b31785958d37ca09b76a5a7320776ca4d328e95ea7b6703f4ab0947070f5c20dfd154a9476099d193dccd487f589759754c43fb81c1941439b4d63eeb6b24c294c70ac0a71ff437a6abb795aed2fa4e2483f2a09ac6d1a85b91155cb368219d17883106568c27c924070ad23ed0193dacc353dbb95a4c1de118c1a710f96db6eb9c146c2d0e07873d495d1ad9e3717e09c067422fe5a2da8d9406e1395aa39cf0c82f9c290d1e31a4a4d9b4c381c95079327c296e5db5f8e3c92ef62c03f1c2ac7c4b0174528b5dc0ff37c8c7d0533e4248fb86ca1cb5df91e05e1cc582482e7f12136e8628a48085ff27d15815506c5013f6f65e3c89286616ffd5d08f3fc2194ae51fc4a706e2cd83b6629ea8cce9ee3f968db84c85e3dc0c4b3e4cd6f105a196260f5873c8590401719ce3afd8de96d465b5258ce6b6e53db24fdc8fea3a9062fd2f10ddfb0adcd355724e366afb6b401118d73873c8e682dacbb4be47452eeb98ce51607903126f83ab6b27723818f816f9ede8ef344b3e7bdafdbe8292c6af60835f572ee68629386575541f1b5d5bae84bd85cb65e1ec46a70abf6365f75f975c3b87944e45d0da01f267b569d9ce55cdc90fb3ba1b5f7337f87a914eb5f6f7dd66a56b052430b148e3e295eb82fed70c6a38f738ac63b820db7183d18156abaf638ae366ce6491f0f0e326e1c9c3c8e7df4d62632f7599805f4a8ef06ddee3191e35f20cb7d03e7afbae7cc71a914aa9c1744b420ab5f406771e944ee7e3aec062bec33696df2c5ac2e09648902ea629aac071346ef87f5e54aaf650785f5ade96e0919ec00b99926594cdbdd1e0482e56fc0b78cb6142d2a4be8aed181918b11a841fbb8b442a3e2af1ef1c29528d6447c72b8c2e514aabd0595ebdc698b90ef61a6ee4b400f01fa5c71a2dead6822f33c3341d8c019bd100aa9028b57e517ac6880112335390a8349b90235486eddee01685a7cf1ea158cf9cc1f348a6b113a9d82f2dae95c7ae56f9183d97cbc35a25baea3552b8c2a7de779f6b74db1cfc75d07d79aae1857b601e73cc3686c175f135233989e6d56bdbe611f96a2b51804467e47b474479ccfabddbb4629a0fdc7f73e172da3c168c6c40461371e05a9a332ee3aeb0c9a5985425e102e421f497ec978de92bf73c98fd7d1bbd068e9d8ae82cbc7ef3923bcfe97bd68fe9563195d26a1afdc3bd91b32044fbe68b4bccf29a8d52d13c625d89f73611e371dfe6b73b50d6388cfd87ba92074533cfb34a4d543126f096002d9ce5ecf89cc11b0449f696e1661a36884ac1bf58f397d993c82bcc03c1db156ffa5148da7a73b3be2b6a3b2436c26c24ac0154bee5837d079dfa65d04a208de8b79691518f9056ccdb382e61c8d7606be73322641c71719a23ad6a571f907aa0bd1d8f3170296f3cc88bacd9b3fe944548808e80d00c07bfddf271ace893f56e01f95329aa944d32d9623180142b32e60ce0b908cf2748a29c42a3a59b378e47ebbc4ada9b43133f51bb4d28b821bec1788cc94ac1c8e9e4499aae9e17fcabff0a824637f125e9b358e0"}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.name != Mode { + t.Skip() + } + + var seed [KeySeedSize]byte + _, _ = hex.Decode(seed[:], []byte(tc.seed)) + + pk, sk := NewKeyFromSeed(seed) + + pk2 := [PublicKeySize]byte(*pk) + sk2 := [PrivateKeySize]byte(*sk) + + if hex.EncodeToString(pk2[:]) != tc.expectedPk { + t.Fatal() + } + + if hex.EncodeToString(sk2[:]) != tc.seed { + t.Fatal() + } + }) + } +} + +func TestVerify(t *testing.T) { + for _, tc := range []struct { + name string + seed string + message string + signature string + }{ + {"MAYO_1", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb14803", "d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8", "165e6139a87a4a4966326ba83e6d1ef68d1d7bec62c9d1d267ba09df6815783980f0a6ca0923aee6e5b9495b7ed6fe6c49cc41f794a80d297b19d3f13b0bfe3e8865378f5d24a210dae0982a1036ec9cbe87c29ce9a05a8290d0d1e8603abd163a27b119878663c06745cee03f417300402bf198ee4ac2be99dfe19700f169efe6a365ae0fe9c1bc22028c864ec55072da4f98f878759de68399a2c8d2a2656996927265ce2908e05913f8057707d39dfd3ef58a79cad49f0bd010ca9e8ebde500057bffd0ff28015a60b3061cee55dfa1d59e1a72a8ce639777e6190542733639fbba575aae945583bf804ea129e1a80e12fb628accdcc3f43da37c7194bb641bb8b6404aee481b4ffb13be31941d30c1f9d818182545738ce2644dc9c9b0a1289c6294d3225cc23ee03cc8258176e1a958e90dfed58ad3a859d1b06dee53af6a"}, + {"MAYO_3", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d", "d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8", "01de18cb2cfce7ba2d15677a57e7d86b20a96c7b93daf27232ddc7a0249796ef9321ace5792a4af1ca49908780398a25f9e4b1293ad8209301c769f123cfa430e5bbff1812dd4a8efd5621c109444cb4a1bcf4f1d85abc6fbe493ae0370aba2b2251de011129be10c25f452c987ddb1c499faf1dc8da2e60e7913ab7ade65844a92ed3a5c04c2245c67d342e5067be4a8c024753a8fd5962481b9c52051723f50165ec2b0ec8f73b4b337361c12014607f9fe41c3591eae8653d18b95616b18a84a1a5951ab5082ceebd5d2cfd202fe3ae29b60a790d04295edfbce886eb98d63cce694c9517f5f6832b7cef38efc9c8f4c14e01429f5487cb752f675f4e4212058db63fc29b9c00701810144ac48e7b007f4dfaa80a2ec9899e60f704ac0d0b58c8a716433c91a27d966d2b7b2685da350c9f6c59233988cbb3f8f2383f5eb10e496e64017a0789ca8f702254e1b839382d42b8399488bd74517e280f1719392621596730862678dec519c50cd6bdc07f118217ff68004401fbccf8606c1af2ae6ce0dde06c1bd06a460bace8dc9408f1d5cf76dd063f63db9a0969098ec45fcde2e878d464f4de4bfe1fe710f2132d8e486f64e7d309d9faed999ce368e821a05d79f781fb895a767a09256541cd54242b5df5e262220fe232c637934380b137df86ab4fa07b11f07f914835c87d1547fe526001a61b8ce00721d90d8a4b54b60000ef75d9bdb94fc7f861ee46908ade105ea098cef938f49dae729817633003c87a0a6c0cbfd10185f7657804a2330a7def349da1df84a1a61dc857d2458425"}, + {"MAYO_5", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2db505d7cfad1b4974", "d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8", "7bc32600e7fe4e0d8caf187997624ba57867ec136d7112aceb161cf813ad98aa9709f0e42bc6e86c4c1a32e5b929df9976a4c0d77886d9e5005fad28c3b0e20dd7bb8d49aaaa77d969c72c9d37d78c3ff556ae777f4bac4d505194b6026fb3e17ad631980dfc373c7510c024817c1900d3428984e4e952b49b4c46c928717ea34f417b5f5d6067caf5edc15c1fcbf74d9f7a158958daf27acf3bcb611195278550d06bd83441083e2a64f0e70a8b668453b7f638a64c6d2424cf2ae31fb6efedf951215d14648d2e3282526c0504e4a0b56ac0336a28b98297d16ca2087c3c46ab10275ea0f5611bbde1b44beb420977aa8ec3693ac097901a8e3aef2d377e19ab6d14a42a5c5d7a13ec1df5be83657b7ee84e7671828aff72265bc7fa90829f92df63a159e51e222da67a5201db70597814ac4d793caeda47d7fdd2c59402a2e8a67beb6f9f43b31feaec24854f429e2aec6fe9e7412584f08719c0c5c90e6bc5371d65eebefcc956ada0659b9b0b5f4d4733873fb4daa0e837fda9062d9a79766ef66686f9b355a219d74728ccad98b04391427a30d8b95391b9e54b9f902853d0086fb1bfd136df4dc1ef6eed4c75f449612c76a506ec6c2d7e9676c776f615300230aae0353afc02d80ff377d79a923d2348eec166e0d1b40aa2428a2bf2f87dd89dffd6f1969b39cd84b3d2813078cd4a7753379a79ab6b33f9e8b54a73dde0c46f4fa9afb8a9d0445ae59d202a3df70143baf1de61de9d55681467e343a025b1e0f67af5b1e3e22568d449af192b3e241b6b635e668e851b39e9909ed77ec007a8fba5aec76551e6bb08f454ccf4d48867456c50fdeeaa80f4557d375bda789dc0a5622ccd20d0e2f32e4c7876464f8c8b5393f45eba04085f356927955fe89c475aba7862590656717b5c77f1f79d2a430a2dfb6c779f1794f5f6d374e6e566706f9bfcf33e2070b10af8bc9b853d38215bcb4ad02123445d91b485952514c63a21fac93146a15962fad262c2cdc8ce18a592e4420f60d18ae4ad558b5229b372ed06f021827e932bdb7c43ed6db84ab9c2e408251619bd8b3ce28b9c55c3a21ae2f300758c80086f0ed4a316403195512c2880239f50afa996ce24374ceb25637183b96cf246d204a04018f4344c81aad9cfa98c2ee4f5f6ce3a6f352a86e9a1b07b"}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.name != Mode { + t.Skip() + } + + var seed [KeySeedSize]byte + _, _ = hex.Decode(seed[:], []byte(tc.seed)) + + m, _ := hex.DecodeString(tc.message) + s, _ := hex.DecodeString(tc.signature) + + pk, _ := NewKeyFromSeed(seed) + epk := pk.Expand() + + if !Verify(m, s, epk) { + t.Fatal("should verify") + } + + epk.p1[0] ^= 1 + if Verify(m, s, epk) { + t.Fatal("should not verify") + } + }) + } +} + +func TestPQCgenKATSign(t *testing.T) { + for _, tc := range []struct { + name string + want string + }{ + {"MAYO_1", "d0da809f52866507b6354588cd44b712bac138a8363fde768adb92285b6e9865"}, + {"MAYO_2", "e7382b9b0fd985023a53f292b5d5caf444541a5bd531cf6e1e4d35b8bd864123"}, + {"MAYO_3", "0a53ad1ea675f6be7364d37b552cfa7ca254ac315724fb2871c2fe0567f509b9"}, + {"MAYO_5", "26a4bf3204c41fcb9911f761066668da34554efdd0684346b348ccd669f16b56"}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.name != Mode { + t.Skip() + } + + var seed [48]byte + var eseed [KeySeedSize]byte + for i := 0; i < 48; i++ { + seed[i] = byte(i) + } + f := sha256.New() + g := nist.NewDRBG(&seed) + fmt.Fprintf(f, "# %s\n\n", tc.name) + for i := 0; i < 100; i++ { + mlen := 33 * (i + 1) + _, _ = g.Read(seed[:]) + msg := make([]byte, mlen) + _, _ = g.Read(msg[:]) + + fmt.Fprintf(f, "count = %d\n", i) + fmt.Fprintf(f, "seed = %X\n", seed) + fmt.Fprintf(f, "mlen = %d\n", mlen) + fmt.Fprintf(f, "msg = %X\n", msg) + + g2 := nist.NewDRBG(&seed) + _, _ = g2.Read(eseed[:]) + pk, sk := NewKeyFromSeed(eseed) + + pk2 := [PublicKeySize]byte(*pk) + sk2 := [PrivateKeySize]byte(*sk) + + fmt.Fprintf(f, "pk = %X\n", pk2) + fmt.Fprintf(f, "sk = %X\n", sk2) + fmt.Fprintf(f, "smlen = %d\n", mlen+SignatureSize) + + sig, err := Sign(msg[:], sk.Expand(), &g2) + if err != nil { + t.Fatal() + } + + fmt.Fprintf(f, "sm = %X%X\n\n", sig, msg) + + if !Verify(msg[:], sig, pk.Expand()) { + t.Fatal() + } + } + + if fmt.Sprintf("%x", f.Sum(nil)) != tc.want { + t.Fatal() + } + }) + } +} + +func BenchmarkKeyGen(b *testing.B) { + var seed [KeySeedSize]byte + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(seed[:], uint64(i)) + _, _ = NewKeyFromSeed(seed) + } +} + +func BenchmarkMatMul(b *testing.B) { + var acc [V * O * P]uint64 + var m1 [V * (V + 1) / 2 * P]uint64 + var m2 [V * O]byte + + for i := 0; i < b.N; i++ { + m1[i%6844] = uint64(i) + binary.LittleEndian.PutUint64(m2[:], uint64(i)) + + mulAddMUpperTriangularMatXMat(acc[:], m1[:], m2[:], V, O) + } +} + +func BenchmarkVerify(b *testing.B) { + var seed [KeySeedSize]byte + var msg [8]byte + pk, sk := NewKeyFromSeed(seed) + sig, _ := Sign(msg[:], sk.Expand(), nil) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Verify(msg[:], sig[:], pk.Expand()) + } +} + +func BenchmarkVerifyExpandedKey(b *testing.B) { + var seed [KeySeedSize]byte + var msg [8]byte + pk, sk := NewKeyFromSeed(seed) + sig, _ := Sign(msg[:], sk.Expand(), nil) + epk := pk.Expand() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Verify(msg[:], sig[:], epk) + } +} + +type zeroReader struct{} + +func (zeroReader) Read(buf []byte) (int, error) { + for i := range buf { + buf[i] = 0 + } + return len(buf), nil +} + +func BenchmarkSign(b *testing.B) { + var seed [KeySeedSize]byte + var msg [8]byte + _, sk := NewKeyFromSeed(seed) + b.ResetTimer() + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(msg[:], uint64(i)) + _, _ = Sign(msg[:], sk.Expand(), zeroReader{}) + } +} + +func BenchmarkSignExpandedKey(b *testing.B) { + var seed [KeySeedSize]byte + var msg [8]byte + _, sk := NewKeyFromSeed(seed) + esk := sk.Expand() + b.ResetTimer() + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(msg[:], uint64(i)) + _, _ = Sign(msg[:], esk, zeroReader{}) + } +} diff --git a/sign/mayo/mode5/internal/params.go b/sign/mayo/mode5/internal/params.go new file mode 100644 index 000000000..d4d728d3e --- /dev/null +++ b/sign/mayo/mode5/internal/params.go @@ -0,0 +1,42 @@ +// Code generated from params.templ.go. DO NOT EDIT. + +package internal + +const ( + Mode = "MAYO_5" + + N = 133 + M = 128 + O = 12 + K = 12 + + KeySeedSize = 40 + DigestSize = 64 +) + +var Tail = [5]uint8{4, 8, 0, 4, 2} + +const ( + V = N - O + + // The division by 2 converts the number of nibbles to bytes (when packed together). + // We don't explicitly round up beause given parameters ensure this will not happen. + OSize = V * O / 2 // O is a V*O matrix of GF(16) + P1Size = (V * (V + 1) / 2) * M / 2 // P1 consists of M V*V triangular matrices + P2Size = V * O * M / 2 + P3Size = (O * (O + 1) / 2) * M / 2 // P3 consists of M O*O triangular matrices + + VSize = (V + 1) / 2 // +1 to round up + + SignatureSize = (K*N+1)/2 + SaltSize + + PublicKeySeedSize = 16 + + PrivateKeySize = KeySeedSize + PublicKeySize = PublicKeySeedSize + P3Size + + SaltSize = KeySeedSize + + // P denotes the number of uint64 words required to fit M GF16 elements + P = M / 16 +) diff --git a/sign/mayo/mode5/mayo.go b/sign/mayo/mode5/mayo.go new file mode 100644 index 000000000..1ba377248 --- /dev/null +++ b/sign/mayo/mode5/mayo.go @@ -0,0 +1,151 @@ +// Code generated from modePkg.templ.go. DO NOT EDIT. + +// mode5 implements the MAYO signature scheme MAYO_5 +// as submitted to round1 of the NIST PQC competition of Additional Signature Scehemes and described in +// +// https://csrc.nist.gov/csrc/media/Projects/pqc-dig-sig/documents/round-1/spec-files/mayo-spec-web.pdf +// +// This implemented the nibble-sliced version as proposed in +// +// https://eprint.iacr.org/2023/1683 +package mode5 + +import ( + "crypto" + "errors" + "io" + + "github.com/cloudflare/circl/sign/mayo/mode5/internal" +) + +const ( + // Size of seed for NewKeyFromSeed + SeedSize = internal.KeySeedSize + + // Size of a packed PublicKey + PublicKeySize = internal.PublicKeySize + + // Size of a packed PrivateKey + PrivateKeySize = internal.PrivateKeySize + + // Size of a signature + SignatureSize = internal.SignatureSize +) + +// PublicKey is the type of Mayo1 public key +type PublicKey internal.PublicKey + +// PrivateKey is the type of Mayo1 private key +type PrivateKey internal.PrivateKey + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { + pk, sk, err := internal.GenerateKey(rand) + return (*PublicKey)(pk), (*PrivateKey)(sk), err +} + +// NewKeyFromSeed derives a public/private key pair using the given seed. +func NewKeyFromSeed(seed [SeedSize]byte) (*PublicKey, *PrivateKey) { + pk, sk := internal.NewKeyFromSeed(seed) + return (*PublicKey)(pk), (*PrivateKey)(sk) +} + +// Sign signs the given message using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { + return internal.Sign( + msg, + (*internal.PrivateKey)(sk).Expand(), + rand, + ) +} + +// Verify checks whether the given signature by pk on msg is valid. +func Verify(pk *PublicKey, msg []byte, signature []byte) bool { + return internal.Verify( + msg, + signature, + (*internal.PublicKey)(pk).Expand(), + ) +} + +// Packs the public key. +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var buf [PublicKeySize]byte + b := [PublicKeySize]byte(*pk) + copy(buf[:], b[:]) + return buf[:], nil +} + +// Packs the private key. +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var buf [PrivateKeySize]byte + b := [PrivateKeySize]byte(*sk) + copy(buf[:], b[:]) + return buf[:], nil +} + +// Unpacks the public key from data. +func (pk *PublicKey) UnmarshalBinary(data []byte) error { + if len(data) != PublicKeySize { + return errors.New("packed public key must be of mode5.PublicKeySize bytes") + } + self := (*(*[PublicKeySize]byte)(pk)) + copy(self[:], data[:]) + return nil +} + +// Unpacks the private key from data. +func (sk *PrivateKey) UnmarshalBinary(data []byte) error { + if len(data) != PublicKeySize { + return errors.New("packed private key must be of mode5.PrivateKeySize bytes") + } + self := (*(*[PrivateKeySize]byte)(sk)) + copy(self[:], data[:]) + return nil +} + +// Sign signs the given message. +// +// opts.HashFunc() must return zero, which can be achieved by passing +// crypto.Hash(0) for opts. Will only return an error +// if opts.HashFunc() is non-zero. +// +// This function is used to make PrivateKey implement the crypto.Signer +// interface. The package-level Sign function might be more convenient +// to use. +func (sk *PrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ( + signature []byte, err error) { + if opts.HashFunc() != crypto.Hash(0) { + return nil, errors.New("mayo: cannot sign hashed message") + } + + return Sign(sk, msg, rand) +} + +// Computes the public key corresponding to this private key. +// +// Returns a *PublicKey. The type crypto.PublicKey is used to make +// PrivateKey implement the crypto.Signer interface. +func (sk *PrivateKey) Public() crypto.PublicKey { + return (*internal.PrivateKey)(sk).Public +} + +// Equal returns whether the two private keys equal. +func (sk *PrivateKey) Equal(other crypto.PrivateKey) bool { + castOther, ok := other.(*PrivateKey) + if !ok { + return false + } + return (*internal.PrivateKey)(sk).Equal((*internal.PrivateKey)(castOther)) +} + +// Equal returns whether the two public keys equal. +func (pk *PublicKey) Equal(other crypto.PublicKey) bool { + castOther, ok := other.(*PublicKey) + if !ok { + return false + } + return (*internal.PublicKey)(pk).Equal((*internal.PublicKey)(castOther)) +} diff --git a/sign/mayo/templates/modePkg.templ.go b/sign/mayo/templates/modePkg.templ.go new file mode 100644 index 000000000..4f65f5150 --- /dev/null +++ b/sign/mayo/templates/modePkg.templ.go @@ -0,0 +1,155 @@ +// +build ignore +// The previous line (and this one up to the warning below) is removed by the +// template generator. + +// Code generated from modePkg.templ.go. DO NOT EDIT. + +// {{.Pkg}} implements the MAYO signature scheme {{.Name}} +// as submitted to round1 of the NIST PQC competition of Additional Signature Scehemes and described in +// +// https://csrc.nist.gov/csrc/media/Projects/pqc-dig-sig/documents/round-1/spec-files/mayo-spec-web.pdf +// +// This implemented the nibble-sliced version as proposed in +// +// https://eprint.iacr.org/2023/1683 +package {{.Pkg}} + +import ( + "crypto" + "errors" + "io" + + "github.com/cloudflare/circl/sign/mayo/{{.Pkg}}/internal" +) + +const ( + // Size of seed for NewKeyFromSeed + SeedSize = internal.KeySeedSize + + // Size of a packed PublicKey + PublicKeySize = internal.PublicKeySize + + // Size of a packed PrivateKey + PrivateKeySize = internal.PrivateKeySize + + // Size of a signature + SignatureSize = internal.SignatureSize +) + +// PublicKey is the type of Mayo1 public key +type PublicKey internal.PublicKey + +// PrivateKey is the type of Mayo1 private key +type PrivateKey internal.PrivateKey + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { + pk, sk, err := internal.GenerateKey(rand) + return (*PublicKey)(pk), (*PrivateKey)(sk), err +} + +// NewKeyFromSeed derives a public/private key pair using the given seed. +func NewKeyFromSeed(seed [SeedSize]byte) (*PublicKey, *PrivateKey) { + pk, sk := internal.NewKeyFromSeed(seed) + return (*PublicKey)(pk), (*PrivateKey)(sk) +} + +// Sign signs the given message using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { + return internal.Sign( + msg, + (*internal.PrivateKey)(sk).Expand(), + rand, + ) +} + +// Verify checks whether the given signature by pk on msg is valid. +func Verify(pk *PublicKey, msg []byte, signature []byte) bool { + return internal.Verify( + msg, + signature, + (*internal.PublicKey)(pk).Expand(), + ) +} + +// Packs the public key. +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var buf [PublicKeySize]byte + b := [PublicKeySize]byte(*pk) + copy(buf[:], b[:]) + return buf[:], nil +} + +// Packs the private key. +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var buf [PrivateKeySize]byte + b := [PrivateKeySize]byte(*sk) + copy(buf[:], b[:]) + return buf[:], nil +} + +// Unpacks the public key from data. +func (pk *PublicKey) UnmarshalBinary(data []byte) error { + if len(data) != PublicKeySize { + return errors.New("packed public key must be of {{.Pkg}}.PublicKeySize bytes") + } + self := (*(*[PublicKeySize]byte)(pk)) + copy(self[:], data[:]) + return nil +} + +// Unpacks the private key from data. +func (sk *PrivateKey) UnmarshalBinary(data []byte) error { + if len(data) != PublicKeySize { + return errors.New("packed private key must be of {{.Pkg}}.PrivateKeySize bytes") + } + self := (*(*[PrivateKeySize]byte)(sk)) + copy(self[:], data[:]) + return nil +} + +// Sign signs the given message. +// +// opts.HashFunc() must return zero, which can be achieved by passing +// crypto.Hash(0) for opts. Will only return an error +// if opts.HashFunc() is non-zero. +// +// This function is used to make PrivateKey implement the crypto.Signer +// interface. The package-level Sign function might be more convenient +// to use. +func (sk *PrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ( + signature []byte, err error) { + if opts.HashFunc() != crypto.Hash(0) { + return nil, errors.New("mayo: cannot sign hashed message") + } + + return Sign(sk, msg, rand) +} + +// Computes the public key corresponding to this private key. +// +// Returns a *PublicKey. The type crypto.PublicKey is used to make +// PrivateKey implement the crypto.Signer interface. +func (sk *PrivateKey) Public() crypto.PublicKey { + return (*internal.PrivateKey)(sk).Public +} + +// Equal returns whether the two private keys equal. +func (sk *PrivateKey) Equal(other crypto.PrivateKey) bool { + castOther, ok := other.(*PrivateKey) + if !ok { + return false + } + return (*internal.PrivateKey)(sk).Equal((*internal.PrivateKey)(castOther)) +} + +// Equal returns whether the two public keys equal. +func (pk *PublicKey) Equal(other crypto.PublicKey) bool { + castOther, ok := other.(*PublicKey) + if !ok { + return false + } + return (*internal.PublicKey)(pk).Equal((*internal.PublicKey)(castOther)) +} diff --git a/sign/mayo/templates/params.templ.go b/sign/mayo/templates/params.templ.go new file mode 100644 index 000000000..619e8dffe --- /dev/null +++ b/sign/mayo/templates/params.templ.go @@ -0,0 +1,46 @@ +// +build ignore +// The previous line (and this one up to the warning below) is removed by the +// template generator. + +// Code generated from params.templ.go. DO NOT EDIT. + +package internal + +const ( + Mode = "{{.Name}}" + + N = {{.N}} + M = {{.M}} + O = {{.O}} + K = {{.K}} + + KeySeedSize = {{.KeySeedSize}} + DigestSize = {{.DigestSize}} +) + +var Tail = [5]uint8{ {{index .Tail 0}}, {{index .Tail 1}}, {{index .Tail 2}}, {{index .Tail 3}}, {{index .Tail 4}} } + +const ( + V = N - O + + // The division by 2 converts the number of nibbles to bytes (when packed together). + // We don't explicitly round up beause given parameters ensure this will not happen. + OSize = V * O / 2 // O is a V*O matrix of GF(16) + P1Size = (V * (V + 1) / 2) * M / 2 // P1 consists of M V*V triangular matrices + P2Size = V * O * M / 2 + P3Size = (O * (O + 1) / 2) * M / 2 // P3 consists of M O*O triangular matrices + + VSize = (V + 1) / 2 // +1 to round up + + SignatureSize = (K*N+1)/2 + SaltSize + + PublicKeySeedSize = 16 + + PrivateKeySize = KeySeedSize + PublicKeySize = PublicKeySeedSize + P3Size + + SaltSize = KeySeedSize + + // P denotes the number of uint64 words required to fit M GF16 elements + P = M / 16 +) From cf3060b6a918df84ec77dcc48f5bee92946ef872 Mon Sep 17 00:00:00 2001 From: Shang-Yi Yang Date: Thu, 29 Feb 2024 13:37:20 +0800 Subject: [PATCH 2/9] Add MAYO to schemes --- sign/mayo/mode1/mayo.go | 10 ++- sign/mayo/mode1/signapi.go | 87 ++++++++++++++++++++++++++ sign/mayo/mode2/mayo.go | 10 ++- sign/mayo/mode2/signapi.go | 87 ++++++++++++++++++++++++++ sign/mayo/mode3/mayo.go | 10 ++- sign/mayo/mode3/signapi.go | 87 ++++++++++++++++++++++++++ sign/mayo/mode5/mayo.go | 10 ++- sign/mayo/mode5/signapi.go | 87 ++++++++++++++++++++++++++ sign/mayo/templates/modePkg.templ.go | 10 ++- sign/mayo/templates/signapi.templ.go | 91 ++++++++++++++++++++++++++++ sign/schemes/schemes.go | 12 ++++ sign/schemes/schemes_test.go | 4 ++ 12 files changed, 490 insertions(+), 15 deletions(-) create mode 100644 sign/mayo/mode1/signapi.go create mode 100644 sign/mayo/mode2/signapi.go create mode 100644 sign/mayo/mode3/signapi.go create mode 100644 sign/mayo/mode5/signapi.go create mode 100644 sign/mayo/templates/signapi.templ.go diff --git a/sign/mayo/mode1/mayo.go b/sign/mayo/mode1/mayo.go index e8d115228..229c41571 100644 --- a/sign/mayo/mode1/mayo.go +++ b/sign/mayo/mode1/mayo.go @@ -15,6 +15,7 @@ import ( "errors" "io" + "github.com/cloudflare/circl/sign" "github.com/cloudflare/circl/sign/mayo/mode1/internal" ) @@ -38,6 +39,9 @@ type PublicKey internal.PublicKey // PrivateKey is the type of Mayo1 private key type PrivateKey internal.PrivateKey +func (sk *PrivateKey) Scheme() sign.Scheme { return sch } +func (pk *PublicKey) Scheme() sign.Scheme { return sch } + // GenerateKey generates a public/private key pair using entropy from rand. // If rand is nil, crypto/rand.Reader will be used. func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { @@ -91,17 +95,17 @@ func (pk *PublicKey) UnmarshalBinary(data []byte) error { if len(data) != PublicKeySize { return errors.New("packed public key must be of mode1.PublicKeySize bytes") } - self := (*(*[PublicKeySize]byte)(pk)) + self := (*[PublicKeySize]byte)(pk) copy(self[:], data[:]) return nil } // Unpacks the private key from data. func (sk *PrivateKey) UnmarshalBinary(data []byte) error { - if len(data) != PublicKeySize { + if len(data) != PrivateKeySize { return errors.New("packed private key must be of mode1.PrivateKeySize bytes") } - self := (*(*[PrivateKeySize]byte)(sk)) + self := (*[PrivateKeySize]byte)(sk) copy(self[:], data[:]) return nil } diff --git a/sign/mayo/mode1/signapi.go b/sign/mayo/mode1/signapi.go new file mode 100644 index 000000000..7fa1a9b03 --- /dev/null +++ b/sign/mayo/mode1/signapi.go @@ -0,0 +1,87 @@ +// Code generated from modePkg.templ.go. DO NOT EDIT. + +package mode1 + +import ( + "github.com/cloudflare/circl/sign" +) + +var sch sign.Scheme = &scheme{} + +// Scheme returns a signature interface. +func Scheme() sign.Scheme { return sch } + +type scheme struct{} + +func (*scheme) Name() string { return "MAYO_1" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SignatureSize() int { return SignatureSize } +func (*scheme) SeedSize() int { return SeedSize } +func (*scheme) SupportsContext() bool { return false } + +func (*scheme) GenerateKey() (sign.PublicKey, sign.PrivateKey, error) { + sk, pk, err := GenerateKey(nil) + return sk, pk, err +} + +func (*scheme) Sign( + sk sign.PrivateKey, + message []byte, + opts *sign.SignatureOpts, +) []byte { + priv, ok := sk.(*PrivateKey) + if !ok { + panic(sign.ErrTypeMismatch) + } + if opts != nil && opts.Context != "" { + panic(sign.ErrContextNotSupported) + } + sig, err := Sign(priv, message, nil) + if err != nil { + panic("") + } + return sig +} + +func (*scheme) Verify( + pk sign.PublicKey, + message, signature []byte, + opts *sign.SignatureOpts, +) bool { + pub, ok := pk.(*PublicKey) + if !ok { + panic(sign.ErrTypeMismatch) + } + if opts != nil && opts.Context != "" { + panic(sign.ErrContextNotSupported) + } + return Verify(pub, message, signature) +} + +func (*scheme) DeriveKey(seed []byte) (sign.PublicKey, sign.PrivateKey) { + if len(seed) != SeedSize { + panic(sign.ErrSeedSize) + } + var tmp [SeedSize]byte + copy(tmp[:], seed) + return NewKeyFromSeed(tmp) +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (sign.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, sign.ErrPubKeySize + } + var ret PublicKey + err := ret.UnmarshalBinary(buf) + return &ret, err +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (sign.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, sign.ErrPrivKeySize + } + var ret PrivateKey + err := ret.UnmarshalBinary(buf) + return &ret, err +} diff --git a/sign/mayo/mode2/mayo.go b/sign/mayo/mode2/mayo.go index 5905c4992..8252e6c58 100644 --- a/sign/mayo/mode2/mayo.go +++ b/sign/mayo/mode2/mayo.go @@ -15,6 +15,7 @@ import ( "errors" "io" + "github.com/cloudflare/circl/sign" "github.com/cloudflare/circl/sign/mayo/mode2/internal" ) @@ -38,6 +39,9 @@ type PublicKey internal.PublicKey // PrivateKey is the type of Mayo1 private key type PrivateKey internal.PrivateKey +func (sk *PrivateKey) Scheme() sign.Scheme { return sch } +func (pk *PublicKey) Scheme() sign.Scheme { return sch } + // GenerateKey generates a public/private key pair using entropy from rand. // If rand is nil, crypto/rand.Reader will be used. func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { @@ -91,17 +95,17 @@ func (pk *PublicKey) UnmarshalBinary(data []byte) error { if len(data) != PublicKeySize { return errors.New("packed public key must be of mode2.PublicKeySize bytes") } - self := (*(*[PublicKeySize]byte)(pk)) + self := (*[PublicKeySize]byte)(pk) copy(self[:], data[:]) return nil } // Unpacks the private key from data. func (sk *PrivateKey) UnmarshalBinary(data []byte) error { - if len(data) != PublicKeySize { + if len(data) != PrivateKeySize { return errors.New("packed private key must be of mode2.PrivateKeySize bytes") } - self := (*(*[PrivateKeySize]byte)(sk)) + self := (*[PrivateKeySize]byte)(sk) copy(self[:], data[:]) return nil } diff --git a/sign/mayo/mode2/signapi.go b/sign/mayo/mode2/signapi.go new file mode 100644 index 000000000..c5a204bfe --- /dev/null +++ b/sign/mayo/mode2/signapi.go @@ -0,0 +1,87 @@ +// Code generated from modePkg.templ.go. DO NOT EDIT. + +package mode2 + +import ( + "github.com/cloudflare/circl/sign" +) + +var sch sign.Scheme = &scheme{} + +// Scheme returns a signature interface. +func Scheme() sign.Scheme { return sch } + +type scheme struct{} + +func (*scheme) Name() string { return "MAYO_2" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SignatureSize() int { return SignatureSize } +func (*scheme) SeedSize() int { return SeedSize } +func (*scheme) SupportsContext() bool { return false } + +func (*scheme) GenerateKey() (sign.PublicKey, sign.PrivateKey, error) { + sk, pk, err := GenerateKey(nil) + return sk, pk, err +} + +func (*scheme) Sign( + sk sign.PrivateKey, + message []byte, + opts *sign.SignatureOpts, +) []byte { + priv, ok := sk.(*PrivateKey) + if !ok { + panic(sign.ErrTypeMismatch) + } + if opts != nil && opts.Context != "" { + panic(sign.ErrContextNotSupported) + } + sig, err := Sign(priv, message, nil) + if err != nil { + panic("") + } + return sig +} + +func (*scheme) Verify( + pk sign.PublicKey, + message, signature []byte, + opts *sign.SignatureOpts, +) bool { + pub, ok := pk.(*PublicKey) + if !ok { + panic(sign.ErrTypeMismatch) + } + if opts != nil && opts.Context != "" { + panic(sign.ErrContextNotSupported) + } + return Verify(pub, message, signature) +} + +func (*scheme) DeriveKey(seed []byte) (sign.PublicKey, sign.PrivateKey) { + if len(seed) != SeedSize { + panic(sign.ErrSeedSize) + } + var tmp [SeedSize]byte + copy(tmp[:], seed) + return NewKeyFromSeed(tmp) +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (sign.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, sign.ErrPubKeySize + } + var ret PublicKey + err := ret.UnmarshalBinary(buf) + return &ret, err +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (sign.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, sign.ErrPrivKeySize + } + var ret PrivateKey + err := ret.UnmarshalBinary(buf) + return &ret, err +} diff --git a/sign/mayo/mode3/mayo.go b/sign/mayo/mode3/mayo.go index 3ca521fe4..d0fafaf75 100644 --- a/sign/mayo/mode3/mayo.go +++ b/sign/mayo/mode3/mayo.go @@ -15,6 +15,7 @@ import ( "errors" "io" + "github.com/cloudflare/circl/sign" "github.com/cloudflare/circl/sign/mayo/mode3/internal" ) @@ -38,6 +39,9 @@ type PublicKey internal.PublicKey // PrivateKey is the type of Mayo1 private key type PrivateKey internal.PrivateKey +func (sk *PrivateKey) Scheme() sign.Scheme { return sch } +func (pk *PublicKey) Scheme() sign.Scheme { return sch } + // GenerateKey generates a public/private key pair using entropy from rand. // If rand is nil, crypto/rand.Reader will be used. func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { @@ -91,17 +95,17 @@ func (pk *PublicKey) UnmarshalBinary(data []byte) error { if len(data) != PublicKeySize { return errors.New("packed public key must be of mode3.PublicKeySize bytes") } - self := (*(*[PublicKeySize]byte)(pk)) + self := (*[PublicKeySize]byte)(pk) copy(self[:], data[:]) return nil } // Unpacks the private key from data. func (sk *PrivateKey) UnmarshalBinary(data []byte) error { - if len(data) != PublicKeySize { + if len(data) != PrivateKeySize { return errors.New("packed private key must be of mode3.PrivateKeySize bytes") } - self := (*(*[PrivateKeySize]byte)(sk)) + self := (*[PrivateKeySize]byte)(sk) copy(self[:], data[:]) return nil } diff --git a/sign/mayo/mode3/signapi.go b/sign/mayo/mode3/signapi.go new file mode 100644 index 000000000..ac894c3b9 --- /dev/null +++ b/sign/mayo/mode3/signapi.go @@ -0,0 +1,87 @@ +// Code generated from modePkg.templ.go. DO NOT EDIT. + +package mode3 + +import ( + "github.com/cloudflare/circl/sign" +) + +var sch sign.Scheme = &scheme{} + +// Scheme returns a signature interface. +func Scheme() sign.Scheme { return sch } + +type scheme struct{} + +func (*scheme) Name() string { return "MAYO_3" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SignatureSize() int { return SignatureSize } +func (*scheme) SeedSize() int { return SeedSize } +func (*scheme) SupportsContext() bool { return false } + +func (*scheme) GenerateKey() (sign.PublicKey, sign.PrivateKey, error) { + sk, pk, err := GenerateKey(nil) + return sk, pk, err +} + +func (*scheme) Sign( + sk sign.PrivateKey, + message []byte, + opts *sign.SignatureOpts, +) []byte { + priv, ok := sk.(*PrivateKey) + if !ok { + panic(sign.ErrTypeMismatch) + } + if opts != nil && opts.Context != "" { + panic(sign.ErrContextNotSupported) + } + sig, err := Sign(priv, message, nil) + if err != nil { + panic("") + } + return sig +} + +func (*scheme) Verify( + pk sign.PublicKey, + message, signature []byte, + opts *sign.SignatureOpts, +) bool { + pub, ok := pk.(*PublicKey) + if !ok { + panic(sign.ErrTypeMismatch) + } + if opts != nil && opts.Context != "" { + panic(sign.ErrContextNotSupported) + } + return Verify(pub, message, signature) +} + +func (*scheme) DeriveKey(seed []byte) (sign.PublicKey, sign.PrivateKey) { + if len(seed) != SeedSize { + panic(sign.ErrSeedSize) + } + var tmp [SeedSize]byte + copy(tmp[:], seed) + return NewKeyFromSeed(tmp) +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (sign.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, sign.ErrPubKeySize + } + var ret PublicKey + err := ret.UnmarshalBinary(buf) + return &ret, err +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (sign.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, sign.ErrPrivKeySize + } + var ret PrivateKey + err := ret.UnmarshalBinary(buf) + return &ret, err +} diff --git a/sign/mayo/mode5/mayo.go b/sign/mayo/mode5/mayo.go index 1ba377248..12d0b2f86 100644 --- a/sign/mayo/mode5/mayo.go +++ b/sign/mayo/mode5/mayo.go @@ -15,6 +15,7 @@ import ( "errors" "io" + "github.com/cloudflare/circl/sign" "github.com/cloudflare/circl/sign/mayo/mode5/internal" ) @@ -38,6 +39,9 @@ type PublicKey internal.PublicKey // PrivateKey is the type of Mayo1 private key type PrivateKey internal.PrivateKey +func (sk *PrivateKey) Scheme() sign.Scheme { return sch } +func (pk *PublicKey) Scheme() sign.Scheme { return sch } + // GenerateKey generates a public/private key pair using entropy from rand. // If rand is nil, crypto/rand.Reader will be used. func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { @@ -91,17 +95,17 @@ func (pk *PublicKey) UnmarshalBinary(data []byte) error { if len(data) != PublicKeySize { return errors.New("packed public key must be of mode5.PublicKeySize bytes") } - self := (*(*[PublicKeySize]byte)(pk)) + self := (*[PublicKeySize]byte)(pk) copy(self[:], data[:]) return nil } // Unpacks the private key from data. func (sk *PrivateKey) UnmarshalBinary(data []byte) error { - if len(data) != PublicKeySize { + if len(data) != PrivateKeySize { return errors.New("packed private key must be of mode5.PrivateKeySize bytes") } - self := (*(*[PrivateKeySize]byte)(sk)) + self := (*[PrivateKeySize]byte)(sk) copy(self[:], data[:]) return nil } diff --git a/sign/mayo/mode5/signapi.go b/sign/mayo/mode5/signapi.go new file mode 100644 index 000000000..c4d3d85ae --- /dev/null +++ b/sign/mayo/mode5/signapi.go @@ -0,0 +1,87 @@ +// Code generated from modePkg.templ.go. DO NOT EDIT. + +package mode5 + +import ( + "github.com/cloudflare/circl/sign" +) + +var sch sign.Scheme = &scheme{} + +// Scheme returns a signature interface. +func Scheme() sign.Scheme { return sch } + +type scheme struct{} + +func (*scheme) Name() string { return "MAYO_5" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SignatureSize() int { return SignatureSize } +func (*scheme) SeedSize() int { return SeedSize } +func (*scheme) SupportsContext() bool { return false } + +func (*scheme) GenerateKey() (sign.PublicKey, sign.PrivateKey, error) { + sk, pk, err := GenerateKey(nil) + return sk, pk, err +} + +func (*scheme) Sign( + sk sign.PrivateKey, + message []byte, + opts *sign.SignatureOpts, +) []byte { + priv, ok := sk.(*PrivateKey) + if !ok { + panic(sign.ErrTypeMismatch) + } + if opts != nil && opts.Context != "" { + panic(sign.ErrContextNotSupported) + } + sig, err := Sign(priv, message, nil) + if err != nil { + panic("") + } + return sig +} + +func (*scheme) Verify( + pk sign.PublicKey, + message, signature []byte, + opts *sign.SignatureOpts, +) bool { + pub, ok := pk.(*PublicKey) + if !ok { + panic(sign.ErrTypeMismatch) + } + if opts != nil && opts.Context != "" { + panic(sign.ErrContextNotSupported) + } + return Verify(pub, message, signature) +} + +func (*scheme) DeriveKey(seed []byte) (sign.PublicKey, sign.PrivateKey) { + if len(seed) != SeedSize { + panic(sign.ErrSeedSize) + } + var tmp [SeedSize]byte + copy(tmp[:], seed) + return NewKeyFromSeed(tmp) +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (sign.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, sign.ErrPubKeySize + } + var ret PublicKey + err := ret.UnmarshalBinary(buf) + return &ret, err +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (sign.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, sign.ErrPrivKeySize + } + var ret PrivateKey + err := ret.UnmarshalBinary(buf) + return &ret, err +} diff --git a/sign/mayo/templates/modePkg.templ.go b/sign/mayo/templates/modePkg.templ.go index 4f65f5150..304ca8432 100644 --- a/sign/mayo/templates/modePkg.templ.go +++ b/sign/mayo/templates/modePkg.templ.go @@ -19,6 +19,7 @@ import ( "errors" "io" + "github.com/cloudflare/circl/sign" "github.com/cloudflare/circl/sign/mayo/{{.Pkg}}/internal" ) @@ -42,6 +43,9 @@ type PublicKey internal.PublicKey // PrivateKey is the type of Mayo1 private key type PrivateKey internal.PrivateKey +func (sk *PrivateKey) Scheme() sign.Scheme { return sch } +func (pk *PublicKey) Scheme() sign.Scheme { return sch } + // GenerateKey generates a public/private key pair using entropy from rand. // If rand is nil, crypto/rand.Reader will be used. func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { @@ -95,17 +99,17 @@ func (pk *PublicKey) UnmarshalBinary(data []byte) error { if len(data) != PublicKeySize { return errors.New("packed public key must be of {{.Pkg}}.PublicKeySize bytes") } - self := (*(*[PublicKeySize]byte)(pk)) + self := (*[PublicKeySize]byte)(pk) copy(self[:], data[:]) return nil } // Unpacks the private key from data. func (sk *PrivateKey) UnmarshalBinary(data []byte) error { - if len(data) != PublicKeySize { + if len(data) != PrivateKeySize { return errors.New("packed private key must be of {{.Pkg}}.PrivateKeySize bytes") } - self := (*(*[PrivateKeySize]byte)(sk)) + self := (*[PrivateKeySize]byte)(sk) copy(self[:], data[:]) return nil } diff --git a/sign/mayo/templates/signapi.templ.go b/sign/mayo/templates/signapi.templ.go new file mode 100644 index 000000000..006f17163 --- /dev/null +++ b/sign/mayo/templates/signapi.templ.go @@ -0,0 +1,91 @@ +// +build ignore +// The previous line (and this one up to the warning below) is removed by the +// template generator. + +// Code generated from modePkg.templ.go. DO NOT EDIT. + +package {{.Pkg}} + +import ( + "github.com/cloudflare/circl/sign" +) + +var sch sign.Scheme = &scheme{} + +// Scheme returns a signature interface. +func Scheme() sign.Scheme { return sch } + +type scheme struct{} + +func (*scheme) Name() string { return "{{.Name}}" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SignatureSize() int { return SignatureSize } +func (*scheme) SeedSize() int { return SeedSize } +func (*scheme) SupportsContext() bool { return false } + +func (*scheme) GenerateKey() (sign.PublicKey, sign.PrivateKey, error) { + sk, pk, err := GenerateKey(nil) + return sk, pk, err +} + +func (*scheme) Sign( + sk sign.PrivateKey, + message []byte, + opts *sign.SignatureOpts, +) []byte { + priv, ok := sk.(*PrivateKey) + if !ok { + panic(sign.ErrTypeMismatch) + } + if opts != nil && opts.Context != "" { + panic(sign.ErrContextNotSupported) + } + sig, err := Sign(priv, message, nil) + if err != nil { + panic("") + } + return sig +} + +func (*scheme) Verify( + pk sign.PublicKey, + message, signature []byte, + opts *sign.SignatureOpts, +) bool { + pub, ok := pk.(*PublicKey) + if !ok { + panic(sign.ErrTypeMismatch) + } + if opts != nil && opts.Context != "" { + panic(sign.ErrContextNotSupported) + } + return Verify(pub, message, signature) +} + +func (*scheme) DeriveKey(seed []byte) (sign.PublicKey, sign.PrivateKey) { + if len(seed) != SeedSize { + panic(sign.ErrSeedSize) + } + var tmp [SeedSize]byte + copy(tmp[:], seed) + return NewKeyFromSeed(tmp) +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (sign.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, sign.ErrPubKeySize + } + var ret PublicKey + err := ret.UnmarshalBinary(buf) + return &ret, err +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (sign.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, sign.ErrPrivKeySize + } + var ret PrivateKey + err := ret.UnmarshalBinary(buf) + return &ret, err +} diff --git a/sign/schemes/schemes.go b/sign/schemes/schemes.go index 66ee61253..aaf3e26b7 100644 --- a/sign/schemes/schemes.go +++ b/sign/schemes/schemes.go @@ -6,6 +6,10 @@ // Ed448 // Ed25519-Dilithium2 // Ed448-Dilithium3 +// MAYO_1 +// MAYO_2 +// MAYO_3 +// MAYO_5 package schemes import ( @@ -16,6 +20,10 @@ import ( "github.com/cloudflare/circl/sign/ed448" "github.com/cloudflare/circl/sign/eddilithium2" "github.com/cloudflare/circl/sign/eddilithium3" + mayo1 "github.com/cloudflare/circl/sign/mayo/mode1" + mayo2 "github.com/cloudflare/circl/sign/mayo/mode2" + mayo3 "github.com/cloudflare/circl/sign/mayo/mode3" + mayo5 "github.com/cloudflare/circl/sign/mayo/mode5" ) var allSchemes = [...]sign.Scheme{ @@ -23,6 +31,10 @@ var allSchemes = [...]sign.Scheme{ ed448.Scheme(), eddilithium2.Scheme(), eddilithium3.Scheme(), + mayo1.Scheme(), + mayo2.Scheme(), + mayo3.Scheme(), + mayo5.Scheme(), } var allSchemeNames map[string]sign.Scheme diff --git a/sign/schemes/schemes_test.go b/sign/schemes/schemes_test.go index 2d8e86512..11c5eee5d 100644 --- a/sign/schemes/schemes_test.go +++ b/sign/schemes/schemes_test.go @@ -117,6 +117,10 @@ func Example() { // Ed448 // Ed25519-Dilithium2 // Ed448-Dilithium3 + // MAYO_1 + // MAYO_2 + // MAYO_3 + // MAYO_5 } func BenchmarkGenerateKeyPair(b *testing.B) { From 9cfb7b1489c02ff1d693c1402af3d7efd02926df Mon Sep 17 00:00:00 2001 From: Shang-Yi Yang Date: Fri, 8 Mar 2024 13:16:04 +0800 Subject: [PATCH 3/9] Add test for resampling --- sign/mayo/mode1/internal/mayo_test.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/sign/mayo/mode1/internal/mayo_test.go b/sign/mayo/mode1/internal/mayo_test.go index b840400de..ee48029bc 100644 --- a/sign/mayo/mode1/internal/mayo_test.go +++ b/sign/mayo/mode1/internal/mayo_test.go @@ -81,6 +81,32 @@ func TestVerify(t *testing.T) { } } +func TestSampleSolution(t *testing.T) { + if Mode != "MAYO_1" { + t.Skip() + } + + // This particular seed will trigger re-sampling (first try yields non-full rank) + seed := [48]byte{0x27, 0x38, 0x46, 0x8a, 0x02, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + g := nist.NewDRBG(&seed) + + msg := [32]byte{0xe} + + pk, sk, err := GenerateKey(&g) + if err != nil { + t.Fatal() + } + + sig, err := Sign(msg[:], sk.Expand(), &g) + if err != nil { + t.Fatal() + } + + if !Verify(msg[:], sig[:], pk.Expand()) { + t.Fatal() + } +} + func TestPQCgenKATSign(t *testing.T) { for _, tc := range []struct { name string From 8f34bba6556adc9212cf2173c61ef141cc6e0f4e Mon Sep 17 00:00:00 2001 From: Shang-Yi Yang Date: Fri, 8 Mar 2024 13:26:33 +0800 Subject: [PATCH 4/9] Make NIST DBRG implement io.Reader --- internal/nist/drbg.go | 9 +++- kem/frodo/kat_test.go | 6 +-- kem/kyber/kat_test.go | 8 +-- sign/dilithium/kat_test.go | 6 +-- sign/mayo/internal/common/nist/drbg.go | 71 -------------------------- sign/mayo/mode1/internal/mayo_test.go | 2 +- sign/mayo/mode2/internal/mayo_test.go | 28 +++++++++- sign/mayo/mode3/internal/mayo_test.go | 28 +++++++++- sign/mayo/mode5/internal/mayo_test.go | 28 +++++++++- 9 files changed, 100 insertions(+), 86 deletions(-) delete mode 100644 sign/mayo/internal/common/nist/drbg.go diff --git a/internal/nist/drbg.go b/internal/nist/drbg.go index e541ec461..25288f3ff 100644 --- a/internal/nist/drbg.go +++ b/internal/nist/drbg.go @@ -3,6 +3,7 @@ package nist import ( "crypto/aes" + "io" ) // See NIST's PQCgenKAT.c. @@ -11,6 +12,8 @@ type DRBG struct { v [16]byte } +var _ io.Reader = (*DRBG)(nil) + func (g *DRBG) incV() { for j := 15; j >= 0; j-- { if g.v[j] == 255 { @@ -46,9 +49,11 @@ func NewDRBG(seed *[48]byte) (g DRBG) { } // randombytes. -func (g *DRBG) Fill(x []byte) { +func (g *DRBG) Read(x []byte) (n int, err error) { var block [16]byte + n, err = len(x), nil + b, _ := aes.NewCipher(g.key[:]) for len(x) > 0 { g.incV() @@ -61,4 +66,6 @@ func (g *DRBG) Fill(x []byte) { x = x[16:] } g.update(nil) + + return } diff --git a/kem/frodo/kat_test.go b/kem/frodo/kat_test.go index 61b0aae25..4ed31989c 100644 --- a/kem/frodo/kat_test.go +++ b/kem/frodo/kat_test.go @@ -46,18 +46,18 @@ func testPQCgenKATKem(t *testing.T, name, expected string) { g := nist.NewDRBG(&seed) fmt.Fprintf(f, "# %s\n\n", name) for i := 0; i < 100; i++ { - g.Fill(seed[:]) + g.Read(seed[:]) fmt.Fprintf(f, "count = %d\n", i) fmt.Fprintf(f, "seed = %X\n", seed) g2 := nist.NewDRBG(&seed) - g2.Fill(kseed[:]) + g2.Read(kseed[:]) pk, sk := scheme.DeriveKeyPair(kseed) ppk, _ := pk.MarshalBinary() psk, _ := sk.MarshalBinary() - g2.Fill(eseed) + g2.Read(eseed) ct, ss, err := scheme.EncapsulateDeterministically(pk, eseed) if err != nil { t.Fatal(err) diff --git a/kem/kyber/kat_test.go b/kem/kyber/kat_test.go index 73eee008d..7f4119da5 100644 --- a/kem/kyber/kat_test.go +++ b/kem/kyber/kat_test.go @@ -47,7 +47,7 @@ func testPQCgenKATKem(t *testing.T, name, expected string) { g := nist.NewDRBG(&seed) fmt.Fprintf(f, "# %s\n\n", name) for i := 0; i < 100; i++ { - g.Fill(seed[:]) + g.Read(seed[:]) fmt.Fprintf(f, "count = %d\n", i) fmt.Fprintf(f, "seed = %X\n", seed) g2 := nist.NewDRBG(&seed) @@ -55,10 +55,10 @@ func testPQCgenKATKem(t *testing.T, name, expected string) { // This is not equivalent to g2.Fill(kseed[:]). As the reference // implementation calls randombytes twice generating the keypair, // we have to do that as well. - g2.Fill(kseed[:32]) - g2.Fill(kseed[32:]) + g2.Read(kseed[:32]) + g2.Read(kseed[32:]) - g2.Fill(eseed) + g2.Read(eseed) pk, sk := scheme.DeriveKeyPair(kseed) ppk, _ := pk.MarshalBinary() psk, _ := sk.MarshalBinary() diff --git a/sign/dilithium/kat_test.go b/sign/dilithium/kat_test.go index 2280e988d..c4bf13828 100644 --- a/sign/dilithium/kat_test.go +++ b/sign/dilithium/kat_test.go @@ -41,9 +41,9 @@ func TestPQCgenKATSign(t *testing.T) { fmt.Fprintf(f, "# %s\n\n", tc.name) for i := 0; i < 100; i++ { mlen := 33 * (i + 1) - g.Fill(seed[:]) + g.Read(seed[:]) msg := make([]byte, mlen) - g.Fill(msg[:]) + g.Read(msg[:]) fmt.Fprintf(f, "count = %d\n", i) fmt.Fprintf(f, "seed = %X\n", seed) @@ -51,7 +51,7 @@ func TestPQCgenKATSign(t *testing.T) { fmt.Fprintf(f, "msg = %X\n", msg) g2 := nist.NewDRBG(&seed) - g2.Fill(eseed[:]) + g2.Read(eseed[:]) pk, sk := mode.NewKeyFromSeed(eseed[:]) fmt.Fprintf(f, "pk = %X\n", pk.Bytes()) diff --git a/sign/mayo/internal/common/nist/drbg.go b/sign/mayo/internal/common/nist/drbg.go deleted file mode 100644 index be9c8ec03..000000000 --- a/sign/mayo/internal/common/nist/drbg.go +++ /dev/null @@ -1,71 +0,0 @@ -// Package nist implements helpers to generate NIST's Known Answer Tests (KATs). -package nist - -import ( - "crypto/aes" - "io" -) - -// See NIST's PQCgenKAT.c. -type DRBG struct { - key [32]byte - v [16]byte - - io.Reader -} - -func (g *DRBG) incV() { - for j := 15; j >= 0; j-- { - if g.v[j] == 255 { - g.v[j] = 0 - } else { - g.v[j]++ - break - } - } -} - -// AES256_CTR_DRBG_Update(pd, &g.key, &g.v). -func (g *DRBG) update(pd *[48]byte) { - var buf [48]byte - b, _ := aes.NewCipher(g.key[:]) - for i := 0; i < 3; i++ { - g.incV() - b.Encrypt(buf[i*16:(i+1)*16], g.v[:]) - } - if pd != nil { - for i := 0; i < 48; i++ { - buf[i] ^= pd[i] - } - } - copy(g.key[:], buf[:32]) - copy(g.v[:], buf[32:]) -} - -// randombyte_init(seed, NULL, 256). -func NewDRBG(seed *[48]byte) (g DRBG) { - g.update(seed) - return -} - -// randombytes. -func (g *DRBG) Read(x []byte) (n int, err error) { - var block [16]byte - - n, err = len(x), nil - - b, _ := aes.NewCipher(g.key[:]) - for len(x) > 0 { - g.incV() - b.Encrypt(block[:], g.v[:]) - if len(x) < 16 { - copy(x[:], block[:len(x)]) - break - } - copy(x[:], block[:]) - x = x[16:] - } - g.update(nil) - - return -} diff --git a/sign/mayo/mode1/internal/mayo_test.go b/sign/mayo/mode1/internal/mayo_test.go index ee48029bc..efb854802 100644 --- a/sign/mayo/mode1/internal/mayo_test.go +++ b/sign/mayo/mode1/internal/mayo_test.go @@ -7,7 +7,7 @@ import ( "fmt" "testing" - "github.com/cloudflare/circl/sign/mayo/internal/common/nist" + "github.com/cloudflare/circl/internal/nist" ) func TestNewKey(t *testing.T) { diff --git a/sign/mayo/mode2/internal/mayo_test.go b/sign/mayo/mode2/internal/mayo_test.go index 1695f70f3..2c0eac0de 100644 --- a/sign/mayo/mode2/internal/mayo_test.go +++ b/sign/mayo/mode2/internal/mayo_test.go @@ -9,7 +9,7 @@ import ( "fmt" "testing" - "github.com/cloudflare/circl/sign/mayo/internal/common/nist" + "github.com/cloudflare/circl/internal/nist" ) func TestNewKey(t *testing.T) { @@ -83,6 +83,32 @@ func TestVerify(t *testing.T) { } } +func TestSampleSolution(t *testing.T) { + if Mode != "MAYO_1" { + t.Skip() + } + + // This particular seed will trigger re-sampling (first try yields non-full rank) + seed := [48]byte{0x27, 0x38, 0x46, 0x8a, 0x02, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + g := nist.NewDRBG(&seed) + + msg := [32]byte{0xe} + + pk, sk, err := GenerateKey(&g) + if err != nil { + t.Fatal() + } + + sig, err := Sign(msg[:], sk.Expand(), &g) + if err != nil { + t.Fatal() + } + + if !Verify(msg[:], sig[:], pk.Expand()) { + t.Fatal() + } +} + func TestPQCgenKATSign(t *testing.T) { for _, tc := range []struct { name string diff --git a/sign/mayo/mode3/internal/mayo_test.go b/sign/mayo/mode3/internal/mayo_test.go index 1695f70f3..2c0eac0de 100644 --- a/sign/mayo/mode3/internal/mayo_test.go +++ b/sign/mayo/mode3/internal/mayo_test.go @@ -9,7 +9,7 @@ import ( "fmt" "testing" - "github.com/cloudflare/circl/sign/mayo/internal/common/nist" + "github.com/cloudflare/circl/internal/nist" ) func TestNewKey(t *testing.T) { @@ -83,6 +83,32 @@ func TestVerify(t *testing.T) { } } +func TestSampleSolution(t *testing.T) { + if Mode != "MAYO_1" { + t.Skip() + } + + // This particular seed will trigger re-sampling (first try yields non-full rank) + seed := [48]byte{0x27, 0x38, 0x46, 0x8a, 0x02, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + g := nist.NewDRBG(&seed) + + msg := [32]byte{0xe} + + pk, sk, err := GenerateKey(&g) + if err != nil { + t.Fatal() + } + + sig, err := Sign(msg[:], sk.Expand(), &g) + if err != nil { + t.Fatal() + } + + if !Verify(msg[:], sig[:], pk.Expand()) { + t.Fatal() + } +} + func TestPQCgenKATSign(t *testing.T) { for _, tc := range []struct { name string diff --git a/sign/mayo/mode5/internal/mayo_test.go b/sign/mayo/mode5/internal/mayo_test.go index 1695f70f3..2c0eac0de 100644 --- a/sign/mayo/mode5/internal/mayo_test.go +++ b/sign/mayo/mode5/internal/mayo_test.go @@ -9,7 +9,7 @@ import ( "fmt" "testing" - "github.com/cloudflare/circl/sign/mayo/internal/common/nist" + "github.com/cloudflare/circl/internal/nist" ) func TestNewKey(t *testing.T) { @@ -83,6 +83,32 @@ func TestVerify(t *testing.T) { } } +func TestSampleSolution(t *testing.T) { + if Mode != "MAYO_1" { + t.Skip() + } + + // This particular seed will trigger re-sampling (first try yields non-full rank) + seed := [48]byte{0x27, 0x38, 0x46, 0x8a, 0x02, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + g := nist.NewDRBG(&seed) + + msg := [32]byte{0xe} + + pk, sk, err := GenerateKey(&g) + if err != nil { + t.Fatal() + } + + sig, err := Sign(msg[:], sk.Expand(), &g) + if err != nil { + t.Fatal() + } + + if !Verify(msg[:], sig[:], pk.Expand()) { + t.Fatal() + } +} + func TestPQCgenKATSign(t *testing.T) { for _, tc := range []struct { name string From 20ad6a50c9a5ec4e4b018a88219a4eb22ab62eee Mon Sep 17 00:00:00 2001 From: Shang-Yi Yang Date: Mon, 11 Mar 2024 13:33:33 +0800 Subject: [PATCH 5/9] mayo: fix signapi.go generation --- sign/mayo/gen.go | 27 +++++++++++++++++++++++++++ sign/mayo/mode1/signapi.go | 2 +- sign/mayo/mode2/signapi.go | 2 +- sign/mayo/mode3/signapi.go | 2 +- sign/mayo/mode5/signapi.go | 2 +- sign/mayo/templates/signapi.templ.go | 2 +- 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/sign/mayo/gen.go b/sign/mayo/gen.go index 87be9aaff..078bd7840 100644 --- a/sign/mayo/gen.go +++ b/sign/mayo/gen.go @@ -88,6 +88,7 @@ func main() { generateModePackageFiles() generateParamsFiles() generateSourceFiles() + generateSignApiFiles() } // Generates modeX/internal/params.go from templates/params.templ.go @@ -150,6 +151,32 @@ func generateModePackageFiles() { } } +// Generates modeX/signapi.go from templates/signapi.templ.go +func generateSignApiFiles() { + tl, err := template.ParseFiles("templates/signapi.templ.go") + if err != nil { + panic(err) + } + + for _, mode := range Modes { + buf := new(bytes.Buffer) + err := tl.Execute(buf, mode) + if err != nil { + panic(err) + } + + res := buf.String() + offset := strings.Index(res, TemplateWarning) + if offset == -1 { + panic("Missing template warning in signapi.templ.go") + } + err = os.WriteFile(mode.Pkg()+"/signapi.go", []byte(res[offset:]), 0o644) + if err != nil { + panic(err) + } + } +} + // Copies mode1 source files to other modes func generateSourceFiles() { files := make(map[string][]byte) diff --git a/sign/mayo/mode1/signapi.go b/sign/mayo/mode1/signapi.go index 7fa1a9b03..ad674f49c 100644 --- a/sign/mayo/mode1/signapi.go +++ b/sign/mayo/mode1/signapi.go @@ -1,4 +1,4 @@ -// Code generated from modePkg.templ.go. DO NOT EDIT. +// Code generated from signapi.templ.go. DO NOT EDIT. package mode1 diff --git a/sign/mayo/mode2/signapi.go b/sign/mayo/mode2/signapi.go index c5a204bfe..c13a65cf4 100644 --- a/sign/mayo/mode2/signapi.go +++ b/sign/mayo/mode2/signapi.go @@ -1,4 +1,4 @@ -// Code generated from modePkg.templ.go. DO NOT EDIT. +// Code generated from signapi.templ.go. DO NOT EDIT. package mode2 diff --git a/sign/mayo/mode3/signapi.go b/sign/mayo/mode3/signapi.go index ac894c3b9..e8b13a47f 100644 --- a/sign/mayo/mode3/signapi.go +++ b/sign/mayo/mode3/signapi.go @@ -1,4 +1,4 @@ -// Code generated from modePkg.templ.go. DO NOT EDIT. +// Code generated from signapi.templ.go. DO NOT EDIT. package mode3 diff --git a/sign/mayo/mode5/signapi.go b/sign/mayo/mode5/signapi.go index c4d3d85ae..86479496c 100644 --- a/sign/mayo/mode5/signapi.go +++ b/sign/mayo/mode5/signapi.go @@ -1,4 +1,4 @@ -// Code generated from modePkg.templ.go. DO NOT EDIT. +// Code generated from signapi.templ.go. DO NOT EDIT. package mode5 diff --git a/sign/mayo/templates/signapi.templ.go b/sign/mayo/templates/signapi.templ.go index 006f17163..0bd50bdd1 100644 --- a/sign/mayo/templates/signapi.templ.go +++ b/sign/mayo/templates/signapi.templ.go @@ -2,7 +2,7 @@ // The previous line (and this one up to the warning below) is removed by the // template generator. -// Code generated from modePkg.templ.go. DO NOT EDIT. +// Code generated from signapi.templ.go. DO NOT EDIT. package {{.Pkg}} From 1a3ff0e231538fa635dcd1a9f6e52f6901dab897 Mon Sep 17 00:00:00 2001 From: Shang-Yi Yang Date: Mon, 11 Mar 2024 14:12:29 +0800 Subject: [PATCH 6/9] mayo: refine function names and explain the techniques better --- sign/mayo/mode1/internal/matrix.go | 49 ++++++++++++++++-------------- sign/mayo/mode1/internal/mayo.go | 11 +++++-- sign/mayo/mode2/internal/matrix.go | 49 ++++++++++++++++-------------- sign/mayo/mode2/internal/mayo.go | 11 +++++-- sign/mayo/mode3/internal/matrix.go | 49 ++++++++++++++++-------------- sign/mayo/mode3/internal/mayo.go | 11 +++++-- sign/mayo/mode5/internal/matrix.go | 49 ++++++++++++++++-------------- sign/mayo/mode5/internal/mayo.go | 11 +++++-- 8 files changed, 144 insertions(+), 96 deletions(-) diff --git a/sign/mayo/mode1/internal/matrix.go b/sign/mayo/mode1/internal/matrix.go index dc5fdc1de..5ac52dd75 100644 --- a/sign/mayo/mode1/internal/matrix.go +++ b/sign/mayo/mode1/internal/matrix.go @@ -154,11 +154,13 @@ func upper(in []uint64, out []uint64, size int) { } } -func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8) { +// The variable time technique is describe in the "Nibbling" paper (https://eprint.iacr.org/2023/1683.pdf) +// Section 4 (and Figure 2). +func calculatePStVarTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8) { var accumulator [K * N][P * 16]uint64 - // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] - // [ 0 P3 ] [S2] [ P3*S2] + // compute P * S^t = [ P1 P2 ] * [S1^t] = [P1*S1^t + P2*S2^t] + // [ 0 P3 ] [S2^t] [ P3*S2^t] // Note that S = S1||S2 is strided at N=V+O @@ -167,7 +169,7 @@ func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8 for r := 0; r < V; r++ { for c := r; c < V; c++ { for k := 0; k < K; k++ { - vecAddPacked(P, p1[P*pos:], accumulator[r*K+k][P*int(s[k*N+c]):]) + vecAddPacked(p1[P*pos:], accumulator[r*K+k][P*int(s[k*N+c]):]) } pos++ } @@ -178,7 +180,7 @@ func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8 for r := 0; r < V; r++ { for c := 0; c < O; c++ { for k := 0; k < K; k++ { - vecAddPacked(P, p2[P*pos:], accumulator[r*K+k][P*int(s[k*N+V+c]):]) + vecAddPacked(p2[P*pos:], accumulator[r*K+k][P*int(s[k*N+V+c]):]) } pos++ } @@ -189,50 +191,51 @@ func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8 for r := 0; r < O; r++ { for c := r; c < O; c++ { for k := 0; k < K; k++ { - vecAddPacked(P, p3[P*pos:], accumulator[(r+V)*K+k][P*int(s[k*N+V+c]):]) + vecAddPacked(p3[P*pos:], accumulator[(r+V)*K+k][P*int(s[k*N+V+c]):]) } pos++ } } for i := 0; i < K*N; i++ { - aggregate(P, accumulator[i], sps[P*i:]) + accumulate(P, accumulator[i], sps[P*i:]) } } -func variableTime2(sps []uint64, s []uint8, pst []uint64) { +func calculateSPstVarTime(sps []uint64, s []uint8, pst []uint64) { var accumulator [K * K][P * 16]uint64 // S * PST : KxN * N*K for r := 0; r < K; r++ { for c := 0; c < N; c++ { for k := 0; k < K; k++ { - vecAddPacked(P, pst[P*(c*K+k):], accumulator[r*K+k][P*int(s[r*N+c]):]) + vecAddPacked(pst[P*(c*K+k):], accumulator[r*K+k][P*int(s[r*N+c]):]) } } } for i := 0; i < K*K; i++ { - aggregate(P, accumulator[i], sps[P*i:]) + accumulate(P, accumulator[i], sps[P*i:]) } } -// p is always P, but is still kept to be consistent with other functions -// -//nolint:unparam -func vecAddPacked(p int, in []uint64, acc []uint64) { - for i := 0; i < p; i++ { +func vecAddPacked(in []uint64, acc []uint64) { + for i := 0; i < P; i++ { acc[i] ^= in[i] } } -func aggregate(p int, bins [P * 16]uint64, out []uint64) { - // The following two methods are mathematically equivalent, but the second one is slightly faster. +func accumulate(p int, bins [P * 16]uint64, out []uint64) { + // The following two approches are mathematically equivalent, but the second one is slightly faster. - // the powers of x mod x^4+x+1, represented as integers, are 1,2,4,8,3,..,13,9 - // out = bins[9]*x^14 + bins[13]*x^13 + bins[15]*x^12 ... + bin[4]*x^2 + bins[2]*x + bins[1] - // = ((bins[9]x+bins[13])x+bins[15])x + ... bins[4])x+bins[2])x+bins[1] + // Here we chose to multiply by x^-1 all the way through, + // unlike Method 3 in Figure 2 (see paper) which interleaves *x and *x^-1 + // which probably gives more parallelism on more complex CPUs. + // + // Also, on M1 Pro, Method 2 in Figure 2 is not faster then Approach 2 coded here. + // Approach 1. Multiplying by x all the way through: + // the powers of x mod x^4+x+1, represented as integers, are 1,2,4,8,3,..,13,9 // vecMulAddPackedByX(p, bins[P*9:], bins[P*13:]) // vecMulAddPackedByX(p, bins[P*13:], bins[P*15:]) // vecMulAddPackedByX(p, bins[P*15:], bins[P*14:]) @@ -249,8 +252,8 @@ func aggregate(p int, bins [P * 16]uint64, out []uint64) { // vecMulAddPackedByX(p, bins[P*2:], bins[P*1:]) // copy(out[:P], bins[P*1:]) - // In the reversed order of the above, because /x turns out to be slightly faster than *x. - // out = ((bins[2]x^-1+bins[4])x^-1+bins[8])x^-1 + ... bins[13])x^-1+bins[9])x^-1+bins[1] + // Approach 2. Multiplying by x^-1 all the way through: + // In the reversed order of the first approach, because /x turns out to be slightly faster than *x. vecMulAddPackedByInvX(p, bins[P*2:], bins[P*4:]) vecMulAddPackedByInvX(p, bins[P*4:], bins[P*8:]) vecMulAddPackedByInvX(p, bins[P*8:], bins[P*3:]) @@ -278,7 +281,9 @@ func aggregate(p int, bins [P * 16]uint64, out []uint64) { // } // } +// It can be seen by comparison to the commented code above that this requires fewer instructions. func vecMulAddPackedByInvX(p int, in []uint64, acc []uint64) { + // Equivalently: // vecMulAddPacked(p, in, 9, acc) lsb := uint64(0x1111111111111111) diff --git a/sign/mayo/mode1/internal/mayo.go b/sign/mayo/mode1/internal/mayo.go index c54e181cd..b0ad23d90 100644 --- a/sign/mayo/mode1/internal/mayo.go +++ b/sign/mayo/mode1/internal/mayo.go @@ -371,6 +371,11 @@ func extract(in []uint64, index int) byte { return byte((in[leg] >> (offset * 4)) & 0xF) } +// The following code to compute echelon form is taken from the reference code: +// https://github.com/PQCMayo/MAYO-C/tree/nibbling-mayo/src +// +// As of the time of this writing, a formally verified implementation is still in progress by scholars. + // put matrix in row echelon form with ones on first nonzero entries *in constant time* func ef(A []byte, nrows, ncols int) { // ncols is actually always K*O + 1 @@ -635,15 +640,17 @@ func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] // [ 0 P3 ] [S2] [ P3*S2] var pst [M * N * K / 16]uint64 + // Constant time apprach: // mulAddMMatXMatTrans(pst[:], P1, s[:], V, V, K, N, true) // mulAddMMatXMatTrans(pst[:], P2, s[V:], V, O, K, N, false) // mulAddMMatXMatTrans(pst[M*V*K/16:], P3, s[V:], O, O, K, N, true) - variableTime(pst[:], P1, P2, P3, s[:]) + // Variable time approach with table access where index depends on input: + calculatePStVarTime(pst[:], P1, P2, P3, s[:]) // compute S * PST var sps [M * K * K / 16]uint64 // mulAddMatXMMat(sps[:], s[:], pst[:], K, N, K) - variableTime2(sps[:], s[:], pst[:]) + calculateSPstVarTime(sps[:], s[:], pst[:]) emulsifyInto(sps[:], t[:]) diff --git a/sign/mayo/mode2/internal/matrix.go b/sign/mayo/mode2/internal/matrix.go index 5ddf0d842..b9a3173f5 100644 --- a/sign/mayo/mode2/internal/matrix.go +++ b/sign/mayo/mode2/internal/matrix.go @@ -156,11 +156,13 @@ func upper(in []uint64, out []uint64, size int) { } } -func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8) { +// The variable time technique is describe in the "Nibbling" paper (https://eprint.iacr.org/2023/1683.pdf) +// Section 4 (and Figure 2). +func calculatePStVarTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8) { var accumulator [K * N][P * 16]uint64 - // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] - // [ 0 P3 ] [S2] [ P3*S2] + // compute P * S^t = [ P1 P2 ] * [S1^t] = [P1*S1^t + P2*S2^t] + // [ 0 P3 ] [S2^t] [ P3*S2^t] // Note that S = S1||S2 is strided at N=V+O @@ -169,7 +171,7 @@ func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8 for r := 0; r < V; r++ { for c := r; c < V; c++ { for k := 0; k < K; k++ { - vecAddPacked(P, p1[P*pos:], accumulator[r*K+k][P*int(s[k*N+c]):]) + vecAddPacked(p1[P*pos:], accumulator[r*K+k][P*int(s[k*N+c]):]) } pos++ } @@ -180,7 +182,7 @@ func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8 for r := 0; r < V; r++ { for c := 0; c < O; c++ { for k := 0; k < K; k++ { - vecAddPacked(P, p2[P*pos:], accumulator[r*K+k][P*int(s[k*N+V+c]):]) + vecAddPacked(p2[P*pos:], accumulator[r*K+k][P*int(s[k*N+V+c]):]) } pos++ } @@ -191,50 +193,51 @@ func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8 for r := 0; r < O; r++ { for c := r; c < O; c++ { for k := 0; k < K; k++ { - vecAddPacked(P, p3[P*pos:], accumulator[(r+V)*K+k][P*int(s[k*N+V+c]):]) + vecAddPacked(p3[P*pos:], accumulator[(r+V)*K+k][P*int(s[k*N+V+c]):]) } pos++ } } for i := 0; i < K*N; i++ { - aggregate(P, accumulator[i], sps[P*i:]) + accumulate(P, accumulator[i], sps[P*i:]) } } -func variableTime2(sps []uint64, s []uint8, pst []uint64) { +func calculateSPstVarTime(sps []uint64, s []uint8, pst []uint64) { var accumulator [K * K][P * 16]uint64 // S * PST : KxN * N*K for r := 0; r < K; r++ { for c := 0; c < N; c++ { for k := 0; k < K; k++ { - vecAddPacked(P, pst[P*(c*K+k):], accumulator[r*K+k][P*int(s[r*N+c]):]) + vecAddPacked(pst[P*(c*K+k):], accumulator[r*K+k][P*int(s[r*N+c]):]) } } } for i := 0; i < K*K; i++ { - aggregate(P, accumulator[i], sps[P*i:]) + accumulate(P, accumulator[i], sps[P*i:]) } } -// p is always P, but is still kept to be consistent with other functions -// -//nolint:unparam -func vecAddPacked(p int, in []uint64, acc []uint64) { - for i := 0; i < p; i++ { +func vecAddPacked(in []uint64, acc []uint64) { + for i := 0; i < P; i++ { acc[i] ^= in[i] } } -func aggregate(p int, bins [P * 16]uint64, out []uint64) { - // The following two methods are mathematically equivalent, but the second one is slightly faster. +func accumulate(p int, bins [P * 16]uint64, out []uint64) { + // The following two approches are mathematically equivalent, but the second one is slightly faster. - // the powers of x mod x^4+x+1, represented as integers, are 1,2,4,8,3,..,13,9 - // out = bins[9]*x^14 + bins[13]*x^13 + bins[15]*x^12 ... + bin[4]*x^2 + bins[2]*x + bins[1] - // = ((bins[9]x+bins[13])x+bins[15])x + ... bins[4])x+bins[2])x+bins[1] + // Here we chose to multiply by x^-1 all the way through, + // unlike Method 3 in Figure 2 (see paper) which interleaves *x and *x^-1 + // which probably gives more parallelism on more complex CPUs. + // + // Also, on M1 Pro, Method 2 in Figure 2 is not faster then Approach 2 coded here. + // Approach 1. Multiplying by x all the way through: + // the powers of x mod x^4+x+1, represented as integers, are 1,2,4,8,3,..,13,9 // vecMulAddPackedByX(p, bins[P*9:], bins[P*13:]) // vecMulAddPackedByX(p, bins[P*13:], bins[P*15:]) // vecMulAddPackedByX(p, bins[P*15:], bins[P*14:]) @@ -251,8 +254,8 @@ func aggregate(p int, bins [P * 16]uint64, out []uint64) { // vecMulAddPackedByX(p, bins[P*2:], bins[P*1:]) // copy(out[:P], bins[P*1:]) - // In the reversed order of the above, because /x turns out to be slightly faster than *x. - // out = ((bins[2]x^-1+bins[4])x^-1+bins[8])x^-1 + ... bins[13])x^-1+bins[9])x^-1+bins[1] + // Approach 2. Multiplying by x^-1 all the way through: + // In the reversed order of the first approach, because /x turns out to be slightly faster than *x. vecMulAddPackedByInvX(p, bins[P*2:], bins[P*4:]) vecMulAddPackedByInvX(p, bins[P*4:], bins[P*8:]) vecMulAddPackedByInvX(p, bins[P*8:], bins[P*3:]) @@ -280,7 +283,9 @@ func aggregate(p int, bins [P * 16]uint64, out []uint64) { // } // } +// It can be seen by comparison to the commented code above that this requires fewer instructions. func vecMulAddPackedByInvX(p int, in []uint64, acc []uint64) { + // Equivalently: // vecMulAddPacked(p, in, 9, acc) lsb := uint64(0x1111111111111111) diff --git a/sign/mayo/mode2/internal/mayo.go b/sign/mayo/mode2/internal/mayo.go index a06568d0e..970a3fcb4 100644 --- a/sign/mayo/mode2/internal/mayo.go +++ b/sign/mayo/mode2/internal/mayo.go @@ -373,6 +373,11 @@ func extract(in []uint64, index int) byte { return byte((in[leg] >> (offset * 4)) & 0xF) } +// The following code to compute echelon form is taken from the reference code: +// https://github.com/PQCMayo/MAYO-C/tree/nibbling-mayo/src +// +// As of the time of this writing, a formally verified implementation is still in progress by scholars. + // put matrix in row echelon form with ones on first nonzero entries *in constant time* func ef(A []byte, nrows, ncols int) { // ncols is actually always K*O + 1 @@ -637,15 +642,17 @@ func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] // [ 0 P3 ] [S2] [ P3*S2] var pst [M * N * K / 16]uint64 + // Constant time apprach: // mulAddMMatXMatTrans(pst[:], P1, s[:], V, V, K, N, true) // mulAddMMatXMatTrans(pst[:], P2, s[V:], V, O, K, N, false) // mulAddMMatXMatTrans(pst[M*V*K/16:], P3, s[V:], O, O, K, N, true) - variableTime(pst[:], P1, P2, P3, s[:]) + // Variable time approach with table access where index depends on input: + calculatePStVarTime(pst[:], P1, P2, P3, s[:]) // compute S * PST var sps [M * K * K / 16]uint64 // mulAddMatXMMat(sps[:], s[:], pst[:], K, N, K) - variableTime2(sps[:], s[:], pst[:]) + calculateSPstVarTime(sps[:], s[:], pst[:]) emulsifyInto(sps[:], t[:]) diff --git a/sign/mayo/mode3/internal/matrix.go b/sign/mayo/mode3/internal/matrix.go index 5ddf0d842..b9a3173f5 100644 --- a/sign/mayo/mode3/internal/matrix.go +++ b/sign/mayo/mode3/internal/matrix.go @@ -156,11 +156,13 @@ func upper(in []uint64, out []uint64, size int) { } } -func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8) { +// The variable time technique is describe in the "Nibbling" paper (https://eprint.iacr.org/2023/1683.pdf) +// Section 4 (and Figure 2). +func calculatePStVarTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8) { var accumulator [K * N][P * 16]uint64 - // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] - // [ 0 P3 ] [S2] [ P3*S2] + // compute P * S^t = [ P1 P2 ] * [S1^t] = [P1*S1^t + P2*S2^t] + // [ 0 P3 ] [S2^t] [ P3*S2^t] // Note that S = S1||S2 is strided at N=V+O @@ -169,7 +171,7 @@ func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8 for r := 0; r < V; r++ { for c := r; c < V; c++ { for k := 0; k < K; k++ { - vecAddPacked(P, p1[P*pos:], accumulator[r*K+k][P*int(s[k*N+c]):]) + vecAddPacked(p1[P*pos:], accumulator[r*K+k][P*int(s[k*N+c]):]) } pos++ } @@ -180,7 +182,7 @@ func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8 for r := 0; r < V; r++ { for c := 0; c < O; c++ { for k := 0; k < K; k++ { - vecAddPacked(P, p2[P*pos:], accumulator[r*K+k][P*int(s[k*N+V+c]):]) + vecAddPacked(p2[P*pos:], accumulator[r*K+k][P*int(s[k*N+V+c]):]) } pos++ } @@ -191,50 +193,51 @@ func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8 for r := 0; r < O; r++ { for c := r; c < O; c++ { for k := 0; k < K; k++ { - vecAddPacked(P, p3[P*pos:], accumulator[(r+V)*K+k][P*int(s[k*N+V+c]):]) + vecAddPacked(p3[P*pos:], accumulator[(r+V)*K+k][P*int(s[k*N+V+c]):]) } pos++ } } for i := 0; i < K*N; i++ { - aggregate(P, accumulator[i], sps[P*i:]) + accumulate(P, accumulator[i], sps[P*i:]) } } -func variableTime2(sps []uint64, s []uint8, pst []uint64) { +func calculateSPstVarTime(sps []uint64, s []uint8, pst []uint64) { var accumulator [K * K][P * 16]uint64 // S * PST : KxN * N*K for r := 0; r < K; r++ { for c := 0; c < N; c++ { for k := 0; k < K; k++ { - vecAddPacked(P, pst[P*(c*K+k):], accumulator[r*K+k][P*int(s[r*N+c]):]) + vecAddPacked(pst[P*(c*K+k):], accumulator[r*K+k][P*int(s[r*N+c]):]) } } } for i := 0; i < K*K; i++ { - aggregate(P, accumulator[i], sps[P*i:]) + accumulate(P, accumulator[i], sps[P*i:]) } } -// p is always P, but is still kept to be consistent with other functions -// -//nolint:unparam -func vecAddPacked(p int, in []uint64, acc []uint64) { - for i := 0; i < p; i++ { +func vecAddPacked(in []uint64, acc []uint64) { + for i := 0; i < P; i++ { acc[i] ^= in[i] } } -func aggregate(p int, bins [P * 16]uint64, out []uint64) { - // The following two methods are mathematically equivalent, but the second one is slightly faster. +func accumulate(p int, bins [P * 16]uint64, out []uint64) { + // The following two approches are mathematically equivalent, but the second one is slightly faster. - // the powers of x mod x^4+x+1, represented as integers, are 1,2,4,8,3,..,13,9 - // out = bins[9]*x^14 + bins[13]*x^13 + bins[15]*x^12 ... + bin[4]*x^2 + bins[2]*x + bins[1] - // = ((bins[9]x+bins[13])x+bins[15])x + ... bins[4])x+bins[2])x+bins[1] + // Here we chose to multiply by x^-1 all the way through, + // unlike Method 3 in Figure 2 (see paper) which interleaves *x and *x^-1 + // which probably gives more parallelism on more complex CPUs. + // + // Also, on M1 Pro, Method 2 in Figure 2 is not faster then Approach 2 coded here. + // Approach 1. Multiplying by x all the way through: + // the powers of x mod x^4+x+1, represented as integers, are 1,2,4,8,3,..,13,9 // vecMulAddPackedByX(p, bins[P*9:], bins[P*13:]) // vecMulAddPackedByX(p, bins[P*13:], bins[P*15:]) // vecMulAddPackedByX(p, bins[P*15:], bins[P*14:]) @@ -251,8 +254,8 @@ func aggregate(p int, bins [P * 16]uint64, out []uint64) { // vecMulAddPackedByX(p, bins[P*2:], bins[P*1:]) // copy(out[:P], bins[P*1:]) - // In the reversed order of the above, because /x turns out to be slightly faster than *x. - // out = ((bins[2]x^-1+bins[4])x^-1+bins[8])x^-1 + ... bins[13])x^-1+bins[9])x^-1+bins[1] + // Approach 2. Multiplying by x^-1 all the way through: + // In the reversed order of the first approach, because /x turns out to be slightly faster than *x. vecMulAddPackedByInvX(p, bins[P*2:], bins[P*4:]) vecMulAddPackedByInvX(p, bins[P*4:], bins[P*8:]) vecMulAddPackedByInvX(p, bins[P*8:], bins[P*3:]) @@ -280,7 +283,9 @@ func aggregate(p int, bins [P * 16]uint64, out []uint64) { // } // } +// It can be seen by comparison to the commented code above that this requires fewer instructions. func vecMulAddPackedByInvX(p int, in []uint64, acc []uint64) { + // Equivalently: // vecMulAddPacked(p, in, 9, acc) lsb := uint64(0x1111111111111111) diff --git a/sign/mayo/mode3/internal/mayo.go b/sign/mayo/mode3/internal/mayo.go index a06568d0e..970a3fcb4 100644 --- a/sign/mayo/mode3/internal/mayo.go +++ b/sign/mayo/mode3/internal/mayo.go @@ -373,6 +373,11 @@ func extract(in []uint64, index int) byte { return byte((in[leg] >> (offset * 4)) & 0xF) } +// The following code to compute echelon form is taken from the reference code: +// https://github.com/PQCMayo/MAYO-C/tree/nibbling-mayo/src +// +// As of the time of this writing, a formally verified implementation is still in progress by scholars. + // put matrix in row echelon form with ones on first nonzero entries *in constant time* func ef(A []byte, nrows, ncols int) { // ncols is actually always K*O + 1 @@ -637,15 +642,17 @@ func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] // [ 0 P3 ] [S2] [ P3*S2] var pst [M * N * K / 16]uint64 + // Constant time apprach: // mulAddMMatXMatTrans(pst[:], P1, s[:], V, V, K, N, true) // mulAddMMatXMatTrans(pst[:], P2, s[V:], V, O, K, N, false) // mulAddMMatXMatTrans(pst[M*V*K/16:], P3, s[V:], O, O, K, N, true) - variableTime(pst[:], P1, P2, P3, s[:]) + // Variable time approach with table access where index depends on input: + calculatePStVarTime(pst[:], P1, P2, P3, s[:]) // compute S * PST var sps [M * K * K / 16]uint64 // mulAddMatXMMat(sps[:], s[:], pst[:], K, N, K) - variableTime2(sps[:], s[:], pst[:]) + calculateSPstVarTime(sps[:], s[:], pst[:]) emulsifyInto(sps[:], t[:]) diff --git a/sign/mayo/mode5/internal/matrix.go b/sign/mayo/mode5/internal/matrix.go index 5ddf0d842..b9a3173f5 100644 --- a/sign/mayo/mode5/internal/matrix.go +++ b/sign/mayo/mode5/internal/matrix.go @@ -156,11 +156,13 @@ func upper(in []uint64, out []uint64, size int) { } } -func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8) { +// The variable time technique is describe in the "Nibbling" paper (https://eprint.iacr.org/2023/1683.pdf) +// Section 4 (and Figure 2). +func calculatePStVarTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8) { var accumulator [K * N][P * 16]uint64 - // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] - // [ 0 P3 ] [S2] [ P3*S2] + // compute P * S^t = [ P1 P2 ] * [S1^t] = [P1*S1^t + P2*S2^t] + // [ 0 P3 ] [S2^t] [ P3*S2^t] // Note that S = S1||S2 is strided at N=V+O @@ -169,7 +171,7 @@ func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8 for r := 0; r < V; r++ { for c := r; c < V; c++ { for k := 0; k < K; k++ { - vecAddPacked(P, p1[P*pos:], accumulator[r*K+k][P*int(s[k*N+c]):]) + vecAddPacked(p1[P*pos:], accumulator[r*K+k][P*int(s[k*N+c]):]) } pos++ } @@ -180,7 +182,7 @@ func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8 for r := 0; r < V; r++ { for c := 0; c < O; c++ { for k := 0; k < K; k++ { - vecAddPacked(P, p2[P*pos:], accumulator[r*K+k][P*int(s[k*N+V+c]):]) + vecAddPacked(p2[P*pos:], accumulator[r*K+k][P*int(s[k*N+V+c]):]) } pos++ } @@ -191,50 +193,51 @@ func variableTime(sps []uint64, p1 []uint64, p2 []uint64, p3 []uint64, s []uint8 for r := 0; r < O; r++ { for c := r; c < O; c++ { for k := 0; k < K; k++ { - vecAddPacked(P, p3[P*pos:], accumulator[(r+V)*K+k][P*int(s[k*N+V+c]):]) + vecAddPacked(p3[P*pos:], accumulator[(r+V)*K+k][P*int(s[k*N+V+c]):]) } pos++ } } for i := 0; i < K*N; i++ { - aggregate(P, accumulator[i], sps[P*i:]) + accumulate(P, accumulator[i], sps[P*i:]) } } -func variableTime2(sps []uint64, s []uint8, pst []uint64) { +func calculateSPstVarTime(sps []uint64, s []uint8, pst []uint64) { var accumulator [K * K][P * 16]uint64 // S * PST : KxN * N*K for r := 0; r < K; r++ { for c := 0; c < N; c++ { for k := 0; k < K; k++ { - vecAddPacked(P, pst[P*(c*K+k):], accumulator[r*K+k][P*int(s[r*N+c]):]) + vecAddPacked(pst[P*(c*K+k):], accumulator[r*K+k][P*int(s[r*N+c]):]) } } } for i := 0; i < K*K; i++ { - aggregate(P, accumulator[i], sps[P*i:]) + accumulate(P, accumulator[i], sps[P*i:]) } } -// p is always P, but is still kept to be consistent with other functions -// -//nolint:unparam -func vecAddPacked(p int, in []uint64, acc []uint64) { - for i := 0; i < p; i++ { +func vecAddPacked(in []uint64, acc []uint64) { + for i := 0; i < P; i++ { acc[i] ^= in[i] } } -func aggregate(p int, bins [P * 16]uint64, out []uint64) { - // The following two methods are mathematically equivalent, but the second one is slightly faster. +func accumulate(p int, bins [P * 16]uint64, out []uint64) { + // The following two approches are mathematically equivalent, but the second one is slightly faster. - // the powers of x mod x^4+x+1, represented as integers, are 1,2,4,8,3,..,13,9 - // out = bins[9]*x^14 + bins[13]*x^13 + bins[15]*x^12 ... + bin[4]*x^2 + bins[2]*x + bins[1] - // = ((bins[9]x+bins[13])x+bins[15])x + ... bins[4])x+bins[2])x+bins[1] + // Here we chose to multiply by x^-1 all the way through, + // unlike Method 3 in Figure 2 (see paper) which interleaves *x and *x^-1 + // which probably gives more parallelism on more complex CPUs. + // + // Also, on M1 Pro, Method 2 in Figure 2 is not faster then Approach 2 coded here. + // Approach 1. Multiplying by x all the way through: + // the powers of x mod x^4+x+1, represented as integers, are 1,2,4,8,3,..,13,9 // vecMulAddPackedByX(p, bins[P*9:], bins[P*13:]) // vecMulAddPackedByX(p, bins[P*13:], bins[P*15:]) // vecMulAddPackedByX(p, bins[P*15:], bins[P*14:]) @@ -251,8 +254,8 @@ func aggregate(p int, bins [P * 16]uint64, out []uint64) { // vecMulAddPackedByX(p, bins[P*2:], bins[P*1:]) // copy(out[:P], bins[P*1:]) - // In the reversed order of the above, because /x turns out to be slightly faster than *x. - // out = ((bins[2]x^-1+bins[4])x^-1+bins[8])x^-1 + ... bins[13])x^-1+bins[9])x^-1+bins[1] + // Approach 2. Multiplying by x^-1 all the way through: + // In the reversed order of the first approach, because /x turns out to be slightly faster than *x. vecMulAddPackedByInvX(p, bins[P*2:], bins[P*4:]) vecMulAddPackedByInvX(p, bins[P*4:], bins[P*8:]) vecMulAddPackedByInvX(p, bins[P*8:], bins[P*3:]) @@ -280,7 +283,9 @@ func aggregate(p int, bins [P * 16]uint64, out []uint64) { // } // } +// It can be seen by comparison to the commented code above that this requires fewer instructions. func vecMulAddPackedByInvX(p int, in []uint64, acc []uint64) { + // Equivalently: // vecMulAddPacked(p, in, 9, acc) lsb := uint64(0x1111111111111111) diff --git a/sign/mayo/mode5/internal/mayo.go b/sign/mayo/mode5/internal/mayo.go index a06568d0e..970a3fcb4 100644 --- a/sign/mayo/mode5/internal/mayo.go +++ b/sign/mayo/mode5/internal/mayo.go @@ -373,6 +373,11 @@ func extract(in []uint64, index int) byte { return byte((in[leg] >> (offset * 4)) & 0xF) } +// The following code to compute echelon form is taken from the reference code: +// https://github.com/PQCMayo/MAYO-C/tree/nibbling-mayo/src +// +// As of the time of this writing, a formally verified implementation is still in progress by scholars. + // put matrix in row echelon form with ones on first nonzero entries *in constant time* func ef(A []byte, nrows, ncols int) { // ncols is actually always K*O + 1 @@ -637,15 +642,17 @@ func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] // [ 0 P3 ] [S2] [ P3*S2] var pst [M * N * K / 16]uint64 + // Constant time apprach: // mulAddMMatXMatTrans(pst[:], P1, s[:], V, V, K, N, true) // mulAddMMatXMatTrans(pst[:], P2, s[V:], V, O, K, N, false) // mulAddMMatXMatTrans(pst[M*V*K/16:], P3, s[V:], O, O, K, N, true) - variableTime(pst[:], P1, P2, P3, s[:]) + // Variable time approach with table access where index depends on input: + calculatePStVarTime(pst[:], P1, P2, P3, s[:]) // compute S * PST var sps [M * K * K / 16]uint64 // mulAddMatXMMat(sps[:], s[:], pst[:], K, N, K) - variableTime2(sps[:], s[:], pst[:]) + calculateSPstVarTime(sps[:], s[:], pst[:]) emulsifyInto(sps[:], t[:]) From 29d480ee3c6c6e4ce5055d2dffa4984740d66137 Mon Sep 17 00:00:00 2001 From: Shang-Yi Yang Date: Mon, 11 Mar 2024 16:42:17 +0800 Subject: [PATCH 7/9] Applying changes after Bas' review. --- sign/mayo/LICENSE | 201 ++++++++++++++++++++++++++ sign/mayo/NOTICE | 4 + sign/mayo/doc.go | 4 + sign/mayo/mode1/internal/mayo.go | 152 ++++++++++--------- sign/mayo/mode1/internal/mayo_test.go | 12 +- sign/mayo/mode1/mayo.go | 2 +- sign/mayo/mode2/internal/mayo.go | 152 ++++++++++--------- sign/mayo/mode2/internal/mayo_test.go | 12 +- sign/mayo/mode2/mayo.go | 2 +- sign/mayo/mode3/internal/mayo.go | 152 ++++++++++--------- sign/mayo/mode3/internal/mayo_test.go | 12 +- sign/mayo/mode3/mayo.go | 2 +- sign/mayo/mode5/internal/mayo.go | 152 ++++++++++--------- sign/mayo/mode5/internal/mayo_test.go | 12 +- sign/mayo/mode5/mayo.go | 2 +- sign/mayo/templates/modePkg.templ.go | 2 +- 16 files changed, 582 insertions(+), 293 deletions(-) create mode 100644 sign/mayo/LICENSE create mode 100644 sign/mayo/NOTICE diff --git a/sign/mayo/LICENSE b/sign/mayo/LICENSE new file mode 100644 index 000000000..a9063b320 --- /dev/null +++ b/sign/mayo/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/sign/mayo/NOTICE b/sign/mayo/NOTICE new file mode 100644 index 000000000..3b1422aff --- /dev/null +++ b/sign/mayo/NOTICE @@ -0,0 +1,4 @@ +Copyright 2022-2023 the MAYO team. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. diff --git a/sign/mayo/doc.go b/sign/mayo/doc.go index 4c7b8ec4c..a064a5794 100644 --- a/sign/mayo/doc.go +++ b/sign/mayo/doc.go @@ -8,4 +8,8 @@ // This implemented the nibble-sliced version as proposed in // // https://eprint.iacr.org/2023/1683 +// +// and the code is written with heavy reference to +// +// https://github.com/PQCMayo/MAYO-C/tree/nibbling-mayo package mayo diff --git a/sign/mayo/mode1/internal/mayo.go b/sign/mayo/mode1/internal/mayo.go index b0ad23d90..26dd15f43 100644 --- a/sign/mayo/mode1/internal/mayo.go +++ b/sign/mayo/mode1/internal/mayo.go @@ -1,12 +1,13 @@ package internal import ( + "bytes" "crypto/aes" "crypto/cipher" cryptoRand "crypto/rand" "crypto/subtle" + "encoding/binary" "io" - "unsafe" "github.com/cloudflare/circl/internal/sha3" ) @@ -40,7 +41,8 @@ type ExpandedPrivateKey struct { func (pk *PublicKey) Expand() *ExpandedPublicKey { seedPk := pk[:PublicKeySeedSize] - var nonce [16]byte // zero-initialized + var nonce [16]byte + // TODO there are unnecessary allocations block, _ := aes.NewCipher(seedPk[:]) ctr := cipher.NewCTR(block, nonce[:]) @@ -54,10 +56,10 @@ func (pk *PublicKey) Expand() *ExpandedPublicKey { } func (sk *PrivateKey) Expand() *ExpandedPrivateKey { - var epk ExpandedPrivateKey + var esk ExpandedPrivateKey seed := (*sk)[:KeySeedSize] - copy(epk.seed[:], seed) + copy(esk.seed[:], seed) var seedPk [PublicKeySeedSize]byte var o [OSize]byte @@ -67,31 +69,28 @@ func (sk *PrivateKey) Expand() *ExpandedPrivateKey { _, _ = h.Read(seedPk[:]) _, _ = h.Read(o[:]) - var nonce [16]byte // zero-initialized + var nonce [16]byte + // TODO there are unnecessary allocations block, _ := aes.NewCipher(seedPk[:]) ctr := cipher.NewCTR(block, nonce[:]) var p12 [P1Size + P2Size]byte ctr.XORKeyStream(p12[:], p12[:]) - decode(o[:], epk.o[:]) + decode(esk.o[:], o[:]) - p1Tri := viewBytesAsUint64Slice(p12[:P1Size]) - p2 := viewBytesAsUint64Slice(p12[P1Size:]) - - // TODO: this copy can be saved by reusing buffers but ugly - copy(epk.p1[:], p1Tri) - copy(epk.l[:], p2) + copyBytesToUint64SliceLE(esk.p1[:P1Size/8], p12[:P1Size]) + copyBytesToUint64SliceLE(esk.l[:], p12[P1Size:]) // compute L_i = (P1 + P1^t)*O + P2 - mulAddMUpperTriangularWithTransposeMatXMat(epk.l[:], p1Tri, epk.o[:], V, O) + mulAddMUpperTriangularWithTransposeMatXMat(esk.l[:], esk.p1[:], esk.o[:], V, O) - return &epk + return &esk } -// decode unpacks N bytes from src to N*2 nibbles to dst. +// decode unpacks N bytes from src to N*2 nibbles of dst. // The length is determined by len(dst) -func decode(src []byte, dst []byte) { +func decode(dst []byte, src []byte) { i := 0 for ; i < len(dst)/2; i++ { dst[i*2] = src[i] & 0xf @@ -99,30 +98,36 @@ func decode(src []byte, dst []byte) { } // Account for odd length - if len(dst)%2 == 1 { + if len(dst)&1 == 1 { dst[i*2] = src[i] & 0xf } } // encode packs N=length low nibbles from src to (N+1)/2 bytes in dst. -func encode(src []byte, dst []byte, length int) { +func encode(dst []byte, src []byte, length int) { var i int for i = 0; i+1 < length; i += 2 { dst[i/2] = (src[i+0] << 0) | (src[i+1] << 4) } - if length%2 == 1 { + if length&1 == 1 { dst[i/2] = (src[i+0] << 0) } } -func viewBytesAsUint64Slice(data []byte) []uint64 { - numUint64 := len(data) / 8 - return unsafe.Slice((*uint64)(unsafe.Pointer(&data[0])), numUint64) +// Assumes len(dst) * 8 == len(src). Loop size depends on len(dst). +func copyBytesToUint64SliceLE(dst []uint64, src []byte) { + for i := range dst { + dst[i] = binary.LittleEndian.Uint64(src) + src = src[8:] + } } -func viewUint64SliceAsBytes(data []uint64) []byte { - numByte := len(data) * 8 - return unsafe.Slice((*uint8)(unsafe.Pointer(&data[0])), numByte) +// Assumes len(dst) == len(src) * 8. Loop size depends on len(src). +func copyUint64SliceToBytesLE(dst []byte, src []uint64) { + for _, s := range src { + binary.LittleEndian.PutUint64(dst, s) + dst = dst[8:] + } } // GenerateKey generates a public/private key pair using entropy from rand. @@ -157,7 +162,8 @@ func (sk *PrivateKey) Public() *PublicKey { _, _ = h.Read(seedPk[:]) _, _ = h.Read(o[:]) - var nonce [16]byte // zero-initialized + var nonce [16]byte + // TODO there are unnecessary allocations block, _ := aes.NewCipher(seedPk[:]) ctr := cipher.NewCTR(block, nonce[:]) @@ -165,21 +171,22 @@ func (sk *PrivateKey) Public() *PublicKey { ctr.XORKeyStream(p12[:], p12[:]) var oo [V * O]byte - decode(o[:], oo[:]) + decode(oo[:], o[:]) - p1Tri := viewBytesAsUint64Slice(p12[:P1Size]) - p1OP2 := viewBytesAsUint64Slice(p12[P1Size:]) + var p1Tri [P1Size / 8]uint64 + var p1OP2 [P2Size / 8]uint64 + copyBytesToUint64SliceLE(p1Tri[:], p12[:P1Size]) + copyBytesToUint64SliceLE(p1OP2[:], p12[P1Size:]) var p3full [M * O * O / 16]uint64 var p3 [P3Size / 8]uint64 - mulAddMUpperTriangularMatXMat(p1OP2, p1Tri, oo[:], V, O) - mulAddMatTransXMMat(p3full[:], oo[:], p1OP2, V, O, O) + mulAddMUpperTriangularMatXMat(p1OP2[:], p1Tri[:], oo[:], V, O) + mulAddMatTransXMMat(p3full[:], oo[:], p1OP2[:], V, O, O) upper(p3full[:], p3[:], O) - xx := viewUint64SliceAsBytes(p3[:]) - copy(pk[PublicKeySeedSize:], xx[:]) + copyUint64SliceToBytesLE(pk[PublicKeySeedSize:], p3[:]) return &pk } @@ -216,7 +223,7 @@ func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { _, _ = h.Read(tenc[:]) var t [M]byte - decode(tenc[:], t[:]) + decode(t[:], tenc[:]) var v [K * V]byte var x [K*O + 1]byte // + 1 for buffer @@ -236,9 +243,9 @@ func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { var r [K * O]byte for i := 0; i < K; i++ { - decode(venc[i*VSize:], v[i*V:(i+1)*V]) + decode(v[i*V:(i+1)*V], venc[i*VSize:]) } - decode(renc[:], r[:]) + decode(r[:], renc[:]) // M = vL var m [M * K * O / 16]uint64 @@ -274,7 +281,7 @@ func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { } var sig [(K*N+1)/2 + SaltSize]byte - encode(s[:], sig[:], K*N) + encode(sig[:], s[:], K*N) copy(sig[(K*N+1)/2:], salt[:]) return sig[:], nil @@ -366,7 +373,7 @@ func ctCompare8(a, b byte) byte { func extract(in []uint64, index int) byte { leg := index / 16 - offset := index % 16 + offset := index & 15 return byte((in[leg] >> (offset * 4)) & 0xF) } @@ -389,10 +396,12 @@ func ef(A []byte, nrows, ncols int) { // nibbleslice the matrix A var packedAbyte [((K*O + 1 + 15) / 16) * M * 8]byte for i := 0; i < nrows; i++ { - encode(A[i*ncols:], packedAbyte[i*rowLen*8:], ncols) + encode(packedAbyte[i*rowLen*8:], A[i*ncols:], ncols) } - packedA := viewBytesAsUint64Slice(packedAbyte[:]) + // packing into uint64 to gain some bitwise parallelism over uint8 + var packedA [((K*O + 1 + 15) / 16) * M]uint64 + copyBytesToUint64SliceLE(packedA[:], packedAbyte[:]) // pivot row is secret, pivot col is not pivotRow := 0 @@ -456,8 +465,10 @@ func ef(A []byte, nrows, ncols int) { var temp [(O*K + 1 + 15)]byte // unnibbleslice the matrix A + copyUint64SliceToBytesLE(packedAbyte[:], packedA[:]) + for i := 0; i < nrows; i++ { - decode(packedAbyte[i*rowLen*8:], temp[:rowLen*16]) + decode(temp[:rowLen*16], packedAbyte[i*rowLen*8:]) for j := 0; j < ncols; j++ { A[i*ncols+j] = temp[j] } @@ -546,25 +557,28 @@ func computeA(m []uint64, _a []byte) { for c := 0; c < OKpadded; c += 16 { for r := M; r < M+(K+1)*K/2; r++ { - pos := (r/16)*OKpadded + c + (r % 16) + pos := (r/16)*OKpadded + c + (r & 15) t0 := a[pos] & lsb t1 := (a[pos] >> 1) & lsb t2 := (a[pos] >> 2) & lsb t3 := (a[pos] >> 3) & lsb for t := 0; t < len(Tail); t++ { - a[((r+t-M)/16)*OKpadded+c+((r+t)%16)] ^= t0*uint64(tab[4*t+0]) ^ t1*uint64(tab[4*t+1]) ^ t2*uint64(tab[4*t+2]) ^ t3*uint64(tab[4*t+3]) + a[((r+t-M)/16)*OKpadded+c+((r+t)&15)] ^= t0*uint64(tab[4*t+0]) ^ t1*uint64(tab[4*t+1]) ^ t2*uint64(tab[4*t+2]) ^ t3*uint64(tab[4*t+3]) } } } // transform the temporary matrix into the desired form of A matrix + var aInBytes [M * OKpadded]byte + copyUint64SliceToBytesLE(aInBytes[:], a[:]) + KO1 := K*O + 1 for r := 0; r < M; r += 16 { for c := 0; c < KO1-1; c += 16 { for i := 0; i < 16; i++ { - src := viewUint64SliceAsBytes(a[r/16*OKpadded+c+i:]) + src := aInBytes[(r/16*OKpadded+c+i)*8:] offset := KO1*(r+i) + c - decode(src, _a[offset:offset+min(16, KO1-1-c)]) + decode(_a[offset:offset+min(16, KO1-1-c)], src) } } } @@ -608,11 +622,14 @@ func transpose16x16Nibbles(m []uint64) { } } -func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { +func Verify(epk *ExpandedPublicKey, msg []byte, sig []byte) bool { if len(sig) != SignatureSize { - panic("sig must be of length SignatureSize") + return false } + senc := sig[:SignatureSize-SaltSize] + salt := sig[SignatureSize-SaltSize : SignatureSize] + var digest [DigestSize]byte h := sha3.NewShake256() @@ -621,20 +638,23 @@ func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { h.Reset() _, _ = h.Write(digest[:]) - _, _ = h.Write(sig[SignatureSize-SaltSize : SignatureSize]) + _, _ = h.Write(salt[:]) var tenc [M / 2]byte _, _ = h.Read(tenc[:]) var t [M]byte - decode(tenc[:], t[:]) + decode(t[:], tenc[:]) var s [K * N]byte - decode(sig[:(K*N+1)/2], s[:]) + decode(s[:], senc[:]) - P1 := viewBytesAsUint64Slice(epk.p1[:]) - P2 := viewBytesAsUint64Slice(epk.p2[:]) - P3 := viewBytesAsUint64Slice(epk.p3[:]) + var P1 [P1Size / 8]uint64 + var P2 [P2Size / 8]uint64 + var P3 [P3Size / 8]uint64 + copyBytesToUint64SliceLE(P1[:], epk.p1[:]) + copyBytesToUint64SliceLE(P2[:], epk.p2[:]) + copyBytesToUint64SliceLE(P3[:], epk.p3[:]) // Note: the variable time approach is overall about 30% faster // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] @@ -645,7 +665,7 @@ func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { // mulAddMMatXMatTrans(pst[:], P2, s[V:], V, O, K, N, false) // mulAddMMatXMatTrans(pst[M*V*K/16:], P3, s[V:], O, O, K, N, true) // Variable time approach with table access where index depends on input: - calculatePStVarTime(pst[:], P1, P2, P3, s[:]) + calculatePStVarTime(pst[:], P1[:], P2[:], P3[:], s[:]) // compute S * PST var sps [M * K * K / 16]uint64 @@ -655,7 +675,7 @@ func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { emulsifyInto(sps[:], t[:]) var zeros [M]byte - return subtle.ConstantTimeCompare(t[:], zeros[:]) == 1 + return bytes.Equal(t[:], zeros[:]) } // GF(16) multiplication mod x^4 + x + 1 @@ -698,7 +718,6 @@ func inverse(a byte) byte { func emulsifyInto(sps []uint64, y []uint8) { var acc [M / 16]uint64 - accBytes := viewUint64SliceAsBytes(acc[:]) for i := K - 1; i >= 0; i-- { for j := i; j < K; j++ { @@ -710,13 +729,11 @@ func emulsifyInto(sps []uint64, y []uint8) { acc[k] <<= 4 } - for k := 0; k < len(Tail); k++ { - if k%2 == 0 { - accBytes[k/2] ^= mul(top, Tail[k]) - } else { - accBytes[k/2] ^= mul(top, Tail[k]) << 4 - } - } + acc[0] ^= uint64(mul(top, Tail[0])) + acc[0] ^= uint64(mul(top, Tail[1])) << 4 + acc[0] ^= uint64(mul(top, Tail[2])) << 8 + acc[0] ^= uint64(mul(top, Tail[3])) << 12 + acc[0] ^= uint64(mul(top, Tail[4])) << 16 for k := 0; k < M/16; k++ { acc[k] ^= sps[(i*K+j)*(M/16)+k] @@ -728,8 +745,11 @@ func emulsifyInto(sps []uint64, y []uint8) { } // add to y - for i := 0; i < M; i += 2 { - y[i] ^= accBytes[i/2] & 0xF - y[i+1] ^= accBytes[i/2] >> 4 + for i := 0; i < M; i += 16 { + a := acc[i/16] + for k := 0; k < 16; k++ { + y[i+k] ^= uint8(a & 0xF) + a >>= 4 + } } } diff --git a/sign/mayo/mode1/internal/mayo_test.go b/sign/mayo/mode1/internal/mayo_test.go index efb854802..ca178f165 100644 --- a/sign/mayo/mode1/internal/mayo_test.go +++ b/sign/mayo/mode1/internal/mayo_test.go @@ -69,12 +69,12 @@ func TestVerify(t *testing.T) { pk, _ := NewKeyFromSeed(seed) epk := pk.Expand() - if !Verify(m, s, epk) { + if !Verify(epk, m, s) { t.Fatal("should verify") } epk.p1[0] ^= 1 - if Verify(m, s, epk) { + if Verify(epk, m, s) { t.Fatal("should not verify") } }) @@ -102,7 +102,7 @@ func TestSampleSolution(t *testing.T) { t.Fatal() } - if !Verify(msg[:], sig[:], pk.Expand()) { + if !Verify(pk.Expand(), msg[:], sig[:]) { t.Fatal() } } @@ -159,7 +159,7 @@ func TestPQCgenKATSign(t *testing.T) { fmt.Fprintf(f, "sm = %X%X\n\n", sig, msg) - if !Verify(msg[:], sig, pk.Expand()) { + if !Verify(pk.Expand(), msg[:], sig) { t.Fatal() } } @@ -199,7 +199,7 @@ func BenchmarkVerify(b *testing.B) { sig, _ := Sign(msg[:], sk.Expand(), nil) b.ResetTimer() for i := 0; i < b.N; i++ { - _ = Verify(msg[:], sig[:], pk.Expand()) + _ = Verify(pk.Expand(), msg[:], sig[:]) } } @@ -211,7 +211,7 @@ func BenchmarkVerifyExpandedKey(b *testing.B) { epk := pk.Expand() b.ResetTimer() for i := 0; i < b.N; i++ { - _ = Verify(msg[:], sig[:], epk) + _ = Verify(epk, msg[:], sig[:]) } } diff --git a/sign/mayo/mode1/mayo.go b/sign/mayo/mode1/mayo.go index 229c41571..840801b51 100644 --- a/sign/mayo/mode1/mayo.go +++ b/sign/mayo/mode1/mayo.go @@ -68,9 +68,9 @@ func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { // Verify checks whether the given signature by pk on msg is valid. func Verify(pk *PublicKey, msg []byte, signature []byte) bool { return internal.Verify( + (*internal.PublicKey)(pk).Expand(), msg, signature, - (*internal.PublicKey)(pk).Expand(), ) } diff --git a/sign/mayo/mode2/internal/mayo.go b/sign/mayo/mode2/internal/mayo.go index 970a3fcb4..e0fabb46a 100644 --- a/sign/mayo/mode2/internal/mayo.go +++ b/sign/mayo/mode2/internal/mayo.go @@ -3,12 +3,13 @@ package internal import ( + "bytes" "crypto/aes" "crypto/cipher" cryptoRand "crypto/rand" "crypto/subtle" + "encoding/binary" "io" - "unsafe" "github.com/cloudflare/circl/internal/sha3" ) @@ -42,7 +43,8 @@ type ExpandedPrivateKey struct { func (pk *PublicKey) Expand() *ExpandedPublicKey { seedPk := pk[:PublicKeySeedSize] - var nonce [16]byte // zero-initialized + var nonce [16]byte + // TODO there are unnecessary allocations block, _ := aes.NewCipher(seedPk[:]) ctr := cipher.NewCTR(block, nonce[:]) @@ -56,10 +58,10 @@ func (pk *PublicKey) Expand() *ExpandedPublicKey { } func (sk *PrivateKey) Expand() *ExpandedPrivateKey { - var epk ExpandedPrivateKey + var esk ExpandedPrivateKey seed := (*sk)[:KeySeedSize] - copy(epk.seed[:], seed) + copy(esk.seed[:], seed) var seedPk [PublicKeySeedSize]byte var o [OSize]byte @@ -69,31 +71,28 @@ func (sk *PrivateKey) Expand() *ExpandedPrivateKey { _, _ = h.Read(seedPk[:]) _, _ = h.Read(o[:]) - var nonce [16]byte // zero-initialized + var nonce [16]byte + // TODO there are unnecessary allocations block, _ := aes.NewCipher(seedPk[:]) ctr := cipher.NewCTR(block, nonce[:]) var p12 [P1Size + P2Size]byte ctr.XORKeyStream(p12[:], p12[:]) - decode(o[:], epk.o[:]) + decode(esk.o[:], o[:]) - p1Tri := viewBytesAsUint64Slice(p12[:P1Size]) - p2 := viewBytesAsUint64Slice(p12[P1Size:]) - - // TODO: this copy can be saved by reusing buffers but ugly - copy(epk.p1[:], p1Tri) - copy(epk.l[:], p2) + copyBytesToUint64SliceLE(esk.p1[:P1Size/8], p12[:P1Size]) + copyBytesToUint64SliceLE(esk.l[:], p12[P1Size:]) // compute L_i = (P1 + P1^t)*O + P2 - mulAddMUpperTriangularWithTransposeMatXMat(epk.l[:], p1Tri, epk.o[:], V, O) + mulAddMUpperTriangularWithTransposeMatXMat(esk.l[:], esk.p1[:], esk.o[:], V, O) - return &epk + return &esk } -// decode unpacks N bytes from src to N*2 nibbles to dst. +// decode unpacks N bytes from src to N*2 nibbles of dst. // The length is determined by len(dst) -func decode(src []byte, dst []byte) { +func decode(dst []byte, src []byte) { i := 0 for ; i < len(dst)/2; i++ { dst[i*2] = src[i] & 0xf @@ -101,30 +100,36 @@ func decode(src []byte, dst []byte) { } // Account for odd length - if len(dst)%2 == 1 { + if len(dst)&1 == 1 { dst[i*2] = src[i] & 0xf } } // encode packs N=length low nibbles from src to (N+1)/2 bytes in dst. -func encode(src []byte, dst []byte, length int) { +func encode(dst []byte, src []byte, length int) { var i int for i = 0; i+1 < length; i += 2 { dst[i/2] = (src[i+0] << 0) | (src[i+1] << 4) } - if length%2 == 1 { + if length&1 == 1 { dst[i/2] = (src[i+0] << 0) } } -func viewBytesAsUint64Slice(data []byte) []uint64 { - numUint64 := len(data) / 8 - return unsafe.Slice((*uint64)(unsafe.Pointer(&data[0])), numUint64) +// Assumes len(dst) * 8 == len(src). Loop size depends on len(dst). +func copyBytesToUint64SliceLE(dst []uint64, src []byte) { + for i := range dst { + dst[i] = binary.LittleEndian.Uint64(src) + src = src[8:] + } } -func viewUint64SliceAsBytes(data []uint64) []byte { - numByte := len(data) * 8 - return unsafe.Slice((*uint8)(unsafe.Pointer(&data[0])), numByte) +// Assumes len(dst) == len(src) * 8. Loop size depends on len(src). +func copyUint64SliceToBytesLE(dst []byte, src []uint64) { + for _, s := range src { + binary.LittleEndian.PutUint64(dst, s) + dst = dst[8:] + } } // GenerateKey generates a public/private key pair using entropy from rand. @@ -159,7 +164,8 @@ func (sk *PrivateKey) Public() *PublicKey { _, _ = h.Read(seedPk[:]) _, _ = h.Read(o[:]) - var nonce [16]byte // zero-initialized + var nonce [16]byte + // TODO there are unnecessary allocations block, _ := aes.NewCipher(seedPk[:]) ctr := cipher.NewCTR(block, nonce[:]) @@ -167,21 +173,22 @@ func (sk *PrivateKey) Public() *PublicKey { ctr.XORKeyStream(p12[:], p12[:]) var oo [V * O]byte - decode(o[:], oo[:]) + decode(oo[:], o[:]) - p1Tri := viewBytesAsUint64Slice(p12[:P1Size]) - p1OP2 := viewBytesAsUint64Slice(p12[P1Size:]) + var p1Tri [P1Size / 8]uint64 + var p1OP2 [P2Size / 8]uint64 + copyBytesToUint64SliceLE(p1Tri[:], p12[:P1Size]) + copyBytesToUint64SliceLE(p1OP2[:], p12[P1Size:]) var p3full [M * O * O / 16]uint64 var p3 [P3Size / 8]uint64 - mulAddMUpperTriangularMatXMat(p1OP2, p1Tri, oo[:], V, O) - mulAddMatTransXMMat(p3full[:], oo[:], p1OP2, V, O, O) + mulAddMUpperTriangularMatXMat(p1OP2[:], p1Tri[:], oo[:], V, O) + mulAddMatTransXMMat(p3full[:], oo[:], p1OP2[:], V, O, O) upper(p3full[:], p3[:], O) - xx := viewUint64SliceAsBytes(p3[:]) - copy(pk[PublicKeySeedSize:], xx[:]) + copyUint64SliceToBytesLE(pk[PublicKeySeedSize:], p3[:]) return &pk } @@ -218,7 +225,7 @@ func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { _, _ = h.Read(tenc[:]) var t [M]byte - decode(tenc[:], t[:]) + decode(t[:], tenc[:]) var v [K * V]byte var x [K*O + 1]byte // + 1 for buffer @@ -238,9 +245,9 @@ func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { var r [K * O]byte for i := 0; i < K; i++ { - decode(venc[i*VSize:], v[i*V:(i+1)*V]) + decode(v[i*V:(i+1)*V], venc[i*VSize:]) } - decode(renc[:], r[:]) + decode(r[:], renc[:]) // M = vL var m [M * K * O / 16]uint64 @@ -276,7 +283,7 @@ func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { } var sig [(K*N+1)/2 + SaltSize]byte - encode(s[:], sig[:], K*N) + encode(sig[:], s[:], K*N) copy(sig[(K*N+1)/2:], salt[:]) return sig[:], nil @@ -368,7 +375,7 @@ func ctCompare8(a, b byte) byte { func extract(in []uint64, index int) byte { leg := index / 16 - offset := index % 16 + offset := index & 15 return byte((in[leg] >> (offset * 4)) & 0xF) } @@ -391,10 +398,12 @@ func ef(A []byte, nrows, ncols int) { // nibbleslice the matrix A var packedAbyte [((K*O + 1 + 15) / 16) * M * 8]byte for i := 0; i < nrows; i++ { - encode(A[i*ncols:], packedAbyte[i*rowLen*8:], ncols) + encode(packedAbyte[i*rowLen*8:], A[i*ncols:], ncols) } - packedA := viewBytesAsUint64Slice(packedAbyte[:]) + // packing into uint64 to gain some bitwise parallelism over uint8 + var packedA [((K*O + 1 + 15) / 16) * M]uint64 + copyBytesToUint64SliceLE(packedA[:], packedAbyte[:]) // pivot row is secret, pivot col is not pivotRow := 0 @@ -458,8 +467,10 @@ func ef(A []byte, nrows, ncols int) { var temp [(O*K + 1 + 15)]byte // unnibbleslice the matrix A + copyUint64SliceToBytesLE(packedAbyte[:], packedA[:]) + for i := 0; i < nrows; i++ { - decode(packedAbyte[i*rowLen*8:], temp[:rowLen*16]) + decode(temp[:rowLen*16], packedAbyte[i*rowLen*8:]) for j := 0; j < ncols; j++ { A[i*ncols+j] = temp[j] } @@ -548,25 +559,28 @@ func computeA(m []uint64, _a []byte) { for c := 0; c < OKpadded; c += 16 { for r := M; r < M+(K+1)*K/2; r++ { - pos := (r/16)*OKpadded + c + (r % 16) + pos := (r/16)*OKpadded + c + (r & 15) t0 := a[pos] & lsb t1 := (a[pos] >> 1) & lsb t2 := (a[pos] >> 2) & lsb t3 := (a[pos] >> 3) & lsb for t := 0; t < len(Tail); t++ { - a[((r+t-M)/16)*OKpadded+c+((r+t)%16)] ^= t0*uint64(tab[4*t+0]) ^ t1*uint64(tab[4*t+1]) ^ t2*uint64(tab[4*t+2]) ^ t3*uint64(tab[4*t+3]) + a[((r+t-M)/16)*OKpadded+c+((r+t)&15)] ^= t0*uint64(tab[4*t+0]) ^ t1*uint64(tab[4*t+1]) ^ t2*uint64(tab[4*t+2]) ^ t3*uint64(tab[4*t+3]) } } } // transform the temporary matrix into the desired form of A matrix + var aInBytes [M * OKpadded]byte + copyUint64SliceToBytesLE(aInBytes[:], a[:]) + KO1 := K*O + 1 for r := 0; r < M; r += 16 { for c := 0; c < KO1-1; c += 16 { for i := 0; i < 16; i++ { - src := viewUint64SliceAsBytes(a[r/16*OKpadded+c+i:]) + src := aInBytes[(r/16*OKpadded+c+i)*8:] offset := KO1*(r+i) + c - decode(src, _a[offset:offset+min(16, KO1-1-c)]) + decode(_a[offset:offset+min(16, KO1-1-c)], src) } } } @@ -610,11 +624,14 @@ func transpose16x16Nibbles(m []uint64) { } } -func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { +func Verify(epk *ExpandedPublicKey, msg []byte, sig []byte) bool { if len(sig) != SignatureSize { - panic("sig must be of length SignatureSize") + return false } + senc := sig[:SignatureSize-SaltSize] + salt := sig[SignatureSize-SaltSize : SignatureSize] + var digest [DigestSize]byte h := sha3.NewShake256() @@ -623,20 +640,23 @@ func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { h.Reset() _, _ = h.Write(digest[:]) - _, _ = h.Write(sig[SignatureSize-SaltSize : SignatureSize]) + _, _ = h.Write(salt[:]) var tenc [M / 2]byte _, _ = h.Read(tenc[:]) var t [M]byte - decode(tenc[:], t[:]) + decode(t[:], tenc[:]) var s [K * N]byte - decode(sig[:(K*N+1)/2], s[:]) + decode(s[:], senc[:]) - P1 := viewBytesAsUint64Slice(epk.p1[:]) - P2 := viewBytesAsUint64Slice(epk.p2[:]) - P3 := viewBytesAsUint64Slice(epk.p3[:]) + var P1 [P1Size / 8]uint64 + var P2 [P2Size / 8]uint64 + var P3 [P3Size / 8]uint64 + copyBytesToUint64SliceLE(P1[:], epk.p1[:]) + copyBytesToUint64SliceLE(P2[:], epk.p2[:]) + copyBytesToUint64SliceLE(P3[:], epk.p3[:]) // Note: the variable time approach is overall about 30% faster // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] @@ -647,7 +667,7 @@ func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { // mulAddMMatXMatTrans(pst[:], P2, s[V:], V, O, K, N, false) // mulAddMMatXMatTrans(pst[M*V*K/16:], P3, s[V:], O, O, K, N, true) // Variable time approach with table access where index depends on input: - calculatePStVarTime(pst[:], P1, P2, P3, s[:]) + calculatePStVarTime(pst[:], P1[:], P2[:], P3[:], s[:]) // compute S * PST var sps [M * K * K / 16]uint64 @@ -657,7 +677,7 @@ func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { emulsifyInto(sps[:], t[:]) var zeros [M]byte - return subtle.ConstantTimeCompare(t[:], zeros[:]) == 1 + return bytes.Equal(t[:], zeros[:]) } // GF(16) multiplication mod x^4 + x + 1 @@ -700,7 +720,6 @@ func inverse(a byte) byte { func emulsifyInto(sps []uint64, y []uint8) { var acc [M / 16]uint64 - accBytes := viewUint64SliceAsBytes(acc[:]) for i := K - 1; i >= 0; i-- { for j := i; j < K; j++ { @@ -712,13 +731,11 @@ func emulsifyInto(sps []uint64, y []uint8) { acc[k] <<= 4 } - for k := 0; k < len(Tail); k++ { - if k%2 == 0 { - accBytes[k/2] ^= mul(top, Tail[k]) - } else { - accBytes[k/2] ^= mul(top, Tail[k]) << 4 - } - } + acc[0] ^= uint64(mul(top, Tail[0])) + acc[0] ^= uint64(mul(top, Tail[1])) << 4 + acc[0] ^= uint64(mul(top, Tail[2])) << 8 + acc[0] ^= uint64(mul(top, Tail[3])) << 12 + acc[0] ^= uint64(mul(top, Tail[4])) << 16 for k := 0; k < M/16; k++ { acc[k] ^= sps[(i*K+j)*(M/16)+k] @@ -730,8 +747,11 @@ func emulsifyInto(sps []uint64, y []uint8) { } // add to y - for i := 0; i < M; i += 2 { - y[i] ^= accBytes[i/2] & 0xF - y[i+1] ^= accBytes[i/2] >> 4 + for i := 0; i < M; i += 16 { + a := acc[i/16] + for k := 0; k < 16; k++ { + y[i+k] ^= uint8(a & 0xF) + a >>= 4 + } } } diff --git a/sign/mayo/mode2/internal/mayo_test.go b/sign/mayo/mode2/internal/mayo_test.go index 2c0eac0de..d8509f8a7 100644 --- a/sign/mayo/mode2/internal/mayo_test.go +++ b/sign/mayo/mode2/internal/mayo_test.go @@ -71,12 +71,12 @@ func TestVerify(t *testing.T) { pk, _ := NewKeyFromSeed(seed) epk := pk.Expand() - if !Verify(m, s, epk) { + if !Verify(epk, m, s) { t.Fatal("should verify") } epk.p1[0] ^= 1 - if Verify(m, s, epk) { + if Verify(epk, m, s) { t.Fatal("should not verify") } }) @@ -104,7 +104,7 @@ func TestSampleSolution(t *testing.T) { t.Fatal() } - if !Verify(msg[:], sig[:], pk.Expand()) { + if !Verify(pk.Expand(), msg[:], sig[:]) { t.Fatal() } } @@ -161,7 +161,7 @@ func TestPQCgenKATSign(t *testing.T) { fmt.Fprintf(f, "sm = %X%X\n\n", sig, msg) - if !Verify(msg[:], sig, pk.Expand()) { + if !Verify(pk.Expand(), msg[:], sig) { t.Fatal() } } @@ -201,7 +201,7 @@ func BenchmarkVerify(b *testing.B) { sig, _ := Sign(msg[:], sk.Expand(), nil) b.ResetTimer() for i := 0; i < b.N; i++ { - _ = Verify(msg[:], sig[:], pk.Expand()) + _ = Verify(pk.Expand(), msg[:], sig[:]) } } @@ -213,7 +213,7 @@ func BenchmarkVerifyExpandedKey(b *testing.B) { epk := pk.Expand() b.ResetTimer() for i := 0; i < b.N; i++ { - _ = Verify(msg[:], sig[:], epk) + _ = Verify(epk, msg[:], sig[:]) } } diff --git a/sign/mayo/mode2/mayo.go b/sign/mayo/mode2/mayo.go index 8252e6c58..eddba8010 100644 --- a/sign/mayo/mode2/mayo.go +++ b/sign/mayo/mode2/mayo.go @@ -68,9 +68,9 @@ func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { // Verify checks whether the given signature by pk on msg is valid. func Verify(pk *PublicKey, msg []byte, signature []byte) bool { return internal.Verify( + (*internal.PublicKey)(pk).Expand(), msg, signature, - (*internal.PublicKey)(pk).Expand(), ) } diff --git a/sign/mayo/mode3/internal/mayo.go b/sign/mayo/mode3/internal/mayo.go index 970a3fcb4..e0fabb46a 100644 --- a/sign/mayo/mode3/internal/mayo.go +++ b/sign/mayo/mode3/internal/mayo.go @@ -3,12 +3,13 @@ package internal import ( + "bytes" "crypto/aes" "crypto/cipher" cryptoRand "crypto/rand" "crypto/subtle" + "encoding/binary" "io" - "unsafe" "github.com/cloudflare/circl/internal/sha3" ) @@ -42,7 +43,8 @@ type ExpandedPrivateKey struct { func (pk *PublicKey) Expand() *ExpandedPublicKey { seedPk := pk[:PublicKeySeedSize] - var nonce [16]byte // zero-initialized + var nonce [16]byte + // TODO there are unnecessary allocations block, _ := aes.NewCipher(seedPk[:]) ctr := cipher.NewCTR(block, nonce[:]) @@ -56,10 +58,10 @@ func (pk *PublicKey) Expand() *ExpandedPublicKey { } func (sk *PrivateKey) Expand() *ExpandedPrivateKey { - var epk ExpandedPrivateKey + var esk ExpandedPrivateKey seed := (*sk)[:KeySeedSize] - copy(epk.seed[:], seed) + copy(esk.seed[:], seed) var seedPk [PublicKeySeedSize]byte var o [OSize]byte @@ -69,31 +71,28 @@ func (sk *PrivateKey) Expand() *ExpandedPrivateKey { _, _ = h.Read(seedPk[:]) _, _ = h.Read(o[:]) - var nonce [16]byte // zero-initialized + var nonce [16]byte + // TODO there are unnecessary allocations block, _ := aes.NewCipher(seedPk[:]) ctr := cipher.NewCTR(block, nonce[:]) var p12 [P1Size + P2Size]byte ctr.XORKeyStream(p12[:], p12[:]) - decode(o[:], epk.o[:]) + decode(esk.o[:], o[:]) - p1Tri := viewBytesAsUint64Slice(p12[:P1Size]) - p2 := viewBytesAsUint64Slice(p12[P1Size:]) - - // TODO: this copy can be saved by reusing buffers but ugly - copy(epk.p1[:], p1Tri) - copy(epk.l[:], p2) + copyBytesToUint64SliceLE(esk.p1[:P1Size/8], p12[:P1Size]) + copyBytesToUint64SliceLE(esk.l[:], p12[P1Size:]) // compute L_i = (P1 + P1^t)*O + P2 - mulAddMUpperTriangularWithTransposeMatXMat(epk.l[:], p1Tri, epk.o[:], V, O) + mulAddMUpperTriangularWithTransposeMatXMat(esk.l[:], esk.p1[:], esk.o[:], V, O) - return &epk + return &esk } -// decode unpacks N bytes from src to N*2 nibbles to dst. +// decode unpacks N bytes from src to N*2 nibbles of dst. // The length is determined by len(dst) -func decode(src []byte, dst []byte) { +func decode(dst []byte, src []byte) { i := 0 for ; i < len(dst)/2; i++ { dst[i*2] = src[i] & 0xf @@ -101,30 +100,36 @@ func decode(src []byte, dst []byte) { } // Account for odd length - if len(dst)%2 == 1 { + if len(dst)&1 == 1 { dst[i*2] = src[i] & 0xf } } // encode packs N=length low nibbles from src to (N+1)/2 bytes in dst. -func encode(src []byte, dst []byte, length int) { +func encode(dst []byte, src []byte, length int) { var i int for i = 0; i+1 < length; i += 2 { dst[i/2] = (src[i+0] << 0) | (src[i+1] << 4) } - if length%2 == 1 { + if length&1 == 1 { dst[i/2] = (src[i+0] << 0) } } -func viewBytesAsUint64Slice(data []byte) []uint64 { - numUint64 := len(data) / 8 - return unsafe.Slice((*uint64)(unsafe.Pointer(&data[0])), numUint64) +// Assumes len(dst) * 8 == len(src). Loop size depends on len(dst). +func copyBytesToUint64SliceLE(dst []uint64, src []byte) { + for i := range dst { + dst[i] = binary.LittleEndian.Uint64(src) + src = src[8:] + } } -func viewUint64SliceAsBytes(data []uint64) []byte { - numByte := len(data) * 8 - return unsafe.Slice((*uint8)(unsafe.Pointer(&data[0])), numByte) +// Assumes len(dst) == len(src) * 8. Loop size depends on len(src). +func copyUint64SliceToBytesLE(dst []byte, src []uint64) { + for _, s := range src { + binary.LittleEndian.PutUint64(dst, s) + dst = dst[8:] + } } // GenerateKey generates a public/private key pair using entropy from rand. @@ -159,7 +164,8 @@ func (sk *PrivateKey) Public() *PublicKey { _, _ = h.Read(seedPk[:]) _, _ = h.Read(o[:]) - var nonce [16]byte // zero-initialized + var nonce [16]byte + // TODO there are unnecessary allocations block, _ := aes.NewCipher(seedPk[:]) ctr := cipher.NewCTR(block, nonce[:]) @@ -167,21 +173,22 @@ func (sk *PrivateKey) Public() *PublicKey { ctr.XORKeyStream(p12[:], p12[:]) var oo [V * O]byte - decode(o[:], oo[:]) + decode(oo[:], o[:]) - p1Tri := viewBytesAsUint64Slice(p12[:P1Size]) - p1OP2 := viewBytesAsUint64Slice(p12[P1Size:]) + var p1Tri [P1Size / 8]uint64 + var p1OP2 [P2Size / 8]uint64 + copyBytesToUint64SliceLE(p1Tri[:], p12[:P1Size]) + copyBytesToUint64SliceLE(p1OP2[:], p12[P1Size:]) var p3full [M * O * O / 16]uint64 var p3 [P3Size / 8]uint64 - mulAddMUpperTriangularMatXMat(p1OP2, p1Tri, oo[:], V, O) - mulAddMatTransXMMat(p3full[:], oo[:], p1OP2, V, O, O) + mulAddMUpperTriangularMatXMat(p1OP2[:], p1Tri[:], oo[:], V, O) + mulAddMatTransXMMat(p3full[:], oo[:], p1OP2[:], V, O, O) upper(p3full[:], p3[:], O) - xx := viewUint64SliceAsBytes(p3[:]) - copy(pk[PublicKeySeedSize:], xx[:]) + copyUint64SliceToBytesLE(pk[PublicKeySeedSize:], p3[:]) return &pk } @@ -218,7 +225,7 @@ func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { _, _ = h.Read(tenc[:]) var t [M]byte - decode(tenc[:], t[:]) + decode(t[:], tenc[:]) var v [K * V]byte var x [K*O + 1]byte // + 1 for buffer @@ -238,9 +245,9 @@ func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { var r [K * O]byte for i := 0; i < K; i++ { - decode(venc[i*VSize:], v[i*V:(i+1)*V]) + decode(v[i*V:(i+1)*V], venc[i*VSize:]) } - decode(renc[:], r[:]) + decode(r[:], renc[:]) // M = vL var m [M * K * O / 16]uint64 @@ -276,7 +283,7 @@ func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { } var sig [(K*N+1)/2 + SaltSize]byte - encode(s[:], sig[:], K*N) + encode(sig[:], s[:], K*N) copy(sig[(K*N+1)/2:], salt[:]) return sig[:], nil @@ -368,7 +375,7 @@ func ctCompare8(a, b byte) byte { func extract(in []uint64, index int) byte { leg := index / 16 - offset := index % 16 + offset := index & 15 return byte((in[leg] >> (offset * 4)) & 0xF) } @@ -391,10 +398,12 @@ func ef(A []byte, nrows, ncols int) { // nibbleslice the matrix A var packedAbyte [((K*O + 1 + 15) / 16) * M * 8]byte for i := 0; i < nrows; i++ { - encode(A[i*ncols:], packedAbyte[i*rowLen*8:], ncols) + encode(packedAbyte[i*rowLen*8:], A[i*ncols:], ncols) } - packedA := viewBytesAsUint64Slice(packedAbyte[:]) + // packing into uint64 to gain some bitwise parallelism over uint8 + var packedA [((K*O + 1 + 15) / 16) * M]uint64 + copyBytesToUint64SliceLE(packedA[:], packedAbyte[:]) // pivot row is secret, pivot col is not pivotRow := 0 @@ -458,8 +467,10 @@ func ef(A []byte, nrows, ncols int) { var temp [(O*K + 1 + 15)]byte // unnibbleslice the matrix A + copyUint64SliceToBytesLE(packedAbyte[:], packedA[:]) + for i := 0; i < nrows; i++ { - decode(packedAbyte[i*rowLen*8:], temp[:rowLen*16]) + decode(temp[:rowLen*16], packedAbyte[i*rowLen*8:]) for j := 0; j < ncols; j++ { A[i*ncols+j] = temp[j] } @@ -548,25 +559,28 @@ func computeA(m []uint64, _a []byte) { for c := 0; c < OKpadded; c += 16 { for r := M; r < M+(K+1)*K/2; r++ { - pos := (r/16)*OKpadded + c + (r % 16) + pos := (r/16)*OKpadded + c + (r & 15) t0 := a[pos] & lsb t1 := (a[pos] >> 1) & lsb t2 := (a[pos] >> 2) & lsb t3 := (a[pos] >> 3) & lsb for t := 0; t < len(Tail); t++ { - a[((r+t-M)/16)*OKpadded+c+((r+t)%16)] ^= t0*uint64(tab[4*t+0]) ^ t1*uint64(tab[4*t+1]) ^ t2*uint64(tab[4*t+2]) ^ t3*uint64(tab[4*t+3]) + a[((r+t-M)/16)*OKpadded+c+((r+t)&15)] ^= t0*uint64(tab[4*t+0]) ^ t1*uint64(tab[4*t+1]) ^ t2*uint64(tab[4*t+2]) ^ t3*uint64(tab[4*t+3]) } } } // transform the temporary matrix into the desired form of A matrix + var aInBytes [M * OKpadded]byte + copyUint64SliceToBytesLE(aInBytes[:], a[:]) + KO1 := K*O + 1 for r := 0; r < M; r += 16 { for c := 0; c < KO1-1; c += 16 { for i := 0; i < 16; i++ { - src := viewUint64SliceAsBytes(a[r/16*OKpadded+c+i:]) + src := aInBytes[(r/16*OKpadded+c+i)*8:] offset := KO1*(r+i) + c - decode(src, _a[offset:offset+min(16, KO1-1-c)]) + decode(_a[offset:offset+min(16, KO1-1-c)], src) } } } @@ -610,11 +624,14 @@ func transpose16x16Nibbles(m []uint64) { } } -func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { +func Verify(epk *ExpandedPublicKey, msg []byte, sig []byte) bool { if len(sig) != SignatureSize { - panic("sig must be of length SignatureSize") + return false } + senc := sig[:SignatureSize-SaltSize] + salt := sig[SignatureSize-SaltSize : SignatureSize] + var digest [DigestSize]byte h := sha3.NewShake256() @@ -623,20 +640,23 @@ func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { h.Reset() _, _ = h.Write(digest[:]) - _, _ = h.Write(sig[SignatureSize-SaltSize : SignatureSize]) + _, _ = h.Write(salt[:]) var tenc [M / 2]byte _, _ = h.Read(tenc[:]) var t [M]byte - decode(tenc[:], t[:]) + decode(t[:], tenc[:]) var s [K * N]byte - decode(sig[:(K*N+1)/2], s[:]) + decode(s[:], senc[:]) - P1 := viewBytesAsUint64Slice(epk.p1[:]) - P2 := viewBytesAsUint64Slice(epk.p2[:]) - P3 := viewBytesAsUint64Slice(epk.p3[:]) + var P1 [P1Size / 8]uint64 + var P2 [P2Size / 8]uint64 + var P3 [P3Size / 8]uint64 + copyBytesToUint64SliceLE(P1[:], epk.p1[:]) + copyBytesToUint64SliceLE(P2[:], epk.p2[:]) + copyBytesToUint64SliceLE(P3[:], epk.p3[:]) // Note: the variable time approach is overall about 30% faster // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] @@ -647,7 +667,7 @@ func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { // mulAddMMatXMatTrans(pst[:], P2, s[V:], V, O, K, N, false) // mulAddMMatXMatTrans(pst[M*V*K/16:], P3, s[V:], O, O, K, N, true) // Variable time approach with table access where index depends on input: - calculatePStVarTime(pst[:], P1, P2, P3, s[:]) + calculatePStVarTime(pst[:], P1[:], P2[:], P3[:], s[:]) // compute S * PST var sps [M * K * K / 16]uint64 @@ -657,7 +677,7 @@ func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { emulsifyInto(sps[:], t[:]) var zeros [M]byte - return subtle.ConstantTimeCompare(t[:], zeros[:]) == 1 + return bytes.Equal(t[:], zeros[:]) } // GF(16) multiplication mod x^4 + x + 1 @@ -700,7 +720,6 @@ func inverse(a byte) byte { func emulsifyInto(sps []uint64, y []uint8) { var acc [M / 16]uint64 - accBytes := viewUint64SliceAsBytes(acc[:]) for i := K - 1; i >= 0; i-- { for j := i; j < K; j++ { @@ -712,13 +731,11 @@ func emulsifyInto(sps []uint64, y []uint8) { acc[k] <<= 4 } - for k := 0; k < len(Tail); k++ { - if k%2 == 0 { - accBytes[k/2] ^= mul(top, Tail[k]) - } else { - accBytes[k/2] ^= mul(top, Tail[k]) << 4 - } - } + acc[0] ^= uint64(mul(top, Tail[0])) + acc[0] ^= uint64(mul(top, Tail[1])) << 4 + acc[0] ^= uint64(mul(top, Tail[2])) << 8 + acc[0] ^= uint64(mul(top, Tail[3])) << 12 + acc[0] ^= uint64(mul(top, Tail[4])) << 16 for k := 0; k < M/16; k++ { acc[k] ^= sps[(i*K+j)*(M/16)+k] @@ -730,8 +747,11 @@ func emulsifyInto(sps []uint64, y []uint8) { } // add to y - for i := 0; i < M; i += 2 { - y[i] ^= accBytes[i/2] & 0xF - y[i+1] ^= accBytes[i/2] >> 4 + for i := 0; i < M; i += 16 { + a := acc[i/16] + for k := 0; k < 16; k++ { + y[i+k] ^= uint8(a & 0xF) + a >>= 4 + } } } diff --git a/sign/mayo/mode3/internal/mayo_test.go b/sign/mayo/mode3/internal/mayo_test.go index 2c0eac0de..d8509f8a7 100644 --- a/sign/mayo/mode3/internal/mayo_test.go +++ b/sign/mayo/mode3/internal/mayo_test.go @@ -71,12 +71,12 @@ func TestVerify(t *testing.T) { pk, _ := NewKeyFromSeed(seed) epk := pk.Expand() - if !Verify(m, s, epk) { + if !Verify(epk, m, s) { t.Fatal("should verify") } epk.p1[0] ^= 1 - if Verify(m, s, epk) { + if Verify(epk, m, s) { t.Fatal("should not verify") } }) @@ -104,7 +104,7 @@ func TestSampleSolution(t *testing.T) { t.Fatal() } - if !Verify(msg[:], sig[:], pk.Expand()) { + if !Verify(pk.Expand(), msg[:], sig[:]) { t.Fatal() } } @@ -161,7 +161,7 @@ func TestPQCgenKATSign(t *testing.T) { fmt.Fprintf(f, "sm = %X%X\n\n", sig, msg) - if !Verify(msg[:], sig, pk.Expand()) { + if !Verify(pk.Expand(), msg[:], sig) { t.Fatal() } } @@ -201,7 +201,7 @@ func BenchmarkVerify(b *testing.B) { sig, _ := Sign(msg[:], sk.Expand(), nil) b.ResetTimer() for i := 0; i < b.N; i++ { - _ = Verify(msg[:], sig[:], pk.Expand()) + _ = Verify(pk.Expand(), msg[:], sig[:]) } } @@ -213,7 +213,7 @@ func BenchmarkVerifyExpandedKey(b *testing.B) { epk := pk.Expand() b.ResetTimer() for i := 0; i < b.N; i++ { - _ = Verify(msg[:], sig[:], epk) + _ = Verify(epk, msg[:], sig[:]) } } diff --git a/sign/mayo/mode3/mayo.go b/sign/mayo/mode3/mayo.go index d0fafaf75..3e1fd0846 100644 --- a/sign/mayo/mode3/mayo.go +++ b/sign/mayo/mode3/mayo.go @@ -68,9 +68,9 @@ func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { // Verify checks whether the given signature by pk on msg is valid. func Verify(pk *PublicKey, msg []byte, signature []byte) bool { return internal.Verify( + (*internal.PublicKey)(pk).Expand(), msg, signature, - (*internal.PublicKey)(pk).Expand(), ) } diff --git a/sign/mayo/mode5/internal/mayo.go b/sign/mayo/mode5/internal/mayo.go index 970a3fcb4..e0fabb46a 100644 --- a/sign/mayo/mode5/internal/mayo.go +++ b/sign/mayo/mode5/internal/mayo.go @@ -3,12 +3,13 @@ package internal import ( + "bytes" "crypto/aes" "crypto/cipher" cryptoRand "crypto/rand" "crypto/subtle" + "encoding/binary" "io" - "unsafe" "github.com/cloudflare/circl/internal/sha3" ) @@ -42,7 +43,8 @@ type ExpandedPrivateKey struct { func (pk *PublicKey) Expand() *ExpandedPublicKey { seedPk := pk[:PublicKeySeedSize] - var nonce [16]byte // zero-initialized + var nonce [16]byte + // TODO there are unnecessary allocations block, _ := aes.NewCipher(seedPk[:]) ctr := cipher.NewCTR(block, nonce[:]) @@ -56,10 +58,10 @@ func (pk *PublicKey) Expand() *ExpandedPublicKey { } func (sk *PrivateKey) Expand() *ExpandedPrivateKey { - var epk ExpandedPrivateKey + var esk ExpandedPrivateKey seed := (*sk)[:KeySeedSize] - copy(epk.seed[:], seed) + copy(esk.seed[:], seed) var seedPk [PublicKeySeedSize]byte var o [OSize]byte @@ -69,31 +71,28 @@ func (sk *PrivateKey) Expand() *ExpandedPrivateKey { _, _ = h.Read(seedPk[:]) _, _ = h.Read(o[:]) - var nonce [16]byte // zero-initialized + var nonce [16]byte + // TODO there are unnecessary allocations block, _ := aes.NewCipher(seedPk[:]) ctr := cipher.NewCTR(block, nonce[:]) var p12 [P1Size + P2Size]byte ctr.XORKeyStream(p12[:], p12[:]) - decode(o[:], epk.o[:]) + decode(esk.o[:], o[:]) - p1Tri := viewBytesAsUint64Slice(p12[:P1Size]) - p2 := viewBytesAsUint64Slice(p12[P1Size:]) - - // TODO: this copy can be saved by reusing buffers but ugly - copy(epk.p1[:], p1Tri) - copy(epk.l[:], p2) + copyBytesToUint64SliceLE(esk.p1[:P1Size/8], p12[:P1Size]) + copyBytesToUint64SliceLE(esk.l[:], p12[P1Size:]) // compute L_i = (P1 + P1^t)*O + P2 - mulAddMUpperTriangularWithTransposeMatXMat(epk.l[:], p1Tri, epk.o[:], V, O) + mulAddMUpperTriangularWithTransposeMatXMat(esk.l[:], esk.p1[:], esk.o[:], V, O) - return &epk + return &esk } -// decode unpacks N bytes from src to N*2 nibbles to dst. +// decode unpacks N bytes from src to N*2 nibbles of dst. // The length is determined by len(dst) -func decode(src []byte, dst []byte) { +func decode(dst []byte, src []byte) { i := 0 for ; i < len(dst)/2; i++ { dst[i*2] = src[i] & 0xf @@ -101,30 +100,36 @@ func decode(src []byte, dst []byte) { } // Account for odd length - if len(dst)%2 == 1 { + if len(dst)&1 == 1 { dst[i*2] = src[i] & 0xf } } // encode packs N=length low nibbles from src to (N+1)/2 bytes in dst. -func encode(src []byte, dst []byte, length int) { +func encode(dst []byte, src []byte, length int) { var i int for i = 0; i+1 < length; i += 2 { dst[i/2] = (src[i+0] << 0) | (src[i+1] << 4) } - if length%2 == 1 { + if length&1 == 1 { dst[i/2] = (src[i+0] << 0) } } -func viewBytesAsUint64Slice(data []byte) []uint64 { - numUint64 := len(data) / 8 - return unsafe.Slice((*uint64)(unsafe.Pointer(&data[0])), numUint64) +// Assumes len(dst) * 8 == len(src). Loop size depends on len(dst). +func copyBytesToUint64SliceLE(dst []uint64, src []byte) { + for i := range dst { + dst[i] = binary.LittleEndian.Uint64(src) + src = src[8:] + } } -func viewUint64SliceAsBytes(data []uint64) []byte { - numByte := len(data) * 8 - return unsafe.Slice((*uint8)(unsafe.Pointer(&data[0])), numByte) +// Assumes len(dst) == len(src) * 8. Loop size depends on len(src). +func copyUint64SliceToBytesLE(dst []byte, src []uint64) { + for _, s := range src { + binary.LittleEndian.PutUint64(dst, s) + dst = dst[8:] + } } // GenerateKey generates a public/private key pair using entropy from rand. @@ -159,7 +164,8 @@ func (sk *PrivateKey) Public() *PublicKey { _, _ = h.Read(seedPk[:]) _, _ = h.Read(o[:]) - var nonce [16]byte // zero-initialized + var nonce [16]byte + // TODO there are unnecessary allocations block, _ := aes.NewCipher(seedPk[:]) ctr := cipher.NewCTR(block, nonce[:]) @@ -167,21 +173,22 @@ func (sk *PrivateKey) Public() *PublicKey { ctr.XORKeyStream(p12[:], p12[:]) var oo [V * O]byte - decode(o[:], oo[:]) + decode(oo[:], o[:]) - p1Tri := viewBytesAsUint64Slice(p12[:P1Size]) - p1OP2 := viewBytesAsUint64Slice(p12[P1Size:]) + var p1Tri [P1Size / 8]uint64 + var p1OP2 [P2Size / 8]uint64 + copyBytesToUint64SliceLE(p1Tri[:], p12[:P1Size]) + copyBytesToUint64SliceLE(p1OP2[:], p12[P1Size:]) var p3full [M * O * O / 16]uint64 var p3 [P3Size / 8]uint64 - mulAddMUpperTriangularMatXMat(p1OP2, p1Tri, oo[:], V, O) - mulAddMatTransXMMat(p3full[:], oo[:], p1OP2, V, O, O) + mulAddMUpperTriangularMatXMat(p1OP2[:], p1Tri[:], oo[:], V, O) + mulAddMatTransXMMat(p3full[:], oo[:], p1OP2[:], V, O, O) upper(p3full[:], p3[:], O) - xx := viewUint64SliceAsBytes(p3[:]) - copy(pk[PublicKeySeedSize:], xx[:]) + copyUint64SliceToBytesLE(pk[PublicKeySeedSize:], p3[:]) return &pk } @@ -218,7 +225,7 @@ func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { _, _ = h.Read(tenc[:]) var t [M]byte - decode(tenc[:], t[:]) + decode(t[:], tenc[:]) var v [K * V]byte var x [K*O + 1]byte // + 1 for buffer @@ -238,9 +245,9 @@ func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { var r [K * O]byte for i := 0; i < K; i++ { - decode(venc[i*VSize:], v[i*V:(i+1)*V]) + decode(v[i*V:(i+1)*V], venc[i*VSize:]) } - decode(renc[:], r[:]) + decode(r[:], renc[:]) // M = vL var m [M * K * O / 16]uint64 @@ -276,7 +283,7 @@ func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { } var sig [(K*N+1)/2 + SaltSize]byte - encode(s[:], sig[:], K*N) + encode(sig[:], s[:], K*N) copy(sig[(K*N+1)/2:], salt[:]) return sig[:], nil @@ -368,7 +375,7 @@ func ctCompare8(a, b byte) byte { func extract(in []uint64, index int) byte { leg := index / 16 - offset := index % 16 + offset := index & 15 return byte((in[leg] >> (offset * 4)) & 0xF) } @@ -391,10 +398,12 @@ func ef(A []byte, nrows, ncols int) { // nibbleslice the matrix A var packedAbyte [((K*O + 1 + 15) / 16) * M * 8]byte for i := 0; i < nrows; i++ { - encode(A[i*ncols:], packedAbyte[i*rowLen*8:], ncols) + encode(packedAbyte[i*rowLen*8:], A[i*ncols:], ncols) } - packedA := viewBytesAsUint64Slice(packedAbyte[:]) + // packing into uint64 to gain some bitwise parallelism over uint8 + var packedA [((K*O + 1 + 15) / 16) * M]uint64 + copyBytesToUint64SliceLE(packedA[:], packedAbyte[:]) // pivot row is secret, pivot col is not pivotRow := 0 @@ -458,8 +467,10 @@ func ef(A []byte, nrows, ncols int) { var temp [(O*K + 1 + 15)]byte // unnibbleslice the matrix A + copyUint64SliceToBytesLE(packedAbyte[:], packedA[:]) + for i := 0; i < nrows; i++ { - decode(packedAbyte[i*rowLen*8:], temp[:rowLen*16]) + decode(temp[:rowLen*16], packedAbyte[i*rowLen*8:]) for j := 0; j < ncols; j++ { A[i*ncols+j] = temp[j] } @@ -548,25 +559,28 @@ func computeA(m []uint64, _a []byte) { for c := 0; c < OKpadded; c += 16 { for r := M; r < M+(K+1)*K/2; r++ { - pos := (r/16)*OKpadded + c + (r % 16) + pos := (r/16)*OKpadded + c + (r & 15) t0 := a[pos] & lsb t1 := (a[pos] >> 1) & lsb t2 := (a[pos] >> 2) & lsb t3 := (a[pos] >> 3) & lsb for t := 0; t < len(Tail); t++ { - a[((r+t-M)/16)*OKpadded+c+((r+t)%16)] ^= t0*uint64(tab[4*t+0]) ^ t1*uint64(tab[4*t+1]) ^ t2*uint64(tab[4*t+2]) ^ t3*uint64(tab[4*t+3]) + a[((r+t-M)/16)*OKpadded+c+((r+t)&15)] ^= t0*uint64(tab[4*t+0]) ^ t1*uint64(tab[4*t+1]) ^ t2*uint64(tab[4*t+2]) ^ t3*uint64(tab[4*t+3]) } } } // transform the temporary matrix into the desired form of A matrix + var aInBytes [M * OKpadded]byte + copyUint64SliceToBytesLE(aInBytes[:], a[:]) + KO1 := K*O + 1 for r := 0; r < M; r += 16 { for c := 0; c < KO1-1; c += 16 { for i := 0; i < 16; i++ { - src := viewUint64SliceAsBytes(a[r/16*OKpadded+c+i:]) + src := aInBytes[(r/16*OKpadded+c+i)*8:] offset := KO1*(r+i) + c - decode(src, _a[offset:offset+min(16, KO1-1-c)]) + decode(_a[offset:offset+min(16, KO1-1-c)], src) } } } @@ -610,11 +624,14 @@ func transpose16x16Nibbles(m []uint64) { } } -func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { +func Verify(epk *ExpandedPublicKey, msg []byte, sig []byte) bool { if len(sig) != SignatureSize { - panic("sig must be of length SignatureSize") + return false } + senc := sig[:SignatureSize-SaltSize] + salt := sig[SignatureSize-SaltSize : SignatureSize] + var digest [DigestSize]byte h := sha3.NewShake256() @@ -623,20 +640,23 @@ func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { h.Reset() _, _ = h.Write(digest[:]) - _, _ = h.Write(sig[SignatureSize-SaltSize : SignatureSize]) + _, _ = h.Write(salt[:]) var tenc [M / 2]byte _, _ = h.Read(tenc[:]) var t [M]byte - decode(tenc[:], t[:]) + decode(t[:], tenc[:]) var s [K * N]byte - decode(sig[:(K*N+1)/2], s[:]) + decode(s[:], senc[:]) - P1 := viewBytesAsUint64Slice(epk.p1[:]) - P2 := viewBytesAsUint64Slice(epk.p2[:]) - P3 := viewBytesAsUint64Slice(epk.p3[:]) + var P1 [P1Size / 8]uint64 + var P2 [P2Size / 8]uint64 + var P3 [P3Size / 8]uint64 + copyBytesToUint64SliceLE(P1[:], epk.p1[:]) + copyBytesToUint64SliceLE(P2[:], epk.p2[:]) + copyBytesToUint64SliceLE(P3[:], epk.p3[:]) // Note: the variable time approach is overall about 30% faster // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] @@ -647,7 +667,7 @@ func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { // mulAddMMatXMatTrans(pst[:], P2, s[V:], V, O, K, N, false) // mulAddMMatXMatTrans(pst[M*V*K/16:], P3, s[V:], O, O, K, N, true) // Variable time approach with table access where index depends on input: - calculatePStVarTime(pst[:], P1, P2, P3, s[:]) + calculatePStVarTime(pst[:], P1[:], P2[:], P3[:], s[:]) // compute S * PST var sps [M * K * K / 16]uint64 @@ -657,7 +677,7 @@ func Verify(msg []byte, sig []byte, epk *ExpandedPublicKey) bool { emulsifyInto(sps[:], t[:]) var zeros [M]byte - return subtle.ConstantTimeCompare(t[:], zeros[:]) == 1 + return bytes.Equal(t[:], zeros[:]) } // GF(16) multiplication mod x^4 + x + 1 @@ -700,7 +720,6 @@ func inverse(a byte) byte { func emulsifyInto(sps []uint64, y []uint8) { var acc [M / 16]uint64 - accBytes := viewUint64SliceAsBytes(acc[:]) for i := K - 1; i >= 0; i-- { for j := i; j < K; j++ { @@ -712,13 +731,11 @@ func emulsifyInto(sps []uint64, y []uint8) { acc[k] <<= 4 } - for k := 0; k < len(Tail); k++ { - if k%2 == 0 { - accBytes[k/2] ^= mul(top, Tail[k]) - } else { - accBytes[k/2] ^= mul(top, Tail[k]) << 4 - } - } + acc[0] ^= uint64(mul(top, Tail[0])) + acc[0] ^= uint64(mul(top, Tail[1])) << 4 + acc[0] ^= uint64(mul(top, Tail[2])) << 8 + acc[0] ^= uint64(mul(top, Tail[3])) << 12 + acc[0] ^= uint64(mul(top, Tail[4])) << 16 for k := 0; k < M/16; k++ { acc[k] ^= sps[(i*K+j)*(M/16)+k] @@ -730,8 +747,11 @@ func emulsifyInto(sps []uint64, y []uint8) { } // add to y - for i := 0; i < M; i += 2 { - y[i] ^= accBytes[i/2] & 0xF - y[i+1] ^= accBytes[i/2] >> 4 + for i := 0; i < M; i += 16 { + a := acc[i/16] + for k := 0; k < 16; k++ { + y[i+k] ^= uint8(a & 0xF) + a >>= 4 + } } } diff --git a/sign/mayo/mode5/internal/mayo_test.go b/sign/mayo/mode5/internal/mayo_test.go index 2c0eac0de..d8509f8a7 100644 --- a/sign/mayo/mode5/internal/mayo_test.go +++ b/sign/mayo/mode5/internal/mayo_test.go @@ -71,12 +71,12 @@ func TestVerify(t *testing.T) { pk, _ := NewKeyFromSeed(seed) epk := pk.Expand() - if !Verify(m, s, epk) { + if !Verify(epk, m, s) { t.Fatal("should verify") } epk.p1[0] ^= 1 - if Verify(m, s, epk) { + if Verify(epk, m, s) { t.Fatal("should not verify") } }) @@ -104,7 +104,7 @@ func TestSampleSolution(t *testing.T) { t.Fatal() } - if !Verify(msg[:], sig[:], pk.Expand()) { + if !Verify(pk.Expand(), msg[:], sig[:]) { t.Fatal() } } @@ -161,7 +161,7 @@ func TestPQCgenKATSign(t *testing.T) { fmt.Fprintf(f, "sm = %X%X\n\n", sig, msg) - if !Verify(msg[:], sig, pk.Expand()) { + if !Verify(pk.Expand(), msg[:], sig) { t.Fatal() } } @@ -201,7 +201,7 @@ func BenchmarkVerify(b *testing.B) { sig, _ := Sign(msg[:], sk.Expand(), nil) b.ResetTimer() for i := 0; i < b.N; i++ { - _ = Verify(msg[:], sig[:], pk.Expand()) + _ = Verify(pk.Expand(), msg[:], sig[:]) } } @@ -213,7 +213,7 @@ func BenchmarkVerifyExpandedKey(b *testing.B) { epk := pk.Expand() b.ResetTimer() for i := 0; i < b.N; i++ { - _ = Verify(msg[:], sig[:], epk) + _ = Verify(epk, msg[:], sig[:]) } } diff --git a/sign/mayo/mode5/mayo.go b/sign/mayo/mode5/mayo.go index 12d0b2f86..7633e5e90 100644 --- a/sign/mayo/mode5/mayo.go +++ b/sign/mayo/mode5/mayo.go @@ -68,9 +68,9 @@ func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { // Verify checks whether the given signature by pk on msg is valid. func Verify(pk *PublicKey, msg []byte, signature []byte) bool { return internal.Verify( + (*internal.PublicKey)(pk).Expand(), msg, signature, - (*internal.PublicKey)(pk).Expand(), ) } diff --git a/sign/mayo/templates/modePkg.templ.go b/sign/mayo/templates/modePkg.templ.go index 304ca8432..14ab11212 100644 --- a/sign/mayo/templates/modePkg.templ.go +++ b/sign/mayo/templates/modePkg.templ.go @@ -72,9 +72,9 @@ func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { // Verify checks whether the given signature by pk on msg is valid. func Verify(pk *PublicKey, msg []byte, signature []byte) bool { return internal.Verify( + (*internal.PublicKey)(pk).Expand(), msg, signature, - (*internal.PublicKey)(pk).Expand(), ) } From 5e88046b03a933f0773804ed2043564dc154e7d5 Mon Sep 17 00:00:00 2001 From: Shang-Yi Yang Date: Tue, 12 Mar 2024 12:29:12 +0800 Subject: [PATCH 8/9] mayo: make key expansion part of keygen --- sign/mayo/mode1/internal/mayo.go | 127 +++++++++++++------------- sign/mayo/mode1/internal/mayo_test.go | 57 ++++-------- sign/mayo/mode1/mayo.go | 58 +++++++++--- sign/mayo/mode2/internal/mayo.go | 127 +++++++++++++------------- sign/mayo/mode2/internal/mayo_test.go | 57 ++++-------- sign/mayo/mode2/mayo.go | 58 +++++++++--- sign/mayo/mode3/internal/mayo.go | 127 +++++++++++++------------- sign/mayo/mode3/internal/mayo_test.go | 57 ++++-------- sign/mayo/mode3/mayo.go | 58 +++++++++--- sign/mayo/mode5/internal/mayo.go | 127 +++++++++++++------------- sign/mayo/mode5/internal/mayo_test.go | 57 ++++-------- sign/mayo/mode5/mayo.go | 58 +++++++++--- sign/mayo/templates/modePkg.templ.go | 58 +++++++++--- 13 files changed, 544 insertions(+), 482 deletions(-) diff --git a/sign/mayo/mode1/internal/mayo.go b/sign/mayo/mode1/internal/mayo.go index 26dd15f43..83ab278d1 100644 --- a/sign/mayo/mode1/internal/mayo.go +++ b/sign/mayo/mode1/internal/mayo.go @@ -12,60 +12,70 @@ import ( "github.com/cloudflare/circl/internal/sha3" ) -type ( - PrivateKey [PrivateKeySize]byte - PublicKey [PublicKeySize]byte -) +type PublicKey struct { + seed [PublicKeySeedSize]byte + p3 [P3Size / 8]uint64 -func (pk *PublicKey) Equal(other *PublicKey) bool { - return *pk == *other + // P1 and P2 are expanded from seed + p1 [P1Size / 8]uint64 + p2 [P2Size / 8]uint64 } -func (sk *PrivateKey) Equal(other *PrivateKey) bool { - return subtle.ConstantTimeCompare((*sk)[:], (*other)[:]) == 1 +type PrivateKey struct { + seed [KeySeedSize]byte + + p1 [P1Size / 8]uint64 + o [V * O]byte + l [M * V * O / 16]uint64 +} + +func (pk *PublicKey) Equal(other *PublicKey) bool { + return pk.seed == other.seed && pk.p3 == other.p3 } -type ExpandedPublicKey struct { - p1 [P1Size]byte - p2 [P2Size]byte - p3 [P3Size]byte +func (sk *PrivateKey) Equal(other *PrivateKey) bool { + return subtle.ConstantTimeCompare(sk.seed[:], other.seed[:]) == 1 } -type ExpandedPrivateKey struct { - seed [KeySeedSize]byte - o [V * O]byte - p1 [M * V * V / 16]uint64 - l [M * V * O / 16]uint64 +// Packs the public key into buf. +func (pk *PublicKey) Pack(buf *[PublicKeySize]byte) { + copy(buf[:PublicKeySeedSize], pk.seed[:]) + copyUint64SliceToBytesLE(buf[PublicKeySeedSize:], pk.p3[:]) } -func (pk *PublicKey) Expand() *ExpandedPublicKey { - seedPk := pk[:PublicKeySeedSize] +// Sets pk to the public key encoded in buf. +func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { + copy(pk.seed[:], buf[:PublicKeySeedSize]) var nonce [16]byte // TODO there are unnecessary allocations - block, _ := aes.NewCipher(seedPk[:]) + block, _ := aes.NewCipher(pk.seed[:]) ctr := cipher.NewCTR(block, nonce[:]) - epk := ExpandedPublicKey{} - ctr.XORKeyStream(epk.p1[:], epk.p1[:]) - ctr.XORKeyStream(epk.p2[:], epk.p2[:]) - - copy(epk.p3[:], pk[PublicKeySeedSize:]) + var p1 [P1Size]byte + var p2 [P2Size]byte + ctr.XORKeyStream(p1[:], p1[:]) + ctr.XORKeyStream(p2[:], p2[:]) - return &epk + copyBytesToUint64SliceLE(pk.p1[:], p1[:]) + copyBytesToUint64SliceLE(pk.p2[:], p2[:]) + copyBytesToUint64SliceLE(pk.p3[:], buf[PublicKeySeedSize:]) } -func (sk *PrivateKey) Expand() *ExpandedPrivateKey { - var esk ExpandedPrivateKey +// Packs the private key into buf. +func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { + copy(buf[:], sk.seed[:]) +} - seed := (*sk)[:KeySeedSize] - copy(esk.seed[:], seed) +// Sets sk to the private key encoded in buf. +func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { + copy(sk.seed[:], buf[:]) var seedPk [PublicKeySeedSize]byte var o [OSize]byte h := sha3.NewShake256() - _, _ = h.Write(seed[:]) + _, _ = h.Write(sk.seed[:]) _, _ = h.Read(seedPk[:]) _, _ = h.Read(o[:]) @@ -77,15 +87,13 @@ func (sk *PrivateKey) Expand() *ExpandedPrivateKey { var p12 [P1Size + P2Size]byte ctr.XORKeyStream(p12[:], p12[:]) - decode(esk.o[:], o[:]) + decode(sk.o[:], o[:]) - copyBytesToUint64SliceLE(esk.p1[:P1Size/8], p12[:P1Size]) - copyBytesToUint64SliceLE(esk.l[:], p12[P1Size:]) + copyBytesToUint64SliceLE(sk.p1[:P1Size/8], p12[:P1Size]) + copyBytesToUint64SliceLE(sk.l[:], p12[P1Size:]) // compute L_i = (P1 + P1^t)*O + P2 - mulAddMUpperTriangularWithTransposeMatXMat(esk.l[:], esk.p1[:], esk.o[:], V, O) - - return &esk + mulAddMUpperTriangularWithTransposeMatXMat(sk.l[:], sk.p1[:], sk.o[:], V, O) } // decode unpacks N bytes from src to N*2 nibbles of dst. @@ -103,7 +111,7 @@ func decode(dst []byte, src []byte) { } } -// encode packs N=length low nibbles from src to (N+1)/2 bytes in dst. +// encode packs N=length low nibbles from src to ceil(N/2) bytes in dst. func encode(dst []byte, src []byte, length int) { var i int for i = 0; i+1 < length; i += 2 { @@ -147,51 +155,49 @@ func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { func NewKeyFromSeed(seed [KeySeedSize]byte) (*PublicKey, *PrivateKey) { var sk PrivateKey - copy(sk[:], seed[:]) + sk.Unpack(&seed) return sk.Public(), &sk } func (sk *PrivateKey) Public() *PublicKey { var pk PublicKey - seedPk := pk[:PublicKeySeedSize] var o [OSize]byte h := sha3.NewShake256() - _, _ = h.Write(sk[:]) - _, _ = h.Read(seedPk[:]) + _, _ = h.Write(sk.seed[:]) + _, _ = h.Read(pk.seed[:]) _, _ = h.Read(o[:]) var nonce [16]byte // TODO there are unnecessary allocations - block, _ := aes.NewCipher(seedPk[:]) + block, _ := aes.NewCipher(pk.seed[:]) ctr := cipher.NewCTR(block, nonce[:]) - var p12 [P1Size + P2Size]byte - ctr.XORKeyStream(p12[:], p12[:]) + var p1 [P1Size]byte + var p2 [P2Size]byte + ctr.XORKeyStream(p1[:], p1[:]) + ctr.XORKeyStream(p2[:], p2[:]) + + copyBytesToUint64SliceLE(pk.p1[:], p1[:]) + copyBytesToUint64SliceLE(pk.p2[:], p2[:]) var oo [V * O]byte decode(oo[:], o[:]) - var p1Tri [P1Size / 8]uint64 var p1OP2 [P2Size / 8]uint64 - copyBytesToUint64SliceLE(p1Tri[:], p12[:P1Size]) - copyBytesToUint64SliceLE(p1OP2[:], p12[P1Size:]) + copy(p1OP2[:], pk.p2[:]) var p3full [M * O * O / 16]uint64 - var p3 [P3Size / 8]uint64 - - mulAddMUpperTriangularMatXMat(p1OP2[:], p1Tri[:], oo[:], V, O) + mulAddMUpperTriangularMatXMat(p1OP2[:], pk.p1[:], oo[:], V, O) mulAddMatTransXMMat(p3full[:], oo[:], p1OP2[:], V, O, O) - upper(p3full[:], p3[:], O) - - copyUint64SliceToBytesLE(pk[PublicKeySeedSize:], p3[:]) + upper(p3full[:], pk.p3[:], O) return &pk } -func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { +func Sign(msg []byte, sk *PrivateKey, rand io.Reader) ([]byte, error) { if rand == nil { rand = cryptoRand.Reader } @@ -622,7 +628,7 @@ func transpose16x16Nibbles(m []uint64) { } } -func Verify(epk *ExpandedPublicKey, msg []byte, sig []byte) bool { +func Verify(pk *PublicKey, msg []byte, sig []byte) bool { if len(sig) != SignatureSize { return false } @@ -649,13 +655,6 @@ func Verify(epk *ExpandedPublicKey, msg []byte, sig []byte) bool { var s [K * N]byte decode(s[:], senc[:]) - var P1 [P1Size / 8]uint64 - var P2 [P2Size / 8]uint64 - var P3 [P3Size / 8]uint64 - copyBytesToUint64SliceLE(P1[:], epk.p1[:]) - copyBytesToUint64SliceLE(P2[:], epk.p2[:]) - copyBytesToUint64SliceLE(P3[:], epk.p3[:]) - // Note: the variable time approach is overall about 30% faster // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] // [ 0 P3 ] [S2] [ P3*S2] @@ -665,7 +664,7 @@ func Verify(epk *ExpandedPublicKey, msg []byte, sig []byte) bool { // mulAddMMatXMatTrans(pst[:], P2, s[V:], V, O, K, N, false) // mulAddMMatXMatTrans(pst[M*V*K/16:], P3, s[V:], O, O, K, N, true) // Variable time approach with table access where index depends on input: - calculatePStVarTime(pst[:], P1[:], P2[:], P3[:], s[:]) + calculatePStVarTime(pst[:], pk.p1[:], pk.p2[:], pk.p3[:], s[:]) // compute S * PST var sps [M * K * K / 16]uint64 diff --git a/sign/mayo/mode1/internal/mayo_test.go b/sign/mayo/mode1/internal/mayo_test.go index ca178f165..2a94c97b8 100644 --- a/sign/mayo/mode1/internal/mayo_test.go +++ b/sign/mayo/mode1/internal/mayo_test.go @@ -30,8 +30,10 @@ func TestNewKey(t *testing.T) { pk, sk := NewKeyFromSeed(seed) - pk2 := [PublicKeySize]byte(*pk) - sk2 := [PrivateKeySize]byte(*sk) + var pk2 [PublicKeySize]byte + var sk2 [PrivateKeySize]byte + pk.Pack(&pk2) + sk.Pack(&sk2) if hex.EncodeToString(pk2[:]) != tc.expectedPk { t.Fatal() @@ -67,14 +69,13 @@ func TestVerify(t *testing.T) { s, _ := hex.DecodeString(tc.signature) pk, _ := NewKeyFromSeed(seed) - epk := pk.Expand() - if !Verify(epk, m, s) { + if !Verify(pk, m, s) { t.Fatal("should verify") } - epk.p1[0] ^= 1 - if Verify(epk, m, s) { + pk.p1[0] ^= 1 + if Verify(pk, m, s) { t.Fatal("should not verify") } }) @@ -97,12 +98,12 @@ func TestSampleSolution(t *testing.T) { t.Fatal() } - sig, err := Sign(msg[:], sk.Expand(), &g) + sig, err := Sign(msg[:], sk, &g) if err != nil { t.Fatal() } - if !Verify(pk.Expand(), msg[:], sig[:]) { + if !Verify(pk, msg[:], sig[:]) { t.Fatal() } } @@ -145,21 +146,23 @@ func TestPQCgenKATSign(t *testing.T) { _, _ = g2.Read(eseed[:]) pk, sk := NewKeyFromSeed(eseed) - pk2 := [PublicKeySize]byte(*pk) - sk2 := [PrivateKeySize]byte(*sk) + var pk2 [PublicKeySize]byte + var sk2 [PrivateKeySize]byte + pk.Pack(&pk2) + sk.Pack(&sk2) fmt.Fprintf(f, "pk = %X\n", pk2) fmt.Fprintf(f, "sk = %X\n", sk2) fmt.Fprintf(f, "smlen = %d\n", mlen+SignatureSize) - sig, err := Sign(msg[:], sk.Expand(), &g2) + sig, err := Sign(msg[:], sk, &g2) if err != nil { t.Fatal() } fmt.Fprintf(f, "sm = %X%X\n\n", sig, msg) - if !Verify(pk.Expand(), msg[:], sig) { + if !Verify(pk, msg[:], sig) { t.Fatal() } } @@ -196,22 +199,10 @@ func BenchmarkVerify(b *testing.B) { var seed [KeySeedSize]byte var msg [8]byte pk, sk := NewKeyFromSeed(seed) - sig, _ := Sign(msg[:], sk.Expand(), nil) + sig, _ := Sign(msg[:], sk, nil) b.ResetTimer() for i := 0; i < b.N; i++ { - _ = Verify(pk.Expand(), msg[:], sig[:]) - } -} - -func BenchmarkVerifyExpandedKey(b *testing.B) { - var seed [KeySeedSize]byte - var msg [8]byte - pk, sk := NewKeyFromSeed(seed) - sig, _ := Sign(msg[:], sk.Expand(), nil) - epk := pk.Expand() - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = Verify(epk, msg[:], sig[:]) + _ = Verify(pk, msg[:], sig[:]) } } @@ -231,18 +222,6 @@ func BenchmarkSign(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { binary.LittleEndian.PutUint64(msg[:], uint64(i)) - _, _ = Sign(msg[:], sk.Expand(), zeroReader{}) - } -} - -func BenchmarkSignExpandedKey(b *testing.B) { - var seed [KeySeedSize]byte - var msg [8]byte - _, sk := NewKeyFromSeed(seed) - esk := sk.Expand() - b.ResetTimer() - for i := 0; i < b.N; i++ { - binary.LittleEndian.PutUint64(msg[:], uint64(i)) - _, _ = Sign(msg[:], esk, zeroReader{}) + _, _ = Sign(msg[:], sk, zeroReader{}) } } diff --git a/sign/mayo/mode1/mayo.go b/sign/mayo/mode1/mayo.go index 840801b51..4c3b1f89d 100644 --- a/sign/mayo/mode1/mayo.go +++ b/sign/mayo/mode1/mayo.go @@ -60,7 +60,7 @@ func NewKeyFromSeed(seed [SeedSize]byte) (*PublicKey, *PrivateKey) { func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { return internal.Sign( msg, - (*internal.PrivateKey)(sk).Expand(), + (*internal.PrivateKey)(sk), rand, ) } @@ -68,26 +68,54 @@ func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { // Verify checks whether the given signature by pk on msg is valid. func Verify(pk *PublicKey, msg []byte, signature []byte) bool { return internal.Verify( - (*internal.PublicKey)(pk).Expand(), + (*internal.PublicKey)(pk), msg, signature, ) } +// Sets pk to the public key encoded in buf. +func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { + (*internal.PublicKey)(pk).Unpack(buf) +} + +// Sets sk to the private key encoded in buf. +func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { + (*internal.PrivateKey)(sk).Unpack(buf) +} + +// Packs the public key into buf. +func (pk *PublicKey) Pack(buf *[PublicKeySize]byte) { + (*internal.PublicKey)(pk).Pack(buf) +} + +// Packs the private key into buf. +func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { + (*internal.PrivateKey)(sk).Pack(buf) +} + // Packs the public key. -func (pk *PublicKey) MarshalBinary() ([]byte, error) { +func (pk *PublicKey) Bytes() []byte { var buf [PublicKeySize]byte - b := [PublicKeySize]byte(*pk) - copy(buf[:], b[:]) - return buf[:], nil + pk.Pack(&buf) + return buf[:] } // Packs the private key. -func (sk *PrivateKey) MarshalBinary() ([]byte, error) { +func (sk *PrivateKey) Bytes() []byte { var buf [PrivateKeySize]byte - b := [PrivateKeySize]byte(*sk) - copy(buf[:], b[:]) - return buf[:], nil + sk.Pack(&buf) + return buf[:] +} + +// Packs the public key. +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + return pk.Bytes(), nil +} + +// Packs the private key. +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + return sk.Bytes(), nil } // Unpacks the public key from data. @@ -95,8 +123,9 @@ func (pk *PublicKey) UnmarshalBinary(data []byte) error { if len(data) != PublicKeySize { return errors.New("packed public key must be of mode1.PublicKeySize bytes") } - self := (*[PublicKeySize]byte)(pk) - copy(self[:], data[:]) + var buf [PublicKeySize]byte + copy(buf[:], data) + pk.Unpack(&buf) return nil } @@ -105,8 +134,9 @@ func (sk *PrivateKey) UnmarshalBinary(data []byte) error { if len(data) != PrivateKeySize { return errors.New("packed private key must be of mode1.PrivateKeySize bytes") } - self := (*[PrivateKeySize]byte)(sk) - copy(self[:], data[:]) + var buf [PrivateKeySize]byte + copy(buf[:], data) + sk.Unpack(&buf) return nil } diff --git a/sign/mayo/mode2/internal/mayo.go b/sign/mayo/mode2/internal/mayo.go index e0fabb46a..90359bc99 100644 --- a/sign/mayo/mode2/internal/mayo.go +++ b/sign/mayo/mode2/internal/mayo.go @@ -14,60 +14,70 @@ import ( "github.com/cloudflare/circl/internal/sha3" ) -type ( - PrivateKey [PrivateKeySize]byte - PublicKey [PublicKeySize]byte -) +type PublicKey struct { + seed [PublicKeySeedSize]byte + p3 [P3Size / 8]uint64 -func (pk *PublicKey) Equal(other *PublicKey) bool { - return *pk == *other + // P1 and P2 are expanded from seed + p1 [P1Size / 8]uint64 + p2 [P2Size / 8]uint64 } -func (sk *PrivateKey) Equal(other *PrivateKey) bool { - return subtle.ConstantTimeCompare((*sk)[:], (*other)[:]) == 1 +type PrivateKey struct { + seed [KeySeedSize]byte + + p1 [P1Size / 8]uint64 + o [V * O]byte + l [M * V * O / 16]uint64 +} + +func (pk *PublicKey) Equal(other *PublicKey) bool { + return pk.seed == other.seed && pk.p3 == other.p3 } -type ExpandedPublicKey struct { - p1 [P1Size]byte - p2 [P2Size]byte - p3 [P3Size]byte +func (sk *PrivateKey) Equal(other *PrivateKey) bool { + return subtle.ConstantTimeCompare(sk.seed[:], other.seed[:]) == 1 } -type ExpandedPrivateKey struct { - seed [KeySeedSize]byte - o [V * O]byte - p1 [M * V * V / 16]uint64 - l [M * V * O / 16]uint64 +// Packs the public key into buf. +func (pk *PublicKey) Pack(buf *[PublicKeySize]byte) { + copy(buf[:PublicKeySeedSize], pk.seed[:]) + copyUint64SliceToBytesLE(buf[PublicKeySeedSize:], pk.p3[:]) } -func (pk *PublicKey) Expand() *ExpandedPublicKey { - seedPk := pk[:PublicKeySeedSize] +// Sets pk to the public key encoded in buf. +func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { + copy(pk.seed[:], buf[:PublicKeySeedSize]) var nonce [16]byte // TODO there are unnecessary allocations - block, _ := aes.NewCipher(seedPk[:]) + block, _ := aes.NewCipher(pk.seed[:]) ctr := cipher.NewCTR(block, nonce[:]) - epk := ExpandedPublicKey{} - ctr.XORKeyStream(epk.p1[:], epk.p1[:]) - ctr.XORKeyStream(epk.p2[:], epk.p2[:]) - - copy(epk.p3[:], pk[PublicKeySeedSize:]) + var p1 [P1Size]byte + var p2 [P2Size]byte + ctr.XORKeyStream(p1[:], p1[:]) + ctr.XORKeyStream(p2[:], p2[:]) - return &epk + copyBytesToUint64SliceLE(pk.p1[:], p1[:]) + copyBytesToUint64SliceLE(pk.p2[:], p2[:]) + copyBytesToUint64SliceLE(pk.p3[:], buf[PublicKeySeedSize:]) } -func (sk *PrivateKey) Expand() *ExpandedPrivateKey { - var esk ExpandedPrivateKey +// Packs the private key into buf. +func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { + copy(buf[:], sk.seed[:]) +} - seed := (*sk)[:KeySeedSize] - copy(esk.seed[:], seed) +// Sets sk to the private key encoded in buf. +func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { + copy(sk.seed[:], buf[:]) var seedPk [PublicKeySeedSize]byte var o [OSize]byte h := sha3.NewShake256() - _, _ = h.Write(seed[:]) + _, _ = h.Write(sk.seed[:]) _, _ = h.Read(seedPk[:]) _, _ = h.Read(o[:]) @@ -79,15 +89,13 @@ func (sk *PrivateKey) Expand() *ExpandedPrivateKey { var p12 [P1Size + P2Size]byte ctr.XORKeyStream(p12[:], p12[:]) - decode(esk.o[:], o[:]) + decode(sk.o[:], o[:]) - copyBytesToUint64SliceLE(esk.p1[:P1Size/8], p12[:P1Size]) - copyBytesToUint64SliceLE(esk.l[:], p12[P1Size:]) + copyBytesToUint64SliceLE(sk.p1[:P1Size/8], p12[:P1Size]) + copyBytesToUint64SliceLE(sk.l[:], p12[P1Size:]) // compute L_i = (P1 + P1^t)*O + P2 - mulAddMUpperTriangularWithTransposeMatXMat(esk.l[:], esk.p1[:], esk.o[:], V, O) - - return &esk + mulAddMUpperTriangularWithTransposeMatXMat(sk.l[:], sk.p1[:], sk.o[:], V, O) } // decode unpacks N bytes from src to N*2 nibbles of dst. @@ -105,7 +113,7 @@ func decode(dst []byte, src []byte) { } } -// encode packs N=length low nibbles from src to (N+1)/2 bytes in dst. +// encode packs N=length low nibbles from src to ceil(N/2) bytes in dst. func encode(dst []byte, src []byte, length int) { var i int for i = 0; i+1 < length; i += 2 { @@ -149,51 +157,49 @@ func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { func NewKeyFromSeed(seed [KeySeedSize]byte) (*PublicKey, *PrivateKey) { var sk PrivateKey - copy(sk[:], seed[:]) + sk.Unpack(&seed) return sk.Public(), &sk } func (sk *PrivateKey) Public() *PublicKey { var pk PublicKey - seedPk := pk[:PublicKeySeedSize] var o [OSize]byte h := sha3.NewShake256() - _, _ = h.Write(sk[:]) - _, _ = h.Read(seedPk[:]) + _, _ = h.Write(sk.seed[:]) + _, _ = h.Read(pk.seed[:]) _, _ = h.Read(o[:]) var nonce [16]byte // TODO there are unnecessary allocations - block, _ := aes.NewCipher(seedPk[:]) + block, _ := aes.NewCipher(pk.seed[:]) ctr := cipher.NewCTR(block, nonce[:]) - var p12 [P1Size + P2Size]byte - ctr.XORKeyStream(p12[:], p12[:]) + var p1 [P1Size]byte + var p2 [P2Size]byte + ctr.XORKeyStream(p1[:], p1[:]) + ctr.XORKeyStream(p2[:], p2[:]) + + copyBytesToUint64SliceLE(pk.p1[:], p1[:]) + copyBytesToUint64SliceLE(pk.p2[:], p2[:]) var oo [V * O]byte decode(oo[:], o[:]) - var p1Tri [P1Size / 8]uint64 var p1OP2 [P2Size / 8]uint64 - copyBytesToUint64SliceLE(p1Tri[:], p12[:P1Size]) - copyBytesToUint64SliceLE(p1OP2[:], p12[P1Size:]) + copy(p1OP2[:], pk.p2[:]) var p3full [M * O * O / 16]uint64 - var p3 [P3Size / 8]uint64 - - mulAddMUpperTriangularMatXMat(p1OP2[:], p1Tri[:], oo[:], V, O) + mulAddMUpperTriangularMatXMat(p1OP2[:], pk.p1[:], oo[:], V, O) mulAddMatTransXMMat(p3full[:], oo[:], p1OP2[:], V, O, O) - upper(p3full[:], p3[:], O) - - copyUint64SliceToBytesLE(pk[PublicKeySeedSize:], p3[:]) + upper(p3full[:], pk.p3[:], O) return &pk } -func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { +func Sign(msg []byte, sk *PrivateKey, rand io.Reader) ([]byte, error) { if rand == nil { rand = cryptoRand.Reader } @@ -624,7 +630,7 @@ func transpose16x16Nibbles(m []uint64) { } } -func Verify(epk *ExpandedPublicKey, msg []byte, sig []byte) bool { +func Verify(pk *PublicKey, msg []byte, sig []byte) bool { if len(sig) != SignatureSize { return false } @@ -651,13 +657,6 @@ func Verify(epk *ExpandedPublicKey, msg []byte, sig []byte) bool { var s [K * N]byte decode(s[:], senc[:]) - var P1 [P1Size / 8]uint64 - var P2 [P2Size / 8]uint64 - var P3 [P3Size / 8]uint64 - copyBytesToUint64SliceLE(P1[:], epk.p1[:]) - copyBytesToUint64SliceLE(P2[:], epk.p2[:]) - copyBytesToUint64SliceLE(P3[:], epk.p3[:]) - // Note: the variable time approach is overall about 30% faster // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] // [ 0 P3 ] [S2] [ P3*S2] @@ -667,7 +666,7 @@ func Verify(epk *ExpandedPublicKey, msg []byte, sig []byte) bool { // mulAddMMatXMatTrans(pst[:], P2, s[V:], V, O, K, N, false) // mulAddMMatXMatTrans(pst[M*V*K/16:], P3, s[V:], O, O, K, N, true) // Variable time approach with table access where index depends on input: - calculatePStVarTime(pst[:], P1[:], P2[:], P3[:], s[:]) + calculatePStVarTime(pst[:], pk.p1[:], pk.p2[:], pk.p3[:], s[:]) // compute S * PST var sps [M * K * K / 16]uint64 diff --git a/sign/mayo/mode2/internal/mayo_test.go b/sign/mayo/mode2/internal/mayo_test.go index d8509f8a7..847949cdc 100644 --- a/sign/mayo/mode2/internal/mayo_test.go +++ b/sign/mayo/mode2/internal/mayo_test.go @@ -32,8 +32,10 @@ func TestNewKey(t *testing.T) { pk, sk := NewKeyFromSeed(seed) - pk2 := [PublicKeySize]byte(*pk) - sk2 := [PrivateKeySize]byte(*sk) + var pk2 [PublicKeySize]byte + var sk2 [PrivateKeySize]byte + pk.Pack(&pk2) + sk.Pack(&sk2) if hex.EncodeToString(pk2[:]) != tc.expectedPk { t.Fatal() @@ -69,14 +71,13 @@ func TestVerify(t *testing.T) { s, _ := hex.DecodeString(tc.signature) pk, _ := NewKeyFromSeed(seed) - epk := pk.Expand() - if !Verify(epk, m, s) { + if !Verify(pk, m, s) { t.Fatal("should verify") } - epk.p1[0] ^= 1 - if Verify(epk, m, s) { + pk.p1[0] ^= 1 + if Verify(pk, m, s) { t.Fatal("should not verify") } }) @@ -99,12 +100,12 @@ func TestSampleSolution(t *testing.T) { t.Fatal() } - sig, err := Sign(msg[:], sk.Expand(), &g) + sig, err := Sign(msg[:], sk, &g) if err != nil { t.Fatal() } - if !Verify(pk.Expand(), msg[:], sig[:]) { + if !Verify(pk, msg[:], sig[:]) { t.Fatal() } } @@ -147,21 +148,23 @@ func TestPQCgenKATSign(t *testing.T) { _, _ = g2.Read(eseed[:]) pk, sk := NewKeyFromSeed(eseed) - pk2 := [PublicKeySize]byte(*pk) - sk2 := [PrivateKeySize]byte(*sk) + var pk2 [PublicKeySize]byte + var sk2 [PrivateKeySize]byte + pk.Pack(&pk2) + sk.Pack(&sk2) fmt.Fprintf(f, "pk = %X\n", pk2) fmt.Fprintf(f, "sk = %X\n", sk2) fmt.Fprintf(f, "smlen = %d\n", mlen+SignatureSize) - sig, err := Sign(msg[:], sk.Expand(), &g2) + sig, err := Sign(msg[:], sk, &g2) if err != nil { t.Fatal() } fmt.Fprintf(f, "sm = %X%X\n\n", sig, msg) - if !Verify(pk.Expand(), msg[:], sig) { + if !Verify(pk, msg[:], sig) { t.Fatal() } } @@ -198,22 +201,10 @@ func BenchmarkVerify(b *testing.B) { var seed [KeySeedSize]byte var msg [8]byte pk, sk := NewKeyFromSeed(seed) - sig, _ := Sign(msg[:], sk.Expand(), nil) + sig, _ := Sign(msg[:], sk, nil) b.ResetTimer() for i := 0; i < b.N; i++ { - _ = Verify(pk.Expand(), msg[:], sig[:]) - } -} - -func BenchmarkVerifyExpandedKey(b *testing.B) { - var seed [KeySeedSize]byte - var msg [8]byte - pk, sk := NewKeyFromSeed(seed) - sig, _ := Sign(msg[:], sk.Expand(), nil) - epk := pk.Expand() - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = Verify(epk, msg[:], sig[:]) + _ = Verify(pk, msg[:], sig[:]) } } @@ -233,18 +224,6 @@ func BenchmarkSign(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { binary.LittleEndian.PutUint64(msg[:], uint64(i)) - _, _ = Sign(msg[:], sk.Expand(), zeroReader{}) - } -} - -func BenchmarkSignExpandedKey(b *testing.B) { - var seed [KeySeedSize]byte - var msg [8]byte - _, sk := NewKeyFromSeed(seed) - esk := sk.Expand() - b.ResetTimer() - for i := 0; i < b.N; i++ { - binary.LittleEndian.PutUint64(msg[:], uint64(i)) - _, _ = Sign(msg[:], esk, zeroReader{}) + _, _ = Sign(msg[:], sk, zeroReader{}) } } diff --git a/sign/mayo/mode2/mayo.go b/sign/mayo/mode2/mayo.go index eddba8010..c0ddfe66f 100644 --- a/sign/mayo/mode2/mayo.go +++ b/sign/mayo/mode2/mayo.go @@ -60,7 +60,7 @@ func NewKeyFromSeed(seed [SeedSize]byte) (*PublicKey, *PrivateKey) { func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { return internal.Sign( msg, - (*internal.PrivateKey)(sk).Expand(), + (*internal.PrivateKey)(sk), rand, ) } @@ -68,26 +68,54 @@ func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { // Verify checks whether the given signature by pk on msg is valid. func Verify(pk *PublicKey, msg []byte, signature []byte) bool { return internal.Verify( - (*internal.PublicKey)(pk).Expand(), + (*internal.PublicKey)(pk), msg, signature, ) } +// Sets pk to the public key encoded in buf. +func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { + (*internal.PublicKey)(pk).Unpack(buf) +} + +// Sets sk to the private key encoded in buf. +func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { + (*internal.PrivateKey)(sk).Unpack(buf) +} + +// Packs the public key into buf. +func (pk *PublicKey) Pack(buf *[PublicKeySize]byte) { + (*internal.PublicKey)(pk).Pack(buf) +} + +// Packs the private key into buf. +func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { + (*internal.PrivateKey)(sk).Pack(buf) +} + // Packs the public key. -func (pk *PublicKey) MarshalBinary() ([]byte, error) { +func (pk *PublicKey) Bytes() []byte { var buf [PublicKeySize]byte - b := [PublicKeySize]byte(*pk) - copy(buf[:], b[:]) - return buf[:], nil + pk.Pack(&buf) + return buf[:] } // Packs the private key. -func (sk *PrivateKey) MarshalBinary() ([]byte, error) { +func (sk *PrivateKey) Bytes() []byte { var buf [PrivateKeySize]byte - b := [PrivateKeySize]byte(*sk) - copy(buf[:], b[:]) - return buf[:], nil + sk.Pack(&buf) + return buf[:] +} + +// Packs the public key. +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + return pk.Bytes(), nil +} + +// Packs the private key. +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + return sk.Bytes(), nil } // Unpacks the public key from data. @@ -95,8 +123,9 @@ func (pk *PublicKey) UnmarshalBinary(data []byte) error { if len(data) != PublicKeySize { return errors.New("packed public key must be of mode2.PublicKeySize bytes") } - self := (*[PublicKeySize]byte)(pk) - copy(self[:], data[:]) + var buf [PublicKeySize]byte + copy(buf[:], data) + pk.Unpack(&buf) return nil } @@ -105,8 +134,9 @@ func (sk *PrivateKey) UnmarshalBinary(data []byte) error { if len(data) != PrivateKeySize { return errors.New("packed private key must be of mode2.PrivateKeySize bytes") } - self := (*[PrivateKeySize]byte)(sk) - copy(self[:], data[:]) + var buf [PrivateKeySize]byte + copy(buf[:], data) + sk.Unpack(&buf) return nil } diff --git a/sign/mayo/mode3/internal/mayo.go b/sign/mayo/mode3/internal/mayo.go index e0fabb46a..90359bc99 100644 --- a/sign/mayo/mode3/internal/mayo.go +++ b/sign/mayo/mode3/internal/mayo.go @@ -14,60 +14,70 @@ import ( "github.com/cloudflare/circl/internal/sha3" ) -type ( - PrivateKey [PrivateKeySize]byte - PublicKey [PublicKeySize]byte -) +type PublicKey struct { + seed [PublicKeySeedSize]byte + p3 [P3Size / 8]uint64 -func (pk *PublicKey) Equal(other *PublicKey) bool { - return *pk == *other + // P1 and P2 are expanded from seed + p1 [P1Size / 8]uint64 + p2 [P2Size / 8]uint64 } -func (sk *PrivateKey) Equal(other *PrivateKey) bool { - return subtle.ConstantTimeCompare((*sk)[:], (*other)[:]) == 1 +type PrivateKey struct { + seed [KeySeedSize]byte + + p1 [P1Size / 8]uint64 + o [V * O]byte + l [M * V * O / 16]uint64 +} + +func (pk *PublicKey) Equal(other *PublicKey) bool { + return pk.seed == other.seed && pk.p3 == other.p3 } -type ExpandedPublicKey struct { - p1 [P1Size]byte - p2 [P2Size]byte - p3 [P3Size]byte +func (sk *PrivateKey) Equal(other *PrivateKey) bool { + return subtle.ConstantTimeCompare(sk.seed[:], other.seed[:]) == 1 } -type ExpandedPrivateKey struct { - seed [KeySeedSize]byte - o [V * O]byte - p1 [M * V * V / 16]uint64 - l [M * V * O / 16]uint64 +// Packs the public key into buf. +func (pk *PublicKey) Pack(buf *[PublicKeySize]byte) { + copy(buf[:PublicKeySeedSize], pk.seed[:]) + copyUint64SliceToBytesLE(buf[PublicKeySeedSize:], pk.p3[:]) } -func (pk *PublicKey) Expand() *ExpandedPublicKey { - seedPk := pk[:PublicKeySeedSize] +// Sets pk to the public key encoded in buf. +func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { + copy(pk.seed[:], buf[:PublicKeySeedSize]) var nonce [16]byte // TODO there are unnecessary allocations - block, _ := aes.NewCipher(seedPk[:]) + block, _ := aes.NewCipher(pk.seed[:]) ctr := cipher.NewCTR(block, nonce[:]) - epk := ExpandedPublicKey{} - ctr.XORKeyStream(epk.p1[:], epk.p1[:]) - ctr.XORKeyStream(epk.p2[:], epk.p2[:]) - - copy(epk.p3[:], pk[PublicKeySeedSize:]) + var p1 [P1Size]byte + var p2 [P2Size]byte + ctr.XORKeyStream(p1[:], p1[:]) + ctr.XORKeyStream(p2[:], p2[:]) - return &epk + copyBytesToUint64SliceLE(pk.p1[:], p1[:]) + copyBytesToUint64SliceLE(pk.p2[:], p2[:]) + copyBytesToUint64SliceLE(pk.p3[:], buf[PublicKeySeedSize:]) } -func (sk *PrivateKey) Expand() *ExpandedPrivateKey { - var esk ExpandedPrivateKey +// Packs the private key into buf. +func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { + copy(buf[:], sk.seed[:]) +} - seed := (*sk)[:KeySeedSize] - copy(esk.seed[:], seed) +// Sets sk to the private key encoded in buf. +func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { + copy(sk.seed[:], buf[:]) var seedPk [PublicKeySeedSize]byte var o [OSize]byte h := sha3.NewShake256() - _, _ = h.Write(seed[:]) + _, _ = h.Write(sk.seed[:]) _, _ = h.Read(seedPk[:]) _, _ = h.Read(o[:]) @@ -79,15 +89,13 @@ func (sk *PrivateKey) Expand() *ExpandedPrivateKey { var p12 [P1Size + P2Size]byte ctr.XORKeyStream(p12[:], p12[:]) - decode(esk.o[:], o[:]) + decode(sk.o[:], o[:]) - copyBytesToUint64SliceLE(esk.p1[:P1Size/8], p12[:P1Size]) - copyBytesToUint64SliceLE(esk.l[:], p12[P1Size:]) + copyBytesToUint64SliceLE(sk.p1[:P1Size/8], p12[:P1Size]) + copyBytesToUint64SliceLE(sk.l[:], p12[P1Size:]) // compute L_i = (P1 + P1^t)*O + P2 - mulAddMUpperTriangularWithTransposeMatXMat(esk.l[:], esk.p1[:], esk.o[:], V, O) - - return &esk + mulAddMUpperTriangularWithTransposeMatXMat(sk.l[:], sk.p1[:], sk.o[:], V, O) } // decode unpacks N bytes from src to N*2 nibbles of dst. @@ -105,7 +113,7 @@ func decode(dst []byte, src []byte) { } } -// encode packs N=length low nibbles from src to (N+1)/2 bytes in dst. +// encode packs N=length low nibbles from src to ceil(N/2) bytes in dst. func encode(dst []byte, src []byte, length int) { var i int for i = 0; i+1 < length; i += 2 { @@ -149,51 +157,49 @@ func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { func NewKeyFromSeed(seed [KeySeedSize]byte) (*PublicKey, *PrivateKey) { var sk PrivateKey - copy(sk[:], seed[:]) + sk.Unpack(&seed) return sk.Public(), &sk } func (sk *PrivateKey) Public() *PublicKey { var pk PublicKey - seedPk := pk[:PublicKeySeedSize] var o [OSize]byte h := sha3.NewShake256() - _, _ = h.Write(sk[:]) - _, _ = h.Read(seedPk[:]) + _, _ = h.Write(sk.seed[:]) + _, _ = h.Read(pk.seed[:]) _, _ = h.Read(o[:]) var nonce [16]byte // TODO there are unnecessary allocations - block, _ := aes.NewCipher(seedPk[:]) + block, _ := aes.NewCipher(pk.seed[:]) ctr := cipher.NewCTR(block, nonce[:]) - var p12 [P1Size + P2Size]byte - ctr.XORKeyStream(p12[:], p12[:]) + var p1 [P1Size]byte + var p2 [P2Size]byte + ctr.XORKeyStream(p1[:], p1[:]) + ctr.XORKeyStream(p2[:], p2[:]) + + copyBytesToUint64SliceLE(pk.p1[:], p1[:]) + copyBytesToUint64SliceLE(pk.p2[:], p2[:]) var oo [V * O]byte decode(oo[:], o[:]) - var p1Tri [P1Size / 8]uint64 var p1OP2 [P2Size / 8]uint64 - copyBytesToUint64SliceLE(p1Tri[:], p12[:P1Size]) - copyBytesToUint64SliceLE(p1OP2[:], p12[P1Size:]) + copy(p1OP2[:], pk.p2[:]) var p3full [M * O * O / 16]uint64 - var p3 [P3Size / 8]uint64 - - mulAddMUpperTriangularMatXMat(p1OP2[:], p1Tri[:], oo[:], V, O) + mulAddMUpperTriangularMatXMat(p1OP2[:], pk.p1[:], oo[:], V, O) mulAddMatTransXMMat(p3full[:], oo[:], p1OP2[:], V, O, O) - upper(p3full[:], p3[:], O) - - copyUint64SliceToBytesLE(pk[PublicKeySeedSize:], p3[:]) + upper(p3full[:], pk.p3[:], O) return &pk } -func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { +func Sign(msg []byte, sk *PrivateKey, rand io.Reader) ([]byte, error) { if rand == nil { rand = cryptoRand.Reader } @@ -624,7 +630,7 @@ func transpose16x16Nibbles(m []uint64) { } } -func Verify(epk *ExpandedPublicKey, msg []byte, sig []byte) bool { +func Verify(pk *PublicKey, msg []byte, sig []byte) bool { if len(sig) != SignatureSize { return false } @@ -651,13 +657,6 @@ func Verify(epk *ExpandedPublicKey, msg []byte, sig []byte) bool { var s [K * N]byte decode(s[:], senc[:]) - var P1 [P1Size / 8]uint64 - var P2 [P2Size / 8]uint64 - var P3 [P3Size / 8]uint64 - copyBytesToUint64SliceLE(P1[:], epk.p1[:]) - copyBytesToUint64SliceLE(P2[:], epk.p2[:]) - copyBytesToUint64SliceLE(P3[:], epk.p3[:]) - // Note: the variable time approach is overall about 30% faster // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] // [ 0 P3 ] [S2] [ P3*S2] @@ -667,7 +666,7 @@ func Verify(epk *ExpandedPublicKey, msg []byte, sig []byte) bool { // mulAddMMatXMatTrans(pst[:], P2, s[V:], V, O, K, N, false) // mulAddMMatXMatTrans(pst[M*V*K/16:], P3, s[V:], O, O, K, N, true) // Variable time approach with table access where index depends on input: - calculatePStVarTime(pst[:], P1[:], P2[:], P3[:], s[:]) + calculatePStVarTime(pst[:], pk.p1[:], pk.p2[:], pk.p3[:], s[:]) // compute S * PST var sps [M * K * K / 16]uint64 diff --git a/sign/mayo/mode3/internal/mayo_test.go b/sign/mayo/mode3/internal/mayo_test.go index d8509f8a7..847949cdc 100644 --- a/sign/mayo/mode3/internal/mayo_test.go +++ b/sign/mayo/mode3/internal/mayo_test.go @@ -32,8 +32,10 @@ func TestNewKey(t *testing.T) { pk, sk := NewKeyFromSeed(seed) - pk2 := [PublicKeySize]byte(*pk) - sk2 := [PrivateKeySize]byte(*sk) + var pk2 [PublicKeySize]byte + var sk2 [PrivateKeySize]byte + pk.Pack(&pk2) + sk.Pack(&sk2) if hex.EncodeToString(pk2[:]) != tc.expectedPk { t.Fatal() @@ -69,14 +71,13 @@ func TestVerify(t *testing.T) { s, _ := hex.DecodeString(tc.signature) pk, _ := NewKeyFromSeed(seed) - epk := pk.Expand() - if !Verify(epk, m, s) { + if !Verify(pk, m, s) { t.Fatal("should verify") } - epk.p1[0] ^= 1 - if Verify(epk, m, s) { + pk.p1[0] ^= 1 + if Verify(pk, m, s) { t.Fatal("should not verify") } }) @@ -99,12 +100,12 @@ func TestSampleSolution(t *testing.T) { t.Fatal() } - sig, err := Sign(msg[:], sk.Expand(), &g) + sig, err := Sign(msg[:], sk, &g) if err != nil { t.Fatal() } - if !Verify(pk.Expand(), msg[:], sig[:]) { + if !Verify(pk, msg[:], sig[:]) { t.Fatal() } } @@ -147,21 +148,23 @@ func TestPQCgenKATSign(t *testing.T) { _, _ = g2.Read(eseed[:]) pk, sk := NewKeyFromSeed(eseed) - pk2 := [PublicKeySize]byte(*pk) - sk2 := [PrivateKeySize]byte(*sk) + var pk2 [PublicKeySize]byte + var sk2 [PrivateKeySize]byte + pk.Pack(&pk2) + sk.Pack(&sk2) fmt.Fprintf(f, "pk = %X\n", pk2) fmt.Fprintf(f, "sk = %X\n", sk2) fmt.Fprintf(f, "smlen = %d\n", mlen+SignatureSize) - sig, err := Sign(msg[:], sk.Expand(), &g2) + sig, err := Sign(msg[:], sk, &g2) if err != nil { t.Fatal() } fmt.Fprintf(f, "sm = %X%X\n\n", sig, msg) - if !Verify(pk.Expand(), msg[:], sig) { + if !Verify(pk, msg[:], sig) { t.Fatal() } } @@ -198,22 +201,10 @@ func BenchmarkVerify(b *testing.B) { var seed [KeySeedSize]byte var msg [8]byte pk, sk := NewKeyFromSeed(seed) - sig, _ := Sign(msg[:], sk.Expand(), nil) + sig, _ := Sign(msg[:], sk, nil) b.ResetTimer() for i := 0; i < b.N; i++ { - _ = Verify(pk.Expand(), msg[:], sig[:]) - } -} - -func BenchmarkVerifyExpandedKey(b *testing.B) { - var seed [KeySeedSize]byte - var msg [8]byte - pk, sk := NewKeyFromSeed(seed) - sig, _ := Sign(msg[:], sk.Expand(), nil) - epk := pk.Expand() - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = Verify(epk, msg[:], sig[:]) + _ = Verify(pk, msg[:], sig[:]) } } @@ -233,18 +224,6 @@ func BenchmarkSign(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { binary.LittleEndian.PutUint64(msg[:], uint64(i)) - _, _ = Sign(msg[:], sk.Expand(), zeroReader{}) - } -} - -func BenchmarkSignExpandedKey(b *testing.B) { - var seed [KeySeedSize]byte - var msg [8]byte - _, sk := NewKeyFromSeed(seed) - esk := sk.Expand() - b.ResetTimer() - for i := 0; i < b.N; i++ { - binary.LittleEndian.PutUint64(msg[:], uint64(i)) - _, _ = Sign(msg[:], esk, zeroReader{}) + _, _ = Sign(msg[:], sk, zeroReader{}) } } diff --git a/sign/mayo/mode3/mayo.go b/sign/mayo/mode3/mayo.go index 3e1fd0846..5fe331217 100644 --- a/sign/mayo/mode3/mayo.go +++ b/sign/mayo/mode3/mayo.go @@ -60,7 +60,7 @@ func NewKeyFromSeed(seed [SeedSize]byte) (*PublicKey, *PrivateKey) { func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { return internal.Sign( msg, - (*internal.PrivateKey)(sk).Expand(), + (*internal.PrivateKey)(sk), rand, ) } @@ -68,26 +68,54 @@ func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { // Verify checks whether the given signature by pk on msg is valid. func Verify(pk *PublicKey, msg []byte, signature []byte) bool { return internal.Verify( - (*internal.PublicKey)(pk).Expand(), + (*internal.PublicKey)(pk), msg, signature, ) } +// Sets pk to the public key encoded in buf. +func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { + (*internal.PublicKey)(pk).Unpack(buf) +} + +// Sets sk to the private key encoded in buf. +func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { + (*internal.PrivateKey)(sk).Unpack(buf) +} + +// Packs the public key into buf. +func (pk *PublicKey) Pack(buf *[PublicKeySize]byte) { + (*internal.PublicKey)(pk).Pack(buf) +} + +// Packs the private key into buf. +func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { + (*internal.PrivateKey)(sk).Pack(buf) +} + // Packs the public key. -func (pk *PublicKey) MarshalBinary() ([]byte, error) { +func (pk *PublicKey) Bytes() []byte { var buf [PublicKeySize]byte - b := [PublicKeySize]byte(*pk) - copy(buf[:], b[:]) - return buf[:], nil + pk.Pack(&buf) + return buf[:] } // Packs the private key. -func (sk *PrivateKey) MarshalBinary() ([]byte, error) { +func (sk *PrivateKey) Bytes() []byte { var buf [PrivateKeySize]byte - b := [PrivateKeySize]byte(*sk) - copy(buf[:], b[:]) - return buf[:], nil + sk.Pack(&buf) + return buf[:] +} + +// Packs the public key. +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + return pk.Bytes(), nil +} + +// Packs the private key. +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + return sk.Bytes(), nil } // Unpacks the public key from data. @@ -95,8 +123,9 @@ func (pk *PublicKey) UnmarshalBinary(data []byte) error { if len(data) != PublicKeySize { return errors.New("packed public key must be of mode3.PublicKeySize bytes") } - self := (*[PublicKeySize]byte)(pk) - copy(self[:], data[:]) + var buf [PublicKeySize]byte + copy(buf[:], data) + pk.Unpack(&buf) return nil } @@ -105,8 +134,9 @@ func (sk *PrivateKey) UnmarshalBinary(data []byte) error { if len(data) != PrivateKeySize { return errors.New("packed private key must be of mode3.PrivateKeySize bytes") } - self := (*[PrivateKeySize]byte)(sk) - copy(self[:], data[:]) + var buf [PrivateKeySize]byte + copy(buf[:], data) + sk.Unpack(&buf) return nil } diff --git a/sign/mayo/mode5/internal/mayo.go b/sign/mayo/mode5/internal/mayo.go index e0fabb46a..90359bc99 100644 --- a/sign/mayo/mode5/internal/mayo.go +++ b/sign/mayo/mode5/internal/mayo.go @@ -14,60 +14,70 @@ import ( "github.com/cloudflare/circl/internal/sha3" ) -type ( - PrivateKey [PrivateKeySize]byte - PublicKey [PublicKeySize]byte -) +type PublicKey struct { + seed [PublicKeySeedSize]byte + p3 [P3Size / 8]uint64 -func (pk *PublicKey) Equal(other *PublicKey) bool { - return *pk == *other + // P1 and P2 are expanded from seed + p1 [P1Size / 8]uint64 + p2 [P2Size / 8]uint64 } -func (sk *PrivateKey) Equal(other *PrivateKey) bool { - return subtle.ConstantTimeCompare((*sk)[:], (*other)[:]) == 1 +type PrivateKey struct { + seed [KeySeedSize]byte + + p1 [P1Size / 8]uint64 + o [V * O]byte + l [M * V * O / 16]uint64 +} + +func (pk *PublicKey) Equal(other *PublicKey) bool { + return pk.seed == other.seed && pk.p3 == other.p3 } -type ExpandedPublicKey struct { - p1 [P1Size]byte - p2 [P2Size]byte - p3 [P3Size]byte +func (sk *PrivateKey) Equal(other *PrivateKey) bool { + return subtle.ConstantTimeCompare(sk.seed[:], other.seed[:]) == 1 } -type ExpandedPrivateKey struct { - seed [KeySeedSize]byte - o [V * O]byte - p1 [M * V * V / 16]uint64 - l [M * V * O / 16]uint64 +// Packs the public key into buf. +func (pk *PublicKey) Pack(buf *[PublicKeySize]byte) { + copy(buf[:PublicKeySeedSize], pk.seed[:]) + copyUint64SliceToBytesLE(buf[PublicKeySeedSize:], pk.p3[:]) } -func (pk *PublicKey) Expand() *ExpandedPublicKey { - seedPk := pk[:PublicKeySeedSize] +// Sets pk to the public key encoded in buf. +func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { + copy(pk.seed[:], buf[:PublicKeySeedSize]) var nonce [16]byte // TODO there are unnecessary allocations - block, _ := aes.NewCipher(seedPk[:]) + block, _ := aes.NewCipher(pk.seed[:]) ctr := cipher.NewCTR(block, nonce[:]) - epk := ExpandedPublicKey{} - ctr.XORKeyStream(epk.p1[:], epk.p1[:]) - ctr.XORKeyStream(epk.p2[:], epk.p2[:]) - - copy(epk.p3[:], pk[PublicKeySeedSize:]) + var p1 [P1Size]byte + var p2 [P2Size]byte + ctr.XORKeyStream(p1[:], p1[:]) + ctr.XORKeyStream(p2[:], p2[:]) - return &epk + copyBytesToUint64SliceLE(pk.p1[:], p1[:]) + copyBytesToUint64SliceLE(pk.p2[:], p2[:]) + copyBytesToUint64SliceLE(pk.p3[:], buf[PublicKeySeedSize:]) } -func (sk *PrivateKey) Expand() *ExpandedPrivateKey { - var esk ExpandedPrivateKey +// Packs the private key into buf. +func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { + copy(buf[:], sk.seed[:]) +} - seed := (*sk)[:KeySeedSize] - copy(esk.seed[:], seed) +// Sets sk to the private key encoded in buf. +func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { + copy(sk.seed[:], buf[:]) var seedPk [PublicKeySeedSize]byte var o [OSize]byte h := sha3.NewShake256() - _, _ = h.Write(seed[:]) + _, _ = h.Write(sk.seed[:]) _, _ = h.Read(seedPk[:]) _, _ = h.Read(o[:]) @@ -79,15 +89,13 @@ func (sk *PrivateKey) Expand() *ExpandedPrivateKey { var p12 [P1Size + P2Size]byte ctr.XORKeyStream(p12[:], p12[:]) - decode(esk.o[:], o[:]) + decode(sk.o[:], o[:]) - copyBytesToUint64SliceLE(esk.p1[:P1Size/8], p12[:P1Size]) - copyBytesToUint64SliceLE(esk.l[:], p12[P1Size:]) + copyBytesToUint64SliceLE(sk.p1[:P1Size/8], p12[:P1Size]) + copyBytesToUint64SliceLE(sk.l[:], p12[P1Size:]) // compute L_i = (P1 + P1^t)*O + P2 - mulAddMUpperTriangularWithTransposeMatXMat(esk.l[:], esk.p1[:], esk.o[:], V, O) - - return &esk + mulAddMUpperTriangularWithTransposeMatXMat(sk.l[:], sk.p1[:], sk.o[:], V, O) } // decode unpacks N bytes from src to N*2 nibbles of dst. @@ -105,7 +113,7 @@ func decode(dst []byte, src []byte) { } } -// encode packs N=length low nibbles from src to (N+1)/2 bytes in dst. +// encode packs N=length low nibbles from src to ceil(N/2) bytes in dst. func encode(dst []byte, src []byte, length int) { var i int for i = 0; i+1 < length; i += 2 { @@ -149,51 +157,49 @@ func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { func NewKeyFromSeed(seed [KeySeedSize]byte) (*PublicKey, *PrivateKey) { var sk PrivateKey - copy(sk[:], seed[:]) + sk.Unpack(&seed) return sk.Public(), &sk } func (sk *PrivateKey) Public() *PublicKey { var pk PublicKey - seedPk := pk[:PublicKeySeedSize] var o [OSize]byte h := sha3.NewShake256() - _, _ = h.Write(sk[:]) - _, _ = h.Read(seedPk[:]) + _, _ = h.Write(sk.seed[:]) + _, _ = h.Read(pk.seed[:]) _, _ = h.Read(o[:]) var nonce [16]byte // TODO there are unnecessary allocations - block, _ := aes.NewCipher(seedPk[:]) + block, _ := aes.NewCipher(pk.seed[:]) ctr := cipher.NewCTR(block, nonce[:]) - var p12 [P1Size + P2Size]byte - ctr.XORKeyStream(p12[:], p12[:]) + var p1 [P1Size]byte + var p2 [P2Size]byte + ctr.XORKeyStream(p1[:], p1[:]) + ctr.XORKeyStream(p2[:], p2[:]) + + copyBytesToUint64SliceLE(pk.p1[:], p1[:]) + copyBytesToUint64SliceLE(pk.p2[:], p2[:]) var oo [V * O]byte decode(oo[:], o[:]) - var p1Tri [P1Size / 8]uint64 var p1OP2 [P2Size / 8]uint64 - copyBytesToUint64SliceLE(p1Tri[:], p12[:P1Size]) - copyBytesToUint64SliceLE(p1OP2[:], p12[P1Size:]) + copy(p1OP2[:], pk.p2[:]) var p3full [M * O * O / 16]uint64 - var p3 [P3Size / 8]uint64 - - mulAddMUpperTriangularMatXMat(p1OP2[:], p1Tri[:], oo[:], V, O) + mulAddMUpperTriangularMatXMat(p1OP2[:], pk.p1[:], oo[:], V, O) mulAddMatTransXMMat(p3full[:], oo[:], p1OP2[:], V, O, O) - upper(p3full[:], p3[:], O) - - copyUint64SliceToBytesLE(pk[PublicKeySeedSize:], p3[:]) + upper(p3full[:], pk.p3[:], O) return &pk } -func Sign(msg []byte, sk *ExpandedPrivateKey, rand io.Reader) ([]byte, error) { +func Sign(msg []byte, sk *PrivateKey, rand io.Reader) ([]byte, error) { if rand == nil { rand = cryptoRand.Reader } @@ -624,7 +630,7 @@ func transpose16x16Nibbles(m []uint64) { } } -func Verify(epk *ExpandedPublicKey, msg []byte, sig []byte) bool { +func Verify(pk *PublicKey, msg []byte, sig []byte) bool { if len(sig) != SignatureSize { return false } @@ -651,13 +657,6 @@ func Verify(epk *ExpandedPublicKey, msg []byte, sig []byte) bool { var s [K * N]byte decode(s[:], senc[:]) - var P1 [P1Size / 8]uint64 - var P2 [P2Size / 8]uint64 - var P3 [P3Size / 8]uint64 - copyBytesToUint64SliceLE(P1[:], epk.p1[:]) - copyBytesToUint64SliceLE(P2[:], epk.p2[:]) - copyBytesToUint64SliceLE(P3[:], epk.p3[:]) - // Note: the variable time approach is overall about 30% faster // compute P * S^t = [ P1 P2 ] * [S1] = [P1*S1 + P2*S2] // [ 0 P3 ] [S2] [ P3*S2] @@ -667,7 +666,7 @@ func Verify(epk *ExpandedPublicKey, msg []byte, sig []byte) bool { // mulAddMMatXMatTrans(pst[:], P2, s[V:], V, O, K, N, false) // mulAddMMatXMatTrans(pst[M*V*K/16:], P3, s[V:], O, O, K, N, true) // Variable time approach with table access where index depends on input: - calculatePStVarTime(pst[:], P1[:], P2[:], P3[:], s[:]) + calculatePStVarTime(pst[:], pk.p1[:], pk.p2[:], pk.p3[:], s[:]) // compute S * PST var sps [M * K * K / 16]uint64 diff --git a/sign/mayo/mode5/internal/mayo_test.go b/sign/mayo/mode5/internal/mayo_test.go index d8509f8a7..847949cdc 100644 --- a/sign/mayo/mode5/internal/mayo_test.go +++ b/sign/mayo/mode5/internal/mayo_test.go @@ -32,8 +32,10 @@ func TestNewKey(t *testing.T) { pk, sk := NewKeyFromSeed(seed) - pk2 := [PublicKeySize]byte(*pk) - sk2 := [PrivateKeySize]byte(*sk) + var pk2 [PublicKeySize]byte + var sk2 [PrivateKeySize]byte + pk.Pack(&pk2) + sk.Pack(&sk2) if hex.EncodeToString(pk2[:]) != tc.expectedPk { t.Fatal() @@ -69,14 +71,13 @@ func TestVerify(t *testing.T) { s, _ := hex.DecodeString(tc.signature) pk, _ := NewKeyFromSeed(seed) - epk := pk.Expand() - if !Verify(epk, m, s) { + if !Verify(pk, m, s) { t.Fatal("should verify") } - epk.p1[0] ^= 1 - if Verify(epk, m, s) { + pk.p1[0] ^= 1 + if Verify(pk, m, s) { t.Fatal("should not verify") } }) @@ -99,12 +100,12 @@ func TestSampleSolution(t *testing.T) { t.Fatal() } - sig, err := Sign(msg[:], sk.Expand(), &g) + sig, err := Sign(msg[:], sk, &g) if err != nil { t.Fatal() } - if !Verify(pk.Expand(), msg[:], sig[:]) { + if !Verify(pk, msg[:], sig[:]) { t.Fatal() } } @@ -147,21 +148,23 @@ func TestPQCgenKATSign(t *testing.T) { _, _ = g2.Read(eseed[:]) pk, sk := NewKeyFromSeed(eseed) - pk2 := [PublicKeySize]byte(*pk) - sk2 := [PrivateKeySize]byte(*sk) + var pk2 [PublicKeySize]byte + var sk2 [PrivateKeySize]byte + pk.Pack(&pk2) + sk.Pack(&sk2) fmt.Fprintf(f, "pk = %X\n", pk2) fmt.Fprintf(f, "sk = %X\n", sk2) fmt.Fprintf(f, "smlen = %d\n", mlen+SignatureSize) - sig, err := Sign(msg[:], sk.Expand(), &g2) + sig, err := Sign(msg[:], sk, &g2) if err != nil { t.Fatal() } fmt.Fprintf(f, "sm = %X%X\n\n", sig, msg) - if !Verify(pk.Expand(), msg[:], sig) { + if !Verify(pk, msg[:], sig) { t.Fatal() } } @@ -198,22 +201,10 @@ func BenchmarkVerify(b *testing.B) { var seed [KeySeedSize]byte var msg [8]byte pk, sk := NewKeyFromSeed(seed) - sig, _ := Sign(msg[:], sk.Expand(), nil) + sig, _ := Sign(msg[:], sk, nil) b.ResetTimer() for i := 0; i < b.N; i++ { - _ = Verify(pk.Expand(), msg[:], sig[:]) - } -} - -func BenchmarkVerifyExpandedKey(b *testing.B) { - var seed [KeySeedSize]byte - var msg [8]byte - pk, sk := NewKeyFromSeed(seed) - sig, _ := Sign(msg[:], sk.Expand(), nil) - epk := pk.Expand() - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = Verify(epk, msg[:], sig[:]) + _ = Verify(pk, msg[:], sig[:]) } } @@ -233,18 +224,6 @@ func BenchmarkSign(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { binary.LittleEndian.PutUint64(msg[:], uint64(i)) - _, _ = Sign(msg[:], sk.Expand(), zeroReader{}) - } -} - -func BenchmarkSignExpandedKey(b *testing.B) { - var seed [KeySeedSize]byte - var msg [8]byte - _, sk := NewKeyFromSeed(seed) - esk := sk.Expand() - b.ResetTimer() - for i := 0; i < b.N; i++ { - binary.LittleEndian.PutUint64(msg[:], uint64(i)) - _, _ = Sign(msg[:], esk, zeroReader{}) + _, _ = Sign(msg[:], sk, zeroReader{}) } } diff --git a/sign/mayo/mode5/mayo.go b/sign/mayo/mode5/mayo.go index 7633e5e90..c6caa73b8 100644 --- a/sign/mayo/mode5/mayo.go +++ b/sign/mayo/mode5/mayo.go @@ -60,7 +60,7 @@ func NewKeyFromSeed(seed [SeedSize]byte) (*PublicKey, *PrivateKey) { func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { return internal.Sign( msg, - (*internal.PrivateKey)(sk).Expand(), + (*internal.PrivateKey)(sk), rand, ) } @@ -68,26 +68,54 @@ func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { // Verify checks whether the given signature by pk on msg is valid. func Verify(pk *PublicKey, msg []byte, signature []byte) bool { return internal.Verify( - (*internal.PublicKey)(pk).Expand(), + (*internal.PublicKey)(pk), msg, signature, ) } +// Sets pk to the public key encoded in buf. +func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { + (*internal.PublicKey)(pk).Unpack(buf) +} + +// Sets sk to the private key encoded in buf. +func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { + (*internal.PrivateKey)(sk).Unpack(buf) +} + +// Packs the public key into buf. +func (pk *PublicKey) Pack(buf *[PublicKeySize]byte) { + (*internal.PublicKey)(pk).Pack(buf) +} + +// Packs the private key into buf. +func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { + (*internal.PrivateKey)(sk).Pack(buf) +} + // Packs the public key. -func (pk *PublicKey) MarshalBinary() ([]byte, error) { +func (pk *PublicKey) Bytes() []byte { var buf [PublicKeySize]byte - b := [PublicKeySize]byte(*pk) - copy(buf[:], b[:]) - return buf[:], nil + pk.Pack(&buf) + return buf[:] } // Packs the private key. -func (sk *PrivateKey) MarshalBinary() ([]byte, error) { +func (sk *PrivateKey) Bytes() []byte { var buf [PrivateKeySize]byte - b := [PrivateKeySize]byte(*sk) - copy(buf[:], b[:]) - return buf[:], nil + sk.Pack(&buf) + return buf[:] +} + +// Packs the public key. +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + return pk.Bytes(), nil +} + +// Packs the private key. +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + return sk.Bytes(), nil } // Unpacks the public key from data. @@ -95,8 +123,9 @@ func (pk *PublicKey) UnmarshalBinary(data []byte) error { if len(data) != PublicKeySize { return errors.New("packed public key must be of mode5.PublicKeySize bytes") } - self := (*[PublicKeySize]byte)(pk) - copy(self[:], data[:]) + var buf [PublicKeySize]byte + copy(buf[:], data) + pk.Unpack(&buf) return nil } @@ -105,8 +134,9 @@ func (sk *PrivateKey) UnmarshalBinary(data []byte) error { if len(data) != PrivateKeySize { return errors.New("packed private key must be of mode5.PrivateKeySize bytes") } - self := (*[PrivateKeySize]byte)(sk) - copy(self[:], data[:]) + var buf [PrivateKeySize]byte + copy(buf[:], data) + sk.Unpack(&buf) return nil } diff --git a/sign/mayo/templates/modePkg.templ.go b/sign/mayo/templates/modePkg.templ.go index 14ab11212..1fb5e7430 100644 --- a/sign/mayo/templates/modePkg.templ.go +++ b/sign/mayo/templates/modePkg.templ.go @@ -64,7 +64,7 @@ func NewKeyFromSeed(seed [SeedSize]byte) (*PublicKey, *PrivateKey) { func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { return internal.Sign( msg, - (*internal.PrivateKey)(sk).Expand(), + (*internal.PrivateKey)(sk), rand, ) } @@ -72,26 +72,54 @@ func Sign(sk *PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { // Verify checks whether the given signature by pk on msg is valid. func Verify(pk *PublicKey, msg []byte, signature []byte) bool { return internal.Verify( - (*internal.PublicKey)(pk).Expand(), + (*internal.PublicKey)(pk), msg, signature, ) } +// Sets pk to the public key encoded in buf. +func (pk *PublicKey) Unpack(buf *[PublicKeySize]byte) { + (*internal.PublicKey)(pk).Unpack(buf) +} + +// Sets sk to the private key encoded in buf. +func (sk *PrivateKey) Unpack(buf *[PrivateKeySize]byte) { + (*internal.PrivateKey)(sk).Unpack(buf) +} + +// Packs the public key into buf. +func (pk *PublicKey) Pack(buf *[PublicKeySize]byte) { + (*internal.PublicKey)(pk).Pack(buf) +} + +// Packs the private key into buf. +func (sk *PrivateKey) Pack(buf *[PrivateKeySize]byte) { + (*internal.PrivateKey)(sk).Pack(buf) +} + // Packs the public key. -func (pk *PublicKey) MarshalBinary() ([]byte, error) { +func (pk *PublicKey) Bytes() []byte { var buf [PublicKeySize]byte - b := [PublicKeySize]byte(*pk) - copy(buf[:], b[:]) - return buf[:], nil + pk.Pack(&buf) + return buf[:] } // Packs the private key. -func (sk *PrivateKey) MarshalBinary() ([]byte, error) { +func (sk *PrivateKey) Bytes() []byte { var buf [PrivateKeySize]byte - b := [PrivateKeySize]byte(*sk) - copy(buf[:], b[:]) - return buf[:], nil + sk.Pack(&buf) + return buf[:] +} + +// Packs the public key. +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + return pk.Bytes(), nil +} + +// Packs the private key. +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + return sk.Bytes(), nil } // Unpacks the public key from data. @@ -99,8 +127,9 @@ func (pk *PublicKey) UnmarshalBinary(data []byte) error { if len(data) != PublicKeySize { return errors.New("packed public key must be of {{.Pkg}}.PublicKeySize bytes") } - self := (*[PublicKeySize]byte)(pk) - copy(self[:], data[:]) + var buf [PublicKeySize]byte + copy(buf[:], data) + pk.Unpack(&buf) return nil } @@ -109,8 +138,9 @@ func (sk *PrivateKey) UnmarshalBinary(data []byte) error { if len(data) != PrivateKeySize { return errors.New("packed private key must be of {{.Pkg}}.PrivateKeySize bytes") } - self := (*[PrivateKeySize]byte)(sk) - copy(self[:], data[:]) + var buf [PrivateKeySize]byte + copy(buf[:], data) + sk.Unpack(&buf) return nil } From 65e18190ddfe946f7e591e68e854cfb2769053f2 Mon Sep 17 00:00:00 2001 From: Shang-Yi Yang Date: Tue, 12 Mar 2024 13:35:40 +0800 Subject: [PATCH 9/9] mayo: add Mode interface --- sign/mayo/doc.go | 15 --- sign/mayo/example_test.go | 56 +++++++++++ sign/mayo/gen.go | 27 +++++ sign/mayo/mayo.go | 100 +++++++++++++++++++ sign/mayo/mayo_test.go | 136 ++++++++++++++++++++++++++ sign/mayo/mode1.go | 87 ++++++++++++++++ sign/mayo/mode1/internal/mayo.go | 6 +- sign/mayo/mode1/internal/mayo_test.go | 12 +-- sign/mayo/mode1/mayo.go | 2 +- sign/mayo/mode1/signapi.go | 2 +- sign/mayo/mode2.go | 87 ++++++++++++++++ sign/mayo/mode2/internal/mayo.go | 6 +- sign/mayo/mode2/internal/mayo_test.go | 12 +-- sign/mayo/mode2/mayo.go | 2 +- sign/mayo/mode2/signapi.go | 2 +- sign/mayo/mode3.go | 87 ++++++++++++++++ sign/mayo/mode3/internal/mayo.go | 6 +- sign/mayo/mode3/internal/mayo_test.go | 12 +-- sign/mayo/mode3/mayo.go | 2 +- sign/mayo/mode3/signapi.go | 2 +- sign/mayo/mode5.go | 87 ++++++++++++++++ sign/mayo/mode5/internal/mayo.go | 6 +- sign/mayo/mode5/internal/mayo_test.go | 12 +-- sign/mayo/mode5/mayo.go | 2 +- sign/mayo/mode5/signapi.go | 2 +- sign/mayo/templates/mode.templ.go | 91 +++++++++++++++++ sign/mayo/templates/modePkg.templ.go | 2 +- sign/mayo/templates/signapi.templ.go | 2 +- 28 files changed, 804 insertions(+), 61 deletions(-) delete mode 100644 sign/mayo/doc.go create mode 100644 sign/mayo/example_test.go create mode 100644 sign/mayo/mayo.go create mode 100644 sign/mayo/mayo_test.go create mode 100644 sign/mayo/mode1.go create mode 100644 sign/mayo/mode2.go create mode 100644 sign/mayo/mode3.go create mode 100644 sign/mayo/mode5.go create mode 100644 sign/mayo/templates/mode.templ.go diff --git a/sign/mayo/doc.go b/sign/mayo/doc.go deleted file mode 100644 index a064a5794..000000000 --- a/sign/mayo/doc.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:generate go run gen.go - -// Package mayo implements the MAYO signature scheme -// as submitted to round1 of the NIST PQC competition of Additional Signature Scehemes and described in -// -// https://csrc.nist.gov/csrc/media/Projects/pqc-dig-sig/documents/round-1/spec-files/mayo-spec-web.pdf -// -// This implemented the nibble-sliced version as proposed in -// -// https://eprint.iacr.org/2023/1683 -// -// and the code is written with heavy reference to -// -// https://github.com/PQCMayo/MAYO-C/tree/nibbling-mayo -package mayo diff --git a/sign/mayo/example_test.go b/sign/mayo/example_test.go new file mode 100644 index 000000000..d03bcf616 --- /dev/null +++ b/sign/mayo/example_test.go @@ -0,0 +1,56 @@ +package mayo_test + +import ( + "fmt" + "sort" + + "github.com/cloudflare/circl/sign/mayo" +) + +func Example() { + // Check supported modes + modes := mayo.ModeNames() + sort.Strings(modes) + fmt.Printf("Supported modes: %v\n", modes) + + // Pick MAYO mode 3. + mode := mayo.ModeByName("MAYO_3") + if mode == nil { + panic("Mode3 not supported") + } + + // Alternatively one could simply write + // + // mode := MAYO.Mode3 + + // Generates a keypair. + pk, sk, err := mode.GenerateKey(nil) + if err != nil { + panic(err) + } + // (Alternatively one can derive a keypair from a seed, + // see mode.NewKeyFromSeed().) + + // Packs public and private key + packedSk := sk.Bytes() + packedPk := pk.Bytes() + + // Load it again + sk2 := mode.PrivateKeyFromBytes(packedSk) + pk2 := mode.PublicKeyFromBytes(packedPk) + + // Creates a signature on our message with the generated private key. + msg := []byte("Some message") + signature, _ := mode.Sign(sk2, msg, nil) + + // Checks whether a signature is correct + if !mode.Verify(pk2, msg, signature) { + panic("incorrect signature") + } + + fmt.Printf("O.K.") + + // Output: + // Supported modes: [MAYO_1 MAYO_2 MAYO_3 MAYO_5] + // O.K. +} diff --git a/sign/mayo/gen.go b/sign/mayo/gen.go index 078bd7840..6b7e8715f 100644 --- a/sign/mayo/gen.go +++ b/sign/mayo/gen.go @@ -86,6 +86,7 @@ var ( func main() { generateModePackageFiles() + generateModeToplevelFiles() generateParamsFiles() generateSourceFiles() generateSignApiFiles() @@ -151,6 +152,32 @@ func generateModePackageFiles() { } } +// Generates modeX.go from templates/mode.templ.go +func generateModeToplevelFiles() { + tl, err := template.ParseFiles("templates/mode.templ.go") + if err != nil { + panic(err) + } + + for _, mode := range Modes { + buf := new(bytes.Buffer) + err := tl.Execute(buf, mode) + if err != nil { + panic(err) + } + + res := buf.String() + offset := strings.Index(res, TemplateWarning) + if offset == -1 { + panic("Missing template warning in mode.templ.go") + } + err = os.WriteFile(mode.Pkg()+".go", []byte(res[offset:]), 0o644) + if err != nil { + panic(err) + } + } +} + // Generates modeX/signapi.go from templates/signapi.templ.go func generateSignApiFiles() { tl, err := template.ParseFiles("templates/signapi.templ.go") diff --git a/sign/mayo/mayo.go b/sign/mayo/mayo.go new file mode 100644 index 000000000..b2149b8ca --- /dev/null +++ b/sign/mayo/mayo.go @@ -0,0 +1,100 @@ +//go:generate go run gen.go + +// Package mayo implements the MAYO signature scheme +// as submitted to round1 of the NIST PQC competition of Additional Signature Scehemes and described in +// +// https://csrc.nist.gov/csrc/media/Projects/pqc-dig-sig/documents/round-1/spec-files/mayo-spec-web.pdf +// +// This implemented the nibble-sliced version as proposed in +// +// https://eprint.iacr.org/2023/1683 +// +// and the code is written with heavy reference to +// +// https://github.com/PQCMayo/MAYO-C/tree/nibbling-mayo +package mayo + +import ( + "crypto" + "io" +) + +// PublicKey is a MAYO public key. +// +// The structure contains values precomputed during unpacking/key generation +// and is therefore significantly larger than a packed public key. +type PublicKey interface { + // Packs public key + Bytes() []byte +} + +// PrivateKey is a MAYO public key. +// +// The structure contains values precomputed during unpacking/key generation +// and is therefore significantly larger than a packed private key. +type PrivateKey interface { + // Packs private key + Bytes() []byte + + crypto.Signer +} + +// Mode is a certain configuration of the MAYO signature scheme. +type Mode interface { + // GenerateKey generates a public/private key pair using entropy from rand. + // If rand is nil, crypto/rand.Reader will be used. + GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) + + // NewKeyFromSeed derives a public/private key pair using the given seed. + // Panics if len(seed) != SeedSize() + NewKeyFromSeed(seed []byte) (PublicKey, PrivateKey) + + // Sign signs the given message using entropy from rand and returns the signature. + // If rand is nil, crypto/rand.Reader will be used. + // It will panic if sk has not been generated for this mode. + Sign(sk PrivateKey, msg []byte, rand io.Reader) ([]byte, error) + + // Verify checks whether the given signature by pk on msg is valid. + // It will panic if pk is of the wrong mode. + Verify(pk PublicKey, msg []byte, signature []byte) bool + + // Unpacks a public key. Panics if the buffer is not of PublicKeySize() + // length. Precomputes values to speed up subsequent calls to Verify. + PublicKeyFromBytes([]byte) PublicKey + + // Unpacks a private key. Panics if the buffer is not + // of PrivateKeySize() length. Precomputes values to speed up subsequent + // calls to Sign(To). + PrivateKeyFromBytes([]byte) PrivateKey + + // SeedSize returns the size of the seed for NewKeyFromSeed + SeedSize() int + + // PublicKeySize returns the size of a packed PublicKey + PublicKeySize() int + + // PrivateKeySize returns the size of a packed PrivateKey + PrivateKeySize() int + + // SignatureSize returns the size of a signature + SignatureSize() int + + // Name returns the name of this mode + Name() string +} + +var modes = make(map[string]Mode) + +// ModeNames returns the list of supported modes. +func ModeNames() []string { + names := []string{} + for name := range modes { + names = append(names, name) + } + return names +} + +// ModeByName returns the mode with the given name or nil when not supported. +func ModeByName(name string) Mode { + return modes[name] +} diff --git a/sign/mayo/mayo_test.go b/sign/mayo/mayo_test.go new file mode 100644 index 000000000..e7e4ee002 --- /dev/null +++ b/sign/mayo/mayo_test.go @@ -0,0 +1,136 @@ +package mayo + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "testing" + + "github.com/cloudflare/circl/internal/nist" +) + +func TestNewKey(t *testing.T) { + for _, tc := range []struct { + name string + seed string + expectedPk string + }{ + {"MAYO_1", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb14803", "5b61421edc1c90efaf6075560f0206173555c55eb845ef3a70ee5ef18618361883a0ade490e9e5bc51cd2b25fb4ec9147a2fa6735d8749ed7e149330a30b3ab62b7991c2494cb21d0bc011ceb357597b8afef939a1fe398421c88f43fb4fe380c10caa3a95e507ebae5a571fd958e0505d994566263be8db31ac833aa20ae7ec97d1a865bb292e89d583975009046053bf91ce7d73e9c23c4244071e17a1095f3918330559f323ce557d66b8771f0003cccaeea82314088d56455d72f258e4c35fc6ce2d195cdae99f359a8307ec15f27da4d0634531cd9a1a719121f7c9b99a0d0c24252316f1ea7c16d73f5ecf7125ad0c8d2e02574131504875af5ab6dea1c328e71122f0cdd09e826cc515b4ec160f31d63253dd8798b2d841e80a28479bcca320853a459d4d659df695eea4b5aeb8f45f6c5fce283c64dd6fe928d452be3723f8b448650ebb72acf231e3c27683afcdec22037f1af29c479db1a97448ce570fef082052c9ef45179ba62abd7dd156f17f4da51557383d40a9d544cdf5cac4f4cc1ce69326a3ccc611fb7a6d04126dc54c55c2286a796be18958846d53e1e3f55f3ac0f4e09d8f302878f4993e25c2164970b337d0364aa61eff5d85e41fe784fd57420a5481b9a15f02a47e3e49c0709a37352a50fd0feb7e7f9938173b3e3c570a79c2424f5dc5bf95363b6d5d764e1a38a1aa05a31bbb9858b51fe3cbb19eaade5d4f482260838f5273b042f6340236395f6c347d4bfb922752ad0b17f1bae8645a9a6b1d72e1e91b4828faa313dd85ec5795252dcd95aa33519a56d740cbab7b9ee13e1b8add763bd07a25455e44e60f664846e32eec329c46e0741b6649c34358905d9d01124c3c8ae14e3fb47cc6477ba8e63bd4a3937588251952f5232bb9999aef509414e11a36d110ea24bedc235c55ab6f80116b036845e291148135a32b0d44fe5a4e9ea9f3ae150e11c62ea91318129f79318d3506ba50db3dbe235427d405a5baa9e6192a17014dd19863087445eb4cc9b6164aab3d5af4e73bc3edd97c76dabba0bc26c43d70f7880a0f7c042465da77ae3f8a31fa65903d90797fc0b5deddf578d52940040276c9b415d880e3782e03a2965a6d3b265628c690bd1b096a355362eb751d528fbc6fa365d1cf3d39cf49d8296c28fe8eb52a31a07e877d1e57e91971cbbd14216db76a08da45258a1a801d0cb232a87280df4522329ecd9312e5489d90a39547735cbd11493d0d86389af40c1b9ec5a993d0cbc508da424392695e1cdf205a294122412fac5a2b4d6e0ed5eed7b4de18f0d4b81f746753fa8744ab845c18800befc926d3290531053bf2cecf23d1b6d7fa9fb8d4827e87abf574afe39a636dbb9fce48854c641981f236da01bd8a5901871891aac7fa1f02300101540e7b05665e5f19912ee898666b64bb3cef688577c7b5482e123c0e8fcc466302eca8fc02f35260569cc1d4b00662be924f188226dbc6086d4aaed1ff9c7604748a556d00f87823f9fafaccf752d645b76a5851723274737e4dd7d5d76fdb256e3f1ef733914eb36932eb1c4c666df436351fb817e7055fbfca2b3103fcec84233feaf3fefd600721d3ca1514ee26db6a608dd4333377a9e36dd2dbadb1651eb99d8e3b4fe6"}, + {"MAYO_3", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d", "1c0ee1111b08003f28e65e8b3bdeb0378c8800a9a87f38ca101b55cbe065f76cd8be17feef5b448fbdc40c759729dd609f2f3e0a70f719bd04bdec59158c1d885127c91066d31b4e53848db9566d8d25393dd3e665c8f4b5fd5d5b836fc2e012a059a5550646dfaf9db63eb4c928198a4dc3e8631fa743e11004a2fe3454a9d537697af4bf5d6bee82c2fe40d5c74c29be0cd7e56c8dff8dacebe18320a455a72265559a287385643668d2a20058d927c87a41b8d94ac1c68b6a07c7b055604c7491f11ac30974ab0d76ff2034b6c73d3b4cfb13e8f341f9f3f445bb6a4e47aa381282f83af4c46fd38207da843135dceac04af08096915318e464a8a1fda2a99efd41c61197a498abc875ce302f94eeb8ec32e56f24e2c6c419250e08e2f69148fe083747340af17e634a98afbdd6902211c767b750365f6200fd92e8431160f1e293058cf2cd57be92816a712d796e6fc0c672026c162d93eb79a19c7d4853df371234fec9c9ce76b6edfe082007ff69c64c9980f2c7b7b6009e3db0990cffad1f4119844967572e1122e5c7ce460ccf732c82d3f18457111ae8e1a5381c5b30e9d76028ca26839455d44aac728a24731a1d69c8c856b3d611dfe5336fd5a42f62ee607219c654aa117a874ca33fbb89ecdb6310cb4e0c4a5e2554be2d4bfe6abb062eef513d726c258dafa690324302d8fb108376f991e50e9037e31ed3ce8d8cd785fe9eb419d33c7ac381f91c463a8a06629db910f5fed1d18f2cf30afcfc968ca5617c6cc6fd9080f53cbd05b727c04bf75dd93f08a0ac1a7bc46e8b5a08e6d3c3b1e1232126d4e65b1a3877689dca9b1e53a73d45ad9eeb38faf942a62a804b088226f4ecf15526e24936f9c28c951f5c101c5c44bf91b72fecbb3a9d28cf77e63343ad760089e0739017cf829297204041acf5b4c10fbca19219db233fabb3a564c8cddb281f290b9a8536c9a1d39a35d8eb124182d9cdc51044067647ee861b8c0c6fbbe1aec13076b8f3d8982b52d1243a9448729361619ba34edce7bbc1b3a1545f791a5f2f6d04b5204e073bf66e7b009c7d4a1660355d066fd0d358623fcee946e69d20bf6fb291906e700004aa001494381382c5e1bcc4fb2b7cfc4bba6866548ecc797fec670abc2e8a8dea2c4219ed32102a0a1f4fa67775512768e57851571fd383a5a684ed1e10b62f3bbbb65d58e08151695008ef6a7cf4b7cdd265d9cf1c158f64c607aaf37e51140518724c39f3ad8a759a7ec0af43778c00832088270934cd08a582196953b283fa047f6365585b6078027be9d9c915569b78c65702561ef5de7953fb6f5e3df3ae8b85d3fb2161cc3af62ed83d027b733507fee131da9d228c79277630c3efcdc9ee1e559bcaee4c818d6d29ea6c8cf9eb46ad58a4ab6a4fb2f6d6f4380efa4d60843ca4eac32f8772073f1cb70021547050c30095fc7c884e31a2f91994985eab00e7565da2ece39e47a6c8cfae012687dea9d65b8745d4d760e656a55243fa9ba8fbbd31b7f2bb2629a0ec9265aa883c8775ba173e5ff08a9a3ae4e76f9925418f4e2dce1d00e4f9377245b8939dc58b7d15c172baf984781afd318b264709bde5f0859a95923ecc42108f2fa7c600e9ff69f3f65751d8a457dde89a1ad900314f90e9b712580eff5e4a73efa3633c5d5e34fa8f2528075fa92dce66506f5e947f1fdef820296cc294ecdc059390b644a1407b13d24541d275a49d9bd1a6f68dab296f52965c7f72d50cdc0ec19456001516c10030086d2efa109f45fb6d1f7550de534d0d4ce856e72763463e6d0d11cd237037fea14abb32465f632ff785f1917322e4574bbfa8f74682da17f2bf7bffa0ff030297d5c999d6a7c387ef2a5e168225a60ebb01c6dfc72ba1fdec8f74651f3e64750f2dc98d72099fc85f43fb28ee0c4efe8b7d76bb432453a848310f6e96d833a27ccc98046a210f568c075dc71545ff0d37ae5138406d569e964bf8e6903de7651b33c05e22747074b730cb6f3e94b891e8105ad6921cf5efd40da88ec3d0765756b7bece012f59f1f68ab80082fda5fa37b026d0b6cb5fab9a0ecab6980a179815bc9e56cbdc0d0682c29c5f9fc8cbfbf5a6a9679de92adb5b4778ecdc70f819549087bef738037de613cbec897e016672ce9eaf00382ce4b60c563cf3d23cfa178403f270215cef465ae9b60f007c75c1fcb85ab4ae9ead303057dbeedcaf55f13ea586f55a2eee9da8dd92bc210d1e2d4fb7d219c54517fe5a4ddf5c8c6a776703ec8407d6b393fb331d7aa62423cbbf7a062bc210e9c6565cacfef1e4a5d448f2cbf5c8861857dc870bc37c2070ce1f1147dfdc285d9df8296d6620e1430884e29571ea95ce1deb9c2ae7198f10732b2b2d5ddabfabda6e77acfa2b10c75842e07de69cb2b34abe41018c9a765e45ac85b63a85ca612b1e9682d7c8f512297bdfb780e21506956fcb528bb1916fc1a8356667033e99e16c223139efef6790b8745eafe30df12d900301643f8bd6717d0b5d1de5e75f25bdc950ce507ce7b3489e72d5abdf5a28105a8063d0a9662672489de16a1628548382277fcfb24bc878f071b13d6e120af00c1cdcacf5f1ef4823a2b5da0e7c8487550d80ac9f6891e50be3dca0096042c97cdea3553db702bbcfcf64f1b64719bc89ba51a09057fd9285c4aa7d95532cdc04ee185beb1318132c836278e368ff8e2653e4aafa829a2d40d91bda7b4af3f16442931b7da2b500f7e3abf80219b8a245fc4748d20d549cd12c5af7ca3a8543bd14d0ddfb4fff8a43c661d0a8163fe80bc0f77b762d010b64b831e7c29f9982e5b7338a6411a37c4f1f46419b610415104f154a009cf3bf1805ff279145477a0d5ff0f3c9584874e8e8376101dec2c8c69592a8c46add07a6c0100cfab2bba03771749b3028d287805a6859a1e3a82f30231b01a7e3453059638e0d7a2430348ff4af26d00481cb3de085d269015fb6043a0346281a05448a81d04ad196d916503c35d31e513bb133d49f9646c191f872cf4d6798c18c87e52695d1adf59c97ba8bff5e826ea17ab2fcb8b559e871e6d62d8a93522554bb5d48de674596761595bf28d0114142a542c2f9cab2be451eb37546ce3eb60bb0cc6050e5d38ac96752a8d2575f67ce28452b00b486e2efc8aa80e94a0a442fd35f5fdeb48e52a82c43ef2c0a02d3bf71da67f0131af8aa90e159056ebf4e5dbbe176c9cd8c07d234135c50b524a4efd7b72ca4f413b29809e7a1a53442a1919bcce952203d17e3c5600dcc3160dcbf7f18b9352289ce78fd337c36416b8342635c3d542bd1a357b8e39b04098fe7b60248fd6ea4a2778ec576e4c34323c81e56386d40c79260e3d914db7cabbf269441dcfa216ef7aa85fae99a89f2c1f56838c30fc180135efb98bcd01e2a88d144ecf7063613d3ba62af5fa8cf7d63465273ac0abb3f94e87130310f4b841db13ad7fab1444a02af5649ef9a29392a4fc15b747799cd8a8d735094cb03a606735ac94ee244a8c5a3504f31ae62d3acf7f1586390efc6e8da967f3b174d9e805554603c4fd5cd875e70352d2c6576e3baf0420d6f3aa161f605219bffb93abd51628014072e7c2e533aa86117f02ccd67b623af4ca911eebe981595fd393e75d5b0d0da9440e3aaf9249110f5e8c22a68f9cfe0b30e8665270763390fb8618668a1a56b71065a85b91ff8dbb8e4ab9c3e6c079a2b0276ba475d388262f36a76c3e230bed8c47faae910"}, + {"MAYO_5", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2db505d7cfad1b4974", "708141534c09f5f604225ef8c3472f3749ffbce6c071fa7bb1f4b50b78ecd32e7f35efb6bfcead3313156f9995fca2d2db6d0f0ec50a66bd3d5b469698ab131f21cbae3785a0c838e2e3316fa73e9670adb76fa9e1ec8a0bb7ddae82c0ca7c1aa5e61c31b84180c3aae027223430e988e28c48ce0d5b2ad2750203707f2e284cf1807f008ae2570092a662b73a9a728f81a79338f3452d6af920ee59037f2c8a20b495420c5bb3fa7518a34ae1e64ffa742db0c3c009504ca98882e4abadaf8396cc242d9a7c1d4d71b17d6c65ed26c4e459d8bef86326ecc156209299424cc015c52f97862998f871ebacba29877e040b7adfecc69081f55162d190f374b704662c138c39782e7a8b74b7867e92c5bd8a7833b4242f04712ef4384ef452fbc3078550f15e563c9238d72088275d02d94cf8c163a2bd73810938a1208883874e1b7d8a9ced0ce6a7177da260ec1a35d2e0f45bbdf18f27cd3f0619d134aa47e4df364fbea1dfb852c225b06be4707824d9d2f8b25d6e15f81079511778cca8b82554591823616a7b7ea029c3c3670dba35bf2f11b6493ba5044c159c368f7e4d3ddb215247ac0fa17f227298b9b1d6af4632fec18ab98c9218f583c75e9b8434cb0b68105e44be85da80a65fd67839b4981e2e89feed17f41b60e75122b5328fc0a5772b8a0ec5e4646e1fc1f98a44a31d516be5cf6c187850d7343078304f8a2a02929dc2aaf268ee590507b325a0d744e413d58dd61e62decd0d4e846f08f9fae80cdb7e33d693c57e46cdbc7e1ebd06fc44133748064db8b77f1f87df1f21891af721e840d768be637ebd41cf8be5de36739045b9bae72cd9192a1ff0addbddaf6a0b6cb0d0923ce5b3d5c261c20b1bac348eed6d1cebcdda642103f3a4246e9ee3a1f1950be11796a2db39c54299a6973bdbc4a9446ff27a85fbce46e4c94c25d6fcf1d8bfc773df36591862c0447a400312969c056399b2ddc92703a4d14f1b5862752257103a300381f41f47d7baaaaef9bccbc1a3ce8c94edd1d7c43ba63c961918c1c9cffdd03659158d0827384f796466449a0bc72e94badcbf567e9f6812e320b772b7d330ca8d58996fb9cb9a4ee7772ef4e77f67e2875070249026c1287ca6b43af591a647c33efc965921c9225cb195898f0c0185d3142cd1d924119b003b0b51ccb42caced8baa8060a1750b53f7c039f857dbb248a2520bac9117da7a12ba07483bb751afb7fa1f3d9e9eecea70a9fd233adbad40fd3ff7ca1267a4a8d2375ecfe35d7984ce860725f68f19a19c0e00af83f13d452491720370effe877ec6ad51e702c9ae828b0ad3a845519d3267f3908c808498ea73bc96b70cf4d9cbaf1b1bcc9b12a093936cdf56cbb703799307d80b3755ad72dcc0b667cc14f6dd47d99d936ae3afd5be34264ddc8eaa7026e6391780444ac43c01cddba3c50548d7a2c03a59b1db7d798043351b51d4fc32f6d5f8ceeb8654977e7ba147936a6d172cfb023308730adaa3973fb4d883b95cdc5aea24a83e70561aaec784023e28dc385aae7076d271ca0551b83a60d7388b9a2c804ed66ff4fa6284fd57045d03b6c31d0d32165222efc6876b14a01c18024b0d6fec28361de82a337fb087ba25daed484b3e17fae66ce2bf2bc88614ed5a2e1db9f485ccbf7e029009c164ae3bb9b7f3b7de396970cda0943036e18d2cf96b9979e8d874980a844d7628b5fae66f00ecc4366a96c814785516d056e44b906ef6dc09e1a976384ccaf1b3f40d79a5f6ad426bb70035e84961c188ad24a8122088fb2a4b5abcb5fdd0a26cd7d44dd341c5e5ad65478269cbb5e30f4c23d63043de9c65fdd57fbae9f7a68c7bcf4e4e6b0eefdeaa3bf376c912b15f00e1a6c1b4c80269cdf9bdf25373fd55a7f51ae9aa0c0de6faf001a2016eef56d82eb696472546ec8bd29f1e76f0666b4c8d4c4cf6e633b30f323110d7b0fef38a2ec90f9359278a23f276da7984e87880105d12051937b037d86688b0aab4a11ceccd776263b8a82aa6d5a5490d33e734c651aafea394fb24d85ef3a0620686e1d96d198509e5f04f406986ff201e0b2d3d7584216e8568c68c2d8ebe1947209dff588969c3c088877b8c1dc0b0fadb7295fc44e3bef5d81789329675ac5c3142fec702f749c6e3aed13071c02680f7cb82e91432f6d6fd7c10ca132e61f2d47d6e2008ac2d5157445c1a3cfa52e03c5ad575fe836cdf4d8c9624e097d969eec08dcb4540be5b4a9f3ca03201457a2dae4a966d0b35abc65559484fe0afecb774e9437eba24d22e4da5927d00ca3a9cd49a1f3843b1f3f7b2872f0b1790788bb216d53b9b61f3f394d140a200ed30c3229761af6d3ecff8fc463ef63ae13f92d3d555261f04a287dac6653b7d493cc9026f5be29c8c550bfc513cfdeaf83ec0280fe2786ca9c07b222f6ccd9c31a8848a4e79be4b590fe4e239ad9239df96539b48cdad99fd0249d50204649e26838a076adcd6800221fff817681a6a1ccf807c1ae1794aa7cbe383cef23610436d51cdeb631df8bc1f7f7207b9972c60dfba18cd5378eab1501c6f8d4e0971862cff6b1cf3a327e1afa6371de15fefbb1c80c658d2b372f0bed979929e9fe3c3769ad0766edb634949c29f9bffd879c961f626d10617c161319957c28519ef29142236ccdbe214f7e3e9ab77edd5fa814f5b599513a46f79cac9fd9ab62d7859ed57a5e6a0af7f5e74ea4f9fbef3c9943f2c69cb66ef4497c92b25059980bd55568e8a5c50c0853fe02f9eafe068ebe710cf284976fe4b3c12d5990951775b891cebaf38ba4c9dbf70423210f723a6c0961f00766f0acf1ae75d3f1298b378cdd7fed999a8b45c923c3c8a9a48f007b88f6fc9e0d37458e441247074e9c89e7429bc76a7bef55e702420c1bc65d66bb28d46ad7e0c5be42de7b89a44dbe7ee416c795c0b7e91ca9cf2f2762816ef4bbed139137a8ad3fa7a5a96c917e8af660e736ebe9d70e4b90fa18b560ad173617f8dff65804a2bae40a4d27e86e97e02e485cb529fad4a5c7f72a2b758a693826bb964eeb28842f5aee51db712d68354d7f1ec129b7785091b1cbc3fb389263e064eb03df94183e0c1bd1824b8f5c232d7970c11e53caaae90ebba5ea862f5aa26aba25b5f627823182444efa64f21125de36afdd615518bb5dfdd117b5485910319b458236dad01de33b8605352517ce3aeee706de917ca76ecdc4ef53a733e5f898c2dd6c4d641d141ebcdca2c5c7cc176eac1b493df81702083bfc124ad9ed3511452a71f831c9a2f8d2d771cd3c1b1c440bdc46e87fb5ddc0d45c3e28c10aa1af313c08bb554590dcd296e0916a9b9df3d527a5db9ec1694083bb99c480231c87444998dc7f922f7159b019460d39be64882528f4df1e3e332f7386cfa34310b666d369ed7fff15ce6db939e448beeb74e221862a901c37275cec3e3718b146d0376c0b44e64d393c231050bf166658c333509aadca601619ba6e473705b5be1c9c290a390ee24062265298ad5bb30cc53571b4e8046856a10da99823e0a33c85fbfe785ba709a3cb9b97dd6d56db92febf8a833941655294134be7d37de581caf4e8fe278d9ad5e31d046c7c4c6e3f162c8b5bdb27f1bb5c772b8e7838f67f97cc3c090510fcb5e2f96191a77eeb7febfdcbff81a58860bd4fa87cb41b1fd51ac670828fd6efdcace5ceb85339db706286e860bacb1667da894ab79d32a67563f9a1935953679b687b558324e801c62b9eff69888a51cb8c79d6eb4aa9651108b53d1b66afb1fcbed7dce98403c64885bef7e45a0689ce783cf7741c2c787c1643e08a109b7f5813c11a16bc9269faf0c74e6e0d08428d3de2cd00e18615e157488b2a4095ade2e101dc3fd48da57bbd1c79e9c5cf0992014f80317fa35ee71560c27500761e8a60449546ffe1a232806a8ec6a5694941485f3e0af3f4cb557a42219435bf38fa10e11f3a243c9c584cde736a02bcc37b8f624931710c159f167cdb41deec3db28535731dcefb4926c8580d290ae818eb3a09738ca5d88ed1909ca95f1c71d9305f89b773cb840e3f481aa2f51dd65082da3afca358bf88d2781b46d82d7685f4ccd0983c75f9f1d6382493692604da6c8e8effb27129b1776509b98efc15c75ea1f11c04d7374742ec8cf28341a1be36deba666dabf6bcade7bc3670c03bf3bd42441edfe79716a7ccd32dc3008693a42800944bea936484bed7b6174718d31ada938690ad1efb0442fcdc7a8967f96fc4fa96821ff9862c3b1495ca55afe8ed5aa8097e20fe54f3ed2f2be1cd241a494bccfd3c75d7acd180ab6eb192402947c273abf6fac2e957c8fcc7757d79361071b2067349823cc9db3d531aaf0561acbb918df2924276ea64eb2bb095680d0be8da8d38d53f97d6cc68d88bc4f22c6865cffbd59a6fa015d2f0a12a985fd97a34e4d7f9504cc5a86acd4c791f105b9cb944ae4d0b0f747a1dd089a7903dda5528752132d2387e4854234a8a3ce95fd8afd4bfe8e55f4c026e80348ece14da917b3a696ad797e70d06356cfd2463f5ccb96549e1a037f938624754c57401cddcc948cbd088a0f276329a87db91308ece6360c743e4f8f360fed28b77f7a60745aea0b89721f4cb342b75ee1e51625292db27a97c2eb18186278fd826fb49cfe87fc9b45bd26a02266da90a7ca7d6c5d9eaaa036a0c7a12171832923facc0aae7554eb365e312e6b7b3e8be167b8e580fd5cdd609b5308864600b98f9b69d71eef1d4765f4ec07a584d9bae8a407777d7334bbb285ddb15a07dd5e3a4e8c8072710a4ac204162987f2b669524d88afb77e5f4a540f9985d560dae55945abcdeb3f90a498f76cf44d0a9e3977cdadc11a8613d50e4fb19577a6f6a16dd1b4003e56268a1134d774b9cc4e94ced0943a0199ea9ec944e891f02deb85c1b870e535f97599f843e8467c771784796c3c8712f3681b475a4eab150d18928aef90f293bdc505784441b6ffec7e525b15b5d1e2203f8ec95db5eddc8c6eb21e82118c75d6dcf2c58eb4487eed220b1eb6f3a3a7e92b0ce13ad5e87c4e4edd2c8bcab83d7622d20f3f5cd74a930515808e500563848ab0893cef9323ebdeeac9716270f3550c1d92ffb4d650c8442300cb7a4c61de2b5876f6563271ab7abdacc962be387556a6452dfdedf99148b4a2fe635dc730cfe9cf07a1ad5e148e2eb639e25838a8b5f6271a1ac8330847da496f042237e9f38ace9c37f096da0d76157825c2c8f578919faa984f4be65f7055e517b8f78589e1ff361d769e5d387795b79bf476959815d9b6ff22361f76e60cab6986e0d164a0984a013101e3cadd3eee5199030b45e8c6b9ea63f80c7e6e2909b39d96d6f62776820281eac9fb55fb5c124b7dd9e9b114d476d2fbc676df79f9e9b214ea69013681e7cd8d379bba254382ae4678986f67eba92cd978ac6353e6207101a3e1d18008c3d5924eef4991e0b576fd9f582980faf24b898796c18388f40ae96403d45b31785958d37ca09b76a5a7320776ca4d328e95ea7b6703f4ab0947070f5c20dfd154a9476099d193dccd487f589759754c43fb81c1941439b4d63eeb6b24c294c70ac0a71ff437a6abb795aed2fa4e2483f2a09ac6d1a85b91155cb368219d17883106568c27c924070ad23ed0193dacc353dbb95a4c1de118c1a710f96db6eb9c146c2d0e07873d495d1ad9e3717e09c067422fe5a2da8d9406e1395aa39cf0c82f9c290d1e31a4a4d9b4c381c95079327c296e5db5f8e3c92ef62c03f1c2ac7c4b0174528b5dc0ff37c8c7d0533e4248fb86ca1cb5df91e05e1cc582482e7f12136e8628a48085ff27d15815506c5013f6f65e3c89286616ffd5d08f3fc2194ae51fc4a706e2cd83b6629ea8cce9ee3f968db84c85e3dc0c4b3e4cd6f105a196260f5873c8590401719ce3afd8de96d465b5258ce6b6e53db24fdc8fea3a9062fd2f10ddfb0adcd355724e366afb6b401118d73873c8e682dacbb4be47452eeb98ce51607903126f83ab6b27723818f816f9ede8ef344b3e7bdafdbe8292c6af60835f572ee68629386575541f1b5d5bae84bd85cb65e1ec46a70abf6365f75f975c3b87944e45d0da01f267b569d9ce55cdc90fb3ba1b5f7337f87a914eb5f6f7dd66a56b052430b148e3e295eb82fed70c6a38f738ac63b820db7183d18156abaf638ae366ce6491f0f0e326e1c9c3c8e7df4d62632f7599805f4a8ef06ddee3191e35f20cb7d03e7afbae7cc71a914aa9c1744b420ab5f406771e944ee7e3aec062bec33696df2c5ac2e09648902ea629aac071346ef87f5e54aaf650785f5ade96e0919ec00b99926594cdbdd1e0482e56fc0b78cb6142d2a4be8aed181918b11a841fbb8b442a3e2af1ef1c29528d6447c72b8c2e514aabd0595ebdc698b90ef61a6ee4b400f01fa5c71a2dead6822f33c3341d8c019bd100aa9028b57e517ac6880112335390a8349b90235486eddee01685a7cf1ea158cf9cc1f348a6b113a9d82f2dae95c7ae56f9183d97cbc35a25baea3552b8c2a7de779f6b74db1cfc75d07d79aae1857b601e73cc3686c175f135233989e6d56bdbe611f96a2b51804467e47b474479ccfabddbb4629a0fdc7f73e172da3c168c6c40461371e05a9a332ee3aeb0c9a5985425e102e421f497ec978de92bf73c98fd7d1bbd068e9d8ae82cbc7ef3923bcfe97bd68fe9563195d26a1afdc3bd91b32044fbe68b4bccf29a8d52d13c625d89f73611e371dfe6b73b50d6388cfd87ba92074533cfb34a4d543126f096002d9ce5ecf89cc11b0449f696e1661a36884ac1bf58f397d993c82bcc03c1db156ffa5148da7a73b3be2b6a3b2436c26c24ac0154bee5837d079dfa65d04a208de8b79691518f9056ccdb382e61c8d7606be73322641c71719a23ad6a571f907aa0bd1d8f3170296f3cc88bacd9b3fe944548808e80d00c07bfddf271ace893f56e01f95329aa944d32d9623180142b32e60ce0b908cf2748a29c42a3a59b378e47ebbc4ada9b43133f51bb4d28b821bec1788cc94ac1c8e9e4499aae9e17fcabff0a824637f125e9b358e0"}, + } { + t.Run(tc.name, func(t *testing.T) { + mode := ModeByName(tc.name) + if mode == nil { + t.Fatal() + } + + seed := make([]byte, mode.SeedSize()) + _, _ = hex.Decode(seed[:], []byte(tc.seed)) + + pk, sk := mode.NewKeyFromSeed(seed) + + if hex.EncodeToString(pk.Bytes()) != tc.expectedPk { + t.Fatal() + } + + if hex.EncodeToString(sk.Bytes()) != tc.seed { + t.Fatal() + } + }) + } +} + +func TestVerify(t *testing.T) { + for _, tc := range []struct { + name string + seed string + message string + signature string + }{ + {"MAYO_1", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb14803", "d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8", "165e6139a87a4a4966326ba83e6d1ef68d1d7bec62c9d1d267ba09df6815783980f0a6ca0923aee6e5b9495b7ed6fe6c49cc41f794a80d297b19d3f13b0bfe3e8865378f5d24a210dae0982a1036ec9cbe87c29ce9a05a8290d0d1e8603abd163a27b119878663c06745cee03f417300402bf198ee4ac2be99dfe19700f169efe6a365ae0fe9c1bc22028c864ec55072da4f98f878759de68399a2c8d2a2656996927265ce2908e05913f8057707d39dfd3ef58a79cad49f0bd010ca9e8ebde500057bffd0ff28015a60b3061cee55dfa1d59e1a72a8ce639777e6190542733639fbba575aae945583bf804ea129e1a80e12fb628accdcc3f43da37c7194bb641bb8b6404aee481b4ffb13be31941d30c1f9d818182545738ce2644dc9c9b0a1289c6294d3225cc23ee03cc8258176e1a958e90dfed58ad3a859d1b06dee53af6a"}, + {"MAYO_3", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d", "d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8", "01de18cb2cfce7ba2d15677a57e7d86b20a96c7b93daf27232ddc7a0249796ef9321ace5792a4af1ca49908780398a25f9e4b1293ad8209301c769f123cfa430e5bbff1812dd4a8efd5621c109444cb4a1bcf4f1d85abc6fbe493ae0370aba2b2251de011129be10c25f452c987ddb1c499faf1dc8da2e60e7913ab7ade65844a92ed3a5c04c2245c67d342e5067be4a8c024753a8fd5962481b9c52051723f50165ec2b0ec8f73b4b337361c12014607f9fe41c3591eae8653d18b95616b18a84a1a5951ab5082ceebd5d2cfd202fe3ae29b60a790d04295edfbce886eb98d63cce694c9517f5f6832b7cef38efc9c8f4c14e01429f5487cb752f675f4e4212058db63fc29b9c00701810144ac48e7b007f4dfaa80a2ec9899e60f704ac0d0b58c8a716433c91a27d966d2b7b2685da350c9f6c59233988cbb3f8f2383f5eb10e496e64017a0789ca8f702254e1b839382d42b8399488bd74517e280f1719392621596730862678dec519c50cd6bdc07f118217ff68004401fbccf8606c1af2ae6ce0dde06c1bd06a460bace8dc9408f1d5cf76dd063f63db9a0969098ec45fcde2e878d464f4de4bfe1fe710f2132d8e486f64e7d309d9faed999ce368e821a05d79f781fb895a767a09256541cd54242b5df5e262220fe232c637934380b137df86ab4fa07b11f07f914835c87d1547fe526001a61b8ce00721d90d8a4b54b60000ef75d9bdb94fc7f861ee46908ade105ea098cef938f49dae729817633003c87a0a6c0cbfd10185f7657804a2330a7def349da1df84a1a61dc857d2458425"}, + {"MAYO_5", "7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2db505d7cfad1b4974", "d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8", "7bc32600e7fe4e0d8caf187997624ba57867ec136d7112aceb161cf813ad98aa9709f0e42bc6e86c4c1a32e5b929df9976a4c0d77886d9e5005fad28c3b0e20dd7bb8d49aaaa77d969c72c9d37d78c3ff556ae777f4bac4d505194b6026fb3e17ad631980dfc373c7510c024817c1900d3428984e4e952b49b4c46c928717ea34f417b5f5d6067caf5edc15c1fcbf74d9f7a158958daf27acf3bcb611195278550d06bd83441083e2a64f0e70a8b668453b7f638a64c6d2424cf2ae31fb6efedf951215d14648d2e3282526c0504e4a0b56ac0336a28b98297d16ca2087c3c46ab10275ea0f5611bbde1b44beb420977aa8ec3693ac097901a8e3aef2d377e19ab6d14a42a5c5d7a13ec1df5be83657b7ee84e7671828aff72265bc7fa90829f92df63a159e51e222da67a5201db70597814ac4d793caeda47d7fdd2c59402a2e8a67beb6f9f43b31feaec24854f429e2aec6fe9e7412584f08719c0c5c90e6bc5371d65eebefcc956ada0659b9b0b5f4d4733873fb4daa0e837fda9062d9a79766ef66686f9b355a219d74728ccad98b04391427a30d8b95391b9e54b9f902853d0086fb1bfd136df4dc1ef6eed4c75f449612c76a506ec6c2d7e9676c776f615300230aae0353afc02d80ff377d79a923d2348eec166e0d1b40aa2428a2bf2f87dd89dffd6f1969b39cd84b3d2813078cd4a7753379a79ab6b33f9e8b54a73dde0c46f4fa9afb8a9d0445ae59d202a3df70143baf1de61de9d55681467e343a025b1e0f67af5b1e3e22568d449af192b3e241b6b635e668e851b39e9909ed77ec007a8fba5aec76551e6bb08f454ccf4d48867456c50fdeeaa80f4557d375bda789dc0a5622ccd20d0e2f32e4c7876464f8c8b5393f45eba04085f356927955fe89c475aba7862590656717b5c77f1f79d2a430a2dfb6c779f1794f5f6d374e6e566706f9bfcf33e2070b10af8bc9b853d38215bcb4ad02123445d91b485952514c63a21fac93146a15962fad262c2cdc8ce18a592e4420f60d18ae4ad558b5229b372ed06f021827e932bdb7c43ed6db84ab9c2e408251619bd8b3ce28b9c55c3a21ae2f300758c80086f0ed4a316403195512c2880239f50afa996ce24374ceb25637183b96cf246d204a04018f4344c81aad9cfa98c2ee4f5f6ce3a6f352a86e9a1b07b"}, + } { + t.Run(tc.name, func(t *testing.T) { + mode := ModeByName(tc.name) + if mode == nil { + t.Fatal() + } + + seed := make([]byte, mode.SeedSize()) + _, _ = hex.Decode(seed[:], []byte(tc.seed)) + + m, _ := hex.DecodeString(tc.message) + s, _ := hex.DecodeString(tc.signature) + + pk, _ := mode.NewKeyFromSeed(seed) + + if !mode.Verify(pk, m, s) { + t.Fatal("should verify") + } + }) + } +} + +func TestPQCgenKATSign(t *testing.T) { + for _, tc := range []struct { + name string + want string + }{ + {"MAYO_1", "d0da809f52866507b6354588cd44b712bac138a8363fde768adb92285b6e9865"}, + {"MAYO_2", "e7382b9b0fd985023a53f292b5d5caf444541a5bd531cf6e1e4d35b8bd864123"}, + {"MAYO_3", "0a53ad1ea675f6be7364d37b552cfa7ca254ac315724fb2871c2fe0567f509b9"}, + {"MAYO_5", "26a4bf3204c41fcb9911f761066668da34554efdd0684346b348ccd669f16b56"}, + } { + t.Run(tc.name, func(t *testing.T) { + mode := ModeByName(tc.name) + if mode == nil { + t.Fatal() + } + + var seed [48]byte + eseed := make([]byte, mode.SeedSize()) + for i := 0; i < 48; i++ { + seed[i] = byte(i) + } + f := sha256.New() + g := nist.NewDRBG(&seed) + fmt.Fprintf(f, "# %s\n\n", tc.name) + for i := 0; i < 100; i++ { + mlen := 33 * (i + 1) + _, _ = g.Read(seed[:]) + msg := make([]byte, mlen) + _, _ = g.Read(msg[:]) + + fmt.Fprintf(f, "count = %d\n", i) + fmt.Fprintf(f, "seed = %X\n", seed) + fmt.Fprintf(f, "mlen = %d\n", mlen) + fmt.Fprintf(f, "msg = %X\n", msg) + + g2 := nist.NewDRBG(&seed) + _, _ = g2.Read(eseed[:]) + pk, sk := mode.NewKeyFromSeed(eseed) + + fmt.Fprintf(f, "pk = %X\n", pk.Bytes()) + fmt.Fprintf(f, "sk = %X\n", sk.Bytes()) + fmt.Fprintf(f, "smlen = %d\n", mlen+mode.SignatureSize()) + + sig, err := mode.Sign(sk, msg[:], &g2) + if err != nil { + t.Fatal() + } + + fmt.Fprintf(f, "sm = %X%X\n\n", sig, msg) + + if !mode.Verify(pk, msg[:], sig) { + t.Fatal() + } + } + + if fmt.Sprintf("%x", f.Sum(nil)) != tc.want { + t.Fatal() + } + }) + } +} diff --git a/sign/mayo/mode1.go b/sign/mayo/mode1.go new file mode 100644 index 000000000..3579d4bc1 --- /dev/null +++ b/sign/mayo/mode1.go @@ -0,0 +1,87 @@ +// Code generated from mode.templ.go. DO NOT EDIT. + +package mayo + +import ( + "fmt" + "io" + + "github.com/cloudflare/circl/sign/mayo/mode1" +) + +// implMode1 implements the mode.Mode interface for MAYO_1. +type implMode1 struct{} + +// Mode1 is MAYO in mode "MAYO_1". +var Mode1 Mode = &implMode1{} + +func (m *implMode1) GenerateKey(rand io.Reader) ( + PublicKey, PrivateKey, error) { + return mode1.GenerateKey(rand) +} + +func (m *implMode1) NewKeyFromSeed(seed []byte) (PublicKey, + PrivateKey) { + if len(seed) != mode1.SeedSize { + panic(fmt.Sprintf("seed must be of length %d", mode1.SeedSize)) + } + seedBuf := [mode1.SeedSize]byte{} + copy(seedBuf[:], seed) + return mode1.NewKeyFromSeed(&seedBuf) +} + +func (m *implMode1) Sign(sk PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { + isk := sk.(*mode1.PrivateKey) + return mode1.Sign(isk, msg, rand) +} + +func (m *implMode1) Verify(pk PublicKey, msg []byte, signature []byte) bool { + ipk := pk.(*mode1.PublicKey) + return mode1.Verify(ipk, msg, signature) +} + +func (m *implMode1) PublicKeyFromBytes(data []byte) PublicKey { + var ret mode1.PublicKey + if len(data) != mode1.PublicKeySize { + panic("packed public key must be of mode1.PublicKeySize bytes") + } + var buf [mode1.PublicKeySize]byte + copy(buf[:], data) + ret.Unpack(&buf) + return &ret +} + +func (m *implMode1) PrivateKeyFromBytes(data []byte) PrivateKey { + var ret mode1.PrivateKey + if len(data) != mode1.PrivateKeySize { + panic("packed public key must be of mode1.PrivateKeySize bytes") + } + var buf [mode1.PrivateKeySize]byte + copy(buf[:], data) + ret.Unpack(&buf) + return &ret +} + +func (m *implMode1) SeedSize() int { + return mode1.SeedSize +} + +func (m *implMode1) PublicKeySize() int { + return mode1.PublicKeySize +} + +func (m *implMode1) PrivateKeySize() int { + return mode1.PrivateKeySize +} + +func (m *implMode1) SignatureSize() int { + return mode1.SignatureSize +} + +func (m *implMode1) Name() string { + return "MAYO_1" +} + +func init() { + modes["MAYO_1"] = Mode1 +} diff --git a/sign/mayo/mode1/internal/mayo.go b/sign/mayo/mode1/internal/mayo.go index 83ab278d1..4ca85ffc9 100644 --- a/sign/mayo/mode1/internal/mayo.go +++ b/sign/mayo/mode1/internal/mayo.go @@ -149,13 +149,13 @@ func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { if err != nil { return nil, nil, err } - pk, sk := NewKeyFromSeed(seed) + pk, sk := NewKeyFromSeed(&seed) return pk, sk, nil } -func NewKeyFromSeed(seed [KeySeedSize]byte) (*PublicKey, *PrivateKey) { +func NewKeyFromSeed(seed *[KeySeedSize]byte) (*PublicKey, *PrivateKey) { var sk PrivateKey - sk.Unpack(&seed) + sk.Unpack(seed) return sk.Public(), &sk } diff --git a/sign/mayo/mode1/internal/mayo_test.go b/sign/mayo/mode1/internal/mayo_test.go index 2a94c97b8..4529b13ba 100644 --- a/sign/mayo/mode1/internal/mayo_test.go +++ b/sign/mayo/mode1/internal/mayo_test.go @@ -28,7 +28,7 @@ func TestNewKey(t *testing.T) { var seed [KeySeedSize]byte _, _ = hex.Decode(seed[:], []byte(tc.seed)) - pk, sk := NewKeyFromSeed(seed) + pk, sk := NewKeyFromSeed(&seed) var pk2 [PublicKeySize]byte var sk2 [PrivateKeySize]byte @@ -68,7 +68,7 @@ func TestVerify(t *testing.T) { m, _ := hex.DecodeString(tc.message) s, _ := hex.DecodeString(tc.signature) - pk, _ := NewKeyFromSeed(seed) + pk, _ := NewKeyFromSeed(&seed) if !Verify(pk, m, s) { t.Fatal("should verify") @@ -144,7 +144,7 @@ func TestPQCgenKATSign(t *testing.T) { g2 := nist.NewDRBG(&seed) _, _ = g2.Read(eseed[:]) - pk, sk := NewKeyFromSeed(eseed) + pk, sk := NewKeyFromSeed(&eseed) var pk2 [PublicKeySize]byte var sk2 [PrivateKeySize]byte @@ -178,7 +178,7 @@ func BenchmarkKeyGen(b *testing.B) { var seed [KeySeedSize]byte for i := 0; i < b.N; i++ { binary.LittleEndian.PutUint64(seed[:], uint64(i)) - _, _ = NewKeyFromSeed(seed) + _, _ = NewKeyFromSeed(&seed) } } @@ -198,7 +198,7 @@ func BenchmarkMatMul(b *testing.B) { func BenchmarkVerify(b *testing.B) { var seed [KeySeedSize]byte var msg [8]byte - pk, sk := NewKeyFromSeed(seed) + pk, sk := NewKeyFromSeed(&seed) sig, _ := Sign(msg[:], sk, nil) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -218,7 +218,7 @@ func (zeroReader) Read(buf []byte) (int, error) { func BenchmarkSign(b *testing.B) { var seed [KeySeedSize]byte var msg [8]byte - _, sk := NewKeyFromSeed(seed) + _, sk := NewKeyFromSeed(&seed) b.ResetTimer() for i := 0; i < b.N; i++ { binary.LittleEndian.PutUint64(msg[:], uint64(i)) diff --git a/sign/mayo/mode1/mayo.go b/sign/mayo/mode1/mayo.go index 4c3b1f89d..f67c85edf 100644 --- a/sign/mayo/mode1/mayo.go +++ b/sign/mayo/mode1/mayo.go @@ -50,7 +50,7 @@ func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { } // NewKeyFromSeed derives a public/private key pair using the given seed. -func NewKeyFromSeed(seed [SeedSize]byte) (*PublicKey, *PrivateKey) { +func NewKeyFromSeed(seed *[SeedSize]byte) (*PublicKey, *PrivateKey) { pk, sk := internal.NewKeyFromSeed(seed) return (*PublicKey)(pk), (*PrivateKey)(sk) } diff --git a/sign/mayo/mode1/signapi.go b/sign/mayo/mode1/signapi.go index ad674f49c..069ad959e 100644 --- a/sign/mayo/mode1/signapi.go +++ b/sign/mayo/mode1/signapi.go @@ -65,7 +65,7 @@ func (*scheme) DeriveKey(seed []byte) (sign.PublicKey, sign.PrivateKey) { } var tmp [SeedSize]byte copy(tmp[:], seed) - return NewKeyFromSeed(tmp) + return NewKeyFromSeed(&tmp) } func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (sign.PublicKey, error) { diff --git a/sign/mayo/mode2.go b/sign/mayo/mode2.go new file mode 100644 index 000000000..24fb1436c --- /dev/null +++ b/sign/mayo/mode2.go @@ -0,0 +1,87 @@ +// Code generated from mode.templ.go. DO NOT EDIT. + +package mayo + +import ( + "fmt" + "io" + + "github.com/cloudflare/circl/sign/mayo/mode2" +) + +// implMode2 implements the mode.Mode interface for MAYO_2. +type implMode2 struct{} + +// Mode2 is MAYO in mode "MAYO_2". +var Mode2 Mode = &implMode2{} + +func (m *implMode2) GenerateKey(rand io.Reader) ( + PublicKey, PrivateKey, error) { + return mode2.GenerateKey(rand) +} + +func (m *implMode2) NewKeyFromSeed(seed []byte) (PublicKey, + PrivateKey) { + if len(seed) != mode2.SeedSize { + panic(fmt.Sprintf("seed must be of length %d", mode2.SeedSize)) + } + seedBuf := [mode2.SeedSize]byte{} + copy(seedBuf[:], seed) + return mode2.NewKeyFromSeed(&seedBuf) +} + +func (m *implMode2) Sign(sk PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { + isk := sk.(*mode2.PrivateKey) + return mode2.Sign(isk, msg, rand) +} + +func (m *implMode2) Verify(pk PublicKey, msg []byte, signature []byte) bool { + ipk := pk.(*mode2.PublicKey) + return mode2.Verify(ipk, msg, signature) +} + +func (m *implMode2) PublicKeyFromBytes(data []byte) PublicKey { + var ret mode2.PublicKey + if len(data) != mode2.PublicKeySize { + panic("packed public key must be of mode2.PublicKeySize bytes") + } + var buf [mode2.PublicKeySize]byte + copy(buf[:], data) + ret.Unpack(&buf) + return &ret +} + +func (m *implMode2) PrivateKeyFromBytes(data []byte) PrivateKey { + var ret mode2.PrivateKey + if len(data) != mode2.PrivateKeySize { + panic("packed public key must be of mode2.PrivateKeySize bytes") + } + var buf [mode2.PrivateKeySize]byte + copy(buf[:], data) + ret.Unpack(&buf) + return &ret +} + +func (m *implMode2) SeedSize() int { + return mode2.SeedSize +} + +func (m *implMode2) PublicKeySize() int { + return mode2.PublicKeySize +} + +func (m *implMode2) PrivateKeySize() int { + return mode2.PrivateKeySize +} + +func (m *implMode2) SignatureSize() int { + return mode2.SignatureSize +} + +func (m *implMode2) Name() string { + return "MAYO_2" +} + +func init() { + modes["MAYO_2"] = Mode2 +} diff --git a/sign/mayo/mode2/internal/mayo.go b/sign/mayo/mode2/internal/mayo.go index 90359bc99..07226f739 100644 --- a/sign/mayo/mode2/internal/mayo.go +++ b/sign/mayo/mode2/internal/mayo.go @@ -151,13 +151,13 @@ func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { if err != nil { return nil, nil, err } - pk, sk := NewKeyFromSeed(seed) + pk, sk := NewKeyFromSeed(&seed) return pk, sk, nil } -func NewKeyFromSeed(seed [KeySeedSize]byte) (*PublicKey, *PrivateKey) { +func NewKeyFromSeed(seed *[KeySeedSize]byte) (*PublicKey, *PrivateKey) { var sk PrivateKey - sk.Unpack(&seed) + sk.Unpack(seed) return sk.Public(), &sk } diff --git a/sign/mayo/mode2/internal/mayo_test.go b/sign/mayo/mode2/internal/mayo_test.go index 847949cdc..2ebd29fb8 100644 --- a/sign/mayo/mode2/internal/mayo_test.go +++ b/sign/mayo/mode2/internal/mayo_test.go @@ -30,7 +30,7 @@ func TestNewKey(t *testing.T) { var seed [KeySeedSize]byte _, _ = hex.Decode(seed[:], []byte(tc.seed)) - pk, sk := NewKeyFromSeed(seed) + pk, sk := NewKeyFromSeed(&seed) var pk2 [PublicKeySize]byte var sk2 [PrivateKeySize]byte @@ -70,7 +70,7 @@ func TestVerify(t *testing.T) { m, _ := hex.DecodeString(tc.message) s, _ := hex.DecodeString(tc.signature) - pk, _ := NewKeyFromSeed(seed) + pk, _ := NewKeyFromSeed(&seed) if !Verify(pk, m, s) { t.Fatal("should verify") @@ -146,7 +146,7 @@ func TestPQCgenKATSign(t *testing.T) { g2 := nist.NewDRBG(&seed) _, _ = g2.Read(eseed[:]) - pk, sk := NewKeyFromSeed(eseed) + pk, sk := NewKeyFromSeed(&eseed) var pk2 [PublicKeySize]byte var sk2 [PrivateKeySize]byte @@ -180,7 +180,7 @@ func BenchmarkKeyGen(b *testing.B) { var seed [KeySeedSize]byte for i := 0; i < b.N; i++ { binary.LittleEndian.PutUint64(seed[:], uint64(i)) - _, _ = NewKeyFromSeed(seed) + _, _ = NewKeyFromSeed(&seed) } } @@ -200,7 +200,7 @@ func BenchmarkMatMul(b *testing.B) { func BenchmarkVerify(b *testing.B) { var seed [KeySeedSize]byte var msg [8]byte - pk, sk := NewKeyFromSeed(seed) + pk, sk := NewKeyFromSeed(&seed) sig, _ := Sign(msg[:], sk, nil) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -220,7 +220,7 @@ func (zeroReader) Read(buf []byte) (int, error) { func BenchmarkSign(b *testing.B) { var seed [KeySeedSize]byte var msg [8]byte - _, sk := NewKeyFromSeed(seed) + _, sk := NewKeyFromSeed(&seed) b.ResetTimer() for i := 0; i < b.N; i++ { binary.LittleEndian.PutUint64(msg[:], uint64(i)) diff --git a/sign/mayo/mode2/mayo.go b/sign/mayo/mode2/mayo.go index c0ddfe66f..e25035cd4 100644 --- a/sign/mayo/mode2/mayo.go +++ b/sign/mayo/mode2/mayo.go @@ -50,7 +50,7 @@ func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { } // NewKeyFromSeed derives a public/private key pair using the given seed. -func NewKeyFromSeed(seed [SeedSize]byte) (*PublicKey, *PrivateKey) { +func NewKeyFromSeed(seed *[SeedSize]byte) (*PublicKey, *PrivateKey) { pk, sk := internal.NewKeyFromSeed(seed) return (*PublicKey)(pk), (*PrivateKey)(sk) } diff --git a/sign/mayo/mode2/signapi.go b/sign/mayo/mode2/signapi.go index c13a65cf4..08961515f 100644 --- a/sign/mayo/mode2/signapi.go +++ b/sign/mayo/mode2/signapi.go @@ -65,7 +65,7 @@ func (*scheme) DeriveKey(seed []byte) (sign.PublicKey, sign.PrivateKey) { } var tmp [SeedSize]byte copy(tmp[:], seed) - return NewKeyFromSeed(tmp) + return NewKeyFromSeed(&tmp) } func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (sign.PublicKey, error) { diff --git a/sign/mayo/mode3.go b/sign/mayo/mode3.go new file mode 100644 index 000000000..4c2db55c8 --- /dev/null +++ b/sign/mayo/mode3.go @@ -0,0 +1,87 @@ +// Code generated from mode.templ.go. DO NOT EDIT. + +package mayo + +import ( + "fmt" + "io" + + "github.com/cloudflare/circl/sign/mayo/mode3" +) + +// implMode3 implements the mode.Mode interface for MAYO_3. +type implMode3 struct{} + +// Mode3 is MAYO in mode "MAYO_3". +var Mode3 Mode = &implMode3{} + +func (m *implMode3) GenerateKey(rand io.Reader) ( + PublicKey, PrivateKey, error) { + return mode3.GenerateKey(rand) +} + +func (m *implMode3) NewKeyFromSeed(seed []byte) (PublicKey, + PrivateKey) { + if len(seed) != mode3.SeedSize { + panic(fmt.Sprintf("seed must be of length %d", mode3.SeedSize)) + } + seedBuf := [mode3.SeedSize]byte{} + copy(seedBuf[:], seed) + return mode3.NewKeyFromSeed(&seedBuf) +} + +func (m *implMode3) Sign(sk PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { + isk := sk.(*mode3.PrivateKey) + return mode3.Sign(isk, msg, rand) +} + +func (m *implMode3) Verify(pk PublicKey, msg []byte, signature []byte) bool { + ipk := pk.(*mode3.PublicKey) + return mode3.Verify(ipk, msg, signature) +} + +func (m *implMode3) PublicKeyFromBytes(data []byte) PublicKey { + var ret mode3.PublicKey + if len(data) != mode3.PublicKeySize { + panic("packed public key must be of mode3.PublicKeySize bytes") + } + var buf [mode3.PublicKeySize]byte + copy(buf[:], data) + ret.Unpack(&buf) + return &ret +} + +func (m *implMode3) PrivateKeyFromBytes(data []byte) PrivateKey { + var ret mode3.PrivateKey + if len(data) != mode3.PrivateKeySize { + panic("packed public key must be of mode3.PrivateKeySize bytes") + } + var buf [mode3.PrivateKeySize]byte + copy(buf[:], data) + ret.Unpack(&buf) + return &ret +} + +func (m *implMode3) SeedSize() int { + return mode3.SeedSize +} + +func (m *implMode3) PublicKeySize() int { + return mode3.PublicKeySize +} + +func (m *implMode3) PrivateKeySize() int { + return mode3.PrivateKeySize +} + +func (m *implMode3) SignatureSize() int { + return mode3.SignatureSize +} + +func (m *implMode3) Name() string { + return "MAYO_3" +} + +func init() { + modes["MAYO_3"] = Mode3 +} diff --git a/sign/mayo/mode3/internal/mayo.go b/sign/mayo/mode3/internal/mayo.go index 90359bc99..07226f739 100644 --- a/sign/mayo/mode3/internal/mayo.go +++ b/sign/mayo/mode3/internal/mayo.go @@ -151,13 +151,13 @@ func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { if err != nil { return nil, nil, err } - pk, sk := NewKeyFromSeed(seed) + pk, sk := NewKeyFromSeed(&seed) return pk, sk, nil } -func NewKeyFromSeed(seed [KeySeedSize]byte) (*PublicKey, *PrivateKey) { +func NewKeyFromSeed(seed *[KeySeedSize]byte) (*PublicKey, *PrivateKey) { var sk PrivateKey - sk.Unpack(&seed) + sk.Unpack(seed) return sk.Public(), &sk } diff --git a/sign/mayo/mode3/internal/mayo_test.go b/sign/mayo/mode3/internal/mayo_test.go index 847949cdc..2ebd29fb8 100644 --- a/sign/mayo/mode3/internal/mayo_test.go +++ b/sign/mayo/mode3/internal/mayo_test.go @@ -30,7 +30,7 @@ func TestNewKey(t *testing.T) { var seed [KeySeedSize]byte _, _ = hex.Decode(seed[:], []byte(tc.seed)) - pk, sk := NewKeyFromSeed(seed) + pk, sk := NewKeyFromSeed(&seed) var pk2 [PublicKeySize]byte var sk2 [PrivateKeySize]byte @@ -70,7 +70,7 @@ func TestVerify(t *testing.T) { m, _ := hex.DecodeString(tc.message) s, _ := hex.DecodeString(tc.signature) - pk, _ := NewKeyFromSeed(seed) + pk, _ := NewKeyFromSeed(&seed) if !Verify(pk, m, s) { t.Fatal("should verify") @@ -146,7 +146,7 @@ func TestPQCgenKATSign(t *testing.T) { g2 := nist.NewDRBG(&seed) _, _ = g2.Read(eseed[:]) - pk, sk := NewKeyFromSeed(eseed) + pk, sk := NewKeyFromSeed(&eseed) var pk2 [PublicKeySize]byte var sk2 [PrivateKeySize]byte @@ -180,7 +180,7 @@ func BenchmarkKeyGen(b *testing.B) { var seed [KeySeedSize]byte for i := 0; i < b.N; i++ { binary.LittleEndian.PutUint64(seed[:], uint64(i)) - _, _ = NewKeyFromSeed(seed) + _, _ = NewKeyFromSeed(&seed) } } @@ -200,7 +200,7 @@ func BenchmarkMatMul(b *testing.B) { func BenchmarkVerify(b *testing.B) { var seed [KeySeedSize]byte var msg [8]byte - pk, sk := NewKeyFromSeed(seed) + pk, sk := NewKeyFromSeed(&seed) sig, _ := Sign(msg[:], sk, nil) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -220,7 +220,7 @@ func (zeroReader) Read(buf []byte) (int, error) { func BenchmarkSign(b *testing.B) { var seed [KeySeedSize]byte var msg [8]byte - _, sk := NewKeyFromSeed(seed) + _, sk := NewKeyFromSeed(&seed) b.ResetTimer() for i := 0; i < b.N; i++ { binary.LittleEndian.PutUint64(msg[:], uint64(i)) diff --git a/sign/mayo/mode3/mayo.go b/sign/mayo/mode3/mayo.go index 5fe331217..7069821df 100644 --- a/sign/mayo/mode3/mayo.go +++ b/sign/mayo/mode3/mayo.go @@ -50,7 +50,7 @@ func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { } // NewKeyFromSeed derives a public/private key pair using the given seed. -func NewKeyFromSeed(seed [SeedSize]byte) (*PublicKey, *PrivateKey) { +func NewKeyFromSeed(seed *[SeedSize]byte) (*PublicKey, *PrivateKey) { pk, sk := internal.NewKeyFromSeed(seed) return (*PublicKey)(pk), (*PrivateKey)(sk) } diff --git a/sign/mayo/mode3/signapi.go b/sign/mayo/mode3/signapi.go index e8b13a47f..56163b644 100644 --- a/sign/mayo/mode3/signapi.go +++ b/sign/mayo/mode3/signapi.go @@ -65,7 +65,7 @@ func (*scheme) DeriveKey(seed []byte) (sign.PublicKey, sign.PrivateKey) { } var tmp [SeedSize]byte copy(tmp[:], seed) - return NewKeyFromSeed(tmp) + return NewKeyFromSeed(&tmp) } func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (sign.PublicKey, error) { diff --git a/sign/mayo/mode5.go b/sign/mayo/mode5.go new file mode 100644 index 000000000..cd9637140 --- /dev/null +++ b/sign/mayo/mode5.go @@ -0,0 +1,87 @@ +// Code generated from mode.templ.go. DO NOT EDIT. + +package mayo + +import ( + "fmt" + "io" + + "github.com/cloudflare/circl/sign/mayo/mode5" +) + +// implMode5 implements the mode.Mode interface for MAYO_5. +type implMode5 struct{} + +// Mode5 is MAYO in mode "MAYO_5". +var Mode5 Mode = &implMode5{} + +func (m *implMode5) GenerateKey(rand io.Reader) ( + PublicKey, PrivateKey, error) { + return mode5.GenerateKey(rand) +} + +func (m *implMode5) NewKeyFromSeed(seed []byte) (PublicKey, + PrivateKey) { + if len(seed) != mode5.SeedSize { + panic(fmt.Sprintf("seed must be of length %d", mode5.SeedSize)) + } + seedBuf := [mode5.SeedSize]byte{} + copy(seedBuf[:], seed) + return mode5.NewKeyFromSeed(&seedBuf) +} + +func (m *implMode5) Sign(sk PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { + isk := sk.(*mode5.PrivateKey) + return mode5.Sign(isk, msg, rand) +} + +func (m *implMode5) Verify(pk PublicKey, msg []byte, signature []byte) bool { + ipk := pk.(*mode5.PublicKey) + return mode5.Verify(ipk, msg, signature) +} + +func (m *implMode5) PublicKeyFromBytes(data []byte) PublicKey { + var ret mode5.PublicKey + if len(data) != mode5.PublicKeySize { + panic("packed public key must be of mode5.PublicKeySize bytes") + } + var buf [mode5.PublicKeySize]byte + copy(buf[:], data) + ret.Unpack(&buf) + return &ret +} + +func (m *implMode5) PrivateKeyFromBytes(data []byte) PrivateKey { + var ret mode5.PrivateKey + if len(data) != mode5.PrivateKeySize { + panic("packed public key must be of mode5.PrivateKeySize bytes") + } + var buf [mode5.PrivateKeySize]byte + copy(buf[:], data) + ret.Unpack(&buf) + return &ret +} + +func (m *implMode5) SeedSize() int { + return mode5.SeedSize +} + +func (m *implMode5) PublicKeySize() int { + return mode5.PublicKeySize +} + +func (m *implMode5) PrivateKeySize() int { + return mode5.PrivateKeySize +} + +func (m *implMode5) SignatureSize() int { + return mode5.SignatureSize +} + +func (m *implMode5) Name() string { + return "MAYO_5" +} + +func init() { + modes["MAYO_5"] = Mode5 +} diff --git a/sign/mayo/mode5/internal/mayo.go b/sign/mayo/mode5/internal/mayo.go index 90359bc99..07226f739 100644 --- a/sign/mayo/mode5/internal/mayo.go +++ b/sign/mayo/mode5/internal/mayo.go @@ -151,13 +151,13 @@ func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { if err != nil { return nil, nil, err } - pk, sk := NewKeyFromSeed(seed) + pk, sk := NewKeyFromSeed(&seed) return pk, sk, nil } -func NewKeyFromSeed(seed [KeySeedSize]byte) (*PublicKey, *PrivateKey) { +func NewKeyFromSeed(seed *[KeySeedSize]byte) (*PublicKey, *PrivateKey) { var sk PrivateKey - sk.Unpack(&seed) + sk.Unpack(seed) return sk.Public(), &sk } diff --git a/sign/mayo/mode5/internal/mayo_test.go b/sign/mayo/mode5/internal/mayo_test.go index 847949cdc..2ebd29fb8 100644 --- a/sign/mayo/mode5/internal/mayo_test.go +++ b/sign/mayo/mode5/internal/mayo_test.go @@ -30,7 +30,7 @@ func TestNewKey(t *testing.T) { var seed [KeySeedSize]byte _, _ = hex.Decode(seed[:], []byte(tc.seed)) - pk, sk := NewKeyFromSeed(seed) + pk, sk := NewKeyFromSeed(&seed) var pk2 [PublicKeySize]byte var sk2 [PrivateKeySize]byte @@ -70,7 +70,7 @@ func TestVerify(t *testing.T) { m, _ := hex.DecodeString(tc.message) s, _ := hex.DecodeString(tc.signature) - pk, _ := NewKeyFromSeed(seed) + pk, _ := NewKeyFromSeed(&seed) if !Verify(pk, m, s) { t.Fatal("should verify") @@ -146,7 +146,7 @@ func TestPQCgenKATSign(t *testing.T) { g2 := nist.NewDRBG(&seed) _, _ = g2.Read(eseed[:]) - pk, sk := NewKeyFromSeed(eseed) + pk, sk := NewKeyFromSeed(&eseed) var pk2 [PublicKeySize]byte var sk2 [PrivateKeySize]byte @@ -180,7 +180,7 @@ func BenchmarkKeyGen(b *testing.B) { var seed [KeySeedSize]byte for i := 0; i < b.N; i++ { binary.LittleEndian.PutUint64(seed[:], uint64(i)) - _, _ = NewKeyFromSeed(seed) + _, _ = NewKeyFromSeed(&seed) } } @@ -200,7 +200,7 @@ func BenchmarkMatMul(b *testing.B) { func BenchmarkVerify(b *testing.B) { var seed [KeySeedSize]byte var msg [8]byte - pk, sk := NewKeyFromSeed(seed) + pk, sk := NewKeyFromSeed(&seed) sig, _ := Sign(msg[:], sk, nil) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -220,7 +220,7 @@ func (zeroReader) Read(buf []byte) (int, error) { func BenchmarkSign(b *testing.B) { var seed [KeySeedSize]byte var msg [8]byte - _, sk := NewKeyFromSeed(seed) + _, sk := NewKeyFromSeed(&seed) b.ResetTimer() for i := 0; i < b.N; i++ { binary.LittleEndian.PutUint64(msg[:], uint64(i)) diff --git a/sign/mayo/mode5/mayo.go b/sign/mayo/mode5/mayo.go index c6caa73b8..61b9dcb3b 100644 --- a/sign/mayo/mode5/mayo.go +++ b/sign/mayo/mode5/mayo.go @@ -50,7 +50,7 @@ func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { } // NewKeyFromSeed derives a public/private key pair using the given seed. -func NewKeyFromSeed(seed [SeedSize]byte) (*PublicKey, *PrivateKey) { +func NewKeyFromSeed(seed *[SeedSize]byte) (*PublicKey, *PrivateKey) { pk, sk := internal.NewKeyFromSeed(seed) return (*PublicKey)(pk), (*PrivateKey)(sk) } diff --git a/sign/mayo/mode5/signapi.go b/sign/mayo/mode5/signapi.go index 86479496c..5bae4dfe7 100644 --- a/sign/mayo/mode5/signapi.go +++ b/sign/mayo/mode5/signapi.go @@ -65,7 +65,7 @@ func (*scheme) DeriveKey(seed []byte) (sign.PublicKey, sign.PrivateKey) { } var tmp [SeedSize]byte copy(tmp[:], seed) - return NewKeyFromSeed(tmp) + return NewKeyFromSeed(&tmp) } func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (sign.PublicKey, error) { diff --git a/sign/mayo/templates/mode.templ.go b/sign/mayo/templates/mode.templ.go new file mode 100644 index 000000000..3fc4319e7 --- /dev/null +++ b/sign/mayo/templates/mode.templ.go @@ -0,0 +1,91 @@ +// +build ignore +// The previous line (and this one up to the warning below) is removed by the +// template generator. + +// Code generated from mode.templ.go. DO NOT EDIT. + +package mayo + +import ( + "fmt" + "io" + + "github.com/cloudflare/circl/sign/mayo/{{.Pkg}}" +) + +// {{.Impl}} implements the mode.Mode interface for {{.Name}}. +type {{.Impl}} struct{} + +// {{.Mode}} is MAYO in mode "{{.Name}}". +var {{.Mode}} Mode = &{{.Impl}}{} + +func (m *{{.Impl}}) GenerateKey(rand io.Reader) ( + PublicKey, PrivateKey, error) { + return {{.Pkg}}.GenerateKey(rand) +} + +func (m *{{.Impl}}) NewKeyFromSeed(seed []byte) (PublicKey, + PrivateKey) { + if len(seed) != {{.Pkg}}.SeedSize { + panic(fmt.Sprintf("seed must be of length %d", {{.Pkg}}.SeedSize)) + } + seedBuf := [{{.Pkg}}.SeedSize]byte{} + copy(seedBuf[:], seed) + return {{.Pkg}}.NewKeyFromSeed(&seedBuf) +} + +func (m *{{.Impl}}) Sign(sk PrivateKey, msg []byte, rand io.Reader) ([]byte, error) { + isk := sk.(*{{.Pkg}}.PrivateKey) + return {{.Pkg}}.Sign(isk, msg, rand) +} + +func (m *{{.Impl}}) Verify(pk PublicKey, msg []byte, signature []byte) bool { + ipk := pk.(*{{.Pkg}}.PublicKey) + return {{.Pkg}}.Verify(ipk, msg, signature) +} + +func (m *{{.Impl}}) PublicKeyFromBytes(data []byte) PublicKey { + var ret {{.Pkg}}.PublicKey + if len(data) != {{.Pkg}}.PublicKeySize { + panic("packed public key must be of {{.Pkg}}.PublicKeySize bytes") + } + var buf [{{.Pkg}}.PublicKeySize]byte + copy(buf[:], data) + ret.Unpack(&buf) + return &ret +} + +func (m *{{.Impl}}) PrivateKeyFromBytes(data []byte) PrivateKey { + var ret {{.Pkg}}.PrivateKey + if len(data) != {{.Pkg}}.PrivateKeySize { + panic("packed public key must be of {{.Pkg}}.PrivateKeySize bytes") + } + var buf [{{.Pkg}}.PrivateKeySize]byte + copy(buf[:], data) + ret.Unpack(&buf) + return &ret +} + +func (m *{{.Impl}}) SeedSize() int { + return {{.Pkg}}.SeedSize +} + +func (m *{{.Impl}}) PublicKeySize() int { + return {{.Pkg}}.PublicKeySize +} + +func (m *{{.Impl}}) PrivateKeySize() int { + return {{.Pkg}}.PrivateKeySize +} + +func (m *{{.Impl}}) SignatureSize() int { + return {{.Pkg}}.SignatureSize +} + +func (m *{{.Impl}}) Name() string { + return "{{.Name}}" +} + +func init() { + modes["{{.Name}}"] = {{.Mode}} +} diff --git a/sign/mayo/templates/modePkg.templ.go b/sign/mayo/templates/modePkg.templ.go index 1fb5e7430..28a492059 100644 --- a/sign/mayo/templates/modePkg.templ.go +++ b/sign/mayo/templates/modePkg.templ.go @@ -54,7 +54,7 @@ func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) { } // NewKeyFromSeed derives a public/private key pair using the given seed. -func NewKeyFromSeed(seed [SeedSize]byte) (*PublicKey, *PrivateKey) { +func NewKeyFromSeed(seed *[SeedSize]byte) (*PublicKey, *PrivateKey) { pk, sk := internal.NewKeyFromSeed(seed) return (*PublicKey)(pk), (*PrivateKey)(sk) } diff --git a/sign/mayo/templates/signapi.templ.go b/sign/mayo/templates/signapi.templ.go index 0bd50bdd1..b996b05e8 100644 --- a/sign/mayo/templates/signapi.templ.go +++ b/sign/mayo/templates/signapi.templ.go @@ -69,7 +69,7 @@ func (*scheme) DeriveKey(seed []byte) (sign.PublicKey, sign.PrivateKey) { } var tmp [SeedSize]byte copy(tmp[:], seed) - return NewKeyFromSeed(tmp) + return NewKeyFromSeed(&tmp) } func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (sign.PublicKey, error) {