Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Ascon-80pq to cipher\ascon #404

Merged
merged 1 commit into from Feb 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
106 changes: 80 additions & 26 deletions cipher/ascon/ascon.go
Expand Up @@ -13,9 +13,10 @@ import (
)

const (
KeySize = 16
NonceSize = 16
TagSize = KeySize
KeySize = 16
KeySize80pq = 20
NonceSize = 16
TagSize = KeySize
)

type Mode int
Expand All @@ -26,6 +27,8 @@ func (m Mode) String() string {
return "Ascon128"
case Ascon128a:
return "Ascon128a"
case Ascon80pq:
return "Ascon80pq"
default:
panic(ErrMode)
}
Expand All @@ -34,33 +37,48 @@ func (m Mode) String() string {
const (
Ascon128 Mode = iota + 1
Ascon128a
// Ascon-80pq has an increased key-size to provide more resistance against a quantum
// adversary using Grover’s algorithm for key search. Since Ascon-128 and Ascon-
// 80pq share the same building blocks and same parameters except the size of the key,
// we claim the same security for Ascon-80pq against classical attacks as for Ascon-128.
Ascon80pq
)

const (
permA = 12
permB = 6 // 6 for Ascon128, or 8 for Ascon128a
blockSize = 8 // 8 for Ascon128, or 16 for Ascon128a
permB = 6 // 6 for Ascon128 and Ascon80pq, or 8 for Ascon128a
blockSize = 8 // 8 for Ascon128 and Ascon80pq, or 16 for Ascon128a
ivSize = 8
stateSize = ivSize + KeySize + NonceSize
)

type Cipher struct {
s [5]uint64
key [2]uint64
key [3]uint64
mode Mode
}

// New returns a Cipher struct implementing the cipher.AEAD interface. Mode is
// one of Ascon128 or Ascon128a.
// one of Ascon128, Ascon128a or Ascon80pq.
func New(key []byte, m Mode) (*Cipher, error) {
if len(key) != KeySize {
if (m == Ascon128 || m == Ascon128a) && len(key) != KeySize {
return nil, ErrKeySize
}
if !(m == Ascon128 || m == Ascon128a) {
if m == Ascon80pq && len(key) != KeySize80pq {
return nil, ErrKeySize
}
if !(m == Ascon128 || m == Ascon128a || m == Ascon80pq) {
return nil, ErrMode
}
c := new(Cipher)
c.mode = m
if m == Ascon80pq {
c.key[0] = (uint64)(binary.BigEndian.Uint32(key[0:4]))
c.key[1] = binary.BigEndian.Uint64(key[4:12])
c.key[2] = binary.BigEndian.Uint64(key[12:20])
return c, nil
}

c.key[0] = binary.BigEndian.Uint64(key[0:8])
c.key[1] = binary.BigEndian.Uint64(key[8:16])

Expand Down Expand Up @@ -136,21 +154,42 @@ func (a *Cipher) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, er
}

func (a *Cipher) initialize(nonce []byte) {
bcs := blockSize * uint64(a.mode)
pB := permB + 2*(uint64(a.mode)-1)
a.s[0] = ((KeySize * 8) << 56) | ((bcs * 8) << 48) | (permA << 40) | (pB << 32)
a.s[1] = a.key[0]
a.s[2] = a.key[1]
if a.mode == Ascon80pq {
bcs := blockSize * uint64(Ascon128)
pB := permB + 2*(uint64(Ascon128)-1)
a.s[0] = ((KeySize80pq * 8) << 56) | ((bcs * 8) << 48) | (permA << 40) | (pB << 32) | a.key[0]
a.s[1] = a.key[1]
a.s[2] = a.key[2]
} else {
bcs := blockSize * uint64(a.mode)
pB := permB + 2*(uint64(a.mode)-1)
a.s[0] = ((KeySize * 8) << 56) | ((bcs * 8) << 48) | (permA << 40) | (pB << 32)
a.s[1] = a.key[0]
a.s[2] = a.key[1]
}

a.s[3] = binary.BigEndian.Uint64(nonce[0:8])
a.s[4] = binary.BigEndian.Uint64(nonce[8:16])
a.perm(permA)
a.s[3] ^= a.key[0]
a.s[4] ^= a.key[1]

if a.mode == Ascon80pq {
a.s[2] ^= a.key[0]
a.s[3] ^= a.key[1]
a.s[4] ^= a.key[2]
} else {
a.s[3] ^= a.key[0]
a.s[4] ^= a.key[1]
}
}

func (a *Cipher) assocData(add []byte) {
bcs := blockSize * int(a.mode)
pB := permB + 2*(int(a.mode)-1)
m := a.mode
if m == Ascon80pq {
m = Ascon128
}

bcs := blockSize * int(m)
pB := permB + 2*(int(m)-1)
if len(add) > 0 {
for ; len(add) >= bcs; add = add[bcs:] {
for i := 0; i < bcs; i += 8 {
Expand All @@ -168,8 +207,13 @@ func (a *Cipher) assocData(add []byte) {
}

func (a *Cipher) procText(in, out []byte, enc bool) {
bcs := blockSize * int(a.mode)
pB := permB + 2*(int(a.mode)-1)
m := a.mode
if m == Ascon80pq {
m = Ascon128
}

bcs := blockSize * int(m)
pB := permB + 2*(int(m)-1)
mask := uint64(0)
if enc {
mask -= 1
Expand Down Expand Up @@ -198,12 +242,22 @@ func (a *Cipher) procText(in, out []byte, enc bool) {
}

func (a *Cipher) finalize(tag []byte) {
bcs := blockSize * int(a.mode)
a.s[bcs/8+0] ^= a.key[0]
a.s[bcs/8+1] ^= a.key[1]
a.perm(permA)
binary.BigEndian.PutUint64(tag[0:8], a.s[3]^a.key[0])
binary.BigEndian.PutUint64(tag[8:16], a.s[4]^a.key[1])
if a.mode == Ascon80pq {
bcs := blockSize * int(Ascon128)
a.s[bcs/8+0] ^= a.key[0]<<32 | a.key[1]>>32
a.s[bcs/8+1] ^= a.key[1]<<32 | a.key[2]>>32
a.s[bcs/8+2] ^= a.key[2] << 32
a.perm(permA)
binary.BigEndian.PutUint64(tag[0:8], a.s[3]^a.key[1])
binary.BigEndian.PutUint64(tag[8:16], a.s[4]^a.key[2])
} else {
bcs := blockSize * int(a.mode)
a.s[bcs/8+0] ^= a.key[0]
a.s[bcs/8+1] ^= a.key[1]
a.perm(permA)
binary.BigEndian.PutUint64(tag[0:8], a.s[3]^a.key[0])
binary.BigEndian.PutUint64(tag[8:16], a.s[4]^a.key[1])
}
}

var roundConst = [12]uint64{0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b}
Expand Down
35 changes: 29 additions & 6 deletions cipher/ascon/ascon_test.go
Expand Up @@ -59,7 +59,7 @@ func readFile(t *testing.T, fileName string) []vector {
func TestAscon(t *testing.T) {
// Test vectors generated with pyascon
// https://github.com/meichlseder/pyascon/
for _, mode := range []ascon.Mode{ascon.Ascon128, ascon.Ascon128a} {
for _, mode := range []ascon.Mode{ascon.Ascon128, ascon.Ascon128a, ascon.Ascon80pq} {
name := mode.String()
t.Run(name, func(t *testing.T) {
vectors := readFile(t, "testdata/"+name+".json")
Expand Down Expand Up @@ -105,6 +105,9 @@ func TestBadInputs(t *testing.T) {
_, err = ascon.New(key[:4], ascon.Ascon128)
test.CheckIsErr(t, err, "should fail due to short key")

_, err = ascon.New(key[:], ascon.Ascon80pq)
test.CheckIsErr(t, err, "should fail due to short key")

a, _ := ascon.New(key[:], ascon.Ascon128)
err = test.CheckPanic(func() { _ = a.Seal(nil, nil, nil, nil) })
test.CheckNoErr(t, err, "should panic due to bad nonce")
Expand All @@ -125,7 +128,7 @@ func TestBadInputs(t *testing.T) {
}

func BenchmarkAscon(b *testing.B) {
for _, mode := range []ascon.Mode{ascon.Ascon128, ascon.Ascon128a} {
for _, mode := range []ascon.Mode{ascon.Ascon128, ascon.Ascon128a, ascon.Ascon80pq} {
for _, length := range []int{64, 1350, 8 * 1024} {
b.Run(mode.String()+"/Open-"+strconv.Itoa(length), func(b *testing.B) { benchmarkOpen(b, make([]byte, length), mode) })
b.Run(mode.String()+"/Seal-"+strconv.Itoa(length), func(b *testing.B) { benchmarkSeal(b, make([]byte, length), mode) })
Expand All @@ -137,10 +140,20 @@ func benchmarkSeal(b *testing.B, buf []byte, mode ascon.Mode) {
b.ReportAllocs()
b.SetBytes(int64(len(buf)))

var key [ascon.KeySize]byte
var key []byte
switch mode {
case ascon.Ascon128, ascon.Ascon128a:
key = make([]byte, ascon.KeySize)
case ascon.Ascon80pq:
key = make([]byte, ascon.KeySize80pq)
}

var nonce [ascon.NonceSize]byte
var ad [13]byte
a, _ := ascon.New(key[:], mode)
a, err := ascon.New(key[:], mode)
if err != nil {
b.Fatal(err)
}
var out []byte

b.ResetTimer()
Expand All @@ -153,10 +166,20 @@ func benchmarkOpen(b *testing.B, buf []byte, mode ascon.Mode) {
b.ReportAllocs()
b.SetBytes(int64(len(buf)))

var key [ascon.KeySize]byte
var key []byte
switch mode {
case ascon.Ascon128, ascon.Ascon128a:
key = make([]byte, ascon.KeySize)
case ascon.Ascon80pq:
key = make([]byte, ascon.KeySize80pq)
}

var nonce [ascon.NonceSize]byte
var ad [13]byte
a, _ := ascon.New(key[:], mode)
a, err := ascon.New(key[:], mode)
if err != nil {
b.Fatal(err)
}
var out []byte

ct := a.Seal(nil, nonce[:], buf, ad[:])
Expand Down