x.crypto.ascon: improve the core of Ascon permutation routine (#25278)

This commit is contained in:
blackshirt 2025-09-11 09:56:17 +07:00 committed by GitHub
parent 919c68e6f9
commit f16452d3a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 83 additions and 103 deletions

View file

@ -1,15 +1,17 @@
# ascon # ascon
`ascon` is a implementation of Ascon-Based Cryptography module implemented in pure V language. `ascon` is an implementation of Ascon-Based Cryptography module implemented in pure V language.
This module was mostly based on NIST Special Publication of 800 NIST SP 800-232 document. This module was mostly based on NIST Special Publication of 800 NIST SP 800-232 document.
Its describes an Ascon-Based Lightweight Cryptography Standards for Constrained Devices Its describes an Ascon-Based Lightweight Cryptography Standards for Constrained Devices
thats provides Authenticated Encryption, Hash, and Extendable Output Functions. thats provides Authenticated Encryption, Hash, and Extendable Output Functions.
See the [NIST.SP.800-232 Standard](https://doi.org/10.6028/NIST.SP.800-232) for more detail. See the [NIST.SP.800-232 Standard](https://doi.org/10.6028/NIST.SP.800-232) for more detail.
This module does not fully implements all the features availables on the document. This module mostly implements all the features availables on the document.
Its currently implements: Its currently implements:
- `Ascon-Hash256`, Ascon-based hashing implementation that produces 256-bits output. - `Ascon-Hash256`, Ascon-based hashing implementation that produces 256-bits output.
- `Ascon-XOF128`, Ascon-based eXtendible Output Function (XOF) where the output size of - `Ascon-XOF128`, Ascon-based eXtendable Output Function (XOF) where the output size of
the hash of the message can be selected by the user. the hash of the message can be selected by the user.
- `Ascon-CXOF128`, a customized XOF that allows users to specify a customization - `Ascon-CXOF128`, a customized XOF that allows users to specify a customization
string and choose the output size of the message hash. string and choose the output size of the message hash.
- `Ascon-AEAD128`, an Authenticated Encryption with Additional Data (AEAD) Scheme based
on Ascon-family crypto.

View file

@ -181,7 +181,7 @@ pub fn (mut c Aead128) encrypt(msg []u8, nonce []u8, ad []u8) ![]u8 {
c.State.e4 = n1 c.State.e4 = n1
// Update state by permutation // Update state by permutation
ascon_pnr(mut c.State, 12) ascon_pnr(mut c.State, ascon_prnd_12)
// XOR-ing with the cipher's key // XOR-ing with the cipher's key
c.State.e3 ^= c.key[0] c.State.e3 ^= c.key[0]
c.State.e4 ^= c.key[1] c.State.e4 ^= c.key[1]
@ -229,7 +229,7 @@ pub fn (mut c Aead128) decrypt(ciphertext []u8, nonce []u8, ad []u8) ![]u8 {
c.State.e4 = n1 c.State.e4 = n1
// scrambled with permutation routine // scrambled with permutation routine
ascon_pnr(mut c.State, 12) ascon_pnr(mut c.State, ascon_prnd_12)
// xor-ing with the cipher's key // xor-ing with the cipher's key
c.State.e3 ^= c.key[0] c.State.e3 ^= c.key[0]
c.State.e4 ^= c.key[1] c.State.e4 ^= c.key[1]
@ -288,7 +288,7 @@ fn aead128_init(mut s State, key []u8, nonce []u8) (u64, u64) {
s.e4 = n1 s.e4 = n1
// updates State using the permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12], S ← 𝐴𝑠𝑐𝑜𝑛-𝑝[12](S) // updates State using the permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12], S ← 𝐴𝑠𝑐𝑜𝑛-𝑝[12](S)
ascon_pnr(mut s, 12) ascon_pnr(mut s, ascon_prnd_12)
// Then XORing the secret key 𝐾 into the last 128 bits of internal state: // Then XORing the secret key 𝐾 into the last 128 bits of internal state:
// S ← S ⊕ (0¹⁹² ∥ 𝐾). // S ← S ⊕ (0¹⁹² ∥ 𝐾).
@ -312,7 +312,7 @@ fn aead128_process_ad(mut s State, ad []u8) {
s.e1 ^= binary.little_endian_u64(block[8..16]) s.e1 ^= binary.little_endian_u64(block[8..16])
// Apply permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[8] to the state // Apply permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[8] to the state
ascon_pnr(mut s, 8) ascon_pnr(mut s, ascon_prnd_8)
// Updates index // Updates index
ad_length -= aead128_block_size ad_length -= aead128_block_size
ad_idx += aead128_block_size ad_idx += aead128_block_size
@ -339,7 +339,7 @@ fn aead128_process_ad(mut s State, ad []u8) {
} }
} }
// Apply permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[8] to the state // Apply permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[8] to the state
ascon_pnr(mut s, 8) ascon_pnr(mut s, ascon_prnd_8)
} }
// The final step of processing associated data is to update the state // The final step of processing associated data is to update the state
// with a constant that provides domain separation. // with a constant that provides domain separation.
@ -361,7 +361,7 @@ fn aead128_process_msg(mut out []u8, mut s State, msg []u8) int {
binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0) binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0)
binary.little_endian_put_u64(mut out[pos + 8..], s.e1) binary.little_endian_put_u64(mut out[pos + 8..], s.e1)
// apply permutation // apply permutation
ascon_pnr(mut s, 8) ascon_pnr(mut s, ascon_prnd_8)
// updates index // updates index
mlen -= aead128_block_size mlen -= aead128_block_size
@ -413,7 +413,7 @@ fn aead128_partial_dec(mut out []u8, mut s State, cmsg []u8) {
s.e0 = c0 s.e0 = c0
s.e1 = c1 s.e1 = c1
ascon_pnr(mut s, 8) ascon_pnr(mut s, ascon_prnd_8)
// updates index // updates index
pos += aead128_block_size pos += aead128_block_size
cmsg_len -= aead128_block_size cmsg_len -= aead128_block_size
@ -448,7 +448,7 @@ fn aead128_finalize(mut s State, k0 u64, k1 u64) {
s.e2 ^= k0 s.e2 ^= k0
s.e3 ^= k1 s.e3 ^= k1
// then updated using the permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12] // then updated using the permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12]
ascon_pnr(mut s, 12) ascon_pnr(mut s, ascon_prnd_12)
// Finally, the tag 𝑇 is generated by XORing the key with the last 128 bits of the state: // Finally, the tag 𝑇 is generated by XORing the key with the last 128 bits of the state:
// 𝑇𝑆[192319] ⊕ 𝐾. // 𝑇𝑆[192319] ⊕ 𝐾.

View file

@ -10,6 +10,10 @@ module ascon
// constants for up to 16 rounds to accommodate potential functionality extensions in the future. // constants for up to 16 rounds to accommodate potential functionality extensions in the future.
const max_nr_perm = 16 const max_nr_perm = 16
// The number how many round(s) for the Ascon permutation routine called.
const ascon_prnd_8 = 8
const ascon_prnd_12 = 12
// The constants to derive round constants of the Ascon permutations // The constants to derive round constants of the Ascon permutations
// See Table 5. of NIST SP 800-232 docs // See Table 5. of NIST SP 800-232 docs
// //
@ -26,72 +30,74 @@ const max_nr_perm = 16
const rnc = [u8(0x3c), 0x2d, 0x1e, 0x0f, 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, const rnc = [u8(0x3c), 0x2d, 0x1e, 0x0f, 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78,
0x69, 0x5a, 0x4b] 0x69, 0x5a, 0x4b]
// ascon_pnr is ascon permutation routine with specified numbers of round nr, where 1 ≤ nr ≤ 16 // ascon_pnr is the core of Ascon family permutation routine with specified numbers of round nr, where 1 ≤ nr ≤ 16
// Its consist of iterations of the round function that is defined as the composition of three steps, ie:
// 1. the constant-addition layer (see Sec. 3.2),
// 2. the substitution layer (see Sec.3.3), and,
// 3. the linear diffusion layer (Sec 3.4)
@[direct_array_access] @[direct_array_access]
fn ascon_pnr(mut s State, nr int) { fn ascon_pnr(mut s State, nr int) {
// We dont allow nr == 0 // We dont allow nr == 0
if nr < 1 || nr > 16 { if nr < 1 || nr > 16 {
panic('Invalid round number') panic('Invalid round number')
} }
// Ascon permutation routine
for i := max_nr_perm - nr; i < max_nr_perm; i++ { for i := max_nr_perm - nr; i < max_nr_perm; i++ {
ascon_perm(mut s, rnc[i]) // 3.2 Constant-Addition Layer step
//
// The constant-addition layer adds a 64-bit round constant 𝑐𝑖
// to 𝑆₂ in round 𝑖, for 𝑖 ≥ 0, ie, this is equivalent to applying
// the constant to only the least significant eight bits of 𝑆₂
s.e2 ^= rnc[i]
// 3.3. Substitution Layer
// The substitution layer updates the state S with 64 parallel applications of the 5-bit
// substitution box SBOX
s.e0 ^= s.e4
s.e4 ^= s.e3
s.e2 ^= s.e1
t0 := s.e4 ^ (~s.e0 & s.e1)
t1 := s.e0 ^ (~s.e1 & s.e2)
t2 := s.e1 ^ (~s.e2 & s.e3)
t3 := s.e2 ^ (~s.e3 & s.e4)
t4 := s.e3 ^ (~s.e4 & s.e0)
s.e0 = t1
s.e1 = t2
s.e2 = t3
s.e3 = t4
s.e4 = t0
s.e1 ^= s.e0
s.e0 ^= s.e4
s.e3 ^= s.e2
s.e2 = ~(s.e2)
// 3.4. Linear Diffusion Layer
//
// The linear diffusion layer provides diffusion within each 64-bit word S,
// defined as :
// Σ0(𝑆0) = 𝑆0 ⊕ (𝑆0 ⋙ 19) ⊕ (𝑆0 ⋙ 28)
// Σ1(𝑆1) = 𝑆1 ⊕ (𝑆1 ⋙ 61) ⊕ (𝑆1 ⋙ 39)
// Σ2(𝑆2) = 𝑆2 ⊕ (𝑆2 ⋙ 1) ⊕ (𝑆2 ⋙ 6)
// Σ3(𝑆3) = 𝑆3 ⊕ (𝑆3 ⋙ 10) ⊕ (𝑆3 ⋙ 17)
// Σ4(𝑆4) = 𝑆4 ⊕ (𝑆4 ⋙ 7) ⊕ (𝑆4 ⋙ 41)
//
// This diffusion layer, especially on the bits right rotation part is a most widely called
// for Ascon permutation routine. So, even bits rotation almost efficient on most platform,
// to reduce overhead on function call, we work on the raw bits right rotation here.
// Bits right rotation, basically can be defined as:
// ror = (x >> n) | x << (64 - n) for some u64 x
//
s.e0 ^= (s.e0 >> 19 | (s.e0 << (64 - 19))) ^ (s.e0 >> 28 | (s.e0 << (64 - 28)))
s.e1 ^= (s.e1 >> 61 | (s.e1 << (64 - 61))) ^ (s.e1 >> 39 | (s.e1 << (64 - 39)))
s.e2 ^= (s.e2 >> 1 | (s.e2 << (64 - 1))) ^ (s.e2 >> 6 | (s.e2 << (64 - 6))) //
s.e3 ^= (s.e3 >> 10 | (s.e3 << (64 - 10))) ^ (s.e3 >> 17 | (s.e3 << (64 - 17)))
s.e4 ^= (s.e4 >> 7 | (s.e4 << (64 - 7))) ^ (s.e4 >> 41 | (s.e4 << (64 - 41)))
} }
} }
// ascon_perm was the main permutations routine in Ascon-family crypto. Its consist of
// iterations of the round function that is defined as the composition of three steps, ie:
// 1. the constant-addition layer (see Sec. 3.2),
// 2. the substitution layer (see Sec.3.3), and,
// 3. the linear diffusion layer
fn ascon_perm(mut s State, c u8) {
// 3.2 Constant-Addition Layer step
//
// The constant-addition layer adds a 64-bit round constant 𝑐𝑖
// to 𝑆₂ in round 𝑖, for 𝑖 ≥ 0, ie, this is equivalent to applying
// the constant to only the least significant eight bits of 𝑆₂
s.e2 ^= c
// 3.3. Substitution Layer
// The substitution layer updates the state S with 64 parallel applications of the 5-bit
// substitution box SBOX
s.e0 ^= s.e4
s.e4 ^= s.e3
s.e2 ^= s.e1
t0 := s.e4 ^ (~s.e0 & s.e1)
t1 := s.e0 ^ (~s.e1 & s.e2)
t2 := s.e1 ^ (~s.e2 & s.e3)
t3 := s.e2 ^ (~s.e3 & s.e4)
t4 := s.e3 ^ (~s.e4 & s.e0)
s.e0 = t1
s.e1 = t2
s.e2 = t3
s.e3 = t4
s.e4 = t0
s.e1 ^= s.e0
s.e0 ^= s.e4
s.e3 ^= s.e2
s.e2 = ~(s.e2)
// 3.4. Linear Diffusion Layer
//
// The linear diffusion layer provides diffusion within each 64-bit word S,
// defined as :
// Σ0(𝑆0) = 𝑆0 ⊕ (𝑆0 ⋙ 19) ⊕ (𝑆0 ⋙ 28)
// Σ1(𝑆1) = 𝑆1 ⊕ (𝑆1 ⋙ 61) ⊕ (𝑆1 ⋙ 39)
// Σ2(𝑆2) = 𝑆2 ⊕ (𝑆2 ⋙ 1) ⊕ (𝑆2 ⋙ 6)
// Σ3(𝑆3) = 𝑆3 ⊕ (𝑆3 ⋙ 10) ⊕ (𝑆3 ⋙ 17)
// Σ4(𝑆4) = 𝑆4 ⊕ (𝑆4 ⋙ 7) ⊕ (𝑆4 ⋙ 41)
s.e0 ^= ascon_rotate_right(s.e0, 19) ^ ascon_rotate_right(s.e0, 28)
s.e1 ^= ascon_rotate_right(s.e1, 61) ^ ascon_rotate_right(s.e1, 39)
s.e2 ^= ascon_rotate_right(s.e2, 1) ^ ascon_rotate_right(s.e2, 6)
s.e3 ^= ascon_rotate_right(s.e3, 10) ^ ascon_rotate_right(s.e3, 17)
s.e4 ^= ascon_rotate_right(s.e4, 7) ^ ascon_rotate_right(s.e4, 41)
}
// State is structure represents Ascon state. Its operates on the 320-bit opaque, // State is structure represents Ascon state. Its operates on the 320-bit opaque,
// which is represented as five of 64-bit words. // which is represented as five of 64-bit words.
@[noinit] @[noinit]

View file

@ -5,23 +5,6 @@
module ascon module ascon
// This test mostly taken from https://docs.rs/ascon/latest/src/ascon/lib.rs.html // This test mostly taken from https://docs.rs/ascon/latest/src/ascon/lib.rs.html
fn test_ascon_round_one() {
mut s := State{
e0: u64(0x0123456789abcdef)
e1: 0x23456789abcdef01
e2: 0x456789abcdef0123
e3: 0x6789abcdef012345
e4: 0x89abcde01234567f
}
ascon_perm(mut s, 0x1f)
assert s.e0 == u64(0x3c1748c9be2892ce)
assert s.e1 == u64(0x5eafb305cd26164f)
assert s.e2 == u64(0xf9470254bb3a4213)
assert s.e3 == u64(0xf0428daf0c5d3948)
assert s.e4 == u64(0x281375af0b294899)
}
fn test_ascon_round_p6() { fn test_ascon_round_p6() {
mut s := State{ mut s := State{
e0: u64(0x0123456789abcdef) e0: u64(0x0123456789abcdef)

View file

@ -33,7 +33,7 @@ fn (mut d Digest) finish() {
d.State.e0 ^= load_bytes(d.buf[..d.length], d.length) d.State.e0 ^= load_bytes(d.buf[..d.length], d.length)
// Permutation step was done in squeezing-phase // Permutation step was done in squeezing-phase
// ascon_pnr(mut d.State, 12) // ascon_pnr(mut d.State, ascon_prnd_12)
// zeroing Digest buffer // zeroing Digest buffer
d.length = 0 d.length = 0
@ -70,7 +70,7 @@ fn (mut d Digest) absorb(msg_ []u8) int {
// If this d.buf length has reached block_size bytes, absorb it. // If this d.buf length has reached block_size bytes, absorb it.
if d.length == block_size { if d.length == block_size {
d.State.e0 ^= binary.little_endian_u64(d.buf) d.State.e0 ^= binary.little_endian_u64(d.buf)
ascon_pnr(mut d.State, 12) ascon_pnr(mut d.State, ascon_prnd_12)
// reset the internal buffer // reset the internal buffer
d.length = 0 d.length = 0
d.buf.reset() d.buf.reset()
@ -87,7 +87,7 @@ fn (mut d Digest) absorb(msg_ []u8) int {
for msg.len >= block_size { for msg.len >= block_size {
d.State.e0 ^= binary.little_endian_u64(msg[0..block_size]) d.State.e0 ^= binary.little_endian_u64(msg[0..block_size])
msg = msg[block_size..] msg = msg[block_size..]
ascon_pnr(mut d.State, 12) ascon_pnr(mut d.State, ascon_prnd_12)
} }
// If there are partial block, just stored into buffer. // If there are partial block, just stored into buffer.
if msg.len > 0 { if msg.len > 0 {
@ -113,14 +113,14 @@ fn (mut d Digest) squeeze(mut dst []u8) int {
} }
// The squeezing phase begins after msg is absorbed with an // The squeezing phase begins after msg is absorbed with an
// permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12] to the state: // permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12] to the state:
ascon_pnr(mut d.State, 12) ascon_pnr(mut d.State, ascon_prnd_12)
mut pos := 0 mut pos := 0
mut clen := dst.len mut clen := dst.len
// process for full block size // process for full block size
for clen >= block_size { for clen >= block_size {
binary.little_endian_put_u64(mut dst[pos..pos + 8], d.State.e0) binary.little_endian_put_u64(mut dst[pos..pos + 8], d.State.e0)
ascon_pnr(mut d.State, 12) ascon_pnr(mut d.State, ascon_prnd_12)
pos += block_size pos += block_size
clen -= block_size clen -= block_size
} }

View file

@ -8,12 +8,6 @@ module ascon
import math.bits import math.bits
import encoding.binary import encoding.binary
// rotate_right_64 rotates x right by k bits
fn rotate_right_64(x u64, k int) u64 {
// call rotate_left_64(x, -k).
return bits.rotate_left_64(x, -k)
}
// clear_bytes clears the bytes of x in n byte // clear_bytes clears the bytes of x in n byte
@[inline] @[inline]
fn clear_bytes(x u64, n int) u64 { fn clear_bytes(x u64, n int) u64 {
@ -100,8 +94,3 @@ fn store_bytes(mut out []u8, x u64, n int) {
out[i] = get_byte(x, i) out[i] = get_byte(x, i)
} }
} }
@[inline]
fn ascon_rotate_right(x u64, n int) u64 {
return (x >> n) | x << (64 - n)
}

View file

@ -305,7 +305,7 @@ pub fn (mut x CXof128) free() {
fn cxof128_absorb_custom_string(mut s State, cs []u8) { fn cxof128_absorb_custom_string(mut s State, cs []u8) {
// absorb Z0, the length of the customization string (in bits) encoded as a u64 // absorb Z0, the length of the customization string (in bits) encoded as a u64
s.e0 ^= u64(cs.len) << 3 s.e0 ^= u64(cs.len) << 3
ascon_pnr(mut s, 12) ascon_pnr(mut s, ascon_prnd_12)
// absorb the customization string // absorb the customization string
mut zlen := cs.len mut zlen := cs.len
@ -313,7 +313,7 @@ fn cxof128_absorb_custom_string(mut s State, cs []u8) {
for zlen >= block_size { for zlen >= block_size {
block := unsafe { cs[zidx..zidx + block_size] } block := unsafe { cs[zidx..zidx + block_size] }
s.e0 ^= binary.little_endian_u64(block) s.e0 ^= binary.little_endian_u64(block)
ascon_pnr(mut s, 12) ascon_pnr(mut s, ascon_prnd_12)
// updates a index // updates a index
zlen -= block_size zlen -= block_size
@ -323,5 +323,5 @@ fn cxof128_absorb_custom_string(mut s State, cs []u8) {
last_block := unsafe { cs[zidx..] } last_block := unsafe { cs[zidx..] }
s.e0 ^= load_bytes(last_block, last_block.len) s.e0 ^= load_bytes(last_block, last_block.len)
s.e0 ^= pad(last_block.len) s.e0 ^= pad(last_block.len)
ascon_pnr(mut s, 12) ascon_pnr(mut s, ascon_prnd_12)
} }