diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ff1609..d89fbb8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,8 @@ jobs: args: [--no-default-features --target wasm32-unknown-unknown] steps: - uses: actions/checkout@main + with: + submodules: recursive - name: Install rust uses: actions-rs/toolchain@v1 with: @@ -42,6 +44,8 @@ jobs: features: ["--features default,std,serde", --no-default-features] steps: - uses: actions/checkout@main + with: + submodules: recursive - name: Install rust uses: actions-rs/toolchain@v1 with: @@ -62,6 +66,8 @@ jobs: features: ["--features default,std,serde", --no-default-features] steps: - uses: actions/checkout@main + with: + submodules: recursive - name: Install minimal nightly with clippy uses: actions-rs/toolchain@v1 with: diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..88ae99f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "PQClean"] + path = PQClean + url = https://github.com/PQClean/PQClean.git diff --git a/Cargo.toml b/Cargo.toml index 4c92e57..c77a1d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ name = "miden-crypto" path = "src/main.rs" bench = false doctest = false -required-features = ["std"] +required-features = ["executable"] [[bench]] name = "hash" @@ -34,12 +34,15 @@ harness = false [features] arch-arm64-sve = ["dep:cc"] default = ["blake3/default", "std", "winter_crypto/default", "winter_math/default", "winter_utils/default"] -std = ["blake3/std", "winter_crypto/std", "winter_math/std", "winter_utils/std", "rand_utils"] +executable = ["dep:clap", "dep:rand_utils", "std"] +std = ["blake3/std", "dep:cc", "dep:libc", "dep:rand", "winter_crypto/std", "winter_math/std", "winter_utils/std"] serde = ["winter_math/serde", "dep:serde", "serde/alloc"] [dependencies] blake3 = { version = "1.4", default-features = false } -clap = { version = "4.3.21", features = ["derive"] } +clap = { version = "4.3", features = ["derive"], optional = true} +libc = { version = "0.2", optional = true, default-features = false } +rand = { version = "0.8", optional = true, default-features = false } winter_crypto = { version = "0.6", package = "winter-crypto", default-features = false } winter_math = { version = "0.6", package = "winter-math", default-features = false } winter_utils = { version = "0.6", package = "winter-utils", default-features = false } @@ -52,4 +55,5 @@ proptest = "1.1.0" rand_utils = { version = "0.6", package = "winter-rand-utils" } [build-dependencies] -cc = { version = "1.0.79", optional = true } +cc = { version = "1.0", features = ["parallel"], optional = true } +glob = "*" diff --git a/PQClean b/PQClean new file mode 160000 index 0000000..c3abebf --- /dev/null +++ b/PQClean @@ -0,0 +1 @@ +Subproject commit c3abebf4ab1ff516ffa71e6337f06d898952c299 diff --git a/build.rs b/build.rs index f65f075..e27f9df 100644 --- a/build.rs +++ b/build.rs @@ -1,8 +1,32 @@ fn main() { + #[cfg(feature = "std")] + compile_rpo_falcon(); + #[cfg(feature = "arch-arm64-sve")] compile_arch_arm64_sve(); } +#[cfg(feature = "std")] +fn compile_rpo_falcon() { + use std::path::PathBuf; + + let target_dir: PathBuf = ["PQClean", "crypto_sign", "falcon-512", "clean"].iter().collect(); + let common_dir: PathBuf = ["PQClean", "common"].iter().collect(); + let rpo_dir: PathBuf = ["src", "dsa", "rpo_falcon512", "falcon_c"].iter().collect(); + + let scheme_files = glob::glob(target_dir.join("*.c").to_str().unwrap()).unwrap(); + let common_files = glob::glob(common_dir.join("*.c").to_str().unwrap()).unwrap(); + let rpo_files = glob::glob(rpo_dir.join("*.c").to_str().unwrap()).unwrap(); + + cc::Build::new() + .include(&common_dir) + .include(target_dir) + .files(scheme_files.into_iter().map(|p| p.unwrap().to_string_lossy().into_owned())) + .files(common_files.into_iter().map(|p| p.unwrap().to_string_lossy().into_owned())) + .files(rpo_files.into_iter().map(|p| p.unwrap().to_string_lossy().into_owned())) + .compile("falcon-512_clean"); +} + #[cfg(feature = "arch-arm64-sve")] fn compile_arch_arm64_sve() { println!("cargo:rerun-if-changed=arch/arm64-sve/rpo/library.c"); diff --git a/rustfmt.toml b/rustfmt.toml index 93e66a1..d73df8c 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -16,5 +16,6 @@ newline_style = "Unix" #normalize_doc_attributes = true #reorder_impl_items = true single_line_if_else_max_width = 60 +struct_lit_width = 40 use_field_init_shorthand = true use_try_shorthand = true diff --git a/src/dsa/mod.rs b/src/dsa/mod.rs new file mode 100644 index 0000000..80fb8a8 --- /dev/null +++ b/src/dsa/mod.rs @@ -0,0 +1 @@ +pub mod rpo_falcon512; diff --git a/src/dsa/rpo_falcon512/error.rs b/src/dsa/rpo_falcon512/error.rs new file mode 100644 index 0000000..447a82c --- /dev/null +++ b/src/dsa/rpo_falcon512/error.rs @@ -0,0 +1,55 @@ +use super::{LOG_N, MODULUS, PK_LEN}; +use core::fmt; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum FalconError { + KeyGenerationFailed, + PubKeyDecodingExtraData, + PubKeyDecodingInvalidCoefficient(u32), + PubKeyDecodingInvalidLength(usize), + PubKeyDecodingInvalidTag(u8), + SigDecodingTooBigHighBits(u32), + SigDecodingInvalidRemainder, + SigDecodingNonZeroUnusedBitsLastByte, + SigDecodingMinusZero, + SigDecodingIncorrectEncodingAlgorithm, + SigDecodingNotSupportedDegree(u8), + SigGenerationFailed, +} + +impl fmt::Display for FalconError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use FalconError::*; + match self { + KeyGenerationFailed => write!(f, "Failed to generate a private-public key pair"), + PubKeyDecodingExtraData => { + write!(f, "Failed to decode public key: input not fully consumed") + } + PubKeyDecodingInvalidCoefficient(val) => { + write!(f, "Failed to decode public key: coefficient {val} is greater than or equal to the field modulus {MODULUS}") + } + PubKeyDecodingInvalidLength(len) => { + write!(f, "Failed to decode public key: expected {PK_LEN} bytes but received {len}") + } + PubKeyDecodingInvalidTag(byte) => { + write!(f, "Failed to decode public key: expected the first byte to be {LOG_N} but was {byte}") + } + SigDecodingTooBigHighBits(m) => { + write!(f, "Failed to decode signature: high bits {m} exceed 2048") + } + SigDecodingInvalidRemainder => { + write!(f, "Failed to decode signature: incorrect remaining data") + } + SigDecodingNonZeroUnusedBitsLastByte => { + write!(f, "Failed to decode signature: Non-zero unused bits in the last byte") + } + SigDecodingMinusZero => write!(f, "Failed to decode signature: -0 is forbidden"), + SigDecodingIncorrectEncodingAlgorithm => write!(f, "Failed to decode signature: not supported encoding algorithm"), + SigDecodingNotSupportedDegree(log_n) => write!(f, "Failed to decode signature: only supported irreducible polynomial degree is 512, 2^{log_n} was provided"), + SigGenerationFailed => write!(f, "Failed to generate a signature"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for FalconError {} diff --git a/src/dsa/rpo_falcon512/falcon_c/api_rpo.h b/src/dsa/rpo_falcon512/falcon_c/api_rpo.h new file mode 100644 index 0000000..bdcc3ec --- /dev/null +++ b/src/dsa/rpo_falcon512/falcon_c/api_rpo.h @@ -0,0 +1,66 @@ +#include +#include + +#define PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES 1281 +#define PQCLEAN_FALCON512_CLEAN_CRYPTO_PUBLICKEYBYTES 897 +#define PQCLEAN_FALCON512_CLEAN_CRYPTO_BYTES 666 + +/* + * Generate a new key pair. Public key goes into pk[], private key in sk[]. + * Key sizes are exact (in bytes): + * public (pk): PQCLEAN_FALCON512_CLEAN_CRYPTO_PUBLICKEYBYTES + * private (sk): PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES + * + * Return value: 0 on success, -1 on error. + * + * Note: This implementation follows the reference implementation in PQClean + * https://github.com/PQClean/PQClean/tree/master/crypto_sign/falcon-512 + * verbatim except for the sections that are marked otherwise. + */ +int PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_rpo( + uint8_t *pk, uint8_t *sk); + +/* + * Generate a new key pair from seed. Public key goes into pk[], private key in sk[]. + * Key sizes are exact (in bytes): + * public (pk): PQCLEAN_FALCON512_CLEAN_CRYPTO_PUBLICKEYBYTES + * private (sk): PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES + * + * Return value: 0 on success, -1 on error. + */ +int PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_from_seed_rpo( + uint8_t *pk, uint8_t *sk, unsigned char *seed); + +/* + * Compute a signature on a provided message (m, mlen), with a given + * private key (sk). Signature is written in sig[], with length written + * into *siglen. Signature length is variable; maximum signature length + * (in bytes) is PQCLEAN_FALCON512_CLEAN_CRYPTO_BYTES. + * + * sig[], m[] and sk[] may overlap each other arbitrarily. + * + * Return value: 0 on success, -1 on error. + * + * Note: This implementation follows the reference implementation in PQClean + * https://github.com/PQClean/PQClean/tree/master/crypto_sign/falcon-512 + * verbatim except for the sections that are marked otherwise. + */ +int PQCLEAN_FALCON512_CLEAN_crypto_sign_signature_rpo( + uint8_t *sig, size_t *siglen, + const uint8_t *m, size_t mlen, const uint8_t *sk); + +/* + * Verify a signature (sig, siglen) on a message (m, mlen) with a given + * public key (pk). + * + * sig[], m[] and pk[] may overlap each other arbitrarily. + * + * Return value: 0 on success, -1 on error. + * + * Note: This implementation follows the reference implementation in PQClean + * https://github.com/PQClean/PQClean/tree/master/crypto_sign/falcon-512 + * verbatim except for the sections that are marked otherwise. + */ +int PQCLEAN_FALCON512_CLEAN_crypto_sign_verify_rpo( + const uint8_t *sig, size_t siglen, + const uint8_t *m, size_t mlen, const uint8_t *pk); diff --git a/src/dsa/rpo_falcon512/falcon_c/falcon_rpo.c b/src/dsa/rpo_falcon512/falcon_c/falcon_rpo.c new file mode 100644 index 0000000..a1294d2 --- /dev/null +++ b/src/dsa/rpo_falcon512/falcon_c/falcon_rpo.c @@ -0,0 +1,387 @@ +/* + * Wrapper for implementing the PQClean API. + */ + +#include +#include "randombytes.h" +#include "api_rpo.h" +#include "inner.h" +#include "rpo.h" + +#define NONCELEN 40 + +/* + * Encoding formats (nnnn = log of degree, 9 for Falcon-512, 10 for Falcon-1024) + * + * private key: + * header byte: 0101nnnn + * private f (6 or 5 bits by element, depending on degree) + * private g (6 or 5 bits by element, depending on degree) + * private F (8 bits by element) + * + * public key: + * header byte: 0000nnnn + * public h (14 bits by element) + * + * signature: + * header byte: 0011nnnn + * nonce 40 bytes + * value (12 bits by element) + * + * message + signature: + * signature length (2 bytes, big-endian) + * nonce 40 bytes + * message + * header byte: 0010nnnn + * value (12 bits by element) + * (signature length is 1+len(value), not counting the nonce) + */ + +/* see api_rpo.h */ +int PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_from_seed_rpo( + uint8_t *pk, uint8_t *sk, unsigned char *seed) +{ + union + { + uint8_t b[FALCON_KEYGEN_TEMP_9]; + uint64_t dummy_u64; + fpr dummy_fpr; + } tmp; + int8_t f[512], g[512], F[512]; + uint16_t h[512]; + inner_shake256_context rng; + size_t u, v; + + /* + * Generate key pair. + */ + inner_shake256_init(&rng); + inner_shake256_inject(&rng, seed, sizeof seed); + inner_shake256_flip(&rng); + PQCLEAN_FALCON512_CLEAN_keygen(&rng, f, g, F, NULL, h, 9, tmp.b); + inner_shake256_ctx_release(&rng); + + /* + * Encode private key. + */ + sk[0] = 0x50 + 9; + u = 1; + v = PQCLEAN_FALCON512_CLEAN_trim_i8_encode( + sk + u, PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES - u, + f, 9, PQCLEAN_FALCON512_CLEAN_max_fg_bits[9]); + if (v == 0) + { + return -1; + } + u += v; + v = PQCLEAN_FALCON512_CLEAN_trim_i8_encode( + sk + u, PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES - u, + g, 9, PQCLEAN_FALCON512_CLEAN_max_fg_bits[9]); + if (v == 0) + { + return -1; + } + u += v; + v = PQCLEAN_FALCON512_CLEAN_trim_i8_encode( + sk + u, PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES - u, + F, 9, PQCLEAN_FALCON512_CLEAN_max_FG_bits[9]); + if (v == 0) + { + return -1; + } + u += v; + if (u != PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES) + { + return -1; + } + + /* + * Encode public key. + */ + pk[0] = 0x00 + 9; + v = PQCLEAN_FALCON512_CLEAN_modq_encode( + pk + 1, PQCLEAN_FALCON512_CLEAN_CRYPTO_PUBLICKEYBYTES - 1, + h, 9); + if (v != PQCLEAN_FALCON512_CLEAN_CRYPTO_PUBLICKEYBYTES - 1) + { + return -1; + } + + return 0; +} + +int PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_rpo( + uint8_t *pk, uint8_t *sk) +{ + unsigned char seed[48]; + + /* + * Generate a random seed. + */ + randombytes(seed, sizeof seed); + + return PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_from_seed_rpo(pk, sk, seed); +} + +/* + * Compute the signature. nonce[] receives the nonce and must have length + * NONCELEN bytes. sigbuf[] receives the signature value (without nonce + * or header byte), with *sigbuflen providing the maximum value length and + * receiving the actual value length. + * + * If a signature could be computed but not encoded because it would + * exceed the output buffer size, then a new signature is computed. If + * the provided buffer size is too low, this could loop indefinitely, so + * the caller must provide a size that can accommodate signatures with a + * large enough probability. + * + * Return value: 0 on success, -1 on error. + */ +static int +do_sign(uint8_t *nonce, uint8_t *sigbuf, size_t *sigbuflen, + const uint8_t *m, size_t mlen, const uint8_t *sk) +{ + union + { + uint8_t b[72 * 512]; + uint64_t dummy_u64; + fpr dummy_fpr; + } tmp; + int8_t f[512], g[512], F[512], G[512]; + struct + { + int16_t sig[512]; + uint16_t hm[512]; + } r; + unsigned char seed[48]; + inner_shake256_context sc; + rpo128_context rc; + size_t u, v; + + /* + * Decode the private key. + */ + if (sk[0] != 0x50 + 9) + { + return -1; + } + u = 1; + v = PQCLEAN_FALCON512_CLEAN_trim_i8_decode( + f, 9, PQCLEAN_FALCON512_CLEAN_max_fg_bits[9], + sk + u, PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES - u); + if (v == 0) + { + return -1; + } + u += v; + v = PQCLEAN_FALCON512_CLEAN_trim_i8_decode( + g, 9, PQCLEAN_FALCON512_CLEAN_max_fg_bits[9], + sk + u, PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES - u); + if (v == 0) + { + return -1; + } + u += v; + v = PQCLEAN_FALCON512_CLEAN_trim_i8_decode( + F, 9, PQCLEAN_FALCON512_CLEAN_max_FG_bits[9], + sk + u, PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES - u); + if (v == 0) + { + return -1; + } + u += v; + if (u != PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES) + { + return -1; + } + if (!PQCLEAN_FALCON512_CLEAN_complete_private(G, f, g, F, 9, tmp.b)) + { + return -1; + } + + /* + * Create a random nonce (40 bytes). + */ + randombytes(nonce, NONCELEN); + + /* ==== Start: Deviation from the reference implementation ================================= */ + + // Transform the nonce into 8 chunks each of size 5 bytes. We do this in order to be sure that + // the conversion to field elements succeeds + uint8_t buffer[64]; + memset(buffer, 0, 64); + for (size_t i = 0; i < 8; i++) + { + buffer[8 * i] = nonce[5 * i]; + buffer[8 * i + 1] = nonce[5 * i + 1]; + buffer[8 * i + 2] = nonce[5 * i + 2]; + buffer[8 * i + 3] = nonce[5 * i + 3]; + buffer[8 * i + 4] = nonce[5 * i + 4]; + } + + /* + * Hash message nonce + message into a vector. + */ + rpo128_init(&rc); + rpo128_absorb(&rc, buffer, NONCELEN + 24); + rpo128_absorb(&rc, m, mlen); + rpo128_finalize(&rc); + PQCLEAN_FALCON512_CLEAN_hash_to_point_rpo(&rc, r.hm, 9); + rpo128_release(&rc); + + /* ==== End: Deviation from the reference implementation =================================== */ + + /* + * Initialize a RNG. + */ + randombytes(seed, sizeof seed); + inner_shake256_init(&sc); + inner_shake256_inject(&sc, seed, sizeof seed); + inner_shake256_flip(&sc); + + /* + * Compute and return the signature. This loops until a signature + * value is found that fits in the provided buffer. + */ + for (;;) + { + PQCLEAN_FALCON512_CLEAN_sign_dyn(r.sig, &sc, f, g, F, G, r.hm, 9, tmp.b); + v = PQCLEAN_FALCON512_CLEAN_comp_encode(sigbuf, *sigbuflen, r.sig, 9); + if (v != 0) + { + inner_shake256_ctx_release(&sc); + *sigbuflen = v; + return 0; + } + } +} + +/* + * Verify a signature. The nonce has size NONCELEN bytes. sigbuf[] + * (of size sigbuflen) contains the signature value, not including the + * header byte or nonce. Return value is 0 on success, -1 on error. + */ +static int +do_verify( + const uint8_t *nonce, const uint8_t *sigbuf, size_t sigbuflen, + const uint8_t *m, size_t mlen, const uint8_t *pk) +{ + union + { + uint8_t b[2 * 512]; + uint64_t dummy_u64; + fpr dummy_fpr; + } tmp; + uint16_t h[512], hm[512]; + int16_t sig[512]; + rpo128_context rc; + + /* + * Decode public key. + */ + if (pk[0] != 0x00 + 9) + { + return -1; + } + if (PQCLEAN_FALCON512_CLEAN_modq_decode(h, 9, + pk + 1, PQCLEAN_FALCON512_CLEAN_CRYPTO_PUBLICKEYBYTES - 1) + != PQCLEAN_FALCON512_CLEAN_CRYPTO_PUBLICKEYBYTES - 1) + { + return -1; + } + PQCLEAN_FALCON512_CLEAN_to_ntt_monty(h, 9); + + /* + * Decode signature. + */ + if (sigbuflen == 0) + { + return -1; + } + if (PQCLEAN_FALCON512_CLEAN_comp_decode(sig, 9, sigbuf, sigbuflen) != sigbuflen) + { + return -1; + } + + /* ==== Start: Deviation from the reference implementation ================================= */ + + /* + * Hash nonce + message into a vector. + */ + + // Transform the nonce into 8 chunks each of size 5 bytes. We do this in order to be sure that + // the conversion to field elements succeeds + uint8_t buffer[64]; + memset(buffer, 0, 64); + for (size_t i = 0; i < 8; i++) + { + buffer[8 * i] = nonce[5 * i]; + buffer[8 * i + 1] = nonce[5 * i + 1]; + buffer[8 * i + 2] = nonce[5 * i + 2]; + buffer[8 * i + 3] = nonce[5 * i + 3]; + buffer[8 * i + 4] = nonce[5 * i + 4]; + } + + rpo128_init(&rc); + rpo128_absorb(&rc, buffer, NONCELEN + 24); + rpo128_absorb(&rc, m, mlen); + rpo128_finalize(&rc); + PQCLEAN_FALCON512_CLEAN_hash_to_point_rpo(&rc, hm, 9); + rpo128_release(&rc); + + /* === End: Deviation from the reference implementation ==================================== */ + + /* + * Verify signature. + */ + if (!PQCLEAN_FALCON512_CLEAN_verify_raw(hm, sig, h, 9, tmp.b)) + { + return -1; + } + return 0; +} + +/* see api_rpo.h */ +int PQCLEAN_FALCON512_CLEAN_crypto_sign_signature_rpo( + uint8_t *sig, size_t *siglen, + const uint8_t *m, size_t mlen, const uint8_t *sk) +{ + /* + * The PQCLEAN_FALCON512_CLEAN_CRYPTO_BYTES constant is used for + * the signed message object (as produced by crypto_sign()) + * and includes a two-byte length value, so we take care here + * to only generate signatures that are two bytes shorter than + * the maximum. This is done to ensure that crypto_sign() + * and crypto_sign_signature() produce the exact same signature + * value, if used on the same message, with the same private key, + * and using the same output from randombytes() (this is for + * reproducibility of tests). + */ + size_t vlen; + + vlen = PQCLEAN_FALCON512_CLEAN_CRYPTO_BYTES - NONCELEN - 3; + if (do_sign(sig + 1, sig + 1 + NONCELEN, &vlen, m, mlen, sk) < 0) + { + return -1; + } + sig[0] = 0x30 + 9; + *siglen = 1 + NONCELEN + vlen; + return 0; +} + +/* see api_rpo.h */ +int PQCLEAN_FALCON512_CLEAN_crypto_sign_verify_rpo( + const uint8_t *sig, size_t siglen, + const uint8_t *m, size_t mlen, const uint8_t *pk) +{ + if (siglen < 1 + NONCELEN) + { + return -1; + } + if (sig[0] != 0x30 + 9) + { + return -1; + } + return do_verify(sig + 1, + sig + 1 + NONCELEN, siglen - 1 - NONCELEN, m, mlen, pk); +} diff --git a/src/dsa/rpo_falcon512/falcon_c/rpo.c b/src/dsa/rpo_falcon512/falcon_c/rpo.c new file mode 100644 index 0000000..824f8a2 --- /dev/null +++ b/src/dsa/rpo_falcon512/falcon_c/rpo.c @@ -0,0 +1,582 @@ +/* + * RPO implementation. + */ + +#include +#include +#include + +/* ================================================================================================ + * Modular Arithmetic + */ + +#define P 0xFFFFFFFF00000001 +#define M 12289 + +// From https://github.com/ncw/iprime/blob/master/mod_math_noasm.go +uint64_t add_mod_p(uint64_t a, uint64_t b) +{ + a = P - a; + uint64_t res = b - a; + if (b < a) + res += P; + return res; +} + +uint64_t sub_mod_p(uint64_t a, uint64_t b) +{ + uint64_t r = a - b; + if (a < b) + r += P; + return r; +} + +uint64_t reduce_mod_p(uint64_t b, uint64_t a) +{ + uint32_t d = b >> 32, + c = b; + if (a >= P) + a -= P; + a = sub_mod_p(a, c); + a = sub_mod_p(a, d); + a = add_mod_p(a, ((uint64_t)c) << 32); + return a; +} + +uint64_t mult_mod_p(uint64_t x, uint64_t y) +{ + uint32_t a = x, + b = x >> 32, + c = y, + d = y >> 32; + + /* first synthesize the product using 32*32 -> 64 bit multiplies */ + x = b * (uint64_t)c; /* b*c */ + y = a * (uint64_t)d; /* a*d */ + uint64_t e = a * (uint64_t)c, /* a*c */ + f = b * (uint64_t)d, /* b*d */ + t; + + x += y; /* b*c + a*d */ + /* carry? */ + if (x < y) + f += 1LL << 32; /* carry into upper 32 bits - can't overflow */ + + t = x << 32; + e += t; /* a*c + LSW(b*c + a*d) */ + /* carry? */ + if (e < t) + f += 1; /* carry into upper 64 bits - can't overflow*/ + t = x >> 32; + f += t; /* b*d + MSW(b*c + a*d) */ + /* can't overflow */ + + /* now reduce: (b*d + MSW(b*c + a*d), a*c + LSW(b*c + a*d)) */ + return reduce_mod_p(f, e); +} + +/* ================================================================================================ + * RPO128 Permutation + */ + +static const uint64_t STATE_WIDTH = 12; +static const uint64_t NUM_ROUNDS = 7; + +/* + * MDS matrix + */ +const uint64_t MDS[12][12] = { + { 7, 23, 8, 26, 13, 10, 9, 7, 6, 22, 21, 8 }, + { 8, 7, 23, 8, 26, 13, 10, 9, 7, 6, 22, 21 }, + { 21, 8, 7, 23, 8, 26, 13, 10, 9, 7, 6, 22 }, + { 22, 21, 8, 7, 23, 8, 26, 13, 10, 9, 7, 6 }, + { 6, 22, 21, 8, 7, 23, 8, 26, 13, 10, 9, 7 }, + { 7, 6, 22, 21, 8, 7, 23, 8, 26, 13, 10, 9 }, + { 9, 7, 6, 22, 21, 8, 7, 23, 8, 26, 13, 10 }, + { 10, 9, 7, 6, 22, 21, 8, 7, 23, 8, 26, 13 }, + { 13, 10, 9, 7, 6, 22, 21, 8, 7, 23, 8, 26 }, + { 26, 13, 10, 9, 7, 6, 22, 21, 8, 7, 23, 8 }, + { 8, 26, 13, 10, 9, 7, 6, 22, 21, 8, 7, 23 }, + { 23, 8, 26, 13, 10, 9, 7, 6, 22, 21, 8, 7 }, +}; + +/* + * Round constants. + */ +const uint64_t ARK1[7][12] = { + { + 5789762306288267392ULL, + 6522564764413701783ULL, + 17809893479458208203ULL, + 107145243989736508ULL, + 6388978042437517382ULL, + 15844067734406016715ULL, + 9975000513555218239ULL, + 3344984123768313364ULL, + 9959189626657347191ULL, + 12960773468763563665ULL, + 9602914297752488475ULL, + 16657542370200465908ULL, + }, + { + 12987190162843096997ULL, + 653957632802705281ULL, + 4441654670647621225ULL, + 4038207883745915761ULL, + 5613464648874830118ULL, + 13222989726778338773ULL, + 3037761201230264149ULL, + 16683759727265180203ULL, + 8337364536491240715ULL, + 3227397518293416448ULL, + 8110510111539674682ULL, + 2872078294163232137ULL, + }, + { + 18072785500942327487ULL, + 6200974112677013481ULL, + 17682092219085884187ULL, + 10599526828986756440ULL, + 975003873302957338ULL, + 8264241093196931281ULL, + 10065763900435475170ULL, + 2181131744534710197ULL, + 6317303992309418647ULL, + 1401440938888741532ULL, + 8884468225181997494ULL, + 13066900325715521532ULL, + }, + { + 5674685213610121970ULL, + 5759084860419474071ULL, + 13943282657648897737ULL, + 1352748651966375394ULL, + 17110913224029905221ULL, + 1003883795902368422ULL, + 4141870621881018291ULL, + 8121410972417424656ULL, + 14300518605864919529ULL, + 13712227150607670181ULL, + 17021852944633065291ULL, + 6252096473787587650ULL, + }, + { + 4887609836208846458ULL, + 3027115137917284492ULL, + 9595098600469470675ULL, + 10528569829048484079ULL, + 7864689113198939815ULL, + 17533723827845969040ULL, + 5781638039037710951ULL, + 17024078752430719006ULL, + 109659393484013511ULL, + 7158933660534805869ULL, + 2955076958026921730ULL, + 7433723648458773977ULL, + }, + { + 16308865189192447297ULL, + 11977192855656444890ULL, + 12532242556065780287ULL, + 14594890931430968898ULL, + 7291784239689209784ULL, + 5514718540551361949ULL, + 10025733853830934803ULL, + 7293794580341021693ULL, + 6728552937464861756ULL, + 6332385040983343262ULL, + 13277683694236792804ULL, + 2600778905124452676ULL, + }, + { + 7123075680859040534ULL, + 1034205548717903090ULL, + 7717824418247931797ULL, + 3019070937878604058ULL, + 11403792746066867460ULL, + 10280580802233112374ULL, + 337153209462421218ULL, + 13333398568519923717ULL, + 3596153696935337464ULL, + 8104208463525993784ULL, + 14345062289456085693ULL, + 17036731477169661256ULL, + }}; + +const uint64_t ARK2[7][12] = { + { + 6077062762357204287ULL, + 15277620170502011191ULL, + 5358738125714196705ULL, + 14233283787297595718ULL, + 13792579614346651365ULL, + 11614812331536767105ULL, + 14871063686742261166ULL, + 10148237148793043499ULL, + 4457428952329675767ULL, + 15590786458219172475ULL, + 10063319113072092615ULL, + 14200078843431360086ULL, + }, + { + 6202948458916099932ULL, + 17690140365333231091ULL, + 3595001575307484651ULL, + 373995945117666487ULL, + 1235734395091296013ULL, + 14172757457833931602ULL, + 707573103686350224ULL, + 15453217512188187135ULL, + 219777875004506018ULL, + 17876696346199469008ULL, + 17731621626449383378ULL, + 2897136237748376248ULL, + }, + { + 8023374565629191455ULL, + 15013690343205953430ULL, + 4485500052507912973ULL, + 12489737547229155153ULL, + 9500452585969030576ULL, + 2054001340201038870ULL, + 12420704059284934186ULL, + 355990932618543755ULL, + 9071225051243523860ULL, + 12766199826003448536ULL, + 9045979173463556963ULL, + 12934431667190679898ULL, + }, + { + 18389244934624494276ULL, + 16731736864863925227ULL, + 4440209734760478192ULL, + 17208448209698888938ULL, + 8739495587021565984ULL, + 17000774922218161967ULL, + 13533282547195532087ULL, + 525402848358706231ULL, + 16987541523062161972ULL, + 5466806524462797102ULL, + 14512769585918244983ULL, + 10973956031244051118ULL, + }, + { + 6982293561042362913ULL, + 14065426295947720331ULL, + 16451845770444974180ULL, + 7139138592091306727ULL, + 9012006439959783127ULL, + 14619614108529063361ULL, + 1394813199588124371ULL, + 4635111139507788575ULL, + 16217473952264203365ULL, + 10782018226466330683ULL, + 6844229992533662050ULL, + 7446486531695178711ULL, + }, + { + 3736792340494631448ULL, + 577852220195055341ULL, + 6689998335515779805ULL, + 13886063479078013492ULL, + 14358505101923202168ULL, + 7744142531772274164ULL, + 16135070735728404443ULL, + 12290902521256031137ULL, + 12059913662657709804ULL, + 16456018495793751911ULL, + 4571485474751953524ULL, + 17200392109565783176ULL, + }, + { + 17130398059294018733ULL, + 519782857322261988ULL, + 9625384390925085478ULL, + 1664893052631119222ULL, + 7629576092524553570ULL, + 3485239601103661425ULL, + 9755891797164033838ULL, + 15218148195153269027ULL, + 16460604813734957368ULL, + 9643968136937729763ULL, + 3611348709641382851ULL, + 18256379591337759196ULL, + }, +}; + +void apply_sbox(uint64_t *const state) +{ + for (uint64_t i = 0; i < STATE_WIDTH; i++) + { + uint64_t t2 = mult_mod_p(*(state + i), *(state + i)); + uint64_t t4 = mult_mod_p(t2, t2); + + *(state + i) = mult_mod_p(*(state + i), mult_mod_p(t2, t4)); + } +} + +void apply_mds(uint64_t *state) +{ + uint64_t res[STATE_WIDTH]; + for (uint64_t i = 0; i < STATE_WIDTH; i++) + { + res[i] = 0; + } + for (uint64_t i = 0; i < STATE_WIDTH; i++) + { + for (uint64_t j = 0; j < STATE_WIDTH; j++) + { + res[i] = add_mod_p(res[i], mult_mod_p(MDS[i][j], *(state + j))); + } + } + + for (uint64_t i = 0; i < STATE_WIDTH; i++) + { + *(state + i) = res[i]; + } +} + +void apply_constants(uint64_t *const state, const uint64_t *ark) +{ + for (uint64_t i = 0; i < STATE_WIDTH; i++) + { + *(state + i) = add_mod_p(*(state + i), *(ark + i)); + } +} + +void exp_acc(const uint64_t m, const uint64_t *base, const uint64_t *tail, uint64_t *const res) +{ + for (uint64_t i = 0; i < m; i++) + { + for (uint64_t j = 0; j < STATE_WIDTH; j++) + { + if (i == 0) + { + *(res + j) = mult_mod_p(*(base + j), *(base + j)); + } + else + { + *(res + j) = mult_mod_p(*(res + j), *(res + j)); + } + } + } + + for (uint64_t i = 0; i < STATE_WIDTH; i++) + { + *(res + i) = mult_mod_p(*(res + i), *(tail + i)); + } +} + +void apply_inv_sbox(uint64_t *const state) +{ + uint64_t t1[STATE_WIDTH]; + for (uint64_t i = 0; i < STATE_WIDTH; i++) + { + t1[i] = 0; + } + for (uint64_t i = 0; i < STATE_WIDTH; i++) + { + t1[i] = mult_mod_p(*(state + i), *(state + i)); + } + + uint64_t t2[STATE_WIDTH]; + for (uint64_t i = 0; i < STATE_WIDTH; i++) + { + t2[i] = 0; + } + for (uint64_t i = 0; i < STATE_WIDTH; i++) + { + t2[i] = mult_mod_p(t1[i], t1[i]); + } + + uint64_t t3[STATE_WIDTH]; + for (uint64_t i = 0; i < STATE_WIDTH; i++) + { + t3[i] = 0; + } + exp_acc(3, t2, t2, t3); + + uint64_t t4[STATE_WIDTH]; + for (uint64_t i = 0; i < STATE_WIDTH; i++) + { + t4[i] = 0; + } + exp_acc(6, t3, t3, t4); + + uint64_t tmp[STATE_WIDTH]; + for (uint64_t i = 0; i < STATE_WIDTH; i++) + { + tmp[i] = 0; + } + exp_acc(12, t4, t4, tmp); + + uint64_t t5[STATE_WIDTH]; + for (uint64_t i = 0; i < STATE_WIDTH; i++) + { + t5[i] = 0; + } + exp_acc(6, tmp, t3, t5); + + uint64_t t6[STATE_WIDTH]; + for (uint64_t i = 0; i < STATE_WIDTH; i++) + { + t6[i] = 0; + } + exp_acc(31, t5, t5, t6); + + for (uint64_t i = 0; i < STATE_WIDTH; i++) + { + uint64_t a = mult_mod_p(mult_mod_p(t6[i], t6[i]), t5[i]); + a = mult_mod_p(a, a); + a = mult_mod_p(a, a); + uint64_t b = mult_mod_p(mult_mod_p(t1[i], t2[i]), *(state + i)); + + *(state + i) = mult_mod_p(a, b); + } +} + +void apply_round(uint64_t *const state, const uint64_t round) +{ + apply_mds(state); + apply_constants(state, ARK1[round]); + apply_sbox(state); + + apply_mds(state); + apply_constants(state, ARK2[round]); + apply_inv_sbox(state); +} + +static void apply_permutation(uint64_t *state) +{ + for (uint64_t i = 0; i < NUM_ROUNDS; i++) + { + apply_round(state, i); + } +} + +/* ================================================================================================ + * RPO128 implementation. This is supposed to substitute SHAKE256 in the hash-to-point algorithm. + */ + +#include "rpo.h" + +void rpo128_init(rpo128_context *rc) +{ + rc->dptr = 32; + + memset(rc->st.A, 0, sizeof rc->st.A); +} + +void rpo128_absorb(rpo128_context *rc, const uint8_t *in, size_t len) +{ + size_t dptr; + + dptr = (size_t)rc->dptr; + while (len > 0) + { + size_t clen, u; + + /* 136 * 8 = 1088 bit for the rate portion in the case of SHAKE256 + * For RPO, this is 64 * 8 = 512 bits + * The capacity for SHAKE256 is at the end while for RPO128 it is at the beginning + */ + clen = 96 - dptr; + if (clen > len) + { + clen = len; + } + + for (u = 0; u < clen; u++) + { + rc->st.dbuf[dptr + u] = in[u]; + } + + dptr += clen; + in += clen; + len -= clen; + if (dptr == 96) + { + apply_permutation(rc->st.A); + dptr = 32; + } + } + rc->dptr = dptr; +} + +void rpo128_finalize(rpo128_context *rc) +{ + // Set dptr to the end of the buffer, so that first call to extract will call the permutation. + rc->dptr = 96; +} + +void rpo128_squeeze(rpo128_context *rc, uint8_t *out, size_t len) +{ + size_t dptr; + + dptr = (size_t)rc->dptr; + while (len > 0) + { + size_t clen; + + if (dptr == 96) + { + apply_permutation(rc->st.A); + dptr = 32; + } + clen = 96 - dptr; + if (clen > len) + { + clen = len; + } + len -= clen; + + memcpy(out, rc->st.dbuf + dptr, clen); + dptr += clen; + out += clen; + } + rc->dptr = dptr; +} + +void rpo128_release(rpo128_context *rc) +{ + memset(rc->st.A, 0, sizeof rc->st.A); + rc->dptr = 32; +} + +/* ================================================================================================ + * Hash-to-Point algorithm implementation based on RPO128 + */ + +void PQCLEAN_FALCON512_CLEAN_hash_to_point_rpo(rpo128_context *rc, uint16_t *x, unsigned logn) +{ + /* + * This implementation avoids the rejection sampling step needed in the + * per-the-spec implementation. It uses a remark in https://falcon-sign.info/falcon.pdf + * page 31, which argues that the current variant is secure for the parameters set by NIST. + * Avoiding the rejection-sampling step leads to an implementation that is constant-time. + * TODO: Check that the current implementation is indeed constant-time. + */ + size_t n; + + n = (size_t)1 << logn; + while (n > 0) + { + uint8_t buf[8]; + uint64_t w; + + rpo128_squeeze(rc, (void *)buf, sizeof buf); + w = ((uint64_t)(buf[7]) << 56) | + ((uint64_t)(buf[6]) << 48) | + ((uint64_t)(buf[5]) << 40) | + ((uint64_t)(buf[4]) << 32) | + ((uint64_t)(buf[3]) << 24) | + ((uint64_t)(buf[2]) << 16) | + ((uint64_t)(buf[1]) << 8) | + ((uint64_t)(buf[0])); + + w %= M; + + *x++ = (uint16_t)w; + n--; + } +} \ No newline at end of file diff --git a/src/dsa/rpo_falcon512/falcon_c/rpo.h b/src/dsa/rpo_falcon512/falcon_c/rpo.h new file mode 100644 index 0000000..d9038af --- /dev/null +++ b/src/dsa/rpo_falcon512/falcon_c/rpo.h @@ -0,0 +1,83 @@ +#include +#include + +/* ================================================================================================ + * RPO hashing algorithm related structs and methods. + */ + +/* + * RPO128 context. + * + * This structure is used by the hashing API. It is composed of an internal state that can be + * viewed as either: + * 1. 12 field elements in the Miden VM. + * 2. 96 bytes. + * + * The first view is used for the internal state in the context of the RPO hashing algorithm. The + * second view is used for the buffer used to absorb the data to be hashed. + * + * The pointer to the buffer is updated as the data is absorbed. + * + * 'rpo128_context' must be initialized with rpo128_init() before first use. + */ +typedef struct +{ + union + { + uint64_t A[12]; + uint8_t dbuf[96]; + } st; + uint64_t dptr; +} rpo128_context; + +/* + * Initializes an RPO state + */ +void rpo128_init(rpo128_context *rc); + +/* + * Absorbs an array of bytes of length 'len' into the state. + */ +void rpo128_absorb(rpo128_context *rc, const uint8_t *in, size_t len); + +/* + * Squeezes an array of bytes of length 'len' from the state. + */ +void rpo128_squeeze(rpo128_context *rc, uint8_t *out, size_t len); + +/* + * Finalizes the state in preparation for squeezing. + * + * This function should be called after all the data has been absorbed. + * + * Note that the current implementation does not perform any sort of padding for domain separation + * purposes. The reason being that, for our purposes, we always perform the following sequence: + * 1. Absorb a Nonce (which is always 40 bytes packed as 8 field elements). + * 2. Absorb the message (which is always 4 field elements). + * 3. Call finalize. + * 4. Squeeze the output. + * 5. Call release. + */ +void rpo128_finalize(rpo128_context *rc); + +/* + * Releases the state. + * + * This function should be called after the squeeze operation is finished. + */ +void rpo128_release(rpo128_context *rc); + +/* ================================================================================================ + * Hash-to-Point algorithm for signature generation and signature verification. + */ + +/* + * Hash-to-Point algorithm. + * + * This function generates a point in Z_q[x]/(phi) from a given message. + * + * It takes a finalized rpo128_context as input and it generates the coefficients of the polynomial + * representing the point. The coefficients are stored in the array 'x'. The number of coefficients + * is given by 'logn', which must in our case is 512. + */ +void PQCLEAN_FALCON512_CLEAN_hash_to_point_rpo(rpo128_context *rc, uint16_t *x, unsigned logn); diff --git a/src/dsa/rpo_falcon512/ffi.rs b/src/dsa/rpo_falcon512/ffi.rs new file mode 100644 index 0000000..f21b3ed --- /dev/null +++ b/src/dsa/rpo_falcon512/ffi.rs @@ -0,0 +1,189 @@ +use libc::c_int; + +// C IMPLEMENTATION INTERFACE +// ================================================================================================ + +extern "C" { + /// Generate a new key pair. Public key goes into pk[], private key in sk[]. + /// Key sizes are exact (in bytes): + /// - public (pk): 897 + /// - private (sk): 1281 + /// + /// Return value: 0 on success, -1 on error. + pub fn PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_rpo(pk: *mut u8, sk: *mut u8) -> c_int; + + /// Generate a new key pair from seed. Public key goes into pk[], private key in sk[]. + /// Key sizes are exact (in bytes): + /// - public (pk): 897 + /// - private (sk): 1281 + /// + /// Return value: 0 on success, -1 on error. + pub fn PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_from_seed_rpo( + pk: *mut u8, + sk: *mut u8, + seed: *const u8, + ) -> c_int; + + /// Compute a signature on a provided message (m, mlen), with a given private key (sk). + /// Signature is written in sig[], with length written into *siglen. Signature length is + /// variable; maximum signature length (in bytes) is 666. + /// + /// sig[], m[] and sk[] may overlap each other arbitrarily. + /// + /// Return value: 0 on success, -1 on error. + pub fn PQCLEAN_FALCON512_CLEAN_crypto_sign_signature_rpo( + sig: *mut u8, + siglen: *mut usize, + m: *const u8, + mlen: usize, + sk: *const u8, + ) -> c_int; + + // TEST HELPERS + // -------------------------------------------------------------------------------------------- + + /// Verify a signature (sig, siglen) on a message (m, mlen) with a given public key (pk). + /// + /// sig[], m[] and pk[] may overlap each other arbitrarily. + /// + /// Return value: 0 on success, -1 on error. + #[cfg(test)] + pub fn PQCLEAN_FALCON512_CLEAN_crypto_sign_verify_rpo( + sig: *const u8, + siglen: usize, + m: *const u8, + mlen: usize, + pk: *const u8, + ) -> c_int; + + /// Hash-to-Point algorithm. + /// + /// This function generates a point in Z_q[x]/(phi) from a given message. + /// + /// It takes a finalized rpo128_context as input and it generates the coefficients of the polynomial + /// representing the point. The coefficients are stored in the array 'x'. The number of coefficients + /// is given by 'logn', which must in our case is 512. + #[cfg(test)] + pub fn PQCLEAN_FALCON512_CLEAN_hash_to_point_rpo( + rc: *mut Rpo128Context, + x: *mut u16, + logn: usize, + ); + + #[cfg(test)] + pub fn rpo128_init(sc: *mut Rpo128Context); + + #[cfg(test)] + pub fn rpo128_absorb( + sc: *mut Rpo128Context, + data: *const ::std::os::raw::c_void, + len: libc::size_t, + ); + + #[cfg(test)] + pub fn rpo128_finalize(sc: *mut Rpo128Context); +} + +#[repr(C)] +#[cfg(test)] +pub struct Rpo128Context { + pub content: [u64; 13usize], +} + +// TESTS +// ================================================================================================ + +#[cfg(all(test, feature = "std"))] +mod tests { + use super::*; + use crate::dsa::rpo_falcon512::{NONCE_LEN, PK_LEN, SIG_LEN, SK_LEN}; + use rand::Rng; + + #[test] + fn falcon_ffi() { + unsafe { + let mut rng = rand::thread_rng(); + + // --- generate a key pair from a seed ---------------------------- + + let mut pk = [0u8; PK_LEN]; + let mut sk = [0u8; SK_LEN]; + let seed: [u8; NONCE_LEN] = + (0..NONCE_LEN).map(|_| rng.gen()).collect::>().try_into().unwrap(); + + assert_eq!( + 0, + PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_from_seed_rpo( + pk.as_mut_ptr(), + sk.as_mut_ptr(), + seed.as_ptr() + ) + ); + + // --- sign a message and make sure it verifies ------------------- + + let mlen: usize = rng.gen::() as usize; + let msg: Vec = (0..mlen).map(|_| rng.gen()).collect(); + let mut detached_sig = [0u8; NONCE_LEN + SIG_LEN]; + let mut siglen = 0; + + assert_eq!( + 0, + PQCLEAN_FALCON512_CLEAN_crypto_sign_signature_rpo( + detached_sig.as_mut_ptr(), + &mut siglen as *mut usize, + msg.as_ptr(), + msg.len(), + sk.as_ptr() + ) + ); + + assert_eq!( + 0, + PQCLEAN_FALCON512_CLEAN_crypto_sign_verify_rpo( + detached_sig.as_ptr(), + siglen, + msg.as_ptr(), + msg.len(), + pk.as_ptr() + ) + ); + + // --- check verification of different signature ------------------ + + assert_eq!( + -1, + PQCLEAN_FALCON512_CLEAN_crypto_sign_verify_rpo( + detached_sig.as_ptr(), + siglen, + msg.as_ptr(), + msg.len() - 1, + pk.as_ptr() + ) + ); + + // --- check verification against a different pub key ------------- + + let mut pk_alt = [0u8; PK_LEN]; + let mut sk_alt = [0u8; SK_LEN]; + assert_eq!( + 0, + PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_rpo( + pk_alt.as_mut_ptr(), + sk_alt.as_mut_ptr() + ) + ); + + assert_eq!( + -1, + PQCLEAN_FALCON512_CLEAN_crypto_sign_verify_rpo( + detached_sig.as_ptr(), + siglen, + msg.as_ptr(), + msg.len(), + pk_alt.as_ptr() + ) + ); + } + } +} diff --git a/src/dsa/rpo_falcon512/keys.rs b/src/dsa/rpo_falcon512/keys.rs new file mode 100644 index 0000000..9170245 --- /dev/null +++ b/src/dsa/rpo_falcon512/keys.rs @@ -0,0 +1,227 @@ +use super::{ + ByteReader, ByteWriter, Deserializable, DeserializationError, FalconError, Polynomial, + PublicKeyBytes, Rpo256, SecretKeyBytes, Serializable, Signature, Word, +}; + +#[cfg(feature = "std")] +use super::{ffi, NonceBytes, StarkField, NONCE_LEN, PK_LEN, SIG_LEN, SK_LEN}; + +// PUBLIC KEY +// ================================================================================================ + +/// A public key for verifying signatures. +/// +/// The public key is a [Word] (i.e., 4 field elements) that is the hash of the coefficients of +/// the polynomial representing the raw bytes of the expanded public key. +/// +/// For Falcon-512, the first byte of the expanded public key is always equal to log2(512) i.e., 9. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct PublicKey(Word); + +impl PublicKey { + /// Returns a new [PublicKey] which is a commitment to the provided expanded public key. + /// + /// # Errors + /// Returns an error if the decoding of the public key fails. + pub fn new(pk: PublicKeyBytes) -> Result { + let h = Polynomial::from_pub_key(&pk)?; + let pk_felts = h.to_elements(); + let pk_digest = Rpo256::hash_elements(&pk_felts).into(); + Ok(Self(pk_digest)) + } + + /// Verifies the provided signature against provided message and this public key. + pub fn verify(&self, message: Word, signature: &Signature) -> bool { + signature.verify(message, self.0) + } +} + +impl From for Word { + fn from(key: PublicKey) -> Self { + key.0 + } +} + +// KEY PAIR +// ================================================================================================ + +/// A key pair (public and secret keys) for signing messages. +/// +/// The secret key is a byte array of length [PK_LEN]. +/// The public key is a byte array of length [SK_LEN]. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct KeyPair { + public_key: PublicKeyBytes, + secret_key: SecretKeyBytes, +} + +#[allow(clippy::new_without_default)] +impl KeyPair { + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Generates a (public_key, secret_key) key pair from OS-provided randomness. + /// + /// # Errors + /// Returns an error if key generation fails. + #[cfg(feature = "std")] + pub fn new() -> Result { + let mut public_key = [0u8; PK_LEN]; + let mut secret_key = [0u8; SK_LEN]; + + let res = unsafe { + ffi::PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_rpo( + public_key.as_mut_ptr(), + secret_key.as_mut_ptr(), + ) + }; + + if res == 0 { + Ok(Self { public_key, secret_key }) + } else { + Err(FalconError::KeyGenerationFailed) + } + } + + /// Generates a (public_key, secret_key) key pair from the provided seed. + /// + /// # Errors + /// Returns an error if key generation fails. + #[cfg(feature = "std")] + pub fn from_seed(seed: &NonceBytes) -> Result { + let mut public_key = [0u8; PK_LEN]; + let mut secret_key = [0u8; SK_LEN]; + + let res = unsafe { + ffi::PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_from_seed_rpo( + public_key.as_mut_ptr(), + secret_key.as_mut_ptr(), + seed.as_ptr(), + ) + }; + + if res == 0 { + Ok(Self { public_key, secret_key }) + } else { + Err(FalconError::KeyGenerationFailed) + } + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the public key corresponding to this key pair. + pub fn public_key(&self) -> PublicKey { + // TODO: memoize public key commitment as computing it requires quite a bit of hashing. + // expect() is fine here because we assume that the key pair was constructed correctly. + PublicKey::new(self.public_key).expect("invalid key pair") + } + + /// Returns the expanded public key corresponding to this key pair. + pub fn expanded_public_key(&self) -> PublicKeyBytes { + self.public_key + } + + // SIGNATURE GENERATION + // -------------------------------------------------------------------------------------------- + + /// Signs a message with a secret key and a seed. + /// + /// # Errors + /// Returns an error of signature generation fails. + #[cfg(feature = "std")] + pub fn sign(&self, message: Word) -> Result { + let msg = message.iter().flat_map(|e| e.as_int().to_le_bytes()).collect::>(); + let msg_len = msg.len(); + let mut sig = [0_u8; SIG_LEN + NONCE_LEN]; + let mut sig_len: usize = 0; + + let res = unsafe { + ffi::PQCLEAN_FALCON512_CLEAN_crypto_sign_signature_rpo( + sig.as_mut_ptr(), + &mut sig_len as *mut usize, + msg.as_ptr(), + msg_len, + self.secret_key.as_ptr(), + ) + }; + + if res == 0 { + Ok(Signature { sig, pk: self.public_key }) + } else { + Err(FalconError::SigGenerationFailed) + } + } +} + +// SERIALIZATION / DESERIALIZATION +// ================================================================================================ + +impl Serializable for KeyPair { + fn write_into(&self, target: &mut W) { + target.write_bytes(&self.public_key); + target.write_bytes(&self.secret_key); + } +} + +impl Deserializable for KeyPair { + fn read_from(source: &mut R) -> Result { + let public_key: PublicKeyBytes = source.read_array()?; + let secret_key: SecretKeyBytes = source.read_array()?; + Ok(Self { public_key, secret_key }) + } +} + +// TESTS +// ================================================================================================ + +#[cfg(all(test, feature = "std"))] +mod tests { + use super::{super::Felt, KeyPair, NonceBytes, Word}; + use rand_utils::{rand_array, rand_vector}; + + #[test] + fn test_falcon_verification() { + // generate random keys + let keys = KeyPair::new().unwrap(); + let pk = keys.public_key(); + + // sign a random message + let message: Word = rand_vector::(4).try_into().expect("Should not fail."); + let signature = keys.sign(message); + + // make sure the signature verifies correctly + assert!(pk.verify(message, signature.as_ref().unwrap())); + + // a signature should not verify against a wrong message + let message2: Word = rand_vector::(4).try_into().expect("Should not fail."); + assert!(!pk.verify(message2, signature.as_ref().unwrap())); + + // a signature should not verify against a wrong public key + let keys2 = KeyPair::new().unwrap(); + assert!(!keys2.public_key().verify(message, signature.as_ref().unwrap())) + } + + #[test] + fn test_falcon_verification_from_seed() { + // generate keys from a random seed + let seed: NonceBytes = rand_array(); + let keys = KeyPair::from_seed(&seed).unwrap(); + let pk = keys.public_key(); + + // sign a random message + let message: Word = rand_vector::(4).try_into().expect("Should not fail."); + let signature = keys.sign(message); + + // make sure the signature verifies correctly + assert!(pk.verify(message, signature.as_ref().unwrap())); + + // a signature should not verify against a wrong message + let message2: Word = rand_vector::(4).try_into().expect("Should not fail."); + assert!(!pk.verify(message2, signature.as_ref().unwrap())); + + // a signature should not verify against a wrong public key + let keys2 = KeyPair::new().unwrap(); + assert!(!keys2.public_key().verify(message, signature.as_ref().unwrap())) + } +} diff --git a/src/dsa/rpo_falcon512/mod.rs b/src/dsa/rpo_falcon512/mod.rs new file mode 100644 index 0000000..5bbe5cf --- /dev/null +++ b/src/dsa/rpo_falcon512/mod.rs @@ -0,0 +1,60 @@ +use crate::{ + hash::rpo::Rpo256, + utils::{ + collections::Vec, ByteReader, ByteWriter, Deserializable, DeserializationError, + Serializable, + }, + Felt, StarkField, Word, ZERO, +}; + +#[cfg(feature = "std")] +mod ffi; + +mod error; +mod keys; +mod polynomial; +mod signature; + +pub use error::FalconError; +pub use keys::{KeyPair, PublicKey}; +pub use polynomial::Polynomial; +pub use signature::Signature; + +// CONSTANTS +// ================================================================================================ + +// The Falcon modulus. +const MODULUS: u16 = 12289; +const MODULUS_MINUS_1_OVER_TWO: u16 = 6144; + +// The Falcon parameters for Falcon-512. This is the degree of the polynomial `phi := x^N + 1` +// defining the ring Z_p[x]/(phi). +const N: usize = 512; +const LOG_N: usize = 9; + +/// Length of nonce used for key-pair generation. +const NONCE_LEN: usize = 40; + +/// Number of filed elements used to encode a nonce. +const NONCE_ELEMENTS: usize = 8; + +/// Public key length as a u8 vector. +const PK_LEN: usize = 897; + +/// Secret key length as a u8 vector. +const SK_LEN: usize = 1281; + +/// Signature length as a u8 vector. +const SIG_LEN: usize = 626; + +/// Bound on the squared-norm of the signature. +const SIG_L2_BOUND: u64 = 34034726; + +// TYPE ALIASES +// ================================================================================================ + +type SignatureBytes = [u8; NONCE_LEN + SIG_LEN]; +type PublicKeyBytes = [u8; PK_LEN]; +type SecretKeyBytes = [u8; SK_LEN]; +type NonceBytes = [u8; NONCE_LEN]; +type NonceElements = [Felt; NONCE_ELEMENTS]; diff --git a/src/dsa/rpo_falcon512/polynomial.rs b/src/dsa/rpo_falcon512/polynomial.rs new file mode 100644 index 0000000..fdb9e34 --- /dev/null +++ b/src/dsa/rpo_falcon512/polynomial.rs @@ -0,0 +1,277 @@ +use super::{FalconError, Felt, Vec, LOG_N, MODULUS, MODULUS_MINUS_1_OVER_TWO, N, PK_LEN}; +use core::ops::{Add, Mul, Sub}; + +// FALCON POLYNOMIAL +// ================================================================================================ + +/// A polynomial over Z_p[x]/(phi) where phi := x^512 + 1 +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Polynomial([u16; N]); + +impl Polynomial { + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Constructs a new polynomial from a list of coefficients. + /// + /// # Safety + /// This constructor validates that the coefficients are in the valid range only in debug mode. + pub unsafe fn new(data: [u16; N]) -> Self { + for value in data { + debug_assert!(value < MODULUS); + } + + Self(data) + } + + /// Decodes raw bytes representing a public key into a polynomial in Z_p[x]/(phi). + /// + /// # Errors + /// Returns an error if: + /// - The provided input is not exactly 897 bytes long. + /// - The first byte of the input is not equal to log2(512) i.e., 9. + /// - Any of the coefficients encoded in the provided input is greater than or equal to the + /// Falcon field modulus. + pub fn from_pub_key(input: &[u8]) -> Result { + if input.len() != PK_LEN { + return Err(FalconError::PubKeyDecodingInvalidLength(input.len())); + } + + if input[0] != LOG_N as u8 { + return Err(FalconError::PubKeyDecodingInvalidTag(input[0])); + } + + let mut acc = 0_u32; + let mut acc_len = 0; + + let mut output = [0_u16; N]; + let mut output_idx = 0; + + for &byte in input.iter().skip(1) { + acc = (acc << 8) | (byte as u32); + acc_len += 8; + + if acc_len >= 14 { + acc_len -= 14; + let w = (acc >> acc_len) & 0x3FFF; + if w >= MODULUS as u32 { + return Err(FalconError::PubKeyDecodingInvalidCoefficient(w)); + } + output[output_idx] = w as u16; + output_idx += 1; + } + } + + if (acc & ((1u32 << acc_len) - 1)) == 0 { + Ok(Self(output)) + } else { + Err(FalconError::PubKeyDecodingExtraData) + } + } + + /// Decodes the signature into the coefficients of a polynomial in Z_p[x]/(phi). It assumes + /// that the signature has been encoded using the uncompressed format. + /// + /// # Errors + /// Returns an error if: + /// - The signature has been encoded using a different algorithm than the reference compressed + /// encoding algorithm. + /// - The encoded signature polynomial is in Z_p[x]/(phi') where phi' = x^N' + 1 and N' != 512. + /// - While decoding the high bits of a coefficient, the current accumulated value of its + /// high bits is larger than 2048. + /// - The decoded coefficient is -0. + /// - The remaining unused bits in the last byte of `input` are non-zero. + pub fn from_signature(input: &[u8]) -> Result { + let (encoding, log_n) = (input[0] >> 4, input[0] & 0b00001111); + + if encoding != 0b0011 { + return Err(FalconError::SigDecodingIncorrectEncodingAlgorithm); + } + if log_n != 0b1001 { + return Err(FalconError::SigDecodingNotSupportedDegree(log_n)); + } + + let input = &input[41..]; + let mut input_idx = 0; + let mut acc = 0u32; + let mut acc_len = 0; + let mut output = [0_u16; N]; + + for e in output.iter_mut() { + acc = (acc << 8) | (input[input_idx] as u32); + input_idx += 1; + let b = acc >> acc_len; + let s = b & 128; + let mut m = b & 127; + + loop { + if acc_len == 0 { + acc = (acc << 8) | (input[input_idx] as u32); + input_idx += 1; + acc_len = 8; + } + acc_len -= 1; + if ((acc >> acc_len) & 1) != 0 { + break; + } + m += 128; + if m >= 2048 { + return Err(FalconError::SigDecodingTooBigHighBits(m)); + } + } + if s != 0 && m == 0 { + return Err(FalconError::SigDecodingMinusZero); + } + + *e = if s != 0 { (MODULUS as u32 - m) as u16 } else { m as u16 }; + } + + if (acc & ((1 << acc_len) - 1)) != 0 { + return Err(FalconError::SigDecodingNonZeroUnusedBitsLastByte); + } + + Ok(Self(output)) + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the coefficients of this polynomial as integers. + pub fn inner(&self) -> [u16; N] { + self.0 + } + + /// Returns the coefficients of this polynomial as field elements. + pub fn to_elements(&self) -> Vec { + self.0.iter().map(|&a| Felt::from(a)).collect() + } + + // POLYNOMIAL OPERATIONS + // -------------------------------------------------------------------------------------------- + + /// Multiplies two polynomials over Z_p[x] without reducing modulo p. Given that the degrees + /// of the input polynomials are less than 512 and their coefficients are less than the modulus + /// q equal to 12289, the resulting product polynomial is guaranteed to have coefficients less + /// than the Miden prime. + /// + /// Note that this multiplication is not over Z_p[x]/(phi). + pub fn mul_modulo_p(a: &Self, b: &Self) -> [u64; 1024] { + let mut c = [0; 2 * N]; + for i in 0..N { + for j in 0..N { + c[i + j] += a.0[i] as u64 * b.0[j] as u64; + } + } + + c + } + + /// Reduces a polynomial, that is the product of two polynomials over Z_p[x], modulo + /// the irreducible polynomial phi. This results in an element in Z_p[x]/(phi). + pub fn reduce_negacyclic(a: &[u64; 1024]) -> Self { + let mut c = [0; N]; + for i in 0..N { + let ai = a[N + i] % MODULUS as u64; + let neg_ai = (MODULUS - ai as u16) % MODULUS; + + let bi = (a[i] % MODULUS as u64) as u16; + c[i] = (neg_ai + bi) % MODULUS; + } + + Self(c) + } + + /// Computes the norm squared of a polynomial in Z_p[x]/(phi) after normalizing its + /// coefficients to be in the interval (-p/2, p/2]. + pub fn sq_norm(&self) -> u64 { + let mut res = 0; + for e in self.0 { + if e > MODULUS_MINUS_1_OVER_TWO { + res += (MODULUS - e) as u64 * (MODULUS - e) as u64 + } else { + res += e as u64 * e as u64 + } + } + res + } +} + +// Returns a polynomial representing the zero polynomial i.e. default element. +impl Default for Polynomial { + fn default() -> Self { + Self([0_u16; N]) + } +} + +/// Multiplication over Z_p[x]/(phi) +impl Mul for Polynomial { + type Output = Self; + + fn mul(self, other: Self) -> >::Output { + let mut result = [0_u16; N]; + for j in 0..N { + for k in 0..N { + let i = (j + k) % N; + let a = self.0[j] as usize; + let b = other.0[k] as usize; + let q = MODULUS as usize; + let mut prod = a * b % q; + if (N - 1) < (j + k) { + prod = (q - prod) % q; + } + result[i] = ((result[i] as usize + prod) % q) as u16; + } + } + + Polynomial(result) + } +} + +/// Addition over Z_p[x]/(phi) +impl Add for Polynomial { + type Output = Self; + + fn add(self, other: Self) -> >::Output { + let mut res = self; + res.0.iter_mut().zip(other.0.iter()).for_each(|(x, y)| *x = (*x + *y) % MODULUS); + + res + } +} + +/// Subtraction over Z_p[x]/(phi) +impl Sub for Polynomial { + type Output = Self; + + fn sub(self, other: Self) -> >::Output { + let mut res = self; + res.0 + .iter_mut() + .zip(other.0.iter()) + .for_each(|(x, y)| *x = (*x + MODULUS - *y) % MODULUS); + + res + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use super::{Polynomial, N}; + + #[test] + fn test_negacyclic_reduction() { + let coef1: [u16; N] = rand_utils::rand_array(); + let coef2: [u16; N] = rand_utils::rand_array(); + + let poly1 = Polynomial(coef1); + let poly2 = Polynomial(coef2); + + assert_eq!( + poly1 * poly2, + Polynomial::reduce_negacyclic(&Polynomial::mul_modulo_p(&poly1, &poly2)) + ); + } +} diff --git a/src/dsa/rpo_falcon512/signature.rs b/src/dsa/rpo_falcon512/signature.rs new file mode 100644 index 0000000..afcde98 --- /dev/null +++ b/src/dsa/rpo_falcon512/signature.rs @@ -0,0 +1,262 @@ +use super::{ + ByteReader, ByteWriter, Deserializable, DeserializationError, NonceBytes, NonceElements, + Polynomial, PublicKeyBytes, Rpo256, Serializable, SignatureBytes, StarkField, Word, MODULUS, N, + SIG_L2_BOUND, ZERO, +}; +use crate::utils::string::ToString; + +// FALCON SIGNATURE +// ================================================================================================ + +/// An RPO Falcon512 signature over a message. +/// +/// The signature is a pair of polynomials (s1, s2) in (Z_p[x]/(phi))^2, where: +/// - p := 12289 +/// - phi := x^512 + 1 +/// - s1 = c - s2 * h +/// - h is a polynomial representing the public key and c is a polynomial that is the hash-to-point +/// of the message being signed. +/// +/// The signature verifies if and only if: +/// 1. s1 = c - s2 * h +/// 2. |s1|^2 + |s2|^2 <= SIG_L2_BOUND +/// +/// where |.| is the norm. +/// +/// [Signature] also includes the extended public key which is serialized as: +/// 1. 1 byte representing the log2(512) i.e., 9. +/// 2. 896 bytes for the public key. This is decoded into the `h` polynomial above. +/// +/// The actual signature is serialized as: +/// 1. A header byte specifying the algorithm used to encode the coefficients of the `s2` polynomial +/// together with the degree of the irreducible polynomial phi. +/// The general format of this byte is 0b0cc1nnnn where: +/// a. cc is either 01 when the compressed encoding algorithm is used and 10 when the +/// uncompressed algorithm is used. +/// b. nnnn is log2(N) where N is the degree of the irreducible polynomial phi. +/// The current implementation works always with cc equal to 0b01 and nnnn equal to 0b1001 and +/// thus the header byte is always equal to 0b00111001. +/// 2. 40 bytes for the nonce. +/// 3. 625 bytes encoding the `s2` polynomial above. +/// +/// The total size of the signature (including the extended public key) is 1563 bytes. +pub struct Signature { + pub(super) pk: PublicKeyBytes, + pub(super) sig: SignatureBytes, +} + +impl Signature { + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the public key polynomial h. + pub fn pub_key_poly(&self) -> Polynomial { + // TODO: memoize + // we assume that the signature was constructed with a valid public key, and thus + // expect() is OK here. + Polynomial::from_pub_key(&self.pk).expect("invalid public key") + } + + /// Returns the nonce component of the signature represented as field elements. + /// + /// Nonce bytes are converted to field elements by taking consecutive 5 byte chunks + /// of the nonce and interpreting them as field elements. + pub fn nonce(&self) -> NonceElements { + // we assume that the signature was constructed with a valid signature, and thus + // expect() is OK here. + let nonce = self.sig[1..41].try_into().expect("invalid signature"); + decode_nonce(nonce) + } + + // Returns the polynomial representation of the signature in Z_p[x]/(phi). + pub fn sig_poly(&self) -> Polynomial { + // TODO: memoize + // we assume that the signature was constructed with a valid signature, and thus + // expect() is OK here. + Polynomial::from_signature(&self.sig).expect("invalid signature") + } + + // HASH-TO-POINT + // -------------------------------------------------------------------------------------------- + + /// Returns a polynomial in Z_p[x]/(phi) representing the hash of the provided message. + pub fn hash_to_point(&self, message: Word) -> Polynomial { + hash_to_point(message, &self.nonce()) + } + + // SIGNATURE VERIFICATION + // -------------------------------------------------------------------------------------------- + /// Returns true if this signature is a valid signature for the specified message generated + /// against key pair matching the specified public key commitment. + pub fn verify(&self, message: Word, pubkey_com: Word) -> bool { + // Make sure the expanded public key matches the provided public key commitment + let h = self.pub_key_poly(); + let h_digest: Word = Rpo256::hash_elements(&h.to_elements()).into(); + if h_digest != pubkey_com { + return false; + } + + // Make sure the signature is valid + let s2 = self.sig_poly(); + let c = self.hash_to_point(message); + + let s1 = c - s2 * h; + + let sq_norm = s1.sq_norm() + s2.sq_norm(); + sq_norm <= SIG_L2_BOUND + } +} + +// SERIALIZATION / DESERIALIZATION +// ================================================================================================ + +impl Serializable for Signature { + fn write_into(&self, target: &mut W) { + target.write_bytes(&self.pk); + target.write_bytes(&self.sig); + } +} + +impl Deserializable for Signature { + fn read_from(source: &mut R) -> Result { + let pk: PublicKeyBytes = source.read_array()?; + let sig: SignatureBytes = source.read_array()?; + + // make sure public key and signature can be decoded correctly + Polynomial::from_pub_key(&pk) + .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?; + Polynomial::from_signature(&sig[41..]) + .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?; + + Ok(Self { pk, sig }) + } +} + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Returns a polynomial in Z_p[x]/(phi) representing the hash of the provided message and +/// nonce. +fn hash_to_point(message: Word, nonce: &NonceElements) -> Polynomial { + let mut state = [ZERO; Rpo256::STATE_WIDTH]; + + // absorb the nonce into the state + for (&n, s) in nonce.iter().zip(state[Rpo256::RATE_RANGE].iter_mut()) { + *s = n; + } + Rpo256::apply_permutation(&mut state); + + // absorb message into the state + for (&m, s) in message.iter().zip(state[Rpo256::RATE_RANGE].iter_mut()) { + *s = m; + } + + // squeeze the coefficients of the polynomial + let mut i = 0; + let mut res = [0_u16; N]; + for _ in 0..64 { + Rpo256::apply_permutation(&mut state); + for a in &state[Rpo256::RATE_RANGE] { + res[i] = (a.as_int() % MODULUS as u64) as u16; + i += 1; + } + } + + // using the raw constructor is OK here because we reduce all coefficients by the modulus above + unsafe { Polynomial::new(res) } +} + +/// Converts byte representation of the nonce into field element representation. +fn decode_nonce(nonce: &NonceBytes) -> NonceElements { + let mut buffer = [0_u8; 8]; + let mut result = [ZERO; 8]; + for (i, bytes) in nonce.chunks(5).enumerate() { + buffer[..5].copy_from_slice(bytes); + result[i] = u64::from_le_bytes(buffer).into(); + } + + result +} + +// TESTS +// ================================================================================================ + +#[cfg(all(test, feature = "std"))] +mod tests { + use super::{ + super::{ffi::*, Felt}, + *, + }; + use libc::c_void; + use rand_utils::rand_vector; + + // Wrappers for unsafe functions + impl Rpo128Context { + /// Initializes the RPO state. + pub fn init() -> Self { + let mut ctx = Rpo128Context { content: [0u64; 13] }; + unsafe { + rpo128_init(&mut ctx as *mut Rpo128Context); + } + ctx + } + + /// Absorbs data into the RPO state. + pub fn absorb(&mut self, data: &[u8]) { + unsafe { + rpo128_absorb( + self as *mut Rpo128Context, + data.as_ptr() as *const c_void, + data.len(), + ) + } + } + + /// Finalizes the RPO state to prepare for squeezing. + pub fn finalize(&mut self) { + unsafe { rpo128_finalize(self as *mut Rpo128Context) } + } + } + + #[test] + fn test_hash_to_point() { + // Create a random message and transform it into a u8 vector + let msg_felts: Word = rand_vector::(4).try_into().unwrap(); + let msg_bytes = msg_felts.iter().flat_map(|e| e.as_int().to_le_bytes()).collect::>(); + + // Create a nonce i.e. a [u8; 40] array and pack into a [Felt; 8] array. + let nonce: [u8; 40] = rand_vector::(40).try_into().unwrap(); + + let mut buffer = [0_u8; 64]; + for i in 0..8 { + buffer[8 * i] = nonce[5 * i]; + buffer[8 * i + 1] = nonce[5 * i + 1]; + buffer[8 * i + 2] = nonce[5 * i + 2]; + buffer[8 * i + 3] = nonce[5 * i + 3]; + buffer[8 * i + 4] = nonce[5 * i + 4]; + } + + // Initialize the RPO state + let mut rng = Rpo128Context::init(); + + // Absorb the nonce and message into the RPO state + rng.absorb(&buffer); + rng.absorb(&msg_bytes); + rng.finalize(); + + // Generate the coefficients of the hash-to-point polynomial. + let mut res: [u16; N] = [0; N]; + + unsafe { + PQCLEAN_FALCON512_CLEAN_hash_to_point_rpo( + &mut rng as *mut Rpo128Context, + res.as_mut_ptr(), + 9, + ); + } + + // Check that the coefficients are correct + let nonce = decode_nonce(&nonce); + assert_eq!(res, hash_to_point(msg_felts, &nonce).inner()); + } +} diff --git a/src/lib.rs b/src/lib.rs index cb0e11e..ddf146c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ #[cfg_attr(test, macro_use)] extern crate alloc; +pub mod dsa; pub mod hash; pub mod merkle; pub mod utils; diff --git a/src/merkle/merkle_tree.rs b/src/merkle/merkle_tree.rs index 206543a..769cff1 100644 --- a/src/merkle/merkle_tree.rs +++ b/src/merkle/merkle_tree.rs @@ -371,21 +371,9 @@ mod tests { let nodes: Vec = tree.inner_nodes().collect(); let expected = vec![ - InnerNodeInfo { - value: root, - left: l1n0, - right: l1n1, - }, - InnerNodeInfo { - value: l1n0, - left: l2n0, - right: l2n1, - }, - InnerNodeInfo { - value: l1n1, - left: l2n2, - right: l2n3, - }, + InnerNodeInfo { value: root, left: l1n0, right: l1n1 }, + InnerNodeInfo { value: l1n0, left: l2n0, right: l2n1 }, + InnerNodeInfo { value: l1n1, left: l2n2, right: l2n3 }, ]; assert_eq!(nodes, expected); diff --git a/src/merkle/mmr/full.rs b/src/merkle/mmr/full.rs index c3dd3ac..af7c212 100644 --- a/src/merkle/mmr/full.rs +++ b/src/merkle/mmr/full.rs @@ -71,10 +71,7 @@ impl Mmr { /// Constructor for an empty `Mmr`. pub fn new() -> Mmr { - Mmr { - forest: 0, - nodes: Vec::new(), - } + Mmr { forest: 0, nodes: Vec::new() } } // ACCESSORS @@ -188,10 +185,7 @@ impl Mmr { .map(|offset| self.nodes[offset - 1]) .collect(); - MmrPeaks { - num_leaves: self.forest, - peaks, - } + MmrPeaks { num_leaves: self.forest, peaks } } /// An iterator over inner nodes in the MMR. The order of iteration is unspecified. diff --git a/src/merkle/mmr/tests.rs b/src/merkle/mmr/tests.rs index 13722e2..3cecaa4 100644 --- a/src/merkle/mmr/tests.rs +++ b/src/merkle/mmr/tests.rs @@ -380,11 +380,7 @@ fn test_mmr_inner_nodes() { left: LEAVES[2], right: LEAVES[3], }, - InnerNodeInfo { - value: h0123, - left: h01, - right: h23, - }, + InnerNodeInfo { value: h0123, left: h01, right: h23 }, InnerNodeInfo { value: h45, left: LEAVES[4], diff --git a/src/merkle/partial_mt/mod.rs b/src/merkle/partial_mt/mod.rs index 2c95972..c29b6e8 100644 --- a/src/merkle/partial_mt/mod.rs +++ b/src/merkle/partial_mt/mod.rs @@ -158,11 +158,7 @@ impl PartialMerkleTree { } } - Ok(PartialMerkleTree { - max_depth, - nodes, - leaves, - }) + Ok(PartialMerkleTree { max_depth, nodes, leaves }) } // PUBLIC ACCESSORS diff --git a/src/merkle/path.rs b/src/merkle/path.rs index 2d11bb3..4535ba9 100644 --- a/src/merkle/path.rs +++ b/src/merkle/path.rs @@ -137,11 +137,7 @@ impl<'a> Iterator for InnerNodeIterator<'a> { self.value = Rpo256::merge(&[left, right]); self.index.move_up(); - Some(InnerNodeInfo { - value: self.value, - left, - right, - }) + Some(InnerNodeInfo { value: self.value, left, right }) } else { None } @@ -163,10 +159,7 @@ pub struct ValuePath { impl ValuePath { /// Returns a new [ValuePath] instantiated from the specified value and path. pub fn new(value: RpoDigest, path: Vec) -> Self { - Self { - value, - path: MerklePath::new(path), - } + Self { value, path: MerklePath::new(path) } } } diff --git a/src/merkle/simple_smt/mod.rs b/src/merkle/simple_smt/mod.rs index 7c40d90..a951122 100644 --- a/src/merkle/simple_smt/mod.rs +++ b/src/merkle/simple_smt/mod.rs @@ -249,10 +249,7 @@ impl SimpleSmt { fn get_branch_node(&self, index: &NodeIndex) -> BranchNode { self.branches.get(index).cloned().unwrap_or_else(|| { let node = self.empty_hashes[index.depth() as usize + 1]; - BranchNode { - left: node, - right: node, - } + BranchNode { left: node, right: node } }) } diff --git a/src/merkle/simple_smt/tests.rs b/src/merkle/simple_smt/tests.rs index 26aaab2..f2c55d1 100644 --- a/src/merkle/simple_smt/tests.rs +++ b/src/merkle/simple_smt/tests.rs @@ -123,21 +123,9 @@ fn test_inner_node_iterator() -> Result<(), MerkleError> { let nodes: Vec = tree.inner_nodes().collect(); let expected = vec![ - InnerNodeInfo { - value: root, - left: l1n0, - right: l1n1, - }, - InnerNodeInfo { - value: l1n0, - left: l2n0, - right: l2n1, - }, - InnerNodeInfo { - value: l1n1, - left: l2n2, - right: l2n3, - }, + InnerNodeInfo { value: root, left: l1n0, right: l1n1 }, + InnerNodeInfo { value: l1n0, left: l2n0, right: l2n1 }, + InnerNodeInfo { value: l1n1, left: l2n2, right: l2n3 }, ]; assert_eq!(nodes, expected); diff --git a/src/merkle/store/mod.rs b/src/merkle/store/mod.rs index a8dd1fb..caba99e 100644 --- a/src/merkle/store/mod.rs +++ b/src/merkle/store/mod.rs @@ -326,11 +326,9 @@ impl> MerkleStore { /// Iterator over the inner nodes of the [MerkleStore]. pub fn inner_nodes(&self) -> impl Iterator + '_ { - self.nodes.iter().map(|(r, n)| InnerNodeInfo { - value: *r, - left: n.left, - right: n.right, - }) + self.nodes + .iter() + .map(|(r, n)| InnerNodeInfo { value: *r, left: n.left, right: n.right }) } /// Iterator over the non-empty leaves of the Merkle tree associated with the specified `root` @@ -450,13 +448,7 @@ impl> MerkleStore { right_root: RpoDigest, ) -> Result { let parent = Rpo256::merge(&[left_root, right_root]); - self.nodes.insert( - parent, - StoreNode { - left: left_root, - right: right_root, - }, - ); + self.nodes.insert(parent, StoreNode { left: left_root, right: right_root }); Ok(parent) } @@ -551,15 +543,10 @@ impl> FromIterator<(RpoDigest, StoreNode)> for Me // ================================================================================================ impl> Extend for MerkleStore { fn extend>(&mut self, iter: I) { - self.nodes.extend(iter.into_iter().map(|info| { - ( - info.value, - StoreNode { - left: info.left, - right: info.right, - }, - ) - })); + self.nodes.extend( + iter.into_iter() + .map(|info| (info.value, StoreNode { left: info.left, right: info.right })), + ); } } @@ -646,17 +633,12 @@ impl> Deserializable for MerkleStore { /// Creates empty hashes for all the subtrees of a tree with a max depth of 255. fn empty_hashes() -> impl IntoIterator { let subtrees = EmptySubtreeRoots::empty_hashes(255); - subtrees.iter().rev().copied().zip(subtrees.iter().rev().skip(1).copied()).map( - |(child, parent)| { - ( - parent, - StoreNode { - left: child, - right: child, - }, - ) - }, - ) + subtrees + .iter() + .rev() + .copied() + .zip(subtrees.iter().rev().skip(1).copied()) + .map(|(child, parent)| (parent, StoreNode { left: child, right: child })) } /// Consumes an iterator of [InnerNodeInfo] and returns an iterator of `(value, node)` tuples @@ -666,14 +648,6 @@ fn combine_nodes_with_empty_hashes( ) -> impl Iterator { nodes .into_iter() - .map(|info| { - ( - info.value, - StoreNode { - left: info.left, - right: info.right, - }, - ) - }) + .map(|info| (info.value, StoreNode { left: info.left, right: info.right })) .chain(empty_hashes()) } diff --git a/src/merkle/tiered_smt/values.rs b/src/merkle/tiered_smt/values.rs index d41ee6b..4fac76a 100644 --- a/src/merkle/tiered_smt/values.rs +++ b/src/merkle/tiered_smt/values.rs @@ -213,10 +213,7 @@ impl StoreEntry { /// Returns an iterator over all key-value pairs in this entry. pub fn iter(&self) -> impl Iterator { - EntryIterator { - entry: self, - pos: 0, - } + EntryIterator { entry: self, pos: 0 } } // STATE MUTATORS diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 8aadabe..7449c89 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -83,10 +83,7 @@ impl std::error::Error for HexParseError {} pub fn hex_to_bytes(value: &str) -> Result<[u8; N], HexParseError> { let expected: usize = (N * 2) + 2; if value.len() != expected { - return Err(HexParseError::InvalidLength { - expected, - got: value.len(), - }); + return Err(HexParseError::InvalidLength { expected, got: value.len() }); } if !value.starts_with("0x") {