diff --git a/vlib/x/crypto/ascon/aead128.v b/vlib/x/crypto/ascon/aead128.v new file mode 100644 index 0000000000..4215301d58 --- /dev/null +++ b/vlib/x/crypto/ascon/aead128.v @@ -0,0 +1,466 @@ +// Copyright Β©2025 blackshirt. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// This file implements an Authenticated Encryption with Associated Data based on Ascon-AEAD128, +// AEAD Scheme defined in NIST.SP.800-232 standard. +module ascon + +import encoding.binary +import crypto.internal.subtle + +// The constants for Ascon-AEAD128 +// +// key_size is 128-bit size of Ascon-AEAD128 key +pub const key_size = 16 +// nonce_size is 128-bit size of Ascon-AEAD128 nonce +pub const nonce_size = 16 +// tag_size is 128-bit size of Ascon-AEAD128 authentication tag +pub const tag_size = 16 + +// aead128_iv is a precomputed initialization phase values for Ascon-AEAD128 +// See Table 14 of NIST SP 800-232 doc. +const aead128_iv = u64(0x0000_1000_808c_0001) + +// aead128_data_limit is a limit amount of data processed during encryption and decryption, +// including the nonce, shall not exceed 2⁡⁴ bytes for a given key. +const aead128_data_limit = u64(1) << 54 - 1 + +// aead128_block_size is a number (rate) of input bytes processed per invocation of the underlying of state. +// Ascon-AEAD128 working with 128-bit rate +const aead128_block_size = 16 + +// encrypt encrypts the message under provided key and nonce and supplied additional data in ad. +// It returns an authenticated output of Ascon-AEAD128 ciphertext where authentication tag +// was stored within the end of ciphertext. +// Note: The Ascon-AEAD128 key shall be kept secret, +@[direct_array_access] +pub fn encrypt(key []u8, nonce []u8, ad []u8, msg []u8) ![]u8 { + // Preliminary check + if key.len != key_size { + return error('encrypt: invalid key size') + } + if nonce.len != nonce_size { + return error('encrypt: invalid nonce size') + } + // The key shall be updated to a new key once the total amount of input data reaches the limit + data_length := u64(nonce.len) + u64(msg.len) + u64(ad.len) + if data_length > aead128_data_limit { + return error('encrypt: exceed data limit') + } + mut s := State{} + mut out := []u8{len: msg.len + tag_size} + + // Ascon-AEAD128 comprises four phases: + // - initialization of the state, + // - associated data processing, + // - plaintext processing, + // - and finalization (includes writing the tag). + k0, k1 := aead128_init(mut s, key, nonce) + aead128_process_ad(mut s, ad) + loc := aead128_process_msg(mut out, mut s, msg) + aead128_finalize(mut s, k0, k1) + aead128_write_tag(mut out, s, loc) + + // clean out the intermediate Ascon state + reset_state(mut s) + return out +} + +// decrypt decrypts authenticated encrypted messages in ciphertext that encrypted under +// provided key and nonce with additional data in `ad`. +// It would check if authentication tag mas matching and return decrypted message +// if success or error on fails. +@[direct_array_access] +pub fn decrypt(key []u8, nonce []u8, ad []u8, ciphertext []u8) ![]u8 { + // Preliminary check + if key.len != key_size { + return error('decrypt: invalid key size') + } + if nonce.len != nonce_size { + return error('decrypt: invalid nonce size') + } + if ciphertext.len < tag_size { + return error('decrypt: invalid ciphertext size') + } + data_length := u64(nonce.len) + u64(ciphertext.len) + u64(ad.len) + if data_length > aead128_data_limit { + return error('decrypt: exceed data limit') + } + mut s := State{} + // Initialization phase and additional data processing + k0, k1 := aead128_init(mut s, key, nonce) + aead128_process_ad(mut s, ad) + + // Decryption phase, start by slicing the ciphertext + cmsg := ciphertext[0..ciphertext.len - tag_size] + stag := ciphertext[ciphertext.len - tag_size..ciphertext.len] + mut msg := []u8{len: ciphertext.len - tag_size} + + // Partially decrypt the cmsg and stored into msg buffer + aead128_partial_dec(mut msg, mut s, cmsg) + + // Finalizes the state and calc the tag and compares with expected tag. + // It would return error if the tag was unmatching. + aead128_finalize(mut s, k0, k1) + mut ctag := []u8{len: tag_size} + aead128_write_tag(mut ctag, s, 0) + if subtle.constant_time_compare(ctag, stag) != 1 { + // clean up + unsafe { + msg.reset() + ctag.reset() + } + reset_state(mut s) + return error('decrypt: unmatching tag') + } + return msg +} + +// Aead128 is an opaque provides an implementation of Ascon-AEAD128 from NIST.SP.800-232 standard. +// Its implements `x.crypto.chacha20poly1305.AEAD` interfaces. +@[noinit] +pub struct Aead128 { + State +mut: + // 32-bytes of underlying key + key [2]u64 +} + +// new_aead128 creates a new Aead128 instance and initialized with supplied key. +@[direct_array_access] +pub fn new_aead128(key []u8) !&Aead128 { + if key.len != key_size { + return error('invalid cipher key size') + } + k0 := binary.little_endian_u64(key[0..8]) + k1 := binary.little_endian_u64(key[8..16]) + + mut c := &Aead128{} + // Partially initializes state + c.State.e0 = aead128_iv + c.State.e1 = k0 + c.State.e2 = k1 + // stores the key + c.key[0] = k0 + c.key[1] = k1 + + return c +} + +// nonce_size returns the nonce size of Ascon-AEAD128 Aead128. +pub fn (c &Aead128) nonce_size() int { + return nonce_size +} + +// overhead returns the maximum difference between the lengths of a plaintext and its ciphertext. +pub fn (c &Aead128) overhead() int { + return tag_size +} + +// encrypt encrypts the message under provided key and nonce and supplied additional data in ad. +// It returns an authenticated output of Ascon-AEAD128 ciphertext where authentication tag +// was stored within the end of ciphertext. +@[direct_array_access] +pub fn (mut c Aead128) encrypt(msg []u8, nonce []u8, ad []u8) ![]u8 { + // Check for the nonce + if nonce.len != nonce_size { + return error('encrypt: invalid nonce size') + } + data_length := u64(msg.len) + u64(ad.len) + 32 + if data_length > aead128_data_limit { + return error('encrypt: exceed data limit') + } + // Initialization phase + n0 := binary.little_endian_u64(nonce[0..8]) + n1 := binary.little_endian_u64(nonce[8..16]) + // setup state + c.State.e0 = aead128_iv + c.State.e1 = c.key[0] + c.State.e2 = c.key[1] + c.State.e3 = n0 + c.State.e4 = n1 + + // Update state by permutation + ascon_pnr(mut c.State, 12) + // XOR-ing with the cipher's key + c.State.e3 ^= c.key[0] + c.State.e4 ^= c.key[1] + + // Associated data processing + aead128_process_ad(mut c.State, ad) + + // Message processing + mut dst := []u8{len: msg.len + tag_size} + n := aead128_process_msg(mut dst, mut c.State, msg) + + // Finalization and writes out the tag into dst + aead128_finalize(mut c.State, c.key[0], c.key[1]) + aead128_write_tag(mut dst, c.State, n) + + return dst +} + +// decrypt decrypts the ciphertext and validates authentication tag with +// provided nonce and additional data ad. It returns error on fails or tag unmatching. +@[direct_array_access] +fn (mut c Aead128) decrypt(ciphertext []u8, nonce []u8, ad []u8) ![]u8 { + // Check for nonce + if nonce.len != nonce_size { + return error('bad nonce size') + } + // Check for ciphertext length, its ahould have length >= tag_size + if ciphertext.len < tag_size { + return error('bad ciphertext size') + } + // check for data limit overflow + data_length := u64(ciphertext.len) + u64(ad.len) + 32 + if data_length > aead128_data_limit { + return error('decrypt: exceed data limit') + } + // load nonce + n0 := binary.little_endian_u64(nonce[0..8]) + n1 := binary.little_endian_u64(nonce[8..16]) + + // Reinitialize internal state + c.State.e0 = aead128_iv + c.State.e1 = c.key[0] + c.State.e2 = c.key[1] + c.State.e3 = n0 + c.State.e4 = n1 + + // scrambled with permutation routine + ascon_pnr(mut c.State, 12) + // xor-ing with the cipher's key + c.State.e3 ^= c.key[0] + c.State.e4 ^= c.key[1] + + // Associated data processing + // + aead128_process_ad(mut c.State, ad) + + // As we know, ciphertext length was sum of encrypted mesage length plus tag_size + // Lets slicing it + cxt_len := ciphertext.len + cmsg := ciphertext[0..cxt_len - tag_size] + ctag := ciphertext[cxt_len - tag_size..cxt_len] + + mut out := []u8{len: cxt_len - tag_size} + aead128_partial_dec(mut out, mut c.State, cmsg) + aead128_finalize(mut c.State, c.key[0], c.key[1]) + + // tag verification + mut tag := []u8{len: tag_size} + aead128_write_tag(mut tag, c.State, 0) + if subtle.constant_time_compare(ctag, tag) != 1 { + // Cleans up previously produces bytes (state) + reset_state(mut c.State) + unsafe { + tag.free() + out.free() + } + return error('Aead128.decrypt: unmatching tag') + } + return out +} + +// Helpers for Ascon-AEAD128 +// + +// aead128_init initializes Ascon-AEAD128 state by provided key and nonce. +// Its return two's u64 values from deserialized key bytes in little-endian form. +@[direct_array_access; inline] +fn aead128_init(mut s State, key []u8, nonce []u8) (u64, u64) { + // load key and nonce into state in little-endian form, + // The endianness has been switched from big endian to little endian + k0 := binary.little_endian_u64(key[0..8]) + k1 := binary.little_endian_u64(key[8..16]) + + n0 := binary.little_endian_u64(nonce[0..8]) + n1 := binary.little_endian_u64(nonce[8..16]) + + // Given a 128-bit 𝐾 and a 128-bit 𝑁, the 320-bit internal + // state S is initialized as the concatenation of 𝐼𝑉, 𝐾, and 𝑁: + // S ← 𝐼𝑉 || 𝐾 || 𝑁, + s.e0 = aead128_iv + s.e1 = k0 + s.e2 = k1 + s.e3 = n0 + s.e4 = n1 + + // updates State using the permutation π΄π‘ π‘π‘œπ‘›-𝑝[12], S ← π΄π‘ π‘π‘œπ‘›-𝑝[12](S) + ascon_pnr(mut s, 12) + + // Then XORing the secret key 𝐾 into the last 128 bits of internal state: + // S ← S βŠ• (0¹⁹² βˆ₯ 𝐾). + s.e3 ^= k0 + s.e4 ^= k1 + + return k0, k1 +} + +// aead128_process_ad absorbs associated data into Ascon-AEAD128 state. +@[direct_array_access; inline] +fn aead128_process_ad(mut s State, ad []u8) { + mut ad_length := ad.len + mut ad_idx := 0 + if ad_length > 0 { + for ad_length >= aead128_block_size { + // Each associated data block 𝐴𝑖 (0 ≀ 𝑖 ≀ π‘š) is absorbed into the first 128 bits of + // state as S[0∢127] ← S[0∢127] βŠ• 𝐴𝑖, + block := unsafe { ad[ad_idx..ad_idx + aead128_block_size] } + s.e0 ^= binary.little_endian_u64(block[0..8]) + s.e1 ^= binary.little_endian_u64(block[8..16]) + + // Apply permutation π΄π‘ π‘π‘œπ‘›-𝑝[8] to the state + ascon_pnr(mut s, 8) + // Updates index + ad_length -= aead128_block_size + ad_idx += aead128_block_size + } + // process partial block if it exists + if ad_length >= 8 { + first_block := unsafe { ad[ad_idx..ad_idx + 8] } + s.e0 ^= binary.little_endian_u64(first_block) + + // Is there more bytes to process on? + last_block := unsafe { ad[ad_idx + 8..] } + s.e1 ^= pad(last_block.len) + if last_block.len > 0 { + s.e1 ^= u64_from_partial_bytes(last_block) + } + // update index + ad_length -= first_block.len + last_block.len + ad_idx += first_block.len + last_block.len + } else { + last_block := unsafe { ad[ad_idx..] } + s.e0 ^= pad(last_block.len) + if last_block.len > 0 { + s.e0 ^= u64_from_partial_bytes(last_block) + } + } + // Apply permutation π΄π‘ π‘π‘œπ‘›-𝑝[8] to the state + ascon_pnr(mut s, 8) + } + // The final step of processing associated data is to update the state + // with a constant that provides domain separation. + s.e4 ^= u64(0x8000_0000_0000_0000) +} + +// aead128_process_msg process (encrypt) the messages msg and asborb it into Ascon-AEAD128 state +// Its written the result into out buffer and return the number of bytes has been written. +@[direct_array_access; inline] +fn aead128_process_msg(mut out []u8, mut s State, msg []u8) int { + mut pos := 0 + mut mlen := msg.len + mut midx := 0 + for mlen >= aead128_block_size { + block := unsafe { msg[midx..midx + aead128_block_size] } + s.e0 ^= binary.little_endian_u64(block[0..8]) + s.e1 ^= binary.little_endian_u64(block[8..16]) + // stores + binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0) + binary.little_endian_put_u64(mut out[pos + 8..], s.e1) + // apply permutation + ascon_pnr(mut s, 8) + + // updates index + mlen -= aead128_block_size + pos += aead128_block_size + midx += aead128_block_size + } + // process partial block if it exists + if mlen >= 8 { + mut first_block := unsafe { msg[midx..] } + s.e0 ^= load_bytes(first_block[0..8], 8) + binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0) + + // Is there are reminder bytes to process on? + last_block := unsafe { msg[midx + 8..] } + if last_block.len > 0 { + // We use `load_bytes` to handle length < 8 + s.e1 ^= load_bytes(last_block, last_block.len) + store_bytes(mut out[pos + 8..], s.e1, last_block.len) + s.e1 ^= pad(last_block.len) + } + } else { + last_block := unsafe { msg[midx..] } + s.e0 ^= load_bytes(last_block, last_block.len) + store_bytes(mut out[pos..], s.e0, last_block.len) + s.e0 ^= pad(last_block.len) + } + // how much we have written + pos += mlen + + return pos +} + +// aead128_partial_dec partially decrypts the encrypted part of the message in the cmsg, +// and stored into out buffer. +// Note: The output buffer should have the same length with cmsg, ie, out.len == cmsg.len +@[direct_array_access] +fn aead128_partial_dec(mut out []u8, mut s State, cmsg []u8) { + mut cmsg_len := cmsg.len + mut pos := 0 + // assert out.len == cmsg.len + for cmsg_len >= aead128_block_size { + block := unsafe { cmsg[pos..pos + aead128_block_size] } + c0 := binary.little_endian_u64(block[0..8]) + c1 := binary.little_endian_u64(block[8..16]) + + binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0 ^ c0) + binary.little_endian_put_u64(mut out[pos + 8..pos + 16], s.e1 ^ c1) + + s.e0 = c0 + s.e1 = c1 + + ascon_pnr(mut s, 8) + // updates index + pos += aead128_block_size + cmsg_len -= aead128_block_size + } + // partial block + if cmsg_len >= 8 { + mut first_block := unsafe { cmsg[pos..pos + 8] } + c0 := binary.little_endian_u64(first_block) + binary.little_endian_put_u64(mut out[pos..pos + 8], c0 ^ s.e0) + + last_block := unsafe { cmsg[pos + 8..] } + c1 := load_bytes(last_block, last_block.len) + store_bytes(mut out[pos + 8..], c1 ^ s.e1, last_block.len) + + s.e0 = c0 + s.e1 = clear_bytes(s.e1, last_block.len) + s.e1 |= c1 + s.e0 ^= pad(last_block.len) + } else { + last_block := unsafe { cmsg[pos..] } + c0 := load_bytes(last_block, last_block.len) + store_bytes(mut out[pos..], s.e0 ^ c0, last_block.len) + s.e0 = clear_bytes(s.e0, last_block.len) + s.e0 |= c0 + s.e0 ^= pad(last_block.len) + } +} + +// aead128_finalize does finalization step and generates tag value. +@[direct_array_access] +fn aead128_finalize(mut s State, k0 u64, k1 u64) { + // Load the key into state, S ← S βŠ• (0¹²⁸ βˆ₯ 𝐾 βˆ₯ 0⁢⁴), + s.e2 ^= k0 + s.e3 ^= k1 + // then updated using the permutation π΄π‘ π‘π‘œπ‘›-𝑝[12] + ascon_pnr(mut s, 12) + + // Finally, the tag 𝑇 is generated by XORing the key with the last 128 bits of the state: + // 𝑇 ← 𝑆[192∢319] βŠ• 𝐾. + s.e3 ^= k0 + s.e4 ^= k1 +} + +// aead128_write_tag writes tag from state into out at loc offset. +@[direct_array_access; inline] +fn aead128_write_tag(mut out []u8, s State, loc int) { + binary.little_endian_put_u64(mut out[loc..loc + 8], s.e3) + binary.little_endian_put_u64(mut out[loc + 8..loc + 16], s.e4) +} diff --git a/vlib/x/crypto/ascon/aead128_test.v b/vlib/x/crypto/ascon/aead128_test.v new file mode 100644 index 0000000000..06dba8a99a --- /dev/null +++ b/vlib/x/crypto/ascon/aead128_test.v @@ -0,0 +1,248 @@ +// Copyright Β©2025 blackshirt. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +module ascon + +import encoding.hex + +// This test materials was taken and adapted into v from references implementation of Ascon-aead128 +// especially for the known answer test data, but, its not all fully-taken, just randomly choosen item. +// See at https://github.com/ascon/ascon-c/blob/main/crypto_aead/asconaead128/LWC_AEAD_KAT_128_128.txt +struct KatTest { + cnt int + key string + nonce string + pt string + ad string + ct string +} + +// testing for Ascon-AEAD128 encryption and decryption. +fn test_ascon_aead128_enc_dec() ! { + for item in aead128_kat_tests_data { + key := hex.decode(item.key)! + nonce := hex.decode(item.nonce)! + pt := hex.decode(item.pt)! + ad := hex.decode(item.ad)! + ct := hex.decode(item.ct)! + + out := encrypt(key, nonce, ad, pt)! + assert out == ct + + msg := decrypt(key, nonce, ad, ct)! + assert msg == pt + + // Work with object-based Cipher + mut c := new_aead128(key)! + // Lets encrypt the message + exp_ct := c.encrypt(msg, nonce, ad)! + assert exp_ct == ct + // Lets decrypt it back + exp_msg := c.decrypt(exp_ct, nonce, ad)! + assert exp_msg == msg + } +} + +const aead128_kat_tests_data = [ + KatTest{ + cnt: 1 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '' + ct: '4F9C278211BEC9316BF68F46EE8B2EC6' + }, + KatTest{ + cnt: 2 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '30' + ct: 'CCCB674FE18A09A285D6AB11B35675C0' + }, + KatTest{ + cnt: 3 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '3031' + ct: 'F65B191550C4DF9CFDD4460EBBCCA782' + }, + KatTest{ + cnt: 4 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '303132' + ct: 'D127CF7D2CD4DA8930616C70B3619F42' + }, + KatTest{ + cnt: 5 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '30313233' + ct: '000BA92E52B5ED6B97C9D913CC4C82DF' + }, + KatTest{ + cnt: 6 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '3031323334' + ct: 'F7CC167F8FED3AEEA99B385B8622157E' + }, + KatTest{ + cnt: 7 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '303132333435' + ct: '51CCBC46D56E93B89B1A3BFDAD0AA4D5' + }, + KatTest{ + cnt: 8 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '30313233343536' + ct: 'B38ABBD573E071C6265EEAC4A68F65AB' + }, + KatTest{ + cnt: 9 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '3031323334353637' + ct: '865C594093A9EDEE2C1D6384CCB4939E' + }, + KatTest{ + cnt: 10 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '303132333435363738' + ct: '24F13284A0F90F906B18C7E4061C0896' + }, + KatTest{ + cnt: 27 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '303132333435363738393A3B3C3D3E3F40414243444546474849' + ct: '4ED362C4407B1D3BE17A51465659DECF' + }, + KatTest{ + cnt: 28 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '303132333435363738393A3B3C3D3E3F404142434445464748494A' + ct: 'A35C52EC6E7C78C051B23D03F691916F' + }, + KatTest{ + cnt: 29 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '303132333435363738393A3B3C3D3E3F404142434445464748494A4B' + ct: 'F1C946363A21CCFFE291A289202FC64C' + }, + KatTest{ + cnt: 30 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C' + ct: 'F1D453E933904578EEC3EA8E85550CE5' + }, + KatTest{ + cnt: 31 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D' + ct: '82E22C860881C0485EC5F5E8CEA42CEA' + }, + KatTest{ + cnt: 32 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E' + ct: 'C6306F1F154C78833984173360AAE874' + }, + KatTest{ + cnt: 33 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '' + ad: '303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F' + ct: 'EFC3E78B02AD9A80A6F0548C5B0BB5BA' + }, + KatTest{ + cnt: 34 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '20' + ad: '' + ct: 'E8DD576ABA1CD3E6FC704DE02AEDB79588' + }, + KatTest{ + cnt: 35 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '20' + ad: '30' + ct: '962B8016836C75A7D86866588CA245D886' + }, + KatTest{ + cnt: 49 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '20' + ad: '303132333435363738393A3B3C3D3E' + ct: '2089CB1DE2AE7D3E45BA7E9CC293548546' + }, + KatTest{ + cnt: 599 + key: '000102030405060708090a0b0c0d0e0f' + nonce: '101112131415161718191a1b1c1d1e1f' + pt: '202122232425262728292a2b2c2d2e2f3031' + ad: '30313233' + ct: 'cf5337fcb70ec45d179e0c3f51bb25ac967a2e7062ee9bd80da6c72e3a9b43aed9e0' + }, + KatTest{ + cnt: 600 + key: '000102030405060708090a0b0c0d0e0f' + nonce: '101112131415161718191a1b1c1d1e1f' + pt: '202122232425262728292a2b2c2d2e2f3031' + ad: '3031323334' + ct: '3076658cba8bf3bb6dccaa2f1255ee2e7db6f6493c7698f65f6860a7433a0f561e6c' + }, + KatTest{ + cnt: 601 + key: '000102030405060708090a0b0c0d0e0f' + nonce: '101112131415161718191a1b1c1d1e1f' + pt: '202122232425262728292a2b2c2d2e2f3031' + ad: '303132333435' + ct: '9310c6dd8e9cbc3e406c0ebfbea312435f2c6975faf3b6b2b17ef1ea2503c3d31ef5' + }, + KatTest{ + cnt: 602 + key: '000102030405060708090a0b0c0d0e0f' + nonce: '101112131415161718191a1b1c1d1e1f' + pt: '202122232425262728292a2b2c2d2e2f3031' + ad: '30313233343536' + ct: '6e024bd403f386eb9d1c56f459cfdcde1b2fdf8fd8be2faf0576c81e8d21c0dd8f8a' + }, + KatTest{ + cnt: 603 + key: '000102030405060708090A0B0C0D0E0F' + nonce: '101112131415161718191A1B1C1D1E1F' + pt: '202122232425262728292A2B2C2D2E2F3031' + ad: '3031323334353637' + ct: 'fabe2cb1e7eba6329a30080f26e7dc72503dfc57f4de06a334b7ebadca03b44b73e9' + }, +] diff --git a/vlib/x/crypto/ascon/ascon.v b/vlib/x/crypto/ascon/ascon.v index 1d1368ba84..1e72d17c1e 100644 --- a/vlib/x/crypto/ascon/ascon.v +++ b/vlib/x/crypto/ascon/ascon.v @@ -33,7 +33,6 @@ fn ascon_pnr(mut s State, nr int) { if nr < 1 || nr > 16 { panic('Invalid round number') } - // handle other number for i := max_nr_perm - nr; i < max_nr_perm; i++ { ascon_perm(mut s, rnc[i]) } @@ -86,13 +85,7 @@ fn ascon_perm(mut s State, c u8) { // Ξ£2(𝑆2) = 𝑆2 βŠ• (𝑆2 β‹™ 1) βŠ• (𝑆2 β‹™ 6) // Ξ£3(𝑆3) = 𝑆3 βŠ• (𝑆3 β‹™ 10) βŠ• (𝑆3 β‹™ 17) // Ξ£4(𝑆4) = 𝑆4 βŠ• (𝑆4 β‹™ 7) βŠ• (𝑆4 β‹™ 41) - /* - s.e0 ^= rotate_right_64(s.e0, 19) ^ rotate_right_64(s.e0, 28) - s.e1 ^= rotate_right_64(s.e1, 61) ^ rotate_right_64(s.e1, 39) - s.e2 ^= rotate_right_64(s.e2, 1) ^ rotate_right_64(s.e2, 6) - s.e3 ^= rotate_right_64(s.e3, 10) ^ rotate_right_64(s.e3, 17) - s.e4 ^= rotate_right_64(s.e4, 7) ^ rotate_right_64(s.e4, 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) diff --git a/vlib/x/crypto/ascon/digest.v b/vlib/x/crypto/ascon/digest.v index 0bc7d6e50a..76e9239300 100644 --- a/vlib/x/crypto/ascon/digest.v +++ b/vlib/x/crypto/ascon/digest.v @@ -29,9 +29,8 @@ fn (mut d Digest) finish() { panic('Digest.finish: internal error') } // Process for the last block stored on the internal buffer - block := unsafe { d.buf[..d.length] } - d.State.e0 ^= pad(block.len) - d.State.e0 ^= load_bytes(block, block.len) + d.State.e0 ^= pad(d.length) + d.State.e0 ^= load_bytes(d.buf[..d.length], d.length) // Permutation step was done in squeezing-phase // ascon_pnr(mut d.State, 12) @@ -44,13 +43,20 @@ fn (mut d Digest) finish() { d.done = true } -// absorb absorbs message msg_ into Ascon-HASH256 state +// absorb absorbs message msg_ into Digest state. @[direct_array_access] fn (mut d Digest) absorb(msg_ []u8) int { // nothing to absorb, just return if msg_.len == 0 { return 0 } + // Absorbing messages into Digest state working in streaming-way. + // Its continuesly updates internal state until you call `.finish` or `.free` it. + // Firstly, it would checking unprocessed bytes on internal buffer, and append it + // with bytes from messasge to align with block_size. + // And then absorb this buffered message into state. + // The process continues until the last partial block thats maybe below the block_size. + // If its happens, it will be stored on the Digest internal buffer for later processing. mut msg := msg_.clone() unsafe { // Check if internal buffer has previous unprocessed bytes. @@ -93,18 +99,22 @@ fn (mut d Digest) absorb(msg_ []u8) int { } } -// squeeze squeezes the Digest's state and calculates checksum output for the current state. +// squeeze squeezes the state and calculates checksum output for the current state. // It accepts destination buffer with desired buffer length you want to output. @[direct_array_access; inline] fn (mut d Digest) squeeze(mut dst []u8) int { + // nothing to store, just return unchanged + if dst.len == 0 { + return 0 + } // check - if dst.len < 1 || dst.len > max_hash_size { + if dst.len > max_hash_size { panic('Digest.squeeze: invalid dst.len') } // The squeezing phase begins after msg is absorbed with an // permutation π΄π‘ π‘π‘œπ‘›-𝑝[12] to the state: ascon_pnr(mut d.State, 12) - // mut out := []u8{len: size} + mut pos := 0 mut clen := dst.len // process for full block size @@ -114,7 +124,7 @@ fn (mut d Digest) squeeze(mut dst []u8) int { pos += block_size clen -= block_size } - // final output, the resulting 256-bit digest is the concatenation of hash blocks + // final output, the resulting hash is the concatenation of hash blocks store_bytes(mut dst[pos..], d.State.e0, clen) pos += clen diff --git a/vlib/x/crypto/ascon/hash.v b/vlib/x/crypto/ascon/hash.v index 03519ae362..8ee9473bac 100644 --- a/vlib/x/crypto/ascon/hash.v +++ b/vlib/x/crypto/ascon/hash.v @@ -79,7 +79,7 @@ pub fn (h &Hash256) block_size() int { return block_size } -// reset reinits internal Hash256 state into default initialized state. +// reset resets and reinit internal Hash256 state into default initialized state. @[direct_array_access] pub fn (mut h Hash256) reset() { h.Digest.State = hash256_initial_state diff --git a/vlib/x/crypto/ascon/xof.v b/vlib/x/crypto/ascon/xof.v index c66e2d6efc..0ae9354002 100644 --- a/vlib/x/crypto/ascon/xof.v +++ b/vlib/x/crypto/ascon/xof.v @@ -117,7 +117,12 @@ pub fn (mut x Xof128) sum(msg []u8) []u8 { // Note: 1 ≀ dst.len ≀ max_hash_size. @[direct_array_access] pub fn (mut x Xof128) read(mut dst []u8) !int { - if dst.len < 1 || dst.len > max_hash_size { + // Left unchanged + if dst.len == 0 { + return 0 + } + // Check output size + if dst.len > max_hash_size { panic('Xof128.read: invalid size') } mut x0 := x.clone() @@ -261,7 +266,11 @@ pub fn (mut x CXof128) sum(msg []u8) []u8 { // read tries to read `dst.len` bytes of hash output from current CXof128 state and stored into dst. @[direct_array_access] pub fn (mut x CXof128) read(mut dst []u8) !int { - if dst.len < 1 || dst.len > max_hash_size { + // Left unchanged, nothing space to store checksum + if dst.len == 0 { + return 0 + } + if dst.len > max_hash_size { panic('CXof128.read: invalid size') } mut x0 := x.clone()