diff --git a/cmd/tools/vtest-all.v b/cmd/tools/vtest-all.v index 137dcb361b..4b2adf9e44 100644 --- a/cmd/tools/vtest-all.v +++ b/cmd/tools/vtest-all.v @@ -91,7 +91,7 @@ fn get_all_commands() []Command { rmfile: 'examples/hello_world' } res << Command{ - line: '${vexe} -W -Wimpure-v run examples/hello_world.v' + line: '${vexe} -W -Wimpure-v examples/hello_world.v' okmsg: 'V can compile hello world with the stricter `-W -Wimpure-v` mode .' rmfile: 'examples/hello_world' } diff --git a/vlib/v/builder/builder.v b/vlib/v/builder/builder.v index 0ab091f85b..d9f76c5e06 100644 --- a/vlib/v/builder/builder.v +++ b/vlib/v/builder/builder.v @@ -131,6 +131,11 @@ pub fn (mut b Builder) middle_stages() ! { b.checker.check_files(b.parsed_files) util.timing_measure('CHECK') + $if trace_type_symbols_after_checker ? { + for t, s in b.table.type_symbols { + println('> t: ${t:10} | s.mod: ${s.mod:-40} | s.name: ${'${s.name#[..30]}':-30} | s.is_builtin: ${s.is_builtin:6} | s.is_pub: ${s.is_pub}') + } + } if b.pref.dump_defines != '' { b.dump_defines() } diff --git a/vlib/v/gen/c/testdata/autofree_toml.vv b/vlib/v/gen/c/testdata/autofree_toml.vv index 42272f8c0d..c4c99cbfc0 100644 --- a/vlib/v/gen/c/testdata/autofree_toml.vv +++ b/vlib/v/gen/c/testdata/autofree_toml.vv @@ -2,14 +2,8 @@ import toml import os -fn main() { - config_fname := 'config.toml' - tab_title := 'test tab title' - if !os.exists(config_fname) { - mut f := os.create(config_fname) or { panic(err) } - f.writeln('tab_title = "${tab_title}"') or { panic(err) } - f.close() - } - doc := toml.parse_file(config_fname) or { panic(err) } - assert doc.value('tab_title').string() == tab_title -} +config_fname := os.join_path(os.vtmp_dir(), 'config.toml') +tab_title := 'test tab title' +os.write_file(config_fname, 'tab_title = "${tab_title}"')! +doc := toml.parse_file(config_fname)! +assert doc.value('tab_title').string() == tab_title diff --git a/vlib/v/gen/native/amd64.v b/vlib/v/gen/native/amd64.v index 2faac98e25..43606579b3 100644 --- a/vlib/v/gen/native/amd64.v +++ b/vlib/v/gen/native/amd64.v @@ -953,7 +953,7 @@ fn (mut c Amd64) lea_var_to_reg(r Register, var_offset i32) { is_far_var := var_offset > 0x80 || var_offset < -0x7f match reg { - .rax, .rbx, .rsi, .rdi { + .rax, .rbx, .rsi, .rdi, .rdx, .rcx { c.g.write8(0x48) } else {} @@ -2753,6 +2753,7 @@ fn (mut c Amd64) gen_type_promotion(from ast.Type, to ast.Type, option Amd64Regi } fn (mut c Amd64) return_stmt(node ast.Return) { + c.g.println('; return statement {') mut s := '?' //${node.exprs[0].val.str()}' if node.exprs.len == 1 { match node.exprs[0] { @@ -2812,6 +2813,8 @@ fn (mut c Amd64) return_stmt(node ast.Return) { c.add(Amd64Register.rax, size % 8) c.add(Amd64Register.rdx, size % 8) c.mov_deref(Amd64Register.rcx, Amd64Register.rax, ast.i64_type_idx) + // TODO: check if it does not write too far as the size of + // the remaining data is not 64bits c.mov_store(.rdx, .rcx, ._64) } c.mov_var_to_reg(c.main_reg(), LocalVar{ @@ -2841,7 +2844,14 @@ fn (mut c Amd64) return_stmt(node ast.Return) { offset := c.g.structs[typ.idx()].offsets[i] c.g.expr(expr) // TODO: expr not on rax - c.mov_reg_to_var(var, Amd64Register.rax, offset: offset, typ: ts.mr_info().types[i]) + e_typ := ts.mr_info().types[i] + e_ts := c.g.table.sym(e_typ) + if e_ts.info is ast.Struct { + c.lea_var_to_reg(Amd64Register.rdx, var.offset - offset) + c.move_struct(.rdx, .rax, c.g.get_type_size(e_typ)) + } else { + c.mov_reg_to_var(var, Amd64Register.rax, offset: offset, typ: ts.mr_info().types[i]) + } } // store the multi return struct value c.lea_var_to_reg(Amd64Register.rax, var.offset) @@ -2897,6 +2907,7 @@ fn (mut c Amd64) return_stmt(node ast.Return) { pos: pos } c.g.println('; jump to label ${label}') + c.g.println('; return statement }') } fn (mut c Amd64) multi_assign_stmt(node ast.AssignStmt) { @@ -2926,7 +2937,7 @@ fn (mut c Amd64) multi_assign_stmt(node ast.AssignStmt) { } else { c.g.expr(node.right[0]) } - c.mov_reg(Amd64Register.rdx, Amd64Register.rax) + c.mov_reg(Amd64Register.rdx, Amd64Register.rax) // value of right expr(s) mut current_offset := i32(0) for i, offset in multi_return.offsets { @@ -2943,7 +2954,7 @@ fn (mut c Amd64) multi_assign_stmt(node ast.AssignStmt) { c.add(Amd64Register.rdx, offset - current_offset) current_offset = offset } - c.g.gen_left_value(node.left[i]) + c.g.gen_left_value(node.left[i]) // in rax left_type := node.left_types[i] right_type := node.right_types[i] if c.g.is_register_type(right_type) { @@ -3001,11 +3012,36 @@ fn (mut c Amd64) multi_assign_stmt(node ast.AssignStmt) { c.g.println('movsd [rax], xmm0') } } else { - c.g.n_error('${@LOCATION} multi return for struct is not supported yet') + c.move_struct(.rax, .rdx, c.g.get_type_size(left_type)) } } } +// Moves a struct of size `_size` (in bytes) from the address stored in input to the address stored in output +fn (mut c Amd64) move_struct(output Amd64Register, input Amd64Register, _size i32) { + mut size := _size + for size != 0 { + c.mov_deref(Amd64Register.rcx, input, ast.i64_type_idx) + // mov_store can only move powers of 2 bytes at once + // the remainder will then get handled the next iteration for simplicity + data_size := i32(match true { + size < 2 { 1 } + size < 4 { 2 } + size < 8 { 4 } + else { 8 } + }) + c.mov_store(output, .rcx, match data_size { + 1 { ._8 } + 2 { ._16 } + 4 { ._32 } + else { ._64 } + }) + size -= data_size + c.add(output, data_size) + c.add(input, data_size) + } +} + fn (mut c Amd64) assign_stmt(node ast.AssignStmt) { // `a, b := foo()` // `a, b := if cond { 1, 2 } else { 3, 4 }` diff --git a/vlib/v/gen/native/tests/multi_assign.vv b/vlib/v/gen/native/tests/multi_assign.vv index 42b7b5c6ed..c62eebb0e4 100644 --- a/vlib/v/gen/native/tests/multi_assign.vv +++ b/vlib/v/gen/native/tests/multi_assign.vv @@ -61,7 +61,44 @@ fn cross_assign_of_struct_test() { // from cross_assign_test.v assert x.b == 1 } +struct MyStruct { + a int + b u64 + c u16 + d u8 +} + +struct MyStruct2 { + a u8 + b u8 + c u8 +} + +fn struct_multi_return() (int, MyStruct) { + return 3, MyStruct{4, 5, 6, 7} +} + +fn struct_multi_return2() (int, MyStruct2) { + return 3, MyStruct2{4, 5, 6} +} + +fn struct_multi_return_test() { + a, b := struct_multi_return() + assert a == 3 + assert b.a == 4 + assert b.b == 5 + assert b.c == 6 + assert b.d == 7 + + c, d := struct_multi_return2() + assert c == 3 + assert d.a == 4 + assert d.b == 5 + assert d.c == 6 +} + fn main() { fn_multi_return_test() cross_assign_of_struct_test() + struct_multi_return_test() } diff --git a/vlib/x/crypto/ascon/README.md b/vlib/x/crypto/ascon/README.md index 61a826506c..2191a20605 100644 --- a/vlib/x/crypto/ascon/README.md +++ b/vlib/x/crypto/ascon/README.md @@ -1,15 +1,17 @@ # 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. Its describes an Ascon-Based Lightweight Cryptography Standards for Constrained Devices 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. -This module does not fully implements all the features availables on the document. -Its currently implements: +This module mostly implements all the features availables on the document. +It currently implements: - `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. - `Ascon-CXOF128`, a customized XOF that allows users to specify a customization 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. diff --git a/vlib/x/crypto/ascon/aead128.v b/vlib/x/crypto/ascon/aead128.v index da215e5e1f..f80cdb2f2b 100644 --- a/vlib/x/crypto/ascon/aead128.v +++ b/vlib/x/crypto/ascon/aead128.v @@ -181,7 +181,7 @@ pub fn (mut c Aead128) encrypt(msg []u8, nonce []u8, ad []u8) ![]u8 { c.State.e4 = n1 // 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 c.State.e3 ^= c.key[0] 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 // 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 c.State.e3 ^= c.key[0] 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 // 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: // 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]) // Apply permutation ๐ด๐‘ ๐‘๐‘œ๐‘›-๐‘[8] to the state - ascon_pnr(mut s, 8) + ascon_pnr(mut s, ascon_prnd_8) // Updates index ad_length -= 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 - ascon_pnr(mut s, 8) + ascon_pnr(mut s, ascon_prnd_8) } // The final step of processing associated data is to update the state // 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 + 8..], s.e1) // apply permutation - ascon_pnr(mut s, 8) + ascon_pnr(mut s, ascon_prnd_8) // updates index mlen -= aead128_block_size @@ -413,7 +413,7 @@ fn aead128_partial_dec(mut out []u8, mut s State, cmsg []u8) { s.e0 = c0 s.e1 = c1 - ascon_pnr(mut s, 8) + ascon_pnr(mut s, ascon_prnd_8) // updates index pos += 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.e3 ^= k1 // 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: // ๐‘‡ โ† ๐‘†[192โˆถ319] โŠ• ๐พ. diff --git a/vlib/x/crypto/ascon/aead128_test.v b/vlib/x/crypto/ascon/aead128_test.v index 8d86f6b3be..06dba8a99a 100644 --- a/vlib/x/crypto/ascon/aead128_test.v +++ b/vlib/x/crypto/ascon/aead128_test.v @@ -2,8 +2,9 @@ // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. // +module ascon + import encoding.hex -import x.crypto.ascon // 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. @@ -26,14 +27,14 @@ fn test_ascon_aead128_enc_dec() ! { ad := hex.decode(item.ad)! ct := hex.decode(item.ct)! - out := ascon.encrypt(key, nonce, ad, pt)! + out := encrypt(key, nonce, ad, pt)! assert out == ct - msg := ascon.decrypt(key, nonce, ad, ct)! + msg := decrypt(key, nonce, ad, ct)! assert msg == pt // Work with object-based Cipher - mut c := ascon.new_aead128(key)! + mut c := new_aead128(key)! // Lets encrypt the message exp_ct := c.encrypt(msg, nonce, ad)! assert exp_ct == ct diff --git a/vlib/x/crypto/ascon/ascon.v b/vlib/x/crypto/ascon/ascon.v index 4f67269607..8bb1b1977a 100644 --- a/vlib/x/crypto/ascon/ascon.v +++ b/vlib/x/crypto/ascon/ascon.v @@ -10,6 +10,10 @@ module ascon // constants for up to 16 rounds to accommodate potential functionality extensions in the future. 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 // 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, 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] fn ascon_pnr(mut s State, nr int) { // We dont allow nr == 0 if nr < 1 || nr > 16 { panic('Invalid round number') } + // Ascon permutation routine 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, // which is represented as five of 64-bit words. @[noinit] diff --git a/vlib/x/crypto/ascon/ascon_test.v b/vlib/x/crypto/ascon/ascon_test.v index 763aa0c071..ebf5ea0769 100644 --- a/vlib/x/crypto/ascon/ascon_test.v +++ b/vlib/x/crypto/ascon/ascon_test.v @@ -5,23 +5,6 @@ module ascon // 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() { mut s := State{ e0: u64(0x0123456789abcdef) diff --git a/vlib/x/crypto/ascon/bench/aead.v b/vlib/x/crypto/ascon/bench/aead.v new file mode 100644 index 0000000000..c7d1cd1043 --- /dev/null +++ b/vlib/x/crypto/ascon/bench/aead.v @@ -0,0 +1,75 @@ +// This benchmark is for Ascon-AEAD128 in `x.crypto.ascon` compared to +// already stocked `x.crypto.cacha20poly1305 for AEAD functionalities. +// +// Here is output in my tests, first item was `x.crypto.ascon` and the later +// for `x.crypto.chacha20poly1305` on encryption or decryption part. +// +// Encryption.. +// ----------- +// Iterations: 10000 Total Duration: 26.008ms ns/op: 2600 B/op: 16 allocs/op: 17 +// Iterations: 10000 Total Duration: 158.865ms ns/op: 15886 B/op: 16 allocs/op: 16 +// +// Decryption.. +// ----------- +// Iterations: 10000 Total Duration: 29.091ms ns/op: 2909 B/op: 6 allocs/op: 8 +// Iterations: 10000 Total Duration: 158.373ms ns/op: 15837 B/op: 8 allocs/op: 12 +// +import encoding.hex +import x.benchmark +import x.crypto.ascon +import x.crypto.chacha20poly1305 + +// randomly generated key and nonce, 16-bytes of ascon key and 32-bytes of chacha20poly1305 key. +const key_ascon = hex.decode('7857bfb462c654d1d1b02971be021235')! +const key_cpoly = hex.decode('9d9603f4fc460e273b80795ea50eab5873c04f589226c7d591b5336feb32fcba')! + +// 16-bytes ascon-nonce +const ascon_nonce = hex.decode('8b521028fb54591472d8d8ee14430835')! + +// 12-bytes chacha20poly1305 nonce +const cpoly_nonce = hex.decode('9a3c83e4236ea9a2c4e482da')! + +const ad = 'Ascon-AEAD128 additional data'.bytes() +const msg = 'Ascon-AEAD128 benchmarking message'.bytes() + +// expected ciphertext for aead128 := 4b21a18cbca65b11aaf73dc74241c89bfcec96a4c8973ae696a938e0a591e846c4eb7b2906664f2318c0fd6ec1c56424aa9b +const ciphertext_aead128 = hex.decode('4b21a18cbca65b11aaf73dc74241c89bfcec96a4c8973ae696a938e0a591e846c4eb7b2906664f2318c0fd6ec1c56424aa9b')! + +fn bench_ascon_aead128_encrypt() ! { + _ := ascon.encrypt(key_ascon, ascon_nonce, ad, msg)! +} + +fn bench_ascon_aead128_decrypt() ! { + _ := ascon.decrypt(key_ascon, ascon_nonce, ad, ciphertext_aead128)! +} + +// expected ciphertext for chacha20poly1305 +const ciphertext_chachapoly1305 = hex.decode('67dea3c65f0f326bcf587f024140a85d9535790d9b16129210a2289eda43bb9b62746450026fc1baf466bcb8a181843cd424')! + +fn bench_chacha20poly1305_encrypt() ! { + _ := chacha20poly1305.encrypt(msg, key_cpoly, cpoly_nonce, ad)! +} + +fn bench_chacha20poly1305_decrypt() ! { + _ := chacha20poly1305.decrypt(ciphertext_chachapoly1305, key_cpoly, cpoly_nonce, ad)! +} + +fn main() { + cf := benchmark.BenchmarkDefaults{ + n: 10000 + } + println('Encryption..') + println('-----------') + mut b0 := benchmark.setup(bench_ascon_aead128_encrypt, cf)! + b0.run() + mut b1 := benchmark.setup(bench_chacha20poly1305_encrypt, cf)! + b1.run() + + println('') + println('Decryption..') + println('-----------') + mut b2 := benchmark.setup(bench_ascon_aead128_decrypt, cf)! + b2.run() + mut b3 := benchmark.setup(bench_chacha20poly1305_decrypt, cf)! + b3.run() +} diff --git a/vlib/x/crypto/ascon/bench/hashxof.v b/vlib/x/crypto/ascon/bench/hashxof.v new file mode 100644 index 0000000000..6b7ec6ce54 --- /dev/null +++ b/vlib/x/crypto/ascon/bench/hashxof.v @@ -0,0 +1,197 @@ +// Ascon-Hash256 (and Ascon-XOF128) benchmark compared to builtin +// crypto.sha256 (for sum256) and sha3.shake256 (for xof outputing 256-bits) +// +// This benchmark code was adapted from argon2 benchmark by @fleximus, the creator argon2 module. +// Credit tributed to @fleximus +// See https://gist.github.com/fleximus/db5b867a9a37da46340db61bdac6e696 +// +// Output +// ====== +// Sum and Xof 256-bits output performance comparison +// ============================================================ +// Iterations per test: 10000 +// -------------------------------------------------------------------------------------------------- +// Data Size | Ascon256 | Sha256 | Ratio 256 || AsconXof128 | Shake256 | Ratio (Xof) | +// -------------------------------------------------------------------------------------------------- +// 4 B | 24.00ms | 33.00ms | 0.73x || 24.00ms | 208.00ms | 0.12x | +// 6 B | 23.00ms | 53.00ms | 0.45x || 25.00ms | 287.00ms | 0.08x | +// 8 B | 35.00ms | 37.00ms | 0.95x || 26.00ms | 202.00ms | 0.18x | +// 16 B | 30.00ms | 37.00ms | 0.83x || 30.00ms | 205.00ms | 0.15x | +// 64 B | 55.00ms | 61.00ms | 0.89x || 53.00ms | 241.00ms | 0.23x | +// 75 B | 61.00ms | 57.00ms | 1.07x || 58.00ms | 182.00ms | 0.34x | +// 256 B | 154.00ms | 123.00ms | 1.25x || 144.00ms | 398.00ms | 0.39x | +// 512 B | 273.00ms | 216.00ms | 1.26x || 265.00ms | 779.00ms | 0.35x | +// 1025 B | 610.00ms | 401.00ms | 1.52x || 509.00ms | 1.37s | 0.45x | +// -------------------------------------------------------------------------------------------------- +// Total | 1.27s | 1.02s | 1.24x || 1.14s | 3.87s | 0.294x| +// -------------------------------------------------------------------------------------------------- +// +// Per-operation averages: +// Ascon256: 14108 ns per hash +// Sha256: 11360 ns per hash +// AsconXof128: 12648 ns per hash +// Shake256: 43036 ns per hash +// +module main + +import time +import crypto.sha3 +import crypto.sha256 +import x.crypto.ascon + +const benchmark_iterations = 10000 + +// We include more small size because, Ascon-Hash256 working with more smaller block size. +const test_data_sizes = [ + 4, // below Ascon-Hash256 block size + 6, // Still below Ascon-Hash256 block size + 8, // align with Ascon-Hash256 block size + 16, // Small data + 64, // Medium data + 75, // above 64-bytes block + 256, // Large data + 512, + 1025, +] + +fn generate_test_data(size int) []u8 { + mut data := []u8{len: size} + for i in 0 .. size { + data[i] = u8(i % 256) + } + return data +} + +fn benchmark_ascon_sha256(data []u8, iterations int) time.Duration { + start := time.now() + for _ in 0 .. iterations { + _ := ascon.sum256(data) + } + return time.since(start) +} + +fn benchmark_sha256_sum256(data []u8, iterations int) time.Duration { + start := time.now() + for _ in 0 .. iterations { + _ := sha256.sum256(data) + } + return time.since(start) +} + +// for eXtendable output functions (XOF) +fn benchmark_ascon_xof128_32(data []u8, iterations int) time.Duration { + start := time.now() + for _ in 0 .. iterations { + _ := ascon.xof128(data, 32) or { panic(err) } + } + return time.since(start) +} + +fn benchmark_sha3_shake256(data []u8, iterations int) time.Duration { + start := time.now() + for _ in 0 .. iterations { + _ := sha3.shake256(data, 32) + } + return time.since(start) +} + +fn format_duration(d time.Duration) string { + if d.microseconds() < 1000 { + return '${d.microseconds():6}ฮผs' + } else if d.milliseconds() < 1000 { + return '${f64(d.milliseconds()):6.2f}ms' + } else { + return '${f64(d.seconds()):6.2f}s' + } +} + +const data_title = 'Data Size' +const ascon_sum256_title = 'Ascon256' +const sha256_title = 'Sha256' +const ascon_xof128_title = 'AsconXof128' +const sha3_shake256_title = 'Shake256' +const ratio_ascon256_w_sha256 = 'Ratio 256' +const ratio_asconxof128_w_shake256 = 'Ratio (Xof)' + +fn main() { + println('') + println('Sum and Xof 256-bits output performance comparison') + println('============================================================') + println('Iterations per test: ${benchmark_iterations}') + + println('${'-'.repeat(98)}') + println('${data_title:12} | ${ascon_sum256_title:10} | ${sha256_title:10} | ${ratio_ascon256_w_sha256:12} || ${ascon_xof128_title:10} | ${sha3_shake256_title:10} | ${ratio_asconxof128_w_shake256:12} |') + println('${'-'.repeat(98)}') + + mut total_ascon256 := time.Duration(0) + mut total_sha256 := time.Duration(0) + mut total_shake256 := time.Duration(0) + mut total_asconxof128 := time.Duration(0) + + for size in test_data_sizes { + test_data := generate_test_data(size) + + // Warm up + _ := ascon.sum256(test_data) + _ := sha256.sum256(test_data) + + _ := ascon.xof128(test_data, 32)! + _ := sha3.shake256(test_data, 32) + + // Benchmark Ascon-HASH256 + ascon256_time := benchmark_ascon_sha256(test_data, benchmark_iterations) + + // Benchmark Sha256 implementation + sha256_time := benchmark_sha256_sum256(test_data, benchmark_iterations) + + // Benchmark Sha3 shake256 implementation + shake256_time := benchmark_sha3_shake256(test_data, benchmark_iterations) + + // Benchmark AsconXof128 256-bits output + asconxof128_time := benchmark_ascon_xof128_32(test_data, benchmark_iterations) + + // Calculate ratio ascon256 / sha256 + ratio_ascon256_sha256 := f64(ascon256_time.nanoseconds()) / f64(sha256_time.nanoseconds()) + + // Calculate ratio asconxof128 / shake256 + ratio_asconxof128_shake256 := f64(asconxof128_time.nanoseconds()) / f64(shake256_time.nanoseconds()) + + ascon256_str := format_duration(ascon256_time) + sha256_str := format_duration(sha256_time) + asconxof128_str := format_duration(asconxof128_time) + shake256_str := format_duration(shake256_time) + + ratio_ascon256_sha256_str := '${ratio_ascon256_sha256:6.2f}x' + ratio_asconxof128_shake256_str := '${ratio_asconxof128_shake256:6.2f}x' + + println('${size:10} B | ${ascon256_str:10} | ${sha256_str:10} | ${ratio_ascon256_sha256_str:12} || ${asconxof128_str:11} | ${shake256_str:10} | ${ratio_asconxof128_shake256_str:12} |') + + total_ascon256 += ascon256_time + total_sha256 += sha256_time + + total_asconxof128 += asconxof128_time + total_shake256 += shake256_time + } + + println('${'-'.repeat(98)}') + + // Overall performance comparison + overall_ascon256_w_sha256_ratio := f64(total_ascon256.nanoseconds()) / f64(total_sha256.nanoseconds()) + overall_asconxof128_w_shake256_ratio := f64(total_asconxof128.nanoseconds()) / f64(total_shake256.nanoseconds()) + total_title := 'Total' + println('${total_title:12} | ${format_duration(total_ascon256):10} | ${format_duration(total_sha256):10} | ${overall_ascon256_w_sha256_ratio:11.2f}x || ${format_duration(total_asconxof128):11} | ${format_duration(total_shake256):10} | ${overall_asconxof128_w_shake256_ratio:12.2f}x|') + println('${'-'.repeat(98)}') + + println('') + println('Per-operation averages:') + avg_ascon256 := total_ascon256.nanoseconds() / (benchmark_iterations * test_data_sizes.len) + avg_sha256 := total_sha256.nanoseconds() / (benchmark_iterations * test_data_sizes.len) + avg_shake256 := total_shake256.nanoseconds() / (benchmark_iterations * test_data_sizes.len) + avg_asconxof128 := total_asconxof128.nanoseconds() / (benchmark_iterations * test_data_sizes.len) + + println(' Ascon256:\t ${avg_ascon256:8} ns per hash') + println(' Sha256:\t ${avg_sha256:8} ns per hash') + println(' AsconXof128:\t ${avg_asconxof128:8} ns per hash') + println(' Shake256:\t ${avg_shake256:8} ns per hash') + println('') +} diff --git a/vlib/x/crypto/ascon/bench/sum.v b/vlib/x/crypto/ascon/bench/sum.v new file mode 100644 index 0000000000..3739d0efff --- /dev/null +++ b/vlib/x/crypto/ascon/bench/sum.v @@ -0,0 +1,36 @@ +import time +import x.crypto.ascon + +// Before: +// Benchmarking ascon.sum256 ... +// Average ascon.sum256 time: 8 ยตs +// Benchmarking ascon.sum256 ... +// Average ascon.sum256 time: 6 ยตs + +// For xof128 (32 bytes) +// Benchmarking ascon.xof128 ... +// Average ascon.xof128 time: 7 ยตs +// Benchmarking ascon.xof128 ... +// Average ascon.xof128 time: 6 ยตs + +// For cxof128 32 bytes +// Benchmarking ascon.cxof128 ... +// Average ascon.cxof128 time: 9 ยตs +// Benchmarking ascon.sum256 ... +// Average ascon.cxof128 time: 7 ยตs +// +fn main() { + iterations := 1000 + msg := [u8(0xff)].repeat(100) + + println('Benchmarking ascon.sum256 ...') + mut total_sum_time := i64(0) + for _ in 0 .. iterations { + sw := time.new_stopwatch() + _ := ascon.sum256(msg) + elapsed := sw.elapsed().microseconds() + total_sum_time += elapsed + } + avg_sum_time := total_sum_time / iterations + println('Average ascon.sum256 time: ${avg_sum_time} ยตs') +} diff --git a/vlib/x/crypto/ascon/digest.v b/vlib/x/crypto/ascon/digest.v index 0b9a6db98d..89a7dac7e1 100644 --- a/vlib/x/crypto/ascon/digest.v +++ b/vlib/x/crypto/ascon/digest.v @@ -33,7 +33,7 @@ fn (mut d Digest) finish() { d.State.e0 ^= load_bytes(d.buf[..d.length], d.length) // Permutation step was done in squeezing-phase - // ascon_pnr(mut d.State, 12) + // ascon_pnr(mut d.State, ascon_prnd_12) // zeroing Digest buffer 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 d.length == block_size { 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 d.length = 0 d.buf.reset() @@ -87,7 +87,7 @@ fn (mut d Digest) absorb(msg_ []u8) int { for msg.len >= block_size { d.State.e0 ^= binary.little_endian_u64(msg[0..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 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 // permutation ๐ด๐‘ ๐‘๐‘œ๐‘›-๐‘[12] to the state: - ascon_pnr(mut d.State, 12) + ascon_pnr(mut d.State, ascon_prnd_12) mut pos := 0 mut clen := dst.len // process for full block size for clen >= block_size { 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 clen -= block_size } @@ -133,3 +133,39 @@ fn (mut d Digest) squeeze(mut dst []u8) int { return pos } + +@[direct_array_access; inline] +fn ascon_generic_hash(mut s State, msg_ []u8, size int) []u8 { + // Assumed state was correctly initialized + // Absorbing the message + mut msg := msg_.clone() + for msg.len >= block_size { + s.e0 ^= binary.little_endian_u64(msg[0..block_size]) + unsafe { + msg = msg[block_size..] + } + ascon_pnr(mut s, ascon_prnd_12) + } + // Absorb the last partial message block + s.e0 ^= load_bytes(msg, msg.len) + s.e0 ^= pad(msg.len) + + // Squeezing phase + // + // The squeezing phase begins after msg is absorbed with an + // permutation ๐ด๐‘ ๐‘๐‘œ๐‘›-๐‘[12] to the state: + ascon_pnr(mut s, ascon_prnd_12) + mut out := []u8{len: size} + mut pos := 0 + mut clen := out.len + for clen >= block_size { + binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0) + ascon_pnr(mut s, ascon_prnd_12) + pos += block_size + clen -= block_size + } + // final output, the resulting 256-bit digest is the concatenation of hash blocks + store_bytes(mut out[pos..], s.e0, clen) + + return out +} diff --git a/vlib/x/crypto/ascon/hash.v b/vlib/x/crypto/ascon/hash.v index 2ec0dda97e..60c02af506 100644 --- a/vlib/x/crypto/ascon/hash.v +++ b/vlib/x/crypto/ascon/hash.v @@ -42,13 +42,13 @@ const hash256_initial_state = State{ } // sum256 creates an Ascon-Hash256 checksum for bytes on msg and produces a 256-bit hash. -pub fn sum256(msg []u8) []u8 { - mut h := new_hash256() - _ := h.write(msg) or { panic(err) } - h.Digest.finish() - mut dst := []u8{len: hash256_size} - _ := h.Digest.squeeze(mut dst) - return dst +pub fn sum256(msg_ []u8) []u8 { + // This is single-shot function, so, no need to use Hash256 opaque that process + // message in streaming way. To reduce this overhead, use raw processing instead. + // + // Initialize state + mut s := hash256_initial_state + return ascon_generic_hash(mut s, msg_, hash256_size) } // Hash256 is an opaque provides an implementation of Ascon-Hash256 from NIST.SP.800-232 standard. diff --git a/vlib/x/crypto/ascon/util.v b/vlib/x/crypto/ascon/util.v index dd123e4a84..345683ea66 100644 --- a/vlib/x/crypto/ascon/util.v +++ b/vlib/x/crypto/ascon/util.v @@ -5,15 +5,8 @@ // Utility helpers used across the module module ascon -import math.bits 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 @[inline] fn clear_bytes(x u64, n int) u64 { @@ -100,8 +93,3 @@ fn store_bytes(mut out []u8, x u64, n int) { out[i] = get_byte(x, i) } } - -@[inline] -fn ascon_rotate_right(x u64, n int) u64 { - return (x >> n) | x << (64 - n) -} diff --git a/vlib/x/crypto/ascon/xof.v b/vlib/x/crypto/ascon/xof.v index c6d48ce149..eea14e35f1 100644 --- a/vlib/x/crypto/ascon/xof.v +++ b/vlib/x/crypto/ascon/xof.v @@ -30,13 +30,11 @@ const xof128_initial_state = State{ // xof128 creates an Ascon-XOF128 checksum of msg with specified desired size of output. pub fn xof128(msg []u8, size int) ![]u8 { - mut x := new_xof128(size) - _ := x.write(msg)! - x.Digest.finish() - mut out := []u8{len: size} - _ := x.Digest.squeeze(mut out) - x.reset() - return out + if size > max_hash_size { + return error('xof128: invalid size') + } + mut s := xof128_initial_state + return ascon_generic_hash(mut s, msg, size) } // xof128_64 creates a 64-bytes of Ascon-XOF128 checksum of msg. @@ -170,13 +168,10 @@ const cxof128_initial_state = State{ // cxof128 creates an Ascon-CXOF128 checksum of msg with supplied size and custom string cs. pub fn cxof128(msg []u8, size int, cs []u8) ![]u8 { - mut cx := new_cxof128(size, cs)! - _ := cx.write(msg)! - cx.Digest.finish() - mut out := []u8{len: size} - _ := cx.Digest.squeeze(mut out) - cx.reset() - return out + // Initialize CXof128 state with precomputed-value and absorb the customization string + mut s := cxof128_initial_state + cxof128_absorb_custom_string(mut s, cs) + return ascon_generic_hash(mut s, msg, size) } // cxof128_64 creates a 64-bytes of Ascon-CXOF128 checksum of msg with supplied custom string in cs. @@ -305,7 +300,7 @@ pub fn (mut x CXof128) free() { fn cxof128_absorb_custom_string(mut s State, cs []u8) { // absorb Z0, the length of the customization string (in bits) encoded as a u64 s.e0 ^= u64(cs.len) << 3 - ascon_pnr(mut s, 12) + ascon_pnr(mut s, ascon_prnd_12) // absorb the customization string mut zlen := cs.len @@ -313,7 +308,7 @@ fn cxof128_absorb_custom_string(mut s State, cs []u8) { for zlen >= block_size { block := unsafe { cs[zidx..zidx + block_size] } s.e0 ^= binary.little_endian_u64(block) - ascon_pnr(mut s, 12) + ascon_pnr(mut s, ascon_prnd_12) // updates a index zlen -= block_size @@ -323,5 +318,5 @@ fn cxof128_absorb_custom_string(mut s State, cs []u8) { last_block := unsafe { cs[zidx..] } s.e0 ^= load_bytes(last_block, last_block.len) s.e0 ^= pad(last_block.len) - ascon_pnr(mut s, 12) + ascon_pnr(mut s, ascon_prnd_12) }