Skip to content

Commit

Permalink
mayo: make key expansion part of keygen
Browse files Browse the repository at this point in the history
  • Loading branch information
ilway25 committed Mar 12, 2024
1 parent 29d480e commit 5e88046
Show file tree
Hide file tree
Showing 13 changed files with 544 additions and 482 deletions.
127 changes: 63 additions & 64 deletions sign/mayo/mode1/internal/mayo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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[:])

Expand All @@ -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.
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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]
Expand All @@ -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
Expand Down
57 changes: 18 additions & 39 deletions sign/mayo/mode1/internal/mayo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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")
}
})
Expand All @@ -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()
}
}
Expand Down Expand Up @@ -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()
}
}
Expand Down Expand Up @@ -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[:])
}
}

Expand All @@ -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{})
}
}

0 comments on commit 5e88046

Please sign in to comment.