mirror of
https://github.com/vlang/v.git
synced 2025-09-13 14:32:26 +03:00
x.crypto: add a new ascon
cryptographic module, based on https://doi.org/10.6028/NIST.SP.800-232 (Lightweight Cryptography Standards for Constrained Devices) (#25260)
Some checks are pending
Graphics CI / gg-regressions (push) Waiting to run
vlib modules CI / build-module-docs (push) Waiting to run
native backend CI / native-backend-ubuntu (push) Waiting to run
native backend CI / native-backend-windows (push) Waiting to run
Shy and PV CI / v-compiles-puzzle-vibes (push) Waiting to run
Sanitized CI / sanitize-address-msvc (push) Waiting to run
Sanitized CI / sanitize-undefined-clang (push) Waiting to run
Sanitized CI / sanitize-undefined-gcc (push) Waiting to run
Sanitized CI / tests-sanitize-address-clang (push) Waiting to run
Sanitized CI / sanitize-address-gcc (push) Waiting to run
Sanitized CI / sanitize-memory-clang (push) Waiting to run
sdl CI / v-compiles-sdl-examples (push) Waiting to run
Time CI / time-linux (push) Waiting to run
Time CI / time-macos (push) Waiting to run
Time CI / time-windows (push) Waiting to run
toml CI / toml-module-pass-external-test-suites (push) Waiting to run
Tools CI / tools-linux (gcc) (push) Waiting to run
Tools CI / tools-linux (clang) (push) Waiting to run
Tools CI / tools-linux (tcc) (push) Waiting to run
Tools CI / tools-macos (clang) (push) Waiting to run
Tools CI / tools-windows (gcc) (push) Waiting to run
Tools CI / tools-windows (msvc) (push) Waiting to run
Tools CI / tools-windows (tcc) (push) Waiting to run
Tools CI / tools-docker-ubuntu-musl (push) Waiting to run
vab CI / vab-compiles-v-examples (push) Waiting to run
vab CI / v-compiles-os-android (push) Waiting to run
wasm backend CI / wasm-backend (ubuntu-22.04) (push) Waiting to run
wasm backend CI / wasm-backend (windows-2022) (push) Waiting to run
Some checks are pending
Graphics CI / gg-regressions (push) Waiting to run
vlib modules CI / build-module-docs (push) Waiting to run
native backend CI / native-backend-ubuntu (push) Waiting to run
native backend CI / native-backend-windows (push) Waiting to run
Shy and PV CI / v-compiles-puzzle-vibes (push) Waiting to run
Sanitized CI / sanitize-address-msvc (push) Waiting to run
Sanitized CI / sanitize-undefined-clang (push) Waiting to run
Sanitized CI / sanitize-undefined-gcc (push) Waiting to run
Sanitized CI / tests-sanitize-address-clang (push) Waiting to run
Sanitized CI / sanitize-address-gcc (push) Waiting to run
Sanitized CI / sanitize-memory-clang (push) Waiting to run
sdl CI / v-compiles-sdl-examples (push) Waiting to run
Time CI / time-linux (push) Waiting to run
Time CI / time-macos (push) Waiting to run
Time CI / time-windows (push) Waiting to run
toml CI / toml-module-pass-external-test-suites (push) Waiting to run
Tools CI / tools-linux (gcc) (push) Waiting to run
Tools CI / tools-linux (clang) (push) Waiting to run
Tools CI / tools-linux (tcc) (push) Waiting to run
Tools CI / tools-macos (clang) (push) Waiting to run
Tools CI / tools-windows (gcc) (push) Waiting to run
Tools CI / tools-windows (msvc) (push) Waiting to run
Tools CI / tools-windows (tcc) (push) Waiting to run
Tools CI / tools-docker-ubuntu-musl (push) Waiting to run
vab CI / vab-compiles-v-examples (push) Waiting to run
vab CI / v-compiles-os-android (push) Waiting to run
wasm backend CI / wasm-backend (ubuntu-22.04) (push) Waiting to run
wasm backend CI / wasm-backend (windows-2022) (push) Waiting to run
This commit is contained in:
parent
e650d6b7f0
commit
f073169177
15 changed files with 2147 additions and 0 deletions
15
vlib/x/crypto/ascon/README.md
Normal file
15
vlib/x/crypto/ascon/README.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
# ascon
|
||||
|
||||
`ascon` is a 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:
|
||||
- `Ascon-Hash256`, Ascon-based hashing implementation that produces 256-bits output.
|
||||
- `Ascon-XOF128`, Ascon-based eXtendible 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.
|
464
vlib/x/crypto/ascon/aead128.v
Normal file
464
vlib/x/crypto/ascon/aead128.v
Normal file
|
@ -0,0 +1,464 @@
|
|||
// 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,
|
||||
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]
|
||||
pub 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.
|
||||
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)
|
||||
}
|
247
vlib/x/crypto/ascon/aead128_test.v
Normal file
247
vlib/x/crypto/ascon/aead128_test.v
Normal file
|
@ -0,0 +1,247 @@
|
|||
// Copyright ©2025 blackshirt.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
//
|
||||
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.
|
||||
// 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 := ascon.encrypt(key, nonce, ad, pt)!
|
||||
assert out == ct
|
||||
|
||||
msg := ascon.decrypt(key, nonce, ad, ct)!
|
||||
assert msg == pt
|
||||
|
||||
// Work with object-based Cipher
|
||||
mut c := ascon.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'
|
||||
},
|
||||
]
|
127
vlib/x/crypto/ascon/ascon.v
Normal file
127
vlib/x/crypto/ascon/ascon.v
Normal file
|
@ -0,0 +1,127 @@
|
|||
// Copyright ©2025 blackshirt.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
//
|
||||
module ascon
|
||||
|
||||
// max_nr_perm is the maximum number of permutations round supported on this module.
|
||||
// The previous Ascon submission defined three Ascon permutations with 6, 8, and 12 rounds.
|
||||
// The NIST SP 800-232 standard specifies additional Ascon permutations by providing round
|
||||
// constants for up to 16 rounds to accommodate potential functionality extensions in the future.
|
||||
const max_nr_perm = 16
|
||||
|
||||
// The constants to derive round constants of the Ascon permutations
|
||||
// See Table 5. of NIST SP 800-232 docs
|
||||
//
|
||||
// 0 0x000000000000003c 8 0x00000000000000b4
|
||||
// 1 0x000000000000002d 9 0x00000000000000a5
|
||||
// 2 0x000000000000001e 10 0x0000000000000096
|
||||
// 3 0x000000000000000f 11 0x0000000000000087
|
||||
// 4 0x00000000000000f0 12 0x0000000000000078
|
||||
// 5 0x00000000000000e1 13 0x0000000000000069
|
||||
// 6 0x00000000000000d2 14 0x000000000000005a
|
||||
// 7 0x00000000000000c3 15 0x000000000000004b
|
||||
//
|
||||
// We use u8 instead, since the first 56 bits of the constants are zero
|
||||
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
|
||||
@[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')
|
||||
}
|
||||
for i := max_nr_perm - nr; i < max_nr_perm; i++ {
|
||||
ascon_perm(mut s, rnc[i])
|
||||
}
|
||||
}
|
||||
|
||||
// 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]
|
||||
struct State {
|
||||
mut:
|
||||
e0 u64
|
||||
e1 u64
|
||||
e2 u64
|
||||
e3 u64
|
||||
e4 u64
|
||||
}
|
||||
|
||||
// clone returns a clone of current state.
|
||||
@[inline]
|
||||
fn clone_state(s State) State {
|
||||
return State{
|
||||
e0: s.e0
|
||||
e1: s.e1
|
||||
e2: s.e2
|
||||
e3: s.e3
|
||||
e4: s.e4
|
||||
}
|
||||
}
|
||||
|
||||
// reset this state with default value
|
||||
@[inline]
|
||||
fn reset_state(mut s State) {
|
||||
s.e0 = 0
|
||||
s.e1 = 0
|
||||
s.e2 = 0
|
||||
s.e3 = 0
|
||||
s.e4 = 0
|
||||
}
|
71
vlib/x/crypto/ascon/ascon_test.v
Normal file
71
vlib/x/crypto/ascon/ascon_test.v
Normal file
|
@ -0,0 +1,71 @@
|
|||
// Copyright ©2025 blackshirt.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
//
|
||||
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)
|
||||
e1: 0xef0123456789abcd
|
||||
e2: 0xcdef0123456789ab
|
||||
e3: 0xabcdef0123456789
|
||||
e4: 0x89abcdef01234567
|
||||
}
|
||||
ascon_pnr(mut s, 6)
|
||||
assert s.e0 == u64(0xc27b505c635eb07f)
|
||||
assert s.e1 == u64(0xd388f5d2a72046fa)
|
||||
assert s.e2 == u64(0x9e415c204d7b15e7)
|
||||
assert s.e3 == u64(0xce0d71450fe44581)
|
||||
assert s.e4 == u64(0xdd7c5fef57befe48)
|
||||
}
|
||||
|
||||
fn test_ascon_round_p8() {
|
||||
mut s := State{
|
||||
e0: u64(0x0123456789abcdef)
|
||||
e1: 0xef0123456789abcd
|
||||
e2: 0xcdef0123456789ab
|
||||
e3: 0xabcdef0123456789
|
||||
e4: 0x89abcdef01234567
|
||||
}
|
||||
ascon_pnr(mut s, 8)
|
||||
assert s.e0 == u64(0x67ed228272f46eee)
|
||||
assert s.e1 == u64(0x80bc0b097aad7944)
|
||||
assert s.e2 == u64(0x2fa599382c6db215)
|
||||
assert s.e3 == u64(0x368133fae2f7667a)
|
||||
assert s.e4 == u64(0x28cefb195a7c651c)
|
||||
}
|
||||
|
||||
fn test_ascon_round_p12() {
|
||||
mut s := State{
|
||||
e0: u64(0x0123456789abcdef)
|
||||
e1: 0xef0123456789abcd
|
||||
e2: 0xcdef0123456789ab
|
||||
e3: 0xabcdef0123456789
|
||||
e4: 0x89abcdef01234567
|
||||
}
|
||||
ascon_pnr(mut s, 12)
|
||||
assert s.e0 == u64(0x206416dfc624bb14)
|
||||
assert s.e1 == u64(0x1b0c47a601058aab)
|
||||
assert s.e2 == u64(0x8934cfc93814cddd)
|
||||
assert s.e3 == u64(0xa9738d287a748e4b)
|
||||
assert s.e4 == u64(0xddd934f058afc7e1)
|
||||
}
|
145
vlib/x/crypto/ascon/cxof_test.v
Normal file
145
vlib/x/crypto/ascon/cxof_test.v
Normal file
|
@ -0,0 +1,145 @@
|
|||
// Copyright ©2025 blackshirt.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
//
|
||||
import arrays
|
||||
import encoding.hex
|
||||
import x.crypto.ascon
|
||||
|
||||
struct CxofTest {
|
||||
count int
|
||||
msg string
|
||||
z string
|
||||
md string
|
||||
}
|
||||
|
||||
// Known Answer Tests (KAT) with 512-bits output for Ascon-CXOF128
|
||||
fn test_cxof128() ! {
|
||||
for item in cxof128_test_data {
|
||||
msg := hex.decode(item.msg)!
|
||||
z := hex.decode(item.z)!
|
||||
md := hex.decode(item.md)!
|
||||
|
||||
out := ascon.cxof128_64(msg, z)!
|
||||
assert md == out
|
||||
|
||||
// With Xof128Digest opaque
|
||||
mut cx := ascon.new_cxof128(ascon.default_xof_size, z)!
|
||||
md0 := cx.sum(msg)
|
||||
assert md0 == md
|
||||
|
||||
// test .read()
|
||||
cx.reset()
|
||||
mut dst := []u8{len: 64}
|
||||
_ := cx.write(msg)!
|
||||
nr := cx.read(mut dst)!
|
||||
assert nr == 64
|
||||
assert dst == md
|
||||
|
||||
// with chunked messages
|
||||
cx.reset()
|
||||
chunks := arrays.chunk[u8](msg, 20)
|
||||
mut tot := 0
|
||||
for chunk in chunks {
|
||||
n := cx.write(chunk)!
|
||||
tot += n
|
||||
}
|
||||
assert msg.len == tot
|
||||
|
||||
chunked_md := cx.sum([]u8{})
|
||||
assert chunked_md == md
|
||||
}
|
||||
}
|
||||
|
||||
const cxof128_test_data = [
|
||||
CxofTest{
|
||||
count: 1
|
||||
msg: ''
|
||||
z: ''
|
||||
md: '4f50159ef70bb3dad8807e034eaebd44c4fa2cbbc8cf1f05511ab66cdcc529905ca12083fc186ad899b270b1473dc5f7ec88d1052082dcdfe69fb75d269e7b74'
|
||||
},
|
||||
CxofTest{
|
||||
count: 2
|
||||
msg: ''
|
||||
z: '10'
|
||||
md: '0c93a483e7d574d49fe52cce03ee646117977d57a8aa57704ab4daf44b501430ff6ac11a5d1fd6f2154b5c65728268270c8bb578508487b8965718ada6272fd6'
|
||||
},
|
||||
CxofTest{
|
||||
count: 3
|
||||
msg: ''
|
||||
z: '1011'
|
||||
md: 'd1106c7622e79fe955bd9d79e03b918e770fe0e0cddde28beb924b02c5fc936b33acca299c89eca5d71886cbbfa4d54a21c55fde2b679f5e2488063a1719dc32'
|
||||
},
|
||||
CxofTest{
|
||||
count: 4
|
||||
msg: ''
|
||||
z: '101112'
|
||||
md: '6a53a6dbf1bec15a79ce1214ff76a4d6bb16f60cfa56bf2c218aec5e160372117d2a2e647b128624e9b1d2259faf083f2bedd0fc751a2e2ff268d0ee026b6449'
|
||||
},
|
||||
CxofTest{
|
||||
count: 5
|
||||
msg: ''
|
||||
z: '10111213'
|
||||
md: 'cc333dd5b4ea61abe4376d61058b16df5eda7056299865ed7d25f43ac5b8541574608bd95ab0a3c3b74abd4abf9e50e63be6efe1b836b58595d8c47705c4dffb'
|
||||
},
|
||||
CxofTest{
|
||||
count: 6
|
||||
msg: ''
|
||||
z: '1011121314'
|
||||
md: '8ee69d28b1bf3eafacf1e169fd10b6b7b72a7e2aaf0625e8e7c00153833b7224ed8c8c127b9808352c5647f9e862958d6de9eb93c4a236d59ecd84665e7164d9'
|
||||
},
|
||||
CxofTest{
|
||||
count: 7
|
||||
msg: ''
|
||||
z: '101112131415'
|
||||
md: '3681695c40d83f60b401ecfa14bc03780ad474438f74b823eec9f0d5a375c13488803d3b4b8c8d4acd03186039f905fa15c7860dd0e9d566f31cd9e5822a937c'
|
||||
},
|
||||
CxofTest{
|
||||
count: 8
|
||||
msg: ''
|
||||
z: '10111213141516'
|
||||
md: '717a9ba3b3c00f4078572b2d3fb3f0a86d45f70bc4e1cd89cb7a952bfa64162383735534ecbe0a7e62e7592cf447404db0361d98c2237245688ead15c05ae59b'
|
||||
},
|
||||
CxofTest{
|
||||
count: 9
|
||||
msg: ''
|
||||
z: '1011121314151617'
|
||||
md: '61324766441dd6c11e1736bad1d2185820885ed76fe2ce537775a6e855eeafd2a6651b5e862a44982765f8b4c7cbe9c8b354f569ead6abc62cc9b7cdd72e0cb3'
|
||||
},
|
||||
CxofTest{
|
||||
count: 10
|
||||
msg: ''
|
||||
z: '101112131415161718'
|
||||
md: '32fde6b9d290f56fc74aac9368f32c69973e1bab35d96118db7181aae577687673c01a9e35327aded556987eed3441d4f42ec36b0c198498d9e7f357b948d560'
|
||||
},
|
||||
CxofTest{
|
||||
count: 11
|
||||
msg: ''
|
||||
z: '10111213141516171819'
|
||||
md: '690fc893055910d7d1d38055cf5589bbbe6b82bf175847ab3e0fd9a578b044dcb42be2932067eaa563a09e634581f34c2b4cfb38e1b06841b45b7b34c746d6dd'
|
||||
},
|
||||
CxofTest{
|
||||
count: 12
|
||||
msg: ''
|
||||
z: '101112131415161718191a'
|
||||
md: 'ecdab5b15324f99a1709be26fc329d305bd475e5f39bc2b63788792166ad08fe720ccd14e0a4de7d83ede1c7744929dc509c73748d6661a3d3215995357d3f88'
|
||||
},
|
||||
CxofTest{
|
||||
count: 13
|
||||
msg: ''
|
||||
z: '101112131415161718191a1b'
|
||||
md: 'ec2ba3309cfa6d6d0b581374e7c020ad17c330ea2b76d48724a415dceca3859c11146c2f64e52e44d27b1c44fd27476990a2e959b9998827a527a7e69089895f'
|
||||
},
|
||||
CxofTest{
|
||||
count: 14
|
||||
msg: ''
|
||||
z: '101112131415161718191a1b1c'
|
||||
md: '659a59bc7fdece2f1bafa9f1bfb4c262043f74da550f85c902c9c4302adfcf898fbcd74c92d67bded153137e0d32ccba88767354be99103dfeb59c686ca98dea'
|
||||
},
|
||||
CxofTest{
|
||||
count: 15
|
||||
msg: ''
|
||||
z: '101112131415161718191a1b1c1d'
|
||||
md: '17756044b742028b508d797c2c75a0722dde763c59d3fe5f70435b82faca5a80fe9c5ec9f3c59072ae48f37a241281c25d2e903c9d9290128265f1fe92b80bed'
|
||||
},
|
||||
]
|
135
vlib/x/crypto/ascon/digest.v
Normal file
135
vlib/x/crypto/ascon/digest.v
Normal file
|
@ -0,0 +1,135 @@
|
|||
// Copyright ©2025 blackshirt.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
//
|
||||
// Common helpers used by Ascon-Hash256, Ascon-XOF128 and Ascon-CXOF128
|
||||
module ascon
|
||||
|
||||
import encoding.binary
|
||||
|
||||
// The Digest is an internal structure used by Ascon-hashing variants.
|
||||
@[noinit]
|
||||
struct Digest {
|
||||
State
|
||||
mut:
|
||||
// buffer for storing unprocessed data for streaming-way
|
||||
buf []u8 = []u8{len: block_size}
|
||||
// length of leftover unprocessed bytes on the digest buffer
|
||||
length int
|
||||
|
||||
// internal flag
|
||||
done bool
|
||||
}
|
||||
|
||||
// finish finalizes state by writing last block in the Digest internal buffer.
|
||||
// and consequently updates Digest state.
|
||||
@[direct_array_access; inline]
|
||||
fn (mut d Digest) finish() {
|
||||
if d.length >= d.buf.len {
|
||||
panic('Digest.finish: internal error')
|
||||
}
|
||||
// Process for the last block stored on the internal buffer
|
||||
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)
|
||||
|
||||
// zeroing Digest buffer
|
||||
d.length = 0
|
||||
unsafe { d.buf.reset() }
|
||||
|
||||
// After finishing phase, we can't write data anymore into state
|
||||
d.done = true
|
||||
}
|
||||
|
||||
// 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.
|
||||
// If its on there, try to empty the buffer.
|
||||
if d.length > 0 {
|
||||
// There are bytes in the d.buf, append it with bytes taken from msg
|
||||
if d.length + msg.len >= block_size {
|
||||
n := copy(mut d.buf[d.length..], msg)
|
||||
msg = msg[n..]
|
||||
d.length += n
|
||||
// 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)
|
||||
// reset the internal buffer
|
||||
d.length = 0
|
||||
d.buf.reset()
|
||||
}
|
||||
} else {
|
||||
// Otherwise, still fit to buffer, but nof fully fills the d.buf
|
||||
// just stores into buffer without processing
|
||||
n := copy(mut d.buf[d.length..], msg)
|
||||
msg = msg[n..]
|
||||
d.length += n
|
||||
}
|
||||
}
|
||||
// process for full block
|
||||
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)
|
||||
}
|
||||
// If there are partial block, just stored into buffer.
|
||||
if msg.len > 0 {
|
||||
n := copy(mut d.buf[d.length..], msg)
|
||||
msg = msg[n..]
|
||||
d.length += n
|
||||
}
|
||||
return msg_.len - msg.len
|
||||
}
|
||||
}
|
||||
|
||||
// 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 > 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 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)
|
||||
pos += block_size
|
||||
clen -= block_size
|
||||
}
|
||||
// final output, the resulting hash is the concatenation of hash blocks
|
||||
store_bytes(mut dst[pos..], d.State.e0, clen)
|
||||
pos += clen
|
||||
|
||||
// for make sure, assert it here
|
||||
assert pos == dst.len
|
||||
|
||||
return pos
|
||||
}
|
42
vlib/x/crypto/ascon/examples/use_of_cxof.v
Normal file
42
vlib/x/crypto/ascon/examples/use_of_cxof.v
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Copyright ©2025 blackshirt.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
//
|
||||
import encoding.hex
|
||||
import x.crypto.ascon
|
||||
|
||||
// The material was generated from https://hashing.tools/ascon/ascon-hash
|
||||
fn main() {
|
||||
msg := 'Example of CXof128 message'.bytes()
|
||||
cs := 'custom-string-cxof128'.bytes()
|
||||
|
||||
// expected output generated from the tool, with 32, 64 dan 75-bytes output
|
||||
digest32 := hex.decode('d71492b816b1ac27f53f9c13be45c1d2d0530b8dde7fde8d34cb563f79b3d3d3')!
|
||||
digest64 := hex.decode('d71492b816b1ac27f53f9c13be45c1d2d0530b8dde7fde8d34cb563f79b3d3d3601d03474ec6fe1f6b8dc5dd79bea20aff4c95ca3549202b1aaeb9e66b5df398')!
|
||||
digest75 := hex.decode('d71492b816b1ac27f53f9c13be45c1d2d0530b8dde7fde8d34cb563f79b3d3d3601d03474ec6fe1f6b8dc5dd79bea20aff4c95ca3549202b1aaeb9e66b5df3985a88fd8bce0f9570962321')!
|
||||
|
||||
out32 := ascon.cxof128(msg, 32, cs)!
|
||||
out64 := ascon.cxof128(msg, 64, cs)!
|
||||
out75 := ascon.cxof128(msg, 75, cs)!
|
||||
dump(out32 == digest32) // out32 == digest32: true
|
||||
dump(out64 == digest64) // out64 == digest64: true
|
||||
dump(out75 == digest75) // out75 == digest75: true
|
||||
|
||||
// With object based
|
||||
mut x := ascon.new_cxof128(32, cs)!
|
||||
s32 := x.sum(msg)
|
||||
dump(s32 == digest32) // s32 == digest32: true
|
||||
|
||||
// with sized output
|
||||
x.reset()
|
||||
_ := x.write(msg)!
|
||||
mut b64 := []u8{len: 64}
|
||||
_ := x.read(mut b64)!
|
||||
dump(b64 == digest64) // b64 == digest64: true
|
||||
|
||||
x.reset()
|
||||
_ := x.write(msg)!
|
||||
mut b75 := []u8{len: 75}
|
||||
_ := x.read(mut b75)!
|
||||
dump(b75 == digest75) // b75 == digest75: true
|
||||
}
|
16
vlib/x/crypto/ascon/examples/use_of_hash26.v
Normal file
16
vlib/x/crypto/ascon/examples/use_of_hash26.v
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright ©2025 blackshirt.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
//
|
||||
import encoding.hex
|
||||
import x.crypto.ascon
|
||||
|
||||
// The material was generated from https://hashing.tools/ascon/ascon-hash
|
||||
fn main() {
|
||||
msg := 'Example of hash256 message'.bytes()
|
||||
// expected output generated from the tool
|
||||
digest := hex.decode('0889515a9cfe28ab3a43882884d5933bb74aa09f3c767f8c699b5d7114811340')!
|
||||
|
||||
out := ascon.sum256(msg)
|
||||
dump(out == digest) // out == digest: true
|
||||
}
|
40
vlib/x/crypto/ascon/examples/use_of_xof.v
Normal file
40
vlib/x/crypto/ascon/examples/use_of_xof.v
Normal file
|
@ -0,0 +1,40 @@
|
|||
// Copyright ©2025 blackshirt.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
//
|
||||
import encoding.hex
|
||||
import x.crypto.ascon
|
||||
|
||||
// The material was generated from https://hashing.tools/ascon/ascon-hash
|
||||
fn main() {
|
||||
msg := 'Example of Xof128 message'.bytes()
|
||||
// expected output generated from the tool, with 32, 64 dan 75-bytes output
|
||||
digest32 := hex.decode('424caaf68eb94aa251536bb6b565c0695b8944f932d011b1049df85b7f27d2f3')!
|
||||
digest64 := hex.decode('424caaf68eb94aa251536bb6b565c0695b8944f932d011b1049df85b7f27d2f3bf704643a643c3f2dcfb1e0bc73ec55781b5283966d2d1da85d89794ca5c292e')!
|
||||
digest75 := hex.decode('424caaf68eb94aa251536bb6b565c0695b8944f932d011b1049df85b7f27d2f3bf704643a643c3f2dcfb1e0bc73ec55781b5283966d2d1da85d89794ca5c292e36260815a8f10088e3804c')!
|
||||
|
||||
out32 := ascon.xof128(msg, 32)!
|
||||
out64 := ascon.xof128(msg, 64)!
|
||||
out75 := ascon.xof128(msg, 75)!
|
||||
dump(out32 == digest32) // out32 == digest32: true
|
||||
dump(out64 == digest64) // out64 == digest64: true
|
||||
dump(out75 == digest75) // out75 == digest75: true
|
||||
|
||||
// With object based
|
||||
mut x := ascon.new_xof128(32)
|
||||
s32 := x.sum(msg)
|
||||
dump(s32 == digest32) // s32 == digest32: true
|
||||
|
||||
// with sized output
|
||||
x.reset()
|
||||
_ := x.write(msg)!
|
||||
mut b64 := []u8{len: 64}
|
||||
_ := x.read(mut b64)!
|
||||
dump(b64 == digest64) // b64 == digest64: true
|
||||
|
||||
x.reset()
|
||||
_ := x.write(msg)!
|
||||
mut b75 := []u8{len: 75}
|
||||
_ := x.read(mut b75)!
|
||||
dump(b75 == digest75) // b75 == digest75: true
|
||||
}
|
130
vlib/x/crypto/ascon/hash.v
Normal file
130
vlib/x/crypto/ascon/hash.v
Normal file
|
@ -0,0 +1,130 @@
|
|||
// Copyright ©2025 blackshirt.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
//
|
||||
// This file implement hashing routines based on Ascon-Hash256 schema defined in NIST SP 800-232 standard,
|
||||
// Ascon-Hash256 hashing produces 256-bits output.
|
||||
module ascon
|
||||
|
||||
// block_size is the size (rate) of Ascon-Hash256, Ascon-XOF128 and Ascon-CXOF128 operates on.
|
||||
const block_size = 8
|
||||
|
||||
// hash256_size is the length of the Ascon-Hash256 checksum output, in bytes
|
||||
const hash256_size = 32
|
||||
|
||||
// hash256_initial_state is a precomputed value for Ascon-Hash256 state.
|
||||
//
|
||||
// The 320-bit internal state of Ascon-Hash256 is initialized with the
|
||||
// concatenation of the 64-bit 𝐼𝑉 = 0x0000080100cc0002 and 256 zeroes, followed
|
||||
// by the 𝐴𝑠𝑐𝑜𝑛-𝑝[12] permutation as S ← 𝐴𝑠𝑐𝑜𝑛-𝑝[12](𝐼𝑉 ∥0256).
|
||||
//
|
||||
// s.e0 = 0x0000080100cc0002
|
||||
// s.e1 = 0
|
||||
// s.e2 = 0
|
||||
// s.e3 = 0
|
||||
// s.e4 = 0
|
||||
// ascon_pnr(mut s, 12)
|
||||
//
|
||||
// Above step can be replaced with precomputed value to reduce runtime computations.
|
||||
// See the detail on the NIST SP 800-232 standard on Sec A.3. Precomputation
|
||||
// 𝑆0 ← 0x9b1e5494e934d681
|
||||
// 𝑆1 ← 0x4bc3a01e333751d2
|
||||
// 𝑆2 ← 0xae65396c6b34b81a
|
||||
// 𝑆3 ← 0x3c7fd4a4d56a4db3
|
||||
// 𝑆4 ← 0x1a5c464906c5976d
|
||||
//
|
||||
const hash256_initial_state = State{
|
||||
e0: u64(0x9b1e5494e934d681)
|
||||
e1: 0x4bc3a01e333751d2
|
||||
e2: 0xae65396c6b34b81a
|
||||
e3: 0x3c7fd4a4d56a4db3
|
||||
e4: 0x1a5c464906c5976d
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Hash256 is an opaque provides an implementation of Ascon-Hash256 from NIST.SP.800-232 standard.
|
||||
// Its implements `hash.Hash` interface.
|
||||
@[noinit]
|
||||
pub struct Hash256 {
|
||||
Digest
|
||||
}
|
||||
|
||||
// new_hash256 creates a new Ascon-Hash256 instance.
|
||||
pub fn new_hash256() &Hash256 {
|
||||
return &Hash256{
|
||||
Digest: Digest{
|
||||
State: hash256_initial_state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// size returns an underlying size of Hash256 checksum, ie, 32-bytes
|
||||
pub fn (h &Hash256) size() int {
|
||||
return hash256_size
|
||||
}
|
||||
|
||||
// block_size returns an underlying Hash256 block size operates on, ie, 8-bytes
|
||||
pub fn (h &Hash256) block_size() int {
|
||||
return block_size
|
||||
}
|
||||
|
||||
// reset resets and reinit internal Hash256 state into default initialized state.
|
||||
pub fn (mut h Hash256) reset() {
|
||||
h.Digest.State = hash256_initial_state
|
||||
unsafe { h.Digest.buf.reset() }
|
||||
h.Digest.length = 0
|
||||
h.Digest.done = false
|
||||
}
|
||||
|
||||
// free releases out the resources taken by the `h`. Dont use x after .free call.
|
||||
@[unsafe]
|
||||
pub fn (mut h Hash256) free() {
|
||||
$if prealloc {
|
||||
return
|
||||
}
|
||||
unsafe {
|
||||
h.Digest.buf.free()
|
||||
}
|
||||
// Mark it as unusable
|
||||
h.Digest.done = true
|
||||
}
|
||||
|
||||
// write writes out the content of message and updates internal Hash256 state.
|
||||
pub fn (mut h Hash256) write(msg []u8) !int {
|
||||
if h.Digest.done {
|
||||
panic('Digest: writing after done ')
|
||||
}
|
||||
return h.absorb(msg)
|
||||
}
|
||||
|
||||
// clone returns the clone of the current Hash256
|
||||
fn (h &Hash256) clone() &Hash256 {
|
||||
digest := Digest{
|
||||
State: h.Digest.State
|
||||
buf: h.Digest.buf.clone()
|
||||
length: h.Digest.length
|
||||
done: h.Digest.done
|
||||
}
|
||||
return &Hash256{digest}
|
||||
}
|
||||
|
||||
// sum returns an Ascon-Hash256 checksum of the bytes in data.
|
||||
pub fn (mut h Hash256) sum(data []u8) []u8 {
|
||||
// working on the clone of the h, so we can keep writing
|
||||
mut h0 := h.clone()
|
||||
_ := h0.write(data) or { panic(err) }
|
||||
h0.Digest.finish()
|
||||
mut dst := []u8{len: hash256_size}
|
||||
_ := h0.Digest.squeeze(mut dst)
|
||||
h0.reset()
|
||||
return dst
|
||||
}
|
169
vlib/x/crypto/ascon/hash_test.v
Normal file
169
vlib/x/crypto/ascon/hash_test.v
Normal file
|
@ -0,0 +1,169 @@
|
|||
// 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 arrays
|
||||
import encoding.hex
|
||||
|
||||
struct HashTest {
|
||||
count int
|
||||
msg string
|
||||
md string
|
||||
}
|
||||
|
||||
// This test material mostly taken and adapted from Known-Answer-Test (KAT) of reference implementation.
|
||||
// See at https://github.com/ascon/ascon-c/blob/main/crypto_hash/asconhash256/LWC_HASH_KAT_128_256.txt
|
||||
fn test_hash256_sum_chunked() ! {
|
||||
item := HashTest{
|
||||
count: 500
|
||||
msg: '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2'
|
||||
md: '8174F5E8DFC9441DBB7A183D56F431B1AEF9513E747A3878BB806BAE27655E3E'
|
||||
}
|
||||
msg := hex.decode(item.msg)!
|
||||
md := hex.decode(item.md)!
|
||||
// One shoot sum256 function
|
||||
md0 := sum256(msg)
|
||||
assert md0 == md
|
||||
|
||||
// Digest based
|
||||
mut h := new_hash256()
|
||||
expected_md := h.sum(msg)
|
||||
assert expected_md == md
|
||||
|
||||
// with multistep
|
||||
h.reset()
|
||||
_ := h.Digest.absorb(msg)
|
||||
h.Digest.finish()
|
||||
mut dst := []u8{len: hash256_size}
|
||||
h.Digest.squeeze(mut dst)
|
||||
assert dst == md
|
||||
|
||||
// with splitted message
|
||||
msg0 := msg[0..200]
|
||||
msg1 := msg[200..400]
|
||||
msg2 := msg[400..]
|
||||
h.reset()
|
||||
_ := h.Digest.absorb(msg0)
|
||||
_ := h.Digest.absorb(msg1)
|
||||
_ := h.Digest.absorb(msg2)
|
||||
h.Digest.finish()
|
||||
h.Digest.squeeze(mut dst)
|
||||
assert dst == md
|
||||
|
||||
// with arrays chunk
|
||||
h.reset()
|
||||
chunks := arrays.chunk[u8](msg, 200)
|
||||
mut n := 0
|
||||
for chunk in chunks {
|
||||
n += h.Digest.absorb(chunk)
|
||||
}
|
||||
assert n == msg.len
|
||||
h.Digest.finish()
|
||||
h.Digest.squeeze(mut dst)
|
||||
assert dst == md
|
||||
|
||||
// with sum
|
||||
h.reset()
|
||||
for chunk in chunks {
|
||||
n += h.write(chunk)!
|
||||
}
|
||||
chunked_md := h.sum([]u8{})
|
||||
assert chunked_md == md
|
||||
}
|
||||
|
||||
fn test_hash256_sum_kat() ! {
|
||||
for item in ascon_hash256_test_data {
|
||||
msg := hex.decode(item.msg)!
|
||||
md := hex.decode(item.md)!
|
||||
out := sum256(msg)
|
||||
assert out == md
|
||||
|
||||
// work with Digest opaque
|
||||
mut h := new_hash256()
|
||||
exp_md := h.sum(msg)
|
||||
assert exp_md == md
|
||||
|
||||
// Lets work in streaming-way
|
||||
chunks := arrays.chunk[u8](msg, 7)
|
||||
h.reset()
|
||||
mut tot := 0
|
||||
for chunk in chunks {
|
||||
n := h.write(chunk)!
|
||||
tot += n
|
||||
}
|
||||
assert msg.len == tot
|
||||
|
||||
chunked_md := h.sum([]u8{})
|
||||
assert chunked_md == md
|
||||
}
|
||||
}
|
||||
|
||||
const ascon_hash256_test_data = [
|
||||
HashTest{
|
||||
count: 1
|
||||
msg: ''
|
||||
md: '0b3be5850f2f6b98caf29f8fdea89b64a1fa70aa249b8f839bd53baa304d92b2'
|
||||
},
|
||||
HashTest{
|
||||
count: 2
|
||||
msg: '00'
|
||||
md: '0728621035af3ed2bca03bf6fde900f9456f5330e4b5ee23e7f6a1e70291bc80'
|
||||
},
|
||||
HashTest{
|
||||
count: 3
|
||||
msg: '0001'
|
||||
md: '6115e7c9c4081c2797fc8fe1bc57a836afa1c5381e556dd583860ca2dfb48dd2'
|
||||
},
|
||||
HashTest{
|
||||
count: 4
|
||||
msg: '000102'
|
||||
md: '265ab89a609f5a05dca57e83fbba700f9a2d2c4211ba4cc9f0a1a369e17b915c'
|
||||
},
|
||||
HashTest{
|
||||
count: 5
|
||||
msg: '00010203'
|
||||
md: 'd7e4c7ed9b8a325cd08b9ef259f8877054ecd8304fe1b2d7fd847137df6727ee'
|
||||
},
|
||||
HashTest{
|
||||
count: 6
|
||||
msg: '0001020304'
|
||||
md: 'c7b28962d4f5c2211f466f83d3c57ae1504387e2a326949747a8376447a6bb51'
|
||||
},
|
||||
HashTest{
|
||||
count: 7
|
||||
msg: '000102030405'
|
||||
md: 'dc0c6748af8ffe63e1084aa3e5786a194685c88c21348b29e184fb50409703bc'
|
||||
},
|
||||
HashTest{
|
||||
count: 8
|
||||
msg: '00010203040506'
|
||||
md: '3e4d273ba69b3b9c53216107e88b75cdbeedbcbf8faf0219c3928ab62b116577'
|
||||
},
|
||||
HashTest{
|
||||
count: 9
|
||||
msg: '0001020304050607'
|
||||
md: 'b88e497ae8e6fb641b87ef622eb8f2fca0ed95383f7ffebe167acf1099ba764f'
|
||||
},
|
||||
HashTest{
|
||||
count: 10
|
||||
msg: '000102030405060708'
|
||||
md: '94269C30E0296E1EC86655041841823EFA1927F520FD58C8E9BCE6197878C1A6'
|
||||
},
|
||||
HashTest{
|
||||
count: 500
|
||||
msg: '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2'
|
||||
md: '8174F5E8DFC9441DBB7A183D56F431B1AEF9513E747A3878BB806BAE27655E3E'
|
||||
},
|
||||
HashTest{
|
||||
count: 501
|
||||
msg: '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3'
|
||||
md: 'E73D4DDB9D248BF2C0F8D49892D7455A4C3053153DE7F79BA4487C7D823F605C'
|
||||
},
|
||||
HashTest{
|
||||
count: 502
|
||||
msg: '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4'
|
||||
md: 'B73FA04079263F6A733B67466552784B436138F41F80B72C4D5D03934B72207D'
|
||||
},
|
||||
]
|
107
vlib/x/crypto/ascon/util.v
Normal file
107
vlib/x/crypto/ascon/util.v
Normal file
|
@ -0,0 +1,107 @@
|
|||
// Copyright ©2025 blackshirt.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
//
|
||||
// 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 {
|
||||
mut c := x
|
||||
for i := 0; i < n; i++ {
|
||||
c &= ~set_byte(0xff, i)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// pad appends a one followed by one or more zeroes to data
|
||||
@[inline]
|
||||
fn pad(n int) u64 {
|
||||
return u64(0x01) << (8 * n)
|
||||
}
|
||||
|
||||
// assume input.len < 8
|
||||
fn u64_from_partial_bytes(input []u8) u64 {
|
||||
mut tmp := []u8{len: 8}
|
||||
ct_copy_first(mut tmp, input)
|
||||
return binary.little_endian_u64(tmp)
|
||||
}
|
||||
|
||||
// ct_copy_at copies of y into x start from at position in constant-time manner.
|
||||
fn ct_copy_at(mut x []u8, y []u8, at int) {
|
||||
ct_copy_internal(1, mut x, y, at)
|
||||
}
|
||||
|
||||
// ct_copy_first copies of y into first y.len of x in constant-time manner.
|
||||
fn ct_copy_first(mut x []u8, y []u8) {
|
||||
ct_copy_internal(1, mut x, y, 0)
|
||||
}
|
||||
|
||||
@[direct_array_access]
|
||||
fn ct_copy_internal(v int, mut x []u8, y []u8, at int) {
|
||||
if at > x.len {
|
||||
panic('at > x.len')
|
||||
}
|
||||
if i64(x.len) < i64(y.len) + i64(at) {
|
||||
panic('invalid pos')
|
||||
}
|
||||
if x.len < y.len {
|
||||
panic('length x < y')
|
||||
}
|
||||
xmask := u8(v - 1)
|
||||
ymask := u8(~(v - 1))
|
||||
for i := 0; i < y.len; i++ {
|
||||
x[i + at] = x[i + at] & xmask | y[i] & ymask
|
||||
}
|
||||
}
|
||||
|
||||
// portable little-endian helper
|
||||
@[inline]
|
||||
fn u64le(x u64) u64 {
|
||||
$if little_endian {
|
||||
return x
|
||||
}
|
||||
// otherwise, change into little-endian format
|
||||
return ((u64(0x00000000000000FF) & x) << 56) | ((u64(0x000000000000FF00) & x) << 40) | ((u64(0x0000000000FF0000) & x) << 24) | ((u64(0x00000000FF000000) & x) << 8) | ((u64(0x000000FF00000000) & x) >> 8) | ((u64(0x0000FF0000000000) & x) >> 24) | ((u64(0x00FF000000000000) & x) >> 40) | ((u64(0xFF00000000000000) & x) >> 56)
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn get_byte(x u64, i int) u8 {
|
||||
return u8(x >> (8 * i))
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn set_byte(b u8, i int) u64 {
|
||||
return u64(b) << (8 * i)
|
||||
}
|
||||
|
||||
// load_bytes load partial bytes with length n, used internally.
|
||||
@[direct_array_access]
|
||||
fn load_bytes(bytes []u8, n int) u64 {
|
||||
mut x := u64(0)
|
||||
for i := 0; i < n; i++ {
|
||||
x |= set_byte(bytes[i], i)
|
||||
}
|
||||
return u64le(x)
|
||||
}
|
||||
|
||||
fn store_bytes(mut out []u8, x u64, n int) {
|
||||
for i := 0; i < n; i++ {
|
||||
out[i] = get_byte(x, i)
|
||||
}
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn ascon_rotate_right(x u64, n int) u64 {
|
||||
return (x >> n) | x << (64 - n)
|
||||
}
|
327
vlib/x/crypto/ascon/xof.v
Normal file
327
vlib/x/crypto/ascon/xof.v
Normal file
|
@ -0,0 +1,327 @@
|
|||
// 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 Ascon-XOF128, an Ascon-based Extendable-output Function (XOF)
|
||||
// and their variant, Ascon-CXOF128 from NIST.SP.800-232 standard.
|
||||
// Ascon-XOF128 is similar to Ascon-Hash256 where Ascon-XOF128 accepts an additional input
|
||||
// which specifies the desired output length in bits.
|
||||
module ascon
|
||||
|
||||
import encoding.binary
|
||||
|
||||
// max_hash_size is a maximum size of Ascon-XOF128 (and Ascon-CXOF128) checksum output.
|
||||
// Its very rare where checksum output bigger than 512-bits, So, we limiting it to prevent unconditional thing.
|
||||
// This limitation was only occurs on this module, wee can change it later.
|
||||
pub const max_hash_size = 4096 // in bytes
|
||||
|
||||
// default_xof_size is the size of Ascon-XOF128 (and Ascon-CXOF128) checksum that has 512-bits length.
|
||||
pub const default_xof_size = 64
|
||||
|
||||
// xof128_initial_state is a precomputed value for Ascon-XOF128 state.
|
||||
// See the comment on `hash256_initial_state` about the values
|
||||
const xof128_initial_state = State{
|
||||
e0: 0xda82ce768d9447eb
|
||||
e1: 0xcc7ce6c75f1ef969
|
||||
e2: 0xe7508fd780085631
|
||||
e3: 0x0ee0ea53416b58cc
|
||||
e4: 0xe0547524db6f0bde
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// xof128_64 creates a 64-bytes of Ascon-XOF128 checksum of msg.
|
||||
pub fn xof128_64(msg []u8) ![]u8 {
|
||||
return xof128(msg, default_xof_size)!
|
||||
}
|
||||
|
||||
// Xof128 is an opaque provides an implementation of Ascon-XOF128 from NIST.SP.800-232 standard.
|
||||
// Its implements `hash.Hash` interface with checksum size stored on instance creation.
|
||||
@[noinit]
|
||||
pub struct Xof128 {
|
||||
Digest
|
||||
mut:
|
||||
// The size of Xof128 cheksum, when you dont specify it
|
||||
size int = default_xof_size
|
||||
}
|
||||
|
||||
// new_xof128 creates a new Ascon-XOF128 instance with provided size parameter.
|
||||
pub fn new_xof128(size int) &Xof128 {
|
||||
if size < 1 || size > max_hash_size {
|
||||
panic('new_xof128: invalid size')
|
||||
}
|
||||
return &Xof128{
|
||||
Digest: Digest{
|
||||
State: xof128_initial_state
|
||||
}
|
||||
size: size
|
||||
}
|
||||
}
|
||||
|
||||
// size returns an underlying size of Xof128 checksum in fixed-sized manner.
|
||||
// Internally, its return underlying size stored on current Xof128 instance.
|
||||
pub fn (x &Xof128) size() int {
|
||||
return x.size
|
||||
}
|
||||
|
||||
// block_size returns an underlying Xof128 block size operates on, ie, 8-bytes
|
||||
pub fn (x &Xof128) block_size() int {
|
||||
return block_size
|
||||
}
|
||||
|
||||
// clone returns a clone of x on the current state.
|
||||
fn (x &Xof128) clone() &Xof128 {
|
||||
return &Xof128{
|
||||
Digest: x.Digest
|
||||
size: x.size
|
||||
}
|
||||
}
|
||||
|
||||
// write writes out the content of message and updates internal Xof128 state.
|
||||
pub fn (mut x Xof128) write(msg []u8) !int {
|
||||
if x.Digest.done {
|
||||
panic('Digest: writing after done ')
|
||||
}
|
||||
return x.Digest.absorb(msg)
|
||||
}
|
||||
|
||||
// sum returns an Ascon-XOF128 checksum of the bytes in msg.
|
||||
// Its produces `x.size` of checksum bytes. If you want to more
|
||||
// extendible output, use `.read` call instead.
|
||||
pub fn (mut x Xof128) sum(msg []u8) []u8 {
|
||||
// working on the clone of the h, so we can keep writing
|
||||
mut x0 := x.clone()
|
||||
_ := x0.write(msg) or { panic(err) }
|
||||
x0.Digest.finish()
|
||||
mut dst := []u8{len: x.size}
|
||||
x0.Digest.squeeze(mut dst)
|
||||
x0.reset()
|
||||
return dst
|
||||
}
|
||||
|
||||
// read tries to read `dst.len` bytes of hash output from current Xof128 state and stored into dst.
|
||||
// Note: 1 ≤ dst.len ≤ max_hash_size.
|
||||
pub fn (mut x Xof128) read(mut dst []u8) !int {
|
||||
// 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()
|
||||
x0.Digest.finish()
|
||||
n := x0.Digest.squeeze(mut dst)
|
||||
x0.reset()
|
||||
return n
|
||||
}
|
||||
|
||||
// reset resets internal Xof128 state into default initialized state.
|
||||
pub fn (mut x Xof128) reset() {
|
||||
// we dont reset the x.size
|
||||
unsafe { x.Digest.buf.reset() }
|
||||
x.Digest.length = 0
|
||||
x.Digest.done = false
|
||||
x.Digest.State = xof128_initial_state
|
||||
}
|
||||
|
||||
// free releases out the resources taken by the `x`. Dont use x after .free call.
|
||||
@[unsafe]
|
||||
pub fn (mut x Xof128) free() {
|
||||
$if prealloc {
|
||||
return
|
||||
}
|
||||
unsafe {
|
||||
x.Digest.buf.free()
|
||||
}
|
||||
// Mark it as unusable
|
||||
x.Digest.done = true
|
||||
}
|
||||
|
||||
// Ascon-CXOF128
|
||||
//
|
||||
// Ascon-CXOF128 is customized variant of Ascon-XOF128 that extends its
|
||||
// functionality by incorporating a user-defined customization string.
|
||||
// In addition to the message 𝑀 and output length 𝐿, Ascon-CXOF128 takes
|
||||
// the customization string 𝑍 as input.
|
||||
|
||||
// The length of the customization string shall be at most 2048 bits (i.e., 256 bytes)
|
||||
const max_cxof128_cstring = 256
|
||||
|
||||
// cxof128_initial_state is a precomputed value for Ascon-CXOF128 state.
|
||||
// See the comment on `hash256_initial_state` about the values
|
||||
const cxof128_initial_state = State{
|
||||
e0: 0x675527c2a0e8de03
|
||||
e1: 0x43d12d7dc0377bbc
|
||||
e2: 0xe9901dec426e81b5
|
||||
e3: 0x2ab14907720780b6
|
||||
e4: 0x8f3f1d02d432bc46
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// cxof128_64 creates a 64-bytes of Ascon-CXOF128 checksum of msg with supplied custom string in cs.
|
||||
pub fn cxof128_64(msg []u8, cs []u8) ![]u8 {
|
||||
return cxof128(msg, default_xof_size, cs)!
|
||||
}
|
||||
|
||||
// CXof128 is an opaque provides an implementation of Ascon-CXOF128 from NIST.SP.800-232 standard.
|
||||
// Its implements `hash.Hash` interface with checksum-size stored on instance creation.
|
||||
@[noinit]
|
||||
pub struct CXof128 {
|
||||
Digest
|
||||
mut:
|
||||
// Customization string
|
||||
cs []u8
|
||||
// checksum size, for fixed-output
|
||||
size int = default_xof_size
|
||||
}
|
||||
|
||||
// new_cxof128 creates a new Ascon-CXOF128 instanace with cheksum size
|
||||
// was set to size and custom string in cs. It returns error on fails.
|
||||
pub fn new_cxof128(size int, cs []u8) !&CXof128 {
|
||||
if cs.len > max_cxof128_cstring {
|
||||
return error('CXof128: custom string length exceed limit')
|
||||
}
|
||||
if size < 1 || size > max_hash_size {
|
||||
return error('CXof128: invalid size')
|
||||
}
|
||||
// Initialize CXof128 state with precomputed-value and absorb the customization string
|
||||
mut s := cxof128_initial_state
|
||||
cxof128_absorb_custom_string(mut s, cs)
|
||||
|
||||
return &CXof128{
|
||||
Digest: Digest{
|
||||
State: s
|
||||
}
|
||||
cs: cs
|
||||
size: size
|
||||
}
|
||||
}
|
||||
|
||||
// size returns an underlying size of CXof128 checksum in fixed-sized manner.
|
||||
// Internally, its return underlying size stored on current CXof128 instance.
|
||||
pub fn (x &CXof128) size() int {
|
||||
return x.size
|
||||
}
|
||||
|
||||
// block_size returns an underlying CXof128 block size operates on, ie, 8-bytes
|
||||
pub fn (x &CXof128) block_size() int {
|
||||
return block_size
|
||||
}
|
||||
|
||||
// write writes out the content of message and updates internal CXof128 state.
|
||||
pub fn (mut x CXof128) write(msg []u8) !int {
|
||||
if x.Digest.done {
|
||||
panic('CXof128: writing after done ')
|
||||
}
|
||||
return x.Digest.absorb(msg)
|
||||
}
|
||||
|
||||
// sum returns an Ascon-CXOF128 checksum of the bytes in msg.
|
||||
// Its produces `x.size` of checksum bytes. If you want to more
|
||||
// extendible output, use `.read` call instead.
|
||||
pub fn (mut x CXof128) sum(msg []u8) []u8 {
|
||||
// working on the clone of the h, so we can keep writing
|
||||
mut x0 := x.clone()
|
||||
_ := x0.write(msg) or { panic(err) }
|
||||
x0.Digest.finish()
|
||||
mut dst := []u8{len: x.size}
|
||||
x0.Digest.squeeze(mut dst)
|
||||
x0.reset()
|
||||
return dst
|
||||
}
|
||||
|
||||
// read tries to read `dst.len` bytes of hash output from current CXof128 state and stored into dst.
|
||||
pub fn (mut x CXof128) read(mut dst []u8) !int {
|
||||
// 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()
|
||||
x0.Digest.finish()
|
||||
n := x0.Digest.squeeze(mut dst)
|
||||
x0.reset()
|
||||
return n
|
||||
}
|
||||
|
||||
// clone returns a clone of x on the current state.
|
||||
fn (x &CXof128) clone() &CXof128 {
|
||||
return &CXof128{
|
||||
Digest: x.Digest
|
||||
size: x.size
|
||||
cs: x.cs
|
||||
}
|
||||
}
|
||||
|
||||
// reset resets internal CXof128 state into default initialized state.
|
||||
pub fn (mut x CXof128) reset() {
|
||||
// we dont reset the x.size and custom string
|
||||
unsafe { x.Digest.buf.reset() }
|
||||
x.Digest.length = 0
|
||||
x.Digest.done = false
|
||||
x.Digest.State = cxof128_initial_state
|
||||
// reabsorbs custom string
|
||||
cxof128_absorb_custom_string(mut x.Digest.State, x.cs)
|
||||
}
|
||||
|
||||
// free releases out the resources taken by the `x`. Dont use x after .free call.
|
||||
@[unsafe]
|
||||
pub fn (mut x CXof128) free() {
|
||||
$if prealloc {
|
||||
return
|
||||
}
|
||||
unsafe {
|
||||
x.Digest.buf.free()
|
||||
}
|
||||
// Mark it as unusable
|
||||
x.Digest.done = true
|
||||
}
|
||||
|
||||
// cxof128_absorb_custom_string performs absorbing phase of custom string in cs for Ascon-CXOF128.
|
||||
@[direct_array_access; inline]
|
||||
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)
|
||||
|
||||
// absorb the customization string
|
||||
mut zlen := cs.len
|
||||
mut zidx := 0
|
||||
for zlen >= block_size {
|
||||
block := unsafe { cs[zidx..zidx + block_size] }
|
||||
s.e0 ^= binary.little_endian_u64(block)
|
||||
ascon_pnr(mut s, 12)
|
||||
|
||||
// updates a index
|
||||
zlen -= block_size
|
||||
zidx += block_size
|
||||
}
|
||||
// absorb final customization string
|
||||
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)
|
||||
}
|
112
vlib/x/crypto/ascon/xof_test.v
Normal file
112
vlib/x/crypto/ascon/xof_test.v
Normal file
|
@ -0,0 +1,112 @@
|
|||
// Copyright ©2025 blackshirt.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
//
|
||||
import arrays
|
||||
import encoding.hex
|
||||
import x.crypto.ascon
|
||||
|
||||
// This test for Xof128 with 512-bits outputs
|
||||
struct XofTest {
|
||||
count int
|
||||
msg string
|
||||
md string
|
||||
}
|
||||
|
||||
fn test_xof128() ! {
|
||||
for item in xof128_test_data {
|
||||
msg := hex.decode(item.msg)!
|
||||
md := hex.decode(item.md)!
|
||||
out := ascon.xof128_64(msg)!
|
||||
assert out == md
|
||||
|
||||
// With Xof128Digest opaque
|
||||
mut x := ascon.new_xof128(ascon.default_xof_size)
|
||||
md0 := x.sum(msg)
|
||||
assert md0 == md
|
||||
|
||||
// test .read()
|
||||
x.reset()
|
||||
mut dst := []u8{len: 64}
|
||||
_ := x.write(msg)!
|
||||
nr := x.read(mut dst)!
|
||||
assert nr == 64
|
||||
assert dst == md
|
||||
|
||||
// with chunked messages
|
||||
x.reset()
|
||||
chunks := arrays.chunk[u8](msg, 20)
|
||||
mut tot := 0
|
||||
for chunk in chunks {
|
||||
n := x.write(chunk)!
|
||||
tot += n
|
||||
}
|
||||
assert msg.len == tot
|
||||
|
||||
chunked_md := x.sum([]u8{})
|
||||
assert chunked_md == md
|
||||
}
|
||||
}
|
||||
|
||||
const xof128_test_data = [
|
||||
XofTest{
|
||||
count: 1
|
||||
msg: ''
|
||||
md: '473d5e6164f58b39dfd84aacdb8ae42ec2d91fed33388ee0d960d9b3993295c6ad77855a5d3b13fe6ad9e6098988373af7d0956d05a8f1665d2c67d1a3ad10ff'
|
||||
},
|
||||
XofTest{
|
||||
count: 2
|
||||
msg: '00'
|
||||
md: '51430e0438ecdf642b393630d977625f5f337656ba58ab1e960784ac32a16e0d446405551f5469384f8ea283cf12e64fa72c426bfebaea3aa1529e2c4ab23a2f'
|
||||
},
|
||||
XofTest{
|
||||
count: 3
|
||||
msg: '0001'
|
||||
md: 'a05383077af971d3830bd37e7b981497a773d441db077c6494cc73125953846eb6427fba4cd308ff90a11385d51101341bf5379249217bfdace9cca1148cc966'
|
||||
},
|
||||
XofTest{
|
||||
count: 4
|
||||
msg: '000102'
|
||||
md: '9c96f31c3e7bdfdc5ef6ba836f760a0d6548d94dd0a512033022c9242e8ba916c30c3961d37d7dd7282e2191494d60dc5058588b276c60c90be2aaa7e7013d96'
|
||||
},
|
||||
XofTest{
|
||||
count: 5
|
||||
msg: '00010203'
|
||||
md: '21f7fd74588e244af45f9016b8db19b857ec5e6208978cfc1b4611ed91fb38f87e8f82a6409fb2b77acfbba8862aa22a7b0c98c1c01d5a4fdf64827b450fa1eb'
|
||||
},
|
||||
XofTest{
|
||||
count: 6
|
||||
msg: '0001020304'
|
||||
md: 'd647cc91aafff06a486f00a33fdfe9222f08b94da3b17804da9aaae167b4285dd6395e2a61fded3cf73c99774aff7066f74f7698f4824ba538602087d7c267fa'
|
||||
},
|
||||
XofTest{
|
||||
count: 7
|
||||
msg: '000102030405'
|
||||
md: '4793fbe6aa7688e52cd3a97a2685c68b218e0ca8754307956509974ab107d8ba19070424d5dfd336c3fc1250a273b9146f9f26d7658b9e213c37aebbe74abc6e'
|
||||
},
|
||||
XofTest{
|
||||
count: 8
|
||||
msg: '00010203040506'
|
||||
md: '7ae562db37212a9acd2673ecfd5b4f1c5cb2e6f64ebf00aa7f6ef8dc82c448d5fe11cd91f4368c37690d79e5de0ca8ad419e1918ce8dab2d42363e9476638a7b'
|
||||
},
|
||||
XofTest{
|
||||
count: 9
|
||||
msg: '0001020304050607'
|
||||
md: '8d1886f5d3ec4af8d15b44bc62b74da6ea91bc28fb82f9c34079b5ed6e38b6c951803d7dfb3c5e512a0ef5e4060062a6fd067f9c73ef9bee527411bda67fc896'
|
||||
},
|
||||
XofTest{
|
||||
count: 10
|
||||
msg: '000102030405060708'
|
||||
md: 'db3013bfbbd132dc1d3152fd955ed48f7cbb675e9ad2a2fecf92b74c957592e0c89959e81c16fd07ead9eeb8e40359c497aa20258b43d87ec69ad0bb0993fd38'
|
||||
},
|
||||
XofTest{
|
||||
count: 601
|
||||
msg: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f5051525354555657'
|
||||
md: '8fbbce48a72faf257e06df9408d4b6e11d35a86dfc29c0f106cb86128a2fe94208dcc8df4a439978cdb77f53317cbe5507b7f33da590be10a424d4ef60b37036'
|
||||
},
|
||||
XofTest{
|
||||
count: 602
|
||||
msg: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758'
|
||||
md: 'b4ca30047dc94ed770e45f4fa07b25a049d4a149a78227e25625cec2cbee7cfdd7d3cfb2635a38770e04009de348726364f648984ef47e9bc90c2a3c0526b227'
|
||||
},
|
||||
]
|
Loading…
Add table
Add a link
Reference in a new issue