* chore: update crate version to v0.9.0 * chore: remove deprecated re-exports * chore: remove Box re-export * feat: implement pure-Rust keygen and signing for RpoFalcon512 (#285) * feat: add reproducible builds (#296) * fix: address a few issues for migrating Miden VM (#298) * feat: add RngCore supertrait for FeltRng (#299) --------- Co-authored-by: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Co-authored-by: Paul-Henry Kajfasz <42912740+phklive@users.noreply.github.com>al-falcon-test-vectors
@ -1,133 +0,0 @@ |
|||||
name: CI |
|
||||
on: |
|
||||
push: |
|
||||
branches: |
|
||||
- main |
|
||||
pull_request: |
|
||||
types: [opened, reopened, synchronize] |
|
||||
|
|
||||
jobs: |
|
||||
rustfmt: |
|
||||
name: rustfmt ${{matrix.toolchain}} on ${{matrix.os}} |
|
||||
runs-on: ${{matrix.os}}-latest |
|
||||
strategy: |
|
||||
fail-fast: false |
|
||||
matrix: |
|
||||
toolchain: [nightly] |
|
||||
os: [ubuntu] |
|
||||
steps: |
|
||||
- uses: actions/checkout@v4 |
|
||||
- name: Install minimal Rust with rustfmt |
|
||||
uses: actions-rs/toolchain@v1 |
|
||||
with: |
|
||||
profile: minimal |
|
||||
toolchain: ${{matrix.toolchain}} |
|
||||
components: rustfmt |
|
||||
override: true |
|
||||
- name: fmt |
|
||||
uses: actions-rs/cargo@v1 |
|
||||
with: |
|
||||
command: fmt |
|
||||
args: --all -- --check |
|
||||
|
|
||||
clippy: |
|
||||
name: clippy ${{matrix.toolchain}} on ${{matrix.os}} |
|
||||
runs-on: ${{matrix.os}}-latest |
|
||||
strategy: |
|
||||
fail-fast: false |
|
||||
matrix: |
|
||||
toolchain: [nightly] |
|
||||
os: [ubuntu] |
|
||||
steps: |
|
||||
- uses: actions/checkout@v4 |
|
||||
with: |
|
||||
submodules: recursive |
|
||||
- name: Install minimal Rust with clippy |
|
||||
uses: actions-rs/toolchain@v1 |
|
||||
with: |
|
||||
profile: minimal |
|
||||
toolchain: ${{matrix.toolchain}} |
|
||||
components: clippy |
|
||||
override: true |
|
||||
- name: Clippy |
|
||||
uses: actions-rs/cargo@v1 |
|
||||
with: |
|
||||
command: clippy |
|
||||
args: --all-targets -- -D clippy::all -D warnings |
|
||||
- name: Clippy all features |
|
||||
uses: actions-rs/cargo@v1 |
|
||||
with: |
|
||||
command: clippy |
|
||||
args: --all-targets --all-features -- -D clippy::all -D warnings |
|
||||
|
|
||||
test: |
|
||||
name: test ${{matrix.toolchain}} on ${{matrix.os}} with ${{matrix.features}} |
|
||||
runs-on: ${{matrix.os}}-latest |
|
||||
strategy: |
|
||||
fail-fast: false |
|
||||
matrix: |
|
||||
toolchain: [stable, nightly] |
|
||||
os: [ubuntu] |
|
||||
features: ["--features default,serde", --no-default-features] |
|
||||
timeout-minutes: 30 |
|
||||
steps: |
|
||||
- uses: actions/checkout@v4 |
|
||||
with: |
|
||||
submodules: recursive |
|
||||
- name: Install rust |
|
||||
uses: actions-rs/toolchain@v1 |
|
||||
with: |
|
||||
toolchain: ${{matrix.toolchain}} |
|
||||
override: true |
|
||||
- name: Test |
|
||||
uses: actions-rs/cargo@v1 |
|
||||
with: |
|
||||
command: test |
|
||||
args: ${{matrix.features}} |
|
||||
|
|
||||
no-std: |
|
||||
name: build ${{matrix.toolchain}} no-std for wasm32-unknown-unknown |
|
||||
runs-on: ubuntu-latest |
|
||||
strategy: |
|
||||
fail-fast: false |
|
||||
matrix: |
|
||||
toolchain: [stable, nightly] |
|
||||
steps: |
|
||||
- uses: actions/checkout@v4 |
|
||||
with: |
|
||||
submodules: recursive |
|
||||
- name: Install rust |
|
||||
uses: actions-rs/toolchain@v1 |
|
||||
with: |
|
||||
toolchain: ${{matrix.toolchain}} |
|
||||
override: true |
|
||||
- run: rustup target add wasm32-unknown-unknown |
|
||||
- name: Build |
|
||||
uses: actions-rs/cargo@v1 |
|
||||
with: |
|
||||
command: build |
|
||||
args: --no-default-features --target wasm32-unknown-unknown |
|
||||
|
|
||||
docs: |
|
||||
name: Verify the docs on ${{matrix.toolchain}} |
|
||||
runs-on: ubuntu-latest |
|
||||
strategy: |
|
||||
fail-fast: false |
|
||||
matrix: |
|
||||
toolchain: [stable] |
|
||||
steps: |
|
||||
- uses: actions/checkout@v4 |
|
||||
with: |
|
||||
submodules: recursive |
|
||||
- name: Install rust |
|
||||
uses: actions-rs/toolchain@v1 |
|
||||
with: |
|
||||
toolchain: ${{matrix.toolchain}} |
|
||||
override: true |
|
||||
- name: Check docs |
|
||||
uses: actions-rs/cargo@v1 |
|
||||
env: |
|
||||
RUSTDOCFLAGS: -D warnings |
|
||||
with: |
|
||||
command: doc |
|
||||
args: --verbose --all-features --keep-going |
|
@ -0,0 +1,31 @@ |
|||||
|
# Runs documentation related jobs. |
||||
|
|
||||
|
name: doc |
||||
|
|
||||
|
on: |
||||
|
push: |
||||
|
branches: |
||||
|
- main |
||||
|
pull_request: |
||||
|
types: [opened, reopened, synchronize] |
||||
|
|
||||
|
jobs: |
||||
|
docs: |
||||
|
name: Verify the docs on ${{matrix.toolchain}} |
||||
|
runs-on: ubuntu-latest |
||||
|
strategy: |
||||
|
fail-fast: false |
||||
|
matrix: |
||||
|
toolchain: [stable] |
||||
|
steps: |
||||
|
- uses: actions/checkout@v4 |
||||
|
with: |
||||
|
submodules: recursive |
||||
|
- name: Install rust |
||||
|
uses: actions-rs/toolchain@v1 |
||||
|
with: |
||||
|
toolchain: ${{matrix.toolchain}} |
||||
|
override: true |
||||
|
- uses: davidB/rust-cargo-make@v1 |
||||
|
- name: cargo make - doc |
||||
|
run: cargo make doc |
@ -0,0 +1,66 @@ |
|||||
|
# Runs linting related jobs. |
||||
|
|
||||
|
name: lint |
||||
|
|
||||
|
on: |
||||
|
push: |
||||
|
branches: |
||||
|
- main |
||||
|
pull_request: |
||||
|
types: [opened, reopened, synchronize] |
||||
|
|
||||
|
jobs: |
||||
|
version: |
||||
|
name: check rust version consistency |
||||
|
runs-on: ubuntu-latest |
||||
|
steps: |
||||
|
- uses: actions/checkout@v4 |
||||
|
with: |
||||
|
profile: minimal |
||||
|
override: true |
||||
|
- name: check rust versions |
||||
|
run: ./scripts/check-rust-version.sh |
||||
|
|
||||
|
rustfmt: |
||||
|
name: rustfmt ${{matrix.toolchain}} on ${{matrix.os}} |
||||
|
runs-on: ${{matrix.os}}-latest |
||||
|
strategy: |
||||
|
fail-fast: false |
||||
|
matrix: |
||||
|
toolchain: [nightly] |
||||
|
os: [ubuntu] |
||||
|
steps: |
||||
|
- uses: actions/checkout@v4 |
||||
|
- name: Install minimal Rust with rustfmt |
||||
|
uses: actions-rs/toolchain@v1 |
||||
|
with: |
||||
|
profile: minimal |
||||
|
toolchain: ${{matrix.toolchain}} |
||||
|
components: rustfmt |
||||
|
override: true |
||||
|
- uses: davidB/rust-cargo-make@v1 |
||||
|
- name: cargo make - format-check |
||||
|
run: cargo make format-check |
||||
|
|
||||
|
clippy: |
||||
|
name: clippy ${{matrix.toolchain}} on ${{matrix.os}} |
||||
|
runs-on: ${{matrix.os}}-latest |
||||
|
strategy: |
||||
|
fail-fast: false |
||||
|
matrix: |
||||
|
toolchain: [stable] |
||||
|
os: [ubuntu] |
||||
|
steps: |
||||
|
- uses: actions/checkout@v4 |
||||
|
with: |
||||
|
submodules: recursive |
||||
|
- name: Install minimal Rust with clippy |
||||
|
uses: actions-rs/toolchain@v1 |
||||
|
with: |
||||
|
profile: minimal |
||||
|
toolchain: ${{matrix.toolchain}} |
||||
|
components: clippy |
||||
|
override: true |
||||
|
- uses: davidB/rust-cargo-make@v1 |
||||
|
- name: cargo make - clippy |
||||
|
run: cargo make clippy |
@ -0,0 +1,32 @@ |
|||||
|
# Runs no-std related jobs. |
||||
|
|
||||
|
name: no-std |
||||
|
|
||||
|
on: |
||||
|
push: |
||||
|
branches: |
||||
|
- main |
||||
|
pull_request: |
||||
|
types: [opened, reopened, synchronize] |
||||
|
|
||||
|
jobs: |
||||
|
no-std: |
||||
|
name: build ${{matrix.toolchain}} no-std for wasm32-unknown-unknown |
||||
|
runs-on: ubuntu-latest |
||||
|
strategy: |
||||
|
fail-fast: false |
||||
|
matrix: |
||||
|
toolchain: [stable, nightly] |
||||
|
steps: |
||||
|
- uses: actions/checkout@v4 |
||||
|
with: |
||||
|
submodules: recursive |
||||
|
- name: Install rust |
||||
|
uses: actions-rs/toolchain@v1 |
||||
|
with: |
||||
|
toolchain: ${{matrix.toolchain}} |
||||
|
override: true |
||||
|
- run: rustup target add wasm32-unknown-unknown |
||||
|
- uses: davidB/rust-cargo-make@v1 |
||||
|
- name: cargo make - build-no-std |
||||
|
run: cargo make build-no-std |
@ -0,0 +1,34 @@ |
|||||
|
# Runs testing related jobs |
||||
|
|
||||
|
name: test |
||||
|
|
||||
|
on: |
||||
|
push: |
||||
|
branches: |
||||
|
- main |
||||
|
pull_request: |
||||
|
types: [opened, reopened, synchronize] |
||||
|
|
||||
|
jobs: |
||||
|
test: |
||||
|
name: test ${{matrix.toolchain}} on ${{matrix.os}} with ${{matrix.features}} |
||||
|
runs-on: ${{matrix.os}}-latest |
||||
|
strategy: |
||||
|
fail-fast: false |
||||
|
matrix: |
||||
|
toolchain: [stable, nightly] |
||||
|
os: [ubuntu] |
||||
|
features: ["test", "test-no-default-features"] |
||||
|
timeout-minutes: 30 |
||||
|
steps: |
||||
|
- uses: actions/checkout@v4 |
||||
|
with: |
||||
|
submodules: recursive |
||||
|
- name: Install rust |
||||
|
uses: actions-rs/toolchain@v1 |
||||
|
with: |
||||
|
toolchain: ${{matrix.toolchain}} |
||||
|
override: true |
||||
|
- uses: davidB/rust-cargo-make@v1 |
||||
|
- name: cargo make - test |
||||
|
run: cargo make ${{matrix.features}} |
@ -1,3 +0,0 @@ |
|||||
[submodule "PQClean"] |
|
||||
path = PQClean |
|
||||
url = https://github.com/PQClean/PQClean.git |
|
@ -0,0 +1,86 @@ |
|||||
|
# Cargo Makefile
|
||||
|
|
||||
|
# -- linting --------------------------------------------------------------------------------------
|
||||
|
[tasks.format] |
||||
|
toolchain = "nightly" |
||||
|
command = "cargo" |
||||
|
args = ["fmt", "--all"] |
||||
|
|
||||
|
[tasks.format-check] |
||||
|
toolchain = "nightly" |
||||
|
command = "cargo" |
||||
|
args = ["fmt", "--all", "--", "--check"] |
||||
|
|
||||
|
[tasks.clippy-default] |
||||
|
command = "cargo" |
||||
|
args = ["clippy","--workspace", "--all-targets", "--", "-D", "clippy::all", "-D", "warnings"] |
||||
|
|
||||
|
[tasks.clippy-all-features] |
||||
|
command = "cargo" |
||||
|
args = ["clippy","--workspace", "--all-targets", "--all-features", "--", "-D", "clippy::all", "-D", "warnings"] |
||||
|
|
||||
|
[tasks.clippy] |
||||
|
dependencies = [ |
||||
|
"clippy-default", |
||||
|
"clippy-all-features" |
||||
|
] |
||||
|
|
||||
|
[tasks.fix] |
||||
|
description = "Runs Fix" |
||||
|
command = "cargo" |
||||
|
toolchain = "nightly" |
||||
|
args = ["fix", "--allow-staged", "--allow-dirty", "--all-targets", "--all-features"] |
||||
|
|
||||
|
[tasks.lint] |
||||
|
description = "Runs all linting tasks (Clippy, fixing, formatting)" |
||||
|
run_task = { name = ["format", "format-check", "clippy", "docs"] } |
||||
|
|
||||
|
# --- docs ----------------------------------------------------------------------------------------
|
||||
|
[tasks.doc] |
||||
|
env = { "RUSTDOCFLAGS" = "-D warnings" } |
||||
|
command = "cargo" |
||||
|
args = ["doc", "--all-features", "--keep-going", "--release"] |
||||
|
|
||||
|
# --- testing -------------------------------------------------------------------------------------
|
||||
|
[tasks.test] |
||||
|
description = "Run tests with default features" |
||||
|
env = { "RUSTFLAGS" = "-C debug-assertions -C overflow-checks -C debuginfo=2" } |
||||
|
workspace = false |
||||
|
command = "cargo" |
||||
|
args = ["test", "--release"] |
||||
|
|
||||
|
[tasks.test-no-default-features] |
||||
|
description = "Run tests with no-default-features" |
||||
|
env = { "RUSTFLAGS" = "-C debug-assertions -C overflow-checks -C debuginfo=2" } |
||||
|
workspace = false |
||||
|
command = "cargo" |
||||
|
args = ["test", "--release", "--no-default-features"] |
||||
|
|
||||
|
[tasks.test-all] |
||||
|
description = "Run all tests" |
||||
|
workspace = false |
||||
|
run_task = { name = ["test", "test-no-default-features"], parallel = true } |
||||
|
|
||||
|
# --- building ------------------------------------------------------------------------------------
|
||||
|
[tasks.build] |
||||
|
description = "Build in release mode" |
||||
|
command = "cargo" |
||||
|
args = ["build", "--release"] |
||||
|
|
||||
|
[tasks.build-no-std] |
||||
|
description = "Build using no-std" |
||||
|
command = "cargo" |
||||
|
args = ["build", "--release", "--no-default-features", "--target", "wasm32-unknown-unknown"] |
||||
|
|
||||
|
[tasks.build-avx2] |
||||
|
description = "Build using AVX2 acceleration" |
||||
|
env = { "RUSTFLAGS" = "-C target-feature=+avx2" } |
||||
|
command = "cargo" |
||||
|
args = ["build", "--release"] |
||||
|
|
||||
|
[tasks.build-sve] |
||||
|
description = "Build with SVE acceleration" |
||||
|
env = { "RUSTFLAGS" = "-C target-feature=+sve" } |
||||
|
command = "cargo" |
||||
|
args = ["build", "--release"] |
||||
|
|
@ -0,0 +1 @@ |
|||||
|
1.75 |
@ -0,0 +1,13 @@ |
|||||
|
#!/bin/bash |
||||
|
|
||||
|
# Check rust-toolchain file |
||||
|
TOOLCHAIN_VERSION=$(cat rust-toolchain) |
||||
|
|
||||
|
# Check workspace Cargo.toml file |
||||
|
CARGO_VERSION=$(cat Cargo.toml | grep "rust-version" | cut -d '"' -f 2) |
||||
|
if [ "$CARGO_VERSION" != "$TOOLCHAIN_VERSION" ]; then |
||||
|
echo "Mismatch in Cargo.toml: Expected $TOOLCHAIN_VERSION, found $CARGO_VERSION" |
||||
|
exit 1 |
||||
|
fi |
||||
|
|
||||
|
echo "Rust versions match ✅" |
@ -1,56 +0,0 @@ |
|||||
use core::fmt;
|
|
||||
|
|
||||
use super::{LOG_N, MODULUS, PK_LEN};
|
|
||||
|
|
||||
#[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 {}
|
|
@ -1,402 +0,0 @@ |
|||||
/* |
|
||||
* Wrapper for implementing the PQClean API. |
|
||||
*/ |
|
||||
|
|
||||
#include <string.h> |
|
||||
#include "randombytes.h" |
|
||||
#include "falcon.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 falcon.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 falcon.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 falcon.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); |
|
||||
} |
|
@ -1,66 +0,0 @@ |
|||||
#include <stddef.h> |
|
||||
#include <stdint.h> |
|
||||
|
|
||||
#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); |
|
@ -1,582 +0,0 @@ |
|||||
/* |
|
||||
* RPO implementation. |
|
||||
*/ |
|
||||
|
|
||||
#include <stdint.h> |
|
||||
#include <string.h> |
|
||||
#include <stdlib.h> |
|
||||
|
|
||||
/* ================================================================================================ |
|
||||
* Modular Arithmetic |
|
||||
*/ |
|
||||
|
|
||||
#define P 0xFFFFFFFF00000001 |
|
||||
#define M 12289 |
|
||||
|
|
||||
// From https://github.com/ncw/iprime/blob/master/mod_math_noasm.go |
|
||||
static 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; |
|
||||
} |
|
||||
|
|
||||
static uint64_t sub_mod_p(uint64_t a, uint64_t b) |
|
||||
{ |
|
||||
uint64_t r = a - b; |
|
||||
if (a < b) |
|
||||
r += P; |
|
||||
return r; |
|
||||
} |
|
||||
|
|
||||
static 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; |
|
||||
} |
|
||||
|
|
||||
static 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 |
|
||||
*/ |
|
||||
|
|
||||
#define STATE_WIDTH 12 |
|
||||
#define NUM_ROUNDS 7 |
|
||||
|
|
||||
/* |
|
||||
* MDS matrix |
|
||||
*/ |
|
||||
static 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. |
|
||||
*/ |
|
||||
static 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, |
|
||||
}, |
|
||||
}; |
|
||||
|
|
||||
static 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)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
static 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]; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
static 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)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
static 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)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
static 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); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
static 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--; |
|
||||
} |
|
||||
} |
|
@ -1,83 +0,0 @@ |
|||||
#include <stdint.h> |
|
||||
#include <string.h> |
|
||||
|
|
||||
/* ================================================================================================ |
|
||||
* 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); |
|
@ -1,194 +0,0 @@ |
|||||
use core::ffi::c_int;
|
|
||||
|
|
||||
// C IMPLEMENTATION INTERFACE
|
|
||||
// ================================================================================================
|
|
||||
|
|
||||
#[link(name = "rpo_falcon512", kind = "static")]
|
|
||||
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 core::ffi::c_void,
|
|
||||
// TODO: When #![feature(c_size_t)] stabilizes, switch this to `core::ffi::size_t` to be
|
|
||||
// more accurate. Currently, however, all Rust targets as of this writing are such that
|
|
||||
// `core::ffi::size_t` and `usize` are the same size.
|
|
||||
len: usize,
|
|
||||
);
|
|
||||
|
|
||||
#[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 alloc::vec::Vec;
|
|
||||
use rand_utils::{rand_array, rand_value, rand_vector};
|
|
||||
|
|
||||
use super::*;
|
|
||||
use crate::dsa::rpo_falcon512::{NONCE_LEN, PK_LEN, SIG_LEN, SK_LEN};
|
|
||||
|
|
||||
#[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] = rand_array();
|
|
||||
|
|
||||
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 = rand_value::<u16>() as usize;
|
|
||||
let msg: Vec<u8> = rand_vector(mlen);
|
|
||||
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()
|
|
||||
)
|
|
||||
);
|
|
||||
}
|
|
||||
}
|
|
||||
}
|
|
@ -0,0 +1,68 @@ |
|||||
|
use super::{math::FalconFelt, Nonce, Polynomial, Rpo256, Word, MODULUS, N, ZERO};
|
||||
|
use alloc::vec::Vec;
|
||||
|
use num::Zero;
|
||||
|
|
||||
|
// HASH-TO-POINT FUNCTIONS
|
||||
|
// ================================================================================================
|
||||
|
|
||||
|
/// Returns a polynomial in Z_p[x]/(phi) representing the hash of the provided message and
|
||||
|
/// nonce using RPO256.
|
||||
|
pub fn hash_to_point_rpo256(message: Word, nonce: &Nonce) -> Polynomial<FalconFelt> {
|
||||
|
let mut state = [ZERO; Rpo256::STATE_WIDTH];
|
||||
|
|
||||
|
// absorb the nonce into the state
|
||||
|
let nonce_elements = nonce.to_elements();
|
||||
|
for (&n, s) in nonce_elements.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 = [FalconFelt::zero(); N];
|
||||
|
for _ in 0..64 {
|
||||
|
Rpo256::apply_permutation(&mut state);
|
||||
|
for a in &state[Rpo256::RATE_RANGE] {
|
||||
|
res[i] = FalconFelt::new((a.as_int() % MODULUS as u64) as i16);
|
||||
|
i += 1;
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
Polynomial::new(res.to_vec())
|
||||
|
}
|
||||
|
|
||||
|
/// Returns a polynomial in Z_p[x]/(phi) representing the hash of the provided message and
|
||||
|
/// nonce using SHAKE256. This is the hash-to-point algorithm used in the reference implementation.
|
||||
|
#[allow(dead_code)]
|
||||
|
pub fn hash_to_point_shake256(message: &[u8], nonce: &Nonce) -> Polynomial<FalconFelt> {
|
||||
|
use sha3::{
|
||||
|
digest::{ExtendableOutput, Update, XofReader},
|
||||
|
Shake256,
|
||||
|
};
|
||||
|
|
||||
|
let mut data = vec![];
|
||||
|
data.extend_from_slice(nonce.as_bytes());
|
||||
|
data.extend_from_slice(message);
|
||||
|
const K: u32 = (1u32 << 16) / MODULUS as u32;
|
||||
|
|
||||
|
let mut hasher = Shake256::default();
|
||||
|
hasher.update(&data);
|
||||
|
let mut reader = hasher.finalize_xof();
|
||||
|
|
||||
|
let mut coefficients: Vec<FalconFelt> = Vec::with_capacity(N);
|
||||
|
while coefficients.len() != N {
|
||||
|
let mut randomness = [0u8; 2];
|
||||
|
reader.read(&mut randomness);
|
||||
|
let t = ((randomness[0] as u32) << 8) | (randomness[1] as u32);
|
||||
|
if t < K * MODULUS as u32 {
|
||||
|
coefficients.push(FalconFelt::new((t % MODULUS as u32) as i16));
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
Polynomial { coefficients }
|
||||
|
}
|
@ -1,235 +0,0 @@ |
|||||
use super::{
|
|
||||
ByteReader, ByteWriter, Deserializable, DeserializationError, FalconError, Polynomial,
|
|
||||
PublicKeyBytes, Rpo256, SecretKeyBytes, Serializable, Signature, Word,
|
|
||||
};
|
|
||||
#[cfg(feature = "std")]
|
|
||||
use {
|
|
||||
super::{ffi, NonceBytes, NONCE_LEN, PK_LEN, SIG_LEN, SK_LEN},
|
|
||||
alloc::vec::Vec,
|
|
||||
};
|
|
||||
|
|
||||
// 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<Self, FalconError> {
|
|
||||
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<PublicKey> 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<Self, FalconError> {
|
|
||||
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<Self, FalconError> {
|
|
||||
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<Signature, FalconError> {
|
|
||||
let msg = message.iter().flat_map(|e| e.as_int().to_le_bytes()).collect::<Vec<_>>();
|
|
||||
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,
|
|
||||
pk_polynomial: Default::default(),
|
|
||||
sig_polynomial: Default::default(),
|
|
||||
})
|
|
||||
} else {
|
|
||||
Err(FalconError::SigGenerationFailed)
|
|
||||
}
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
// SERIALIZATION / DESERIALIZATION
|
|
||||
// ================================================================================================
|
|
||||
|
|
||||
impl Serializable for KeyPair {
|
|
||||
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
|
||||
target.write_bytes(&self.public_key);
|
|
||||
target.write_bytes(&self.secret_key);
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
impl Deserializable for KeyPair {
|
|
||||
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
|
||||
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 rand_utils::{rand_array, rand_vector};
|
|
||||
|
|
||||
use super::{super::Felt, KeyPair, NonceBytes, Word};
|
|
||||
|
|
||||
#[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::<Felt>(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::<Felt>(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::<Felt>(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::<Felt>(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()))
|
|
||||
}
|
|
||||
}
|
|
@ -0,0 +1,53 @@ |
|||||
|
use super::{
|
||||
|
math::{FalconFelt, Polynomial},
|
||||
|
ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, Serializable, Signature,
|
||||
|
Word,
|
||||
|
};
|
||||
|
|
||||
|
mod public_key;
|
||||
|
pub use public_key::{PubKeyPoly, PublicKey};
|
||||
|
|
||||
|
mod secret_key;
|
||||
|
pub use secret_key::SecretKey;
|
||||
|
|
||||
|
// TESTS
|
||||
|
// ================================================================================================
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
mod tests {
|
||||
|
use crate::{dsa::rpo_falcon512::SecretKey, Word, ONE};
|
||||
|
use rand::SeedableRng;
|
||||
|
use rand_chacha::ChaCha20Rng;
|
||||
|
use winter_math::FieldElement;
|
||||
|
use winter_utils::{Deserializable, Serializable};
|
||||
|
|
||||
|
#[test]
|
||||
|
fn test_falcon_verification() {
|
||||
|
let seed = [0_u8; 32];
|
||||
|
let mut rng = ChaCha20Rng::from_seed(seed);
|
||||
|
|
||||
|
// generate random keys
|
||||
|
let sk = SecretKey::with_rng(&mut rng);
|
||||
|
let pk = sk.public_key();
|
||||
|
|
||||
|
// test secret key serialization/deserialization
|
||||
|
let mut buffer = vec![];
|
||||
|
sk.write_into(&mut buffer);
|
||||
|
let sk = SecretKey::read_from_bytes(&buffer).unwrap();
|
||||
|
|
||||
|
// sign a random message
|
||||
|
let message: Word = [ONE; 4];
|
||||
|
let signature = sk.sign_with_rng(message, &mut rng);
|
||||
|
|
||||
|
// make sure the signature verifies correctly
|
||||
|
assert!(pk.verify(message, &signature));
|
||||
|
|
||||
|
// a signature should not verify against a wrong message
|
||||
|
let message2: Word = [ONE.double(); 4];
|
||||
|
assert!(!pk.verify(message2, &signature));
|
||||
|
|
||||
|
// a signature should not verify against a wrong public key
|
||||
|
let sk2 = SecretKey::with_rng(&mut rng);
|
||||
|
assert!(!sk2.public_key().verify(message, &signature))
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,138 @@ |
|||||
|
use crate::dsa::rpo_falcon512::FALCON_ENCODING_BITS;
|
||||
|
|
||||
|
use super::{
|
||||
|
super::{Rpo256, LOG_N, N, PK_LEN},
|
||||
|
ByteReader, ByteWriter, Deserializable, DeserializationError, FalconFelt, Felt, Polynomial,
|
||||
|
Serializable, Signature, Word,
|
||||
|
};
|
||||
|
use alloc::string::ToString;
|
||||
|
use core::ops::Deref;
|
||||
|
use num::Zero;
|
||||
|
|
||||
|
// 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. The hash is computed
|
||||
|
/// using Rpo256.
|
||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
pub struct PublicKey(Word);
|
||||
|
|
||||
|
impl PublicKey {
|
||||
|
/// Returns a new [PublicKey] which is a commitment to the provided expanded public key.
|
||||
|
pub fn new(pub_key: Word) -> Self {
|
||||
|
Self(pub_key)
|
||||
|
}
|
||||
|
|
||||
|
/// 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<PubKeyPoly> for PublicKey {
|
||||
|
fn from(pk_poly: PubKeyPoly) -> Self {
|
||||
|
let pk_felts: Polynomial<Felt> = pk_poly.0.into();
|
||||
|
let pk_digest = Rpo256::hash_elements(&pk_felts.coefficients).into();
|
||||
|
Self(pk_digest)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl From<PublicKey> for Word {
|
||||
|
fn from(key: PublicKey) -> Self {
|
||||
|
key.0
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
// PUBLIC KEY POLYNOMIAL
|
||||
|
// ================================================================================================
|
||||
|
|
||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
pub struct PubKeyPoly(pub Polynomial<FalconFelt>);
|
||||
|
|
||||
|
impl Deref for PubKeyPoly {
|
||||
|
type Target = Polynomial<FalconFelt>;
|
||||
|
|
||||
|
fn deref(&self) -> &Self::Target {
|
||||
|
&self.0
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl From<Polynomial<FalconFelt>> for PubKeyPoly {
|
||||
|
fn from(pk_poly: Polynomial<FalconFelt>) -> Self {
|
||||
|
Self(pk_poly)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl Serializable for &PubKeyPoly {
|
||||
|
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
|
let mut buf = [0_u8; PK_LEN];
|
||||
|
buf[0] = LOG_N;
|
||||
|
|
||||
|
let mut acc = 0_u32;
|
||||
|
let mut acc_len: u32 = 0;
|
||||
|
|
||||
|
let mut input_pos = 1;
|
||||
|
for c in self.0.coefficients.iter() {
|
||||
|
let c = c.value();
|
||||
|
acc = (acc << FALCON_ENCODING_BITS) | c as u32;
|
||||
|
acc_len += FALCON_ENCODING_BITS;
|
||||
|
while acc_len >= 8 {
|
||||
|
acc_len -= 8;
|
||||
|
buf[input_pos] = (acc >> acc_len) as u8;
|
||||
|
input_pos += 1;
|
||||
|
}
|
||||
|
}
|
||||
|
if acc_len > 0 {
|
||||
|
buf[input_pos] = (acc >> (8 - acc_len)) as u8;
|
||||
|
}
|
||||
|
|
||||
|
target.write(buf);
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl Deserializable for PubKeyPoly {
|
||||
|
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||
|
let buf = source.read_array::<PK_LEN>()?;
|
||||
|
|
||||
|
if buf[0] != LOG_N {
|
||||
|
return Err(DeserializationError::InvalidValue(format!(
|
||||
|
"Failed to decode public key: expected the first byte to be {LOG_N} but was {}",
|
||||
|
buf[0]
|
||||
|
)));
|
||||
|
}
|
||||
|
|
||||
|
let mut acc = 0_u32;
|
||||
|
let mut acc_len = 0;
|
||||
|
|
||||
|
let mut output = [FalconFelt::zero(); N];
|
||||
|
let mut output_idx = 0;
|
||||
|
|
||||
|
for &byte in buf.iter().skip(1) {
|
||||
|
acc = (acc << 8) | (byte as u32);
|
||||
|
acc_len += 8;
|
||||
|
|
||||
|
if acc_len >= FALCON_ENCODING_BITS {
|
||||
|
acc_len -= FALCON_ENCODING_BITS;
|
||||
|
let w = (acc >> acc_len) & 0x3FFF;
|
||||
|
let element = w.try_into().map_err(|err| {
|
||||
|
DeserializationError::InvalidValue(format!(
|
||||
|
"Failed to decode public key: {err}"
|
||||
|
))
|
||||
|
})?;
|
||||
|
output[output_idx] = element;
|
||||
|
output_idx += 1;
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
if (acc & ((1u32 << acc_len) - 1)) == 0 {
|
||||
|
Ok(Polynomial::new(output.to_vec()).into())
|
||||
|
} else {
|
||||
|
Err(DeserializationError::InvalidValue(
|
||||
|
"Failed to decode public key: input not fully consumed".to_string(),
|
||||
|
))
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,386 @@ |
|||||
|
use super::{
|
||||
|
super::{
|
||||
|
math::{ffldl, ffsampling, gram, normalize_tree, FalconFelt, FastFft, LdlTree, Polynomial},
|
||||
|
signature::SignaturePoly,
|
||||
|
ByteReader, ByteWriter, Deserializable, DeserializationError, Nonce, Serializable,
|
||||
|
ShortLatticeBasis, Signature, Word, MODULUS, N, SIGMA, SIG_L2_BOUND,
|
||||
|
},
|
||||
|
PubKeyPoly, PublicKey,
|
||||
|
};
|
||||
|
use crate::dsa::rpo_falcon512::{
|
||||
|
hash_to_point::hash_to_point_rpo256, math::ntru_gen, SIG_NONCE_LEN, SK_LEN,
|
||||
|
};
|
||||
|
use alloc::{string::ToString, vec::Vec};
|
||||
|
use num::Complex;
|
||||
|
use num_complex::Complex64;
|
||||
|
use rand::Rng;
|
||||
|
|
||||
|
#[cfg(not(feature = "std"))]
|
||||
|
use num::Float;
|
||||
|
|
||||
|
// CONSTANTS
|
||||
|
// ================================================================================================
|
||||
|
|
||||
|
const WIDTH_BIG_POLY_COEFFICIENT: usize = 8;
|
||||
|
const WIDTH_SMALL_POLY_COEFFICIENT: usize = 6;
|
||||
|
|
||||
|
// SECRET KEY
|
||||
|
// ================================================================================================
|
||||
|
|
||||
|
/// The secret key is a quadruple [[g, -f], [G, -F]] of polynomials with integer coefficients. Each
|
||||
|
/// polynomial is of degree at most N = 512 and computations with these polynomials is done modulo
|
||||
|
/// the monic irreducible polynomial ϕ = x^N + 1. The secret key is a basis for a lattice and has
|
||||
|
/// the property of being short with respect to a certain norm and an upper bound appropriate for
|
||||
|
/// a given security parameter. The public key on the other hand is another basis for the same
|
||||
|
/// lattice and can be described by a single polynomial h with integer coefficients modulo ϕ.
|
||||
|
/// The two keys are related by the following relation:
|
||||
|
///
|
||||
|
/// 1. h = g /f [mod ϕ][mod p]
|
||||
|
/// 2. f.G - g.F = p [mod ϕ]
|
||||
|
///
|
||||
|
/// where p = 12289 is the Falcon prime. Equation 2 is called the NTRU equation.
|
||||
|
/// The secret key is generated by first sampling a random pair (f, g) of polynomials using
|
||||
|
/// an appropriate distribution that yields short but not too short polynomials with integer
|
||||
|
/// coefficients modulo ϕ. The NTRU equation is then used to find a matching pair (F, G).
|
||||
|
/// The public key is then derived from the secret key using equation 1.
|
||||
|
///
|
||||
|
/// To allow for fast signature generation, the secret key is pre-processed into a more suitable
|
||||
|
/// form, called the LDL tree, and this allows for fast sampling of short vectors in the lattice
|
||||
|
/// using Fast Fourier sampling during signature generation (ffSampling algorithm 11 in [1]).
|
||||
|
///
|
||||
|
/// [1]: https://falcon-sign.info/falcon.pdf
|
||||
|
#[derive(Debug, Clone)]
|
||||
|
pub struct SecretKey {
|
||||
|
secret_key: ShortLatticeBasis,
|
||||
|
tree: LdlTree,
|
||||
|
}
|
||||
|
|
||||
|
#[allow(clippy::new_without_default)]
|
||||
|
impl SecretKey {
|
||||
|
// CONSTRUCTORS
|
||||
|
// --------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/// Generates a secret key from OS-provided randomness.
|
||||
|
#[cfg(feature = "std")]
|
||||
|
pub fn new() -> Self {
|
||||
|
use rand::{rngs::StdRng, SeedableRng};
|
||||
|
|
||||
|
let mut rng = StdRng::from_entropy();
|
||||
|
Self::with_rng(&mut rng)
|
||||
|
}
|
||||
|
|
||||
|
/// Generates a secret_key using the provided random number generator `Rng`.
|
||||
|
pub fn with_rng<R: Rng>(rng: &mut R) -> Self {
|
||||
|
let basis = ntru_gen(N, rng);
|
||||
|
Self::from_short_lattice_basis(basis)
|
||||
|
}
|
||||
|
|
||||
|
/// Given a short basis [[g, -f], [G, -F]], computes the normalized LDL tree i.e., Falcon tree.
|
||||
|
fn from_short_lattice_basis(basis: ShortLatticeBasis) -> SecretKey {
|
||||
|
// FFT each polynomial of the short basis.
|
||||
|
let basis_fft = to_complex_fft(&basis);
|
||||
|
// compute the Gram matrix.
|
||||
|
let gram_fft = gram(basis_fft);
|
||||
|
// construct the LDL tree of the Gram matrix.
|
||||
|
let mut tree = ffldl(gram_fft);
|
||||
|
// normalize the leaves of the LDL tree.
|
||||
|
normalize_tree(&mut tree, SIGMA);
|
||||
|
Self { secret_key: basis, tree }
|
||||
|
}
|
||||
|
|
||||
|
// PUBLIC ACCESSORS
|
||||
|
// --------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/// Returns the polynomials of the short lattice basis of this secret key.
|
||||
|
pub fn short_lattice_basis(&self) -> &ShortLatticeBasis {
|
||||
|
&self.secret_key
|
||||
|
}
|
||||
|
|
||||
|
/// Returns the public key corresponding to this secret key.
|
||||
|
pub fn public_key(&self) -> PublicKey {
|
||||
|
self.compute_pub_key_poly().into()
|
||||
|
}
|
||||
|
|
||||
|
/// Returns the LDL tree associated to this secret key.
|
||||
|
pub fn tree(&self) -> &LdlTree {
|
||||
|
&self.tree
|
||||
|
}
|
||||
|
|
||||
|
// SIGNATURE GENERATION
|
||||
|
// --------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/// Signs a message with this secret key.
|
||||
|
#[cfg(feature = "std")]
|
||||
|
pub fn sign(&self, message: Word) -> Signature {
|
||||
|
use rand::{rngs::StdRng, SeedableRng};
|
||||
|
|
||||
|
let mut rng = StdRng::from_entropy();
|
||||
|
self.sign_with_rng(message, &mut rng)
|
||||
|
}
|
||||
|
|
||||
|
/// Signs a message with the secret key relying on the provided randomness generator.
|
||||
|
pub fn sign_with_rng<R: Rng>(&self, message: Word, rng: &mut R) -> Signature {
|
||||
|
let mut nonce_bytes = [0u8; SIG_NONCE_LEN];
|
||||
|
rng.fill_bytes(&mut nonce_bytes);
|
||||
|
let nonce = Nonce::new(nonce_bytes);
|
||||
|
|
||||
|
let h = self.compute_pub_key_poly();
|
||||
|
let c = hash_to_point_rpo256(message, &nonce);
|
||||
|
let s2 = self.sign_helper(c, rng);
|
||||
|
|
||||
|
Signature::new(nonce, h, s2)
|
||||
|
}
|
||||
|
|
||||
|
// HELPER METHODS
|
||||
|
// --------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/// Derives the public key corresponding to this secret key using h = g /f [mod ϕ][mod p].
|
||||
|
pub fn compute_pub_key_poly(&self) -> PubKeyPoly {
|
||||
|
let g: Polynomial<FalconFelt> = self.secret_key[0].clone().into();
|
||||
|
let g_fft = g.fft();
|
||||
|
let minus_f: Polynomial<FalconFelt> = self.secret_key[1].clone().into();
|
||||
|
let f = -minus_f;
|
||||
|
let f_fft = f.fft();
|
||||
|
let h_fft = g_fft.hadamard_div(&f_fft);
|
||||
|
h_fft.ifft().into()
|
||||
|
}
|
||||
|
|
||||
|
/// Signs a message polynomial with the secret key.
|
||||
|
///
|
||||
|
/// Takes a randomness generator implementing `Rng` and message polynomial representing `c`
|
||||
|
/// the hash-to-point of the message to be signed. It outputs a signature polynomial `s2`.
|
||||
|
fn sign_helper<R: Rng>(&self, c: Polynomial<FalconFelt>, rng: &mut R) -> SignaturePoly {
|
||||
|
let one_over_q = 1.0 / (MODULUS as f64);
|
||||
|
let c_over_q_fft = c.map(|cc| Complex::new(one_over_q * cc.value() as f64, 0.0)).fft();
|
||||
|
|
||||
|
// B = [[FFT(g), -FFT(f)], [FFT(G), -FFT(F)]]
|
||||
|
let [g_fft, minus_f_fft, big_g_fft, minus_big_f_fft] = to_complex_fft(&self.secret_key);
|
||||
|
let t0 = c_over_q_fft.hadamard_mul(&minus_big_f_fft);
|
||||
|
let t1 = -c_over_q_fft.hadamard_mul(&minus_f_fft);
|
||||
|
|
||||
|
loop {
|
||||
|
let bold_s = loop {
|
||||
|
let z = ffsampling(&(t0.clone(), t1.clone()), &self.tree, rng);
|
||||
|
let t0_min_z0 = t0.clone() - z.0;
|
||||
|
let t1_min_z1 = t1.clone() - z.1;
|
||||
|
|
||||
|
// s = (t-z) * B
|
||||
|
let s0 = t0_min_z0.hadamard_mul(&g_fft) + t1_min_z1.hadamard_mul(&big_g_fft);
|
||||
|
let s1 =
|
||||
|
t0_min_z0.hadamard_mul(&minus_f_fft) + t1_min_z1.hadamard_mul(&minus_big_f_fft);
|
||||
|
|
||||
|
// compute the norm of (s0||s1) and note that they are in FFT representation
|
||||
|
let length_squared: f64 =
|
||||
|
(s0.coefficients.iter().map(|a| (a * a.conj()).re).sum::<f64>()
|
||||
|
+ s1.coefficients.iter().map(|a| (a * a.conj()).re).sum::<f64>())
|
||||
|
/ (N as f64);
|
||||
|
|
||||
|
if length_squared > (SIG_L2_BOUND as f64) {
|
||||
|
continue;
|
||||
|
}
|
||||
|
|
||||
|
break [-s0, s1];
|
||||
|
};
|
||||
|
|
||||
|
let s2 = bold_s[1].ifft();
|
||||
|
let s2_coef: [i16; N] = s2
|
||||
|
.coefficients
|
||||
|
.iter()
|
||||
|
.map(|a| a.re.round() as i16)
|
||||
|
.collect::<Vec<i16>>()
|
||||
|
.try_into()
|
||||
|
.expect("The number of coefficients should be equal to N");
|
||||
|
|
||||
|
if let Ok(s2) = SignaturePoly::try_from(&s2_coef) {
|
||||
|
return s2;
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
// SERIALIZATION / DESERIALIZATION
|
||||
|
// ================================================================================================
|
||||
|
|
||||
|
impl Serializable for SecretKey {
|
||||
|
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
|
let basis = &self.secret_key;
|
||||
|
|
||||
|
// header
|
||||
|
let n = basis[0].coefficients.len();
|
||||
|
let l = n.checked_ilog2().unwrap() as u8;
|
||||
|
let header: u8 = (5 << 4) | l;
|
||||
|
|
||||
|
let f = &basis[1];
|
||||
|
let g = &basis[0];
|
||||
|
let capital_f = &basis[3];
|
||||
|
|
||||
|
let mut buffer = Vec::with_capacity(1281);
|
||||
|
buffer.push(header);
|
||||
|
|
||||
|
let f_i8: Vec<i8> = f.coefficients.iter().map(|&a| -a as i8).collect();
|
||||
|
let f_i8_encoded = encode_i8(&f_i8, WIDTH_SMALL_POLY_COEFFICIENT).unwrap();
|
||||
|
buffer.extend_from_slice(&f_i8_encoded);
|
||||
|
|
||||
|
let g_i8: Vec<i8> = g.coefficients.iter().map(|&a| a as i8).collect();
|
||||
|
let g_i8_encoded = encode_i8(&g_i8, WIDTH_SMALL_POLY_COEFFICIENT).unwrap();
|
||||
|
buffer.extend_from_slice(&g_i8_encoded);
|
||||
|
|
||||
|
let big_f_i8: Vec<i8> = capital_f.coefficients.iter().map(|&a| -a as i8).collect();
|
||||
|
let big_f_i8_encoded = encode_i8(&big_f_i8, WIDTH_BIG_POLY_COEFFICIENT).unwrap();
|
||||
|
buffer.extend_from_slice(&big_f_i8_encoded);
|
||||
|
target.write_bytes(&buffer);
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl Deserializable for SecretKey {
|
||||
|
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||
|
let byte_vector: [u8; SK_LEN] = source.read_array()?;
|
||||
|
|
||||
|
// check length
|
||||
|
if byte_vector.len() < 2 {
|
||||
|
return Err(DeserializationError::InvalidValue("Invalid encoding length: Failed to decode as length is different from the one expected".to_string()));
|
||||
|
}
|
||||
|
|
||||
|
// read fields
|
||||
|
let header = byte_vector[0];
|
||||
|
|
||||
|
// check fixed bits in header
|
||||
|
if (header >> 4) != 5 {
|
||||
|
return Err(DeserializationError::InvalidValue("Invalid header format".to_string()));
|
||||
|
}
|
||||
|
|
||||
|
// check log n
|
||||
|
let logn = (header & 15) as usize;
|
||||
|
let n = 1 << logn;
|
||||
|
|
||||
|
// match against const variant generic parameter
|
||||
|
if n != N {
|
||||
|
return Err(DeserializationError::InvalidValue(
|
||||
|
"Unsupported Falcon DSA variant".to_string(),
|
||||
|
));
|
||||
|
}
|
||||
|
|
||||
|
if byte_vector.len() != SK_LEN {
|
||||
|
return Err(DeserializationError::InvalidValue("Invalid encoding length: Failed to decode as length is different from the one expected".to_string()));
|
||||
|
}
|
||||
|
|
||||
|
let chunk_size_f = ((n * WIDTH_SMALL_POLY_COEFFICIENT) + 7) >> 3;
|
||||
|
let chunk_size_g = ((n * WIDTH_SMALL_POLY_COEFFICIENT) + 7) >> 3;
|
||||
|
let chunk_size_big_f = ((n * WIDTH_BIG_POLY_COEFFICIENT) + 7) >> 3;
|
||||
|
|
||||
|
let f = decode_i8(&byte_vector[1..chunk_size_f + 1], WIDTH_SMALL_POLY_COEFFICIENT).unwrap();
|
||||
|
let g = decode_i8(
|
||||
|
&byte_vector[chunk_size_f + 1..(chunk_size_f + chunk_size_g + 1)],
|
||||
|
WIDTH_SMALL_POLY_COEFFICIENT,
|
||||
|
)
|
||||
|
.unwrap();
|
||||
|
let big_f = decode_i8(
|
||||
|
&byte_vector[(chunk_size_f + chunk_size_g + 1)
|
||||
|
..(chunk_size_f + chunk_size_g + chunk_size_big_f + 1)],
|
||||
|
WIDTH_BIG_POLY_COEFFICIENT,
|
||||
|
)
|
||||
|
.unwrap();
|
||||
|
|
||||
|
let f = Polynomial::new(f.iter().map(|&c| FalconFelt::new(c.into())).collect());
|
||||
|
let g = Polynomial::new(g.iter().map(|&c| FalconFelt::new(c.into())).collect());
|
||||
|
let big_f = Polynomial::new(big_f.iter().map(|&c| FalconFelt::new(c.into())).collect());
|
||||
|
|
||||
|
// big_g * f - g * big_f = p (mod X^n + 1)
|
||||
|
let big_g = g.fft().hadamard_div(&f.fft()).hadamard_mul(&big_f.fft()).ifft();
|
||||
|
let basis = [
|
||||
|
g.map(|f| f.balanced_value()),
|
||||
|
-f.map(|f| f.balanced_value()),
|
||||
|
big_g.map(|f| f.balanced_value()),
|
||||
|
-big_f.map(|f| f.balanced_value()),
|
||||
|
];
|
||||
|
Ok(Self::from_short_lattice_basis(basis))
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
// HELPER FUNCTIONS
|
||||
|
// ================================================================================================
|
||||
|
|
||||
|
/// Computes the complex FFT of the secret key polynomials.
|
||||
|
fn to_complex_fft(basis: &[Polynomial<i16>; 4]) -> [Polynomial<Complex<f64>>; 4] {
|
||||
|
let [g, f, big_g, big_f] = basis.clone();
|
||||
|
let g_fft = g.map(|cc| Complex64::new(*cc as f64, 0.0)).fft();
|
||||
|
let minus_f_fft = f.map(|cc| -Complex64::new(*cc as f64, 0.0)).fft();
|
||||
|
let big_g_fft = big_g.map(|cc| Complex64::new(*cc as f64, 0.0)).fft();
|
||||
|
let minus_big_f_fft = big_f.map(|cc| -Complex64::new(*cc as f64, 0.0)).fft();
|
||||
|
[g_fft, minus_f_fft, big_g_fft, minus_big_f_fft]
|
||||
|
}
|
||||
|
|
||||
|
/// Encodes a sequence of signed integers such that each integer x satisfies |x| < 2^(bits-1)
|
||||
|
/// for a given parameter bits. bits can take either the value 6 or 8.
|
||||
|
pub fn encode_i8(x: &[i8], bits: usize) -> Option<Vec<u8>> {
|
||||
|
let maxv = (1 << (bits - 1)) - 1_usize;
|
||||
|
let maxv = maxv as i8;
|
||||
|
let minv = -maxv;
|
||||
|
|
||||
|
for &c in x {
|
||||
|
if c > maxv || c < minv {
|
||||
|
return None;
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
let out_len = ((N * bits) + 7) >> 3;
|
||||
|
let mut buf = vec![0_u8; out_len];
|
||||
|
|
||||
|
let mut acc = 0_u32;
|
||||
|
let mut acc_len = 0;
|
||||
|
let mask = ((1_u16 << bits) - 1) as u8;
|
||||
|
|
||||
|
let mut input_pos = 0;
|
||||
|
for &c in x {
|
||||
|
acc = (acc << bits) | (c as u8 & mask) as u32;
|
||||
|
acc_len += bits;
|
||||
|
while acc_len >= 8 {
|
||||
|
acc_len -= 8;
|
||||
|
buf[input_pos] = (acc >> acc_len) as u8;
|
||||
|
input_pos += 1;
|
||||
|
}
|
||||
|
}
|
||||
|
if acc_len > 0 {
|
||||
|
buf[input_pos] = (acc >> (8 - acc_len)) as u8;
|
||||
|
}
|
||||
|
|
||||
|
Some(buf)
|
||||
|
}
|
||||
|
|
||||
|
/// Decodes a sequence of bytes into a sequence of signed integers such that each integer x
|
||||
|
/// satisfies |x| < 2^(bits-1) for a given parameter bits. bits can take either the value 6 or 8.
|
||||
|
pub fn decode_i8(buf: &[u8], bits: usize) -> Option<Vec<i8>> {
|
||||
|
let mut x = [0_i8; N];
|
||||
|
|
||||
|
let mut i = 0;
|
||||
|
let mut j = 0;
|
||||
|
let mut acc = 0_u32;
|
||||
|
let mut acc_len = 0;
|
||||
|
let mask = (1_u32 << bits) - 1;
|
||||
|
let a = (1 << bits) as u8;
|
||||
|
let b = ((1 << (bits - 1)) - 1) as u8;
|
||||
|
|
||||
|
while i < N {
|
||||
|
acc = (acc << 8) | (buf[j] as u32);
|
||||
|
j += 1;
|
||||
|
acc_len += 8;
|
||||
|
|
||||
|
while acc_len >= bits && i < N {
|
||||
|
acc_len -= bits;
|
||||
|
let w = (acc >> acc_len) & mask;
|
||||
|
|
||||
|
let w = w as u8;
|
||||
|
|
||||
|
let z = if w > b { w as i8 - a as i8 } else { w as i8 };
|
||||
|
|
||||
|
x[i] = z;
|
||||
|
i += 1;
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
if (acc & ((1u32 << acc_len) - 1)) == 0 {
|
||||
|
Some(x.to_vec())
|
||||
|
} else {
|
||||
|
None
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,123 @@ |
|||||
|
use super::{fft::FastFft, polynomial::Polynomial, samplerz::sampler_z};
|
||||
|
use alloc::boxed::Box;
|
||||
|
use num::{One, Zero};
|
||||
|
use num_complex::{Complex, Complex64};
|
||||
|
use rand::Rng;
|
||||
|
|
||||
|
#[cfg(not(feature = "std"))]
|
||||
|
use num::Float;
|
||||
|
|
||||
|
const SIGMIN: f64 = 1.2778336969128337;
|
||||
|
|
||||
|
/// Computes the Gram matrix. The argument must be a 2x2 matrix
|
||||
|
/// whose elements are equal-length vectors of complex numbers,
|
||||
|
/// representing polynomials in FFT domain.
|
||||
|
pub fn gram(b: [Polynomial<Complex64>; 4]) -> [Polynomial<Complex64>; 4] {
|
||||
|
const N: usize = 2;
|
||||
|
let mut g: [Polynomial<Complex<f64>>; 4] =
|
||||
|
[Polynomial::zero(), Polynomial::zero(), Polynomial::zero(), Polynomial::zero()];
|
||||
|
for i in 0..N {
|
||||
|
for j in 0..N {
|
||||
|
for k in 0..N {
|
||||
|
g[N * i + j] = g[N * i + j].clone()
|
||||
|
+ b[N * i + k].hadamard_mul(&b[N * j + k].map(|c| c.conj()));
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
g
|
||||
|
}
|
||||
|
|
||||
|
/// Computes the LDL decomposition of a 2x2 matrix G such that
|
||||
|
/// L D L* = G
|
||||
|
/// where D is diagonal, and L is lower-triangular. The elements of the matrices are in FFT domain.
|
||||
|
pub fn ldl(
|
||||
|
g: [Polynomial<Complex64>; 4],
|
||||
|
) -> ([Polynomial<Complex64>; 4], [Polynomial<Complex64>; 4]) {
|
||||
|
let zero = Polynomial::<Complex64>::one();
|
||||
|
let one = Polynomial::<Complex64>::zero();
|
||||
|
|
||||
|
let l10 = g[2].hadamard_div(&g[0]);
|
||||
|
let bc = l10.map(|c| c * c.conj());
|
||||
|
let abc = g[0].hadamard_mul(&bc);
|
||||
|
let d11 = g[3].clone() - abc;
|
||||
|
|
||||
|
let l = [one.clone(), zero.clone(), l10.clone(), one];
|
||||
|
let d = [g[0].clone(), zero.clone(), zero, d11];
|
||||
|
(l, d)
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Debug, Clone)]
|
||||
|
pub enum LdlTree {
|
||||
|
Branch(Polynomial<Complex64>, Box<LdlTree>, Box<LdlTree>),
|
||||
|
Leaf([Complex64; 2]),
|
||||
|
}
|
||||
|
|
||||
|
/// Computes the LDL Tree of G. Corresponds to Algorithm 9 of the specification [1, p.37].
|
||||
|
/// The argument is a 2x2 matrix of polynomials, given in FFT form.
|
||||
|
/// [1]: https://falcon-sign.info/falcon.pdf
|
||||
|
pub fn ffldl(gram_matrix: [Polynomial<Complex64>; 4]) -> LdlTree {
|
||||
|
let n = gram_matrix[0].coefficients.len();
|
||||
|
let (l, d) = ldl(gram_matrix);
|
||||
|
|
||||
|
if n > 2 {
|
||||
|
let (d00, d01) = d[0].split_fft();
|
||||
|
let (d10, d11) = d[3].split_fft();
|
||||
|
let g0 = [d00.clone(), d01.clone(), d01.map(|c| c.conj()), d00];
|
||||
|
let g1 = [d10.clone(), d11.clone(), d11.map(|c| c.conj()), d10];
|
||||
|
LdlTree::Branch(l[2].clone(), Box::new(ffldl(g0)), Box::new(ffldl(g1)))
|
||||
|
} else {
|
||||
|
LdlTree::Branch(
|
||||
|
l[2].clone(),
|
||||
|
Box::new(LdlTree::Leaf(d[0].clone().coefficients.try_into().unwrap())),
|
||||
|
Box::new(LdlTree::Leaf(d[3].clone().coefficients.try_into().unwrap())),
|
||||
|
)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
/// Normalizes the leaves of an LDL tree using a given normalization value `sigma`.
|
||||
|
pub fn normalize_tree(tree: &mut LdlTree, sigma: f64) {
|
||||
|
match tree {
|
||||
|
LdlTree::Branch(_ell, left, right) => {
|
||||
|
normalize_tree(left, sigma);
|
||||
|
normalize_tree(right, sigma);
|
||||
|
}
|
||||
|
LdlTree::Leaf(vector) => {
|
||||
|
vector[0] = Complex::new(sigma / vector[0].re.sqrt(), 0.0);
|
||||
|
vector[1] = Complex64::zero();
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
/// Samples short polynomials using a Falcon tree. Algorithm 11 from the spec [1, p.40].
|
||||
|
///
|
||||
|
/// [1]: https://falcon-sign.info/falcon.pdf
|
||||
|
pub fn ffsampling<R: Rng>(
|
||||
|
t: &(Polynomial<Complex64>, Polynomial<Complex64>),
|
||||
|
tree: &LdlTree,
|
||||
|
mut rng: &mut R,
|
||||
|
) -> (Polynomial<Complex64>, Polynomial<Complex64>) {
|
||||
|
match tree {
|
||||
|
LdlTree::Branch(ell, left, right) => {
|
||||
|
let bold_t1 = t.1.split_fft();
|
||||
|
let bold_z1 = ffsampling(&bold_t1, right, rng);
|
||||
|
let z1 = Polynomial::<Complex64>::merge_fft(&bold_z1.0, &bold_z1.1);
|
||||
|
|
||||
|
// t0' = t0 + (t1 - z1) * l
|
||||
|
let t0_prime = t.0.clone() + (t.1.clone() - z1.clone()).hadamard_mul(ell);
|
||||
|
|
||||
|
let bold_t0 = t0_prime.split_fft();
|
||||
|
let bold_z0 = ffsampling(&bold_t0, left, rng);
|
||||
|
let z0 = Polynomial::<Complex64>::merge_fft(&bold_z0.0, &bold_z0.1);
|
||||
|
|
||||
|
(z0, z1)
|
||||
|
}
|
||||
|
LdlTree::Leaf(value) => {
|
||||
|
let z0 = sampler_z(t.0.coefficients[0].re, value[0].re, SIGMIN, &mut rng);
|
||||
|
let z1 = sampler_z(t.1.coefficients[0].re, value[0].re, SIGMIN, &mut rng);
|
||||
|
(
|
||||
|
Polynomial::new(vec![Complex64::new(z0 as f64, 0.0)]),
|
||||
|
Polynomial::new(vec![Complex64::new(z1 as f64, 0.0)]),
|
||||
|
)
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,172 @@ |
|||||
|
use super::{fft::CyclotomicFourier, Inverse, MODULUS};
|
||||
|
use alloc::string::String;
|
||||
|
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
|
||||
|
use num::{One, Zero};
|
||||
|
|
||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
|
pub struct FalconFelt(u32);
|
||||
|
|
||||
|
impl FalconFelt {
|
||||
|
pub const fn new(value: i16) -> Self {
|
||||
|
let gtz_bool = value >= 0;
|
||||
|
let gtz_int = gtz_bool as i16;
|
||||
|
let gtz_sign = gtz_int - ((!gtz_bool) as i16);
|
||||
|
let reduced = gtz_sign * (gtz_sign * value) % MODULUS;
|
||||
|
let canonical_representative = (reduced + MODULUS * (1 - gtz_int)) as u32;
|
||||
|
FalconFelt(canonical_representative)
|
||||
|
}
|
||||
|
|
||||
|
pub const fn value(&self) -> i16 {
|
||||
|
self.0 as i16
|
||||
|
}
|
||||
|
|
||||
|
pub fn balanced_value(&self) -> i16 {
|
||||
|
let value = self.value();
|
||||
|
let g = (value > ((MODULUS) / 2)) as i16;
|
||||
|
value - (MODULUS) * g
|
||||
|
}
|
||||
|
|
||||
|
pub const fn multiply(&self, other: Self) -> Self {
|
||||
|
FalconFelt((self.0 * other.0) % MODULUS as u32)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl Add for FalconFelt {
|
||||
|
type Output = Self;
|
||||
|
|
||||
|
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||
|
let (s, _) = self.0.overflowing_add(rhs.0);
|
||||
|
let (d, n) = s.overflowing_sub(MODULUS as u32);
|
||||
|
let (r, _) = d.overflowing_add(MODULUS as u32 * (n as u32));
|
||||
|
FalconFelt(r)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl AddAssign for FalconFelt {
|
||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||
|
*self = *self + rhs;
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl Sub for FalconFelt {
|
||||
|
type Output = Self;
|
||||
|
|
||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||
|
self + -rhs
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl SubAssign for FalconFelt {
|
||||
|
fn sub_assign(&mut self, rhs: Self) {
|
||||
|
*self = *self - rhs;
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl Neg for FalconFelt {
|
||||
|
type Output = FalconFelt;
|
||||
|
|
||||
|
fn neg(self) -> Self::Output {
|
||||
|
let is_nonzero = self.0 != 0;
|
||||
|
let r = MODULUS as u32 - self.0;
|
||||
|
FalconFelt(r * (is_nonzero as u32))
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl Mul for FalconFelt {
|
||||
|
fn mul(self, rhs: Self) -> Self::Output {
|
||||
|
FalconFelt((self.0 * rhs.0) % MODULUS as u32)
|
||||
|
}
|
||||
|
|
||||
|
type Output = Self;
|
||||
|
}
|
||||
|
|
||||
|
impl MulAssign for FalconFelt {
|
||||
|
fn mul_assign(&mut self, rhs: Self) {
|
||||
|
*self = *self * rhs;
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl Div for FalconFelt {
|
||||
|
type Output = FalconFelt;
|
||||
|
|
||||
|
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
|
fn div(self, rhs: Self) -> Self::Output {
|
||||
|
self * rhs.inverse_or_zero()
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl DivAssign for FalconFelt {
|
||||
|
fn div_assign(&mut self, rhs: Self) {
|
||||
|
*self = *self / rhs
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl Zero for FalconFelt {
|
||||
|
fn zero() -> Self {
|
||||
|
FalconFelt::new(0)
|
||||
|
}
|
||||
|
|
||||
|
fn is_zero(&self) -> bool {
|
||||
|
self.0 == 0
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl One for FalconFelt {
|
||||
|
fn one() -> Self {
|
||||
|
FalconFelt::new(1)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl Inverse for FalconFelt {
|
||||
|
fn inverse_or_zero(self) -> Self {
|
||||
|
// q-2 = 0b10 11 11 11 11 11 11
|
||||
|
let two = self.multiply(self);
|
||||
|
let three = two.multiply(self);
|
||||
|
let six = three.multiply(three);
|
||||
|
let twelve = six.multiply(six);
|
||||
|
let fifteen = twelve.multiply(three);
|
||||
|
let thirty = fifteen.multiply(fifteen);
|
||||
|
let sixty = thirty.multiply(thirty);
|
||||
|
let sixty_three = sixty.multiply(three);
|
||||
|
|
||||
|
let sixty_three_sq = sixty_three.multiply(sixty_three);
|
||||
|
let sixty_three_qu = sixty_three_sq.multiply(sixty_three_sq);
|
||||
|
let sixty_three_oc = sixty_three_qu.multiply(sixty_three_qu);
|
||||
|
let sixty_three_hx = sixty_three_oc.multiply(sixty_three_oc);
|
||||
|
let sixty_three_tt = sixty_three_hx.multiply(sixty_three_hx);
|
||||
|
let sixty_three_sf = sixty_three_tt.multiply(sixty_three_tt);
|
||||
|
|
||||
|
let all_ones = sixty_three_sf.multiply(sixty_three);
|
||||
|
let two_e_twelve = all_ones.multiply(self);
|
||||
|
let two_e_thirteen = two_e_twelve.multiply(two_e_twelve);
|
||||
|
|
||||
|
two_e_thirteen.multiply(all_ones)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl CyclotomicFourier for FalconFelt {
|
||||
|
fn primitive_root_of_unity(n: usize) -> Self {
|
||||
|
let log2n = n.ilog2();
|
||||
|
assert!(log2n <= 12);
|
||||
|
// and 1331 is a twelfth root of unity
|
||||
|
let mut a = FalconFelt::new(1331);
|
||||
|
let num_squarings = 12 - n.ilog2();
|
||||
|
for _ in 0..num_squarings {
|
||||
|
a *= a;
|
||||
|
}
|
||||
|
a
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl TryFrom<u32> for FalconFelt {
|
||||
|
type Error = String;
|
||||
|
|
||||
|
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
|
if value >= MODULUS as u32 {
|
||||
|
Err(format!("value {value} is greater than or equal to the field modulus {MODULUS}"))
|
||||
|
} else {
|
||||
|
Ok(FalconFelt::new(value as i16))
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,320 @@ |
|||||
|
//! Contains different structs and methods related to the Falcon DSA.
|
||||
|
//!
|
||||
|
//! It uses and acknowledges the work in:
|
||||
|
//!
|
||||
|
//! 1. The [reference](https://falcon-sign.info/impl/README.txt.html) implementation by Thomas Pornin.
|
||||
|
//! 2. The [Rust](https://github.com/aszepieniec/falcon-rust) implementation by Alan Szepieniec.
|
||||
|
use super::MODULUS;
|
||||
|
use alloc::{string::String, vec::Vec};
|
||||
|
use core::ops::MulAssign;
|
||||
|
use num::{BigInt, FromPrimitive, One, Zero};
|
||||
|
use num_complex::Complex64;
|
||||
|
use rand::Rng;
|
||||
|
|
||||
|
#[cfg(not(feature = "std"))]
|
||||
|
use num::Float;
|
||||
|
|
||||
|
mod fft;
|
||||
|
pub use fft::{CyclotomicFourier, FastFft};
|
||||
|
|
||||
|
mod field;
|
||||
|
pub use field::FalconFelt;
|
||||
|
|
||||
|
mod ffsampling;
|
||||
|
pub use ffsampling::{ffldl, ffsampling, gram, normalize_tree, LdlTree};
|
||||
|
|
||||
|
mod samplerz;
|
||||
|
use self::samplerz::sampler_z;
|
||||
|
|
||||
|
mod polynomial;
|
||||
|
pub use polynomial::Polynomial;
|
||||
|
|
||||
|
pub trait Inverse: Copy + Zero + MulAssign + One {
|
||||
|
/// Gets the inverse of a, or zero if it is zero.
|
||||
|
fn inverse_or_zero(self) -> Self;
|
||||
|
|
||||
|
/// Gets the inverses of a batch of elements, and skip over any that are zero.
|
||||
|
fn batch_inverse_or_zero(batch: &[Self]) -> Vec<Self> {
|
||||
|
let mut acc = Self::one();
|
||||
|
let mut rp: Vec<Self> = Vec::with_capacity(batch.len());
|
||||
|
for batch_item in batch {
|
||||
|
if !batch_item.is_zero() {
|
||||
|
rp.push(acc);
|
||||
|
acc = *batch_item * acc;
|
||||
|
} else {
|
||||
|
rp.push(Self::zero());
|
||||
|
}
|
||||
|
}
|
||||
|
let mut inv = Self::inverse_or_zero(acc);
|
||||
|
for i in (0..batch.len()).rev() {
|
||||
|
if !batch[i].is_zero() {
|
||||
|
rp[i] *= inv;
|
||||
|
inv *= batch[i];
|
||||
|
}
|
||||
|
}
|
||||
|
rp
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl Inverse for Complex64 {
|
||||
|
fn inverse_or_zero(self) -> Self {
|
||||
|
let modulus = self.re * self.re + self.im * self.im;
|
||||
|
Complex64::new(self.re / modulus, -self.im / modulus)
|
||||
|
}
|
||||
|
fn batch_inverse_or_zero(batch: &[Self]) -> Vec<Self> {
|
||||
|
batch.iter().map(|&c| Complex64::new(1.0, 0.0) / c).collect()
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl Inverse for f64 {
|
||||
|
fn inverse_or_zero(self) -> Self {
|
||||
|
1.0 / self
|
||||
|
}
|
||||
|
fn batch_inverse_or_zero(batch: &[Self]) -> Vec<Self> {
|
||||
|
batch.iter().map(|&c| 1.0 / c).collect()
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
/// Samples 4 small polynomials f, g, F, G such that f * G - g * F = q mod (X^n + 1).
|
||||
|
/// Algorithm 5 (NTRUgen) of the documentation [1, p.34].
|
||||
|
///
|
||||
|
/// [1]: https://falcon-sign.info/falcon.pdf
|
||||
|
pub(crate) fn ntru_gen<R: Rng>(n: usize, rng: &mut R) -> [Polynomial<i16>; 4] {
|
||||
|
loop {
|
||||
|
let f = gen_poly(n, rng);
|
||||
|
let g = gen_poly(n, rng);
|
||||
|
let f_ntt = f.map(|&i| FalconFelt::new(i)).fft();
|
||||
|
if f_ntt.coefficients.iter().any(|e| e.is_zero()) {
|
||||
|
continue;
|
||||
|
}
|
||||
|
let gamma = gram_schmidt_norm_squared(&f, &g);
|
||||
|
if gamma > 1.3689f64 * (MODULUS as f64) {
|
||||
|
continue;
|
||||
|
}
|
||||
|
|
||||
|
if let Some((capital_f, capital_g)) =
|
||||
|
ntru_solve(&f.map(|&i| i.into()), &g.map(|&i| i.into()))
|
||||
|
{
|
||||
|
return [
|
||||
|
f,
|
||||
|
g,
|
||||
|
capital_f.map(|i| i.try_into().unwrap()),
|
||||
|
capital_g.map(|i| i.try_into().unwrap()),
|
||||
|
];
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
/// Solves the NTRU equation. Given f, g in ZZ[X], find F, G in ZZ[X] such that:
|
||||
|
///
|
||||
|
/// f G - g F = q mod (X^n + 1)
|
||||
|
///
|
||||
|
/// Algorithm 6 of the specification [1, p.35].
|
||||
|
///
|
||||
|
/// [1]: https://falcon-sign.info/falcon.pdf
|
||||
|
fn ntru_solve(
|
||||
|
f: &Polynomial<BigInt>,
|
||||
|
g: &Polynomial<BigInt>,
|
||||
|
) -> Option<(Polynomial<BigInt>, Polynomial<BigInt>)> {
|
||||
|
let n = f.coefficients.len();
|
||||
|
if n == 1 {
|
||||
|
let (gcd, u, v) = xgcd(&f.coefficients[0], &g.coefficients[0]);
|
||||
|
if gcd != BigInt::one() {
|
||||
|
return None;
|
||||
|
}
|
||||
|
return Some((
|
||||
|
(Polynomial::new(vec![-v * BigInt::from_u32(MODULUS as u32).unwrap()])),
|
||||
|
Polynomial::new(vec![u * BigInt::from_u32(MODULUS as u32).unwrap()]),
|
||||
|
));
|
||||
|
}
|
||||
|
|
||||
|
let f_prime = f.field_norm();
|
||||
|
let g_prime = g.field_norm();
|
||||
|
|
||||
|
let (capital_f_prime, capital_g_prime) = ntru_solve(&f_prime, &g_prime)?;
|
||||
|
let capital_f_prime_xsq = capital_f_prime.lift_next_cyclotomic();
|
||||
|
let capital_g_prime_xsq = capital_g_prime.lift_next_cyclotomic();
|
||||
|
|
||||
|
let f_minx = f.galois_adjoint();
|
||||
|
let g_minx = g.galois_adjoint();
|
||||
|
|
||||
|
let mut capital_f = (capital_f_prime_xsq.karatsuba(&g_minx)).reduce_by_cyclotomic(n);
|
||||
|
let mut capital_g = (capital_g_prime_xsq.karatsuba(&f_minx)).reduce_by_cyclotomic(n);
|
||||
|
|
||||
|
match babai_reduce(f, g, &mut capital_f, &mut capital_g) {
|
||||
|
Ok(_) => Some((capital_f, capital_g)),
|
||||
|
Err(_e) => {
|
||||
|
#[cfg(test)]
|
||||
|
{
|
||||
|
panic!("{}", _e);
|
||||
|
}
|
||||
|
#[cfg(not(test))]
|
||||
|
{
|
||||
|
None
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
/// Generates a polynomial of degree at most n-1 whose coefficients are distributed according
|
||||
|
/// to a discrete Gaussian with mu = 0 and sigma = 1.17 * sqrt(Q / (2n)).
|
||||
|
fn gen_poly<R: Rng>(n: usize, rng: &mut R) -> Polynomial<i16> {
|
||||
|
let mu = 0.0;
|
||||
|
let sigma_star = 1.43300980528773;
|
||||
|
Polynomial {
|
||||
|
coefficients: (0..4096)
|
||||
|
.map(|_| sampler_z(mu, sigma_star, sigma_star - 0.001, rng))
|
||||
|
.collect::<Vec<i16>>()
|
||||
|
.chunks(4096 / n)
|
||||
|
.map(|ch| ch.iter().sum())
|
||||
|
.collect(),
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
/// Computes the Gram-Schmidt norm of B = [[g, -f], [G, -F]] from f and g.
|
||||
|
/// Corresponds to line 9 in algorithm 5 of the spec [1, p.34]
|
||||
|
///
|
||||
|
/// [1]: https://falcon-sign.info/falcon.pdf
|
||||
|
fn gram_schmidt_norm_squared(f: &Polynomial<i16>, g: &Polynomial<i16>) -> f64 {
|
||||
|
let n = f.coefficients.len();
|
||||
|
let norm_f_squared = f.l2_norm_squared();
|
||||
|
let norm_g_squared = g.l2_norm_squared();
|
||||
|
let gamma1 = norm_f_squared + norm_g_squared;
|
||||
|
|
||||
|
let f_fft = f.map(|i| Complex64::new(*i as f64, 0.0)).fft();
|
||||
|
let g_fft = g.map(|i| Complex64::new(*i as f64, 0.0)).fft();
|
||||
|
let f_adj_fft = f_fft.map(|c| c.conj());
|
||||
|
let g_adj_fft = g_fft.map(|c| c.conj());
|
||||
|
let ffgg_fft = f_fft.hadamard_mul(&f_adj_fft) + g_fft.hadamard_mul(&g_adj_fft);
|
||||
|
let ffgg_fft_inverse = ffgg_fft.hadamard_inv();
|
||||
|
let qf_over_ffgg_fft = f_adj_fft.map(|c| c * (MODULUS as f64)).hadamard_mul(&ffgg_fft_inverse);
|
||||
|
let qg_over_ffgg_fft = g_adj_fft.map(|c| c * (MODULUS as f64)).hadamard_mul(&ffgg_fft_inverse);
|
||||
|
let norm_f_over_ffgg_squared =
|
||||
|
qf_over_ffgg_fft.coefficients.iter().map(|c| (c * c.conj()).re).sum::<f64>() / (n as f64);
|
||||
|
let norm_g_over_ffgg_squared =
|
||||
|
qg_over_ffgg_fft.coefficients.iter().map(|c| (c * c.conj()).re).sum::<f64>() / (n as f64);
|
||||
|
|
||||
|
let gamma2 = norm_f_over_ffgg_squared + norm_g_over_ffgg_squared;
|
||||
|
|
||||
|
f64::max(gamma1, gamma2)
|
||||
|
}
|
||||
|
|
||||
|
/// Reduces the vector (F,G) relative to (f,g). This method follows the python implementation [1].
|
||||
|
/// Note that this algorithm can end up in an infinite loop. (It's one of the things the author
|
||||
|
/// would like to fix.) When this happens, control returns an error (hence the return type) and
|
||||
|
/// generates another keypair with fresh randomness.
|
||||
|
///
|
||||
|
/// Algorithm 7 in the spec [2, p.35]
|
||||
|
///
|
||||
|
/// [1]: https://github.com/tprest/falcon.py
|
||||
|
///
|
||||
|
/// [2]: https://falcon-sign.info/falcon.pdf
|
||||
|
fn babai_reduce(
|
||||
|
f: &Polynomial<BigInt>,
|
||||
|
g: &Polynomial<BigInt>,
|
||||
|
capital_f: &mut Polynomial<BigInt>,
|
||||
|
capital_g: &mut Polynomial<BigInt>,
|
||||
|
) -> Result<(), String> {
|
||||
|
let bitsize = |bi: &BigInt| (bi.bits() + 7) & (u64::MAX ^ 7);
|
||||
|
let n = f.coefficients.len();
|
||||
|
let size = [
|
||||
|
f.map(bitsize).fold(0, |a, &b| u64::max(a, b)),
|
||||
|
g.map(bitsize).fold(0, |a, &b| u64::max(a, b)),
|
||||
|
53,
|
||||
|
]
|
||||
|
.into_iter()
|
||||
|
.max()
|
||||
|
.unwrap();
|
||||
|
let shift = (size as i64) - 53;
|
||||
|
let f_adjusted = f
|
||||
|
.map(|bi| Complex64::new(i64::try_from(bi >> shift).unwrap() as f64, 0.0))
|
||||
|
.fft();
|
||||
|
let g_adjusted = g
|
||||
|
.map(|bi| Complex64::new(i64::try_from(bi >> shift).unwrap() as f64, 0.0))
|
||||
|
.fft();
|
||||
|
|
||||
|
let f_star_adjusted = f_adjusted.map(|c| c.conj());
|
||||
|
let g_star_adjusted = g_adjusted.map(|c| c.conj());
|
||||
|
let denominator_fft =
|
||||
|
f_adjusted.hadamard_mul(&f_star_adjusted) + g_adjusted.hadamard_mul(&g_star_adjusted);
|
||||
|
|
||||
|
let mut counter = 0;
|
||||
|
loop {
|
||||
|
let capital_size = [
|
||||
|
capital_f.map(bitsize).fold(0, |a, &b| u64::max(a, b)),
|
||||
|
capital_g.map(bitsize).fold(0, |a, &b| u64::max(a, b)),
|
||||
|
53,
|
||||
|
]
|
||||
|
.into_iter()
|
||||
|
.max()
|
||||
|
.unwrap();
|
||||
|
|
||||
|
if capital_size < size {
|
||||
|
break;
|
||||
|
}
|
||||
|
let capital_shift = (capital_size as i64) - 53;
|
||||
|
let capital_f_adjusted = capital_f
|
||||
|
.map(|bi| Complex64::new(i64::try_from(bi >> capital_shift).unwrap() as f64, 0.0))
|
||||
|
.fft();
|
||||
|
let capital_g_adjusted = capital_g
|
||||
|
.map(|bi| Complex64::new(i64::try_from(bi >> capital_shift).unwrap() as f64, 0.0))
|
||||
|
.fft();
|
||||
|
|
||||
|
let numerator = capital_f_adjusted.hadamard_mul(&f_star_adjusted)
|
||||
|
+ capital_g_adjusted.hadamard_mul(&g_star_adjusted);
|
||||
|
let quotient = numerator.hadamard_div(&denominator_fft).ifft();
|
||||
|
|
||||
|
let k = quotient.map(|f| Into::<BigInt>::into(f.re.round() as i64));
|
||||
|
|
||||
|
if k.is_zero() {
|
||||
|
break;
|
||||
|
}
|
||||
|
let kf = (k.clone().karatsuba(f))
|
||||
|
.reduce_by_cyclotomic(n)
|
||||
|
.map(|bi| bi << (capital_size - size));
|
||||
|
let kg = (k.clone().karatsuba(g))
|
||||
|
.reduce_by_cyclotomic(n)
|
||||
|
.map(|bi| bi << (capital_size - size));
|
||||
|
*capital_f -= kf;
|
||||
|
*capital_g -= kg;
|
||||
|
|
||||
|
counter += 1;
|
||||
|
if counter > 1000 {
|
||||
|
// If we get here, that means that (with high likelihood) we are in an
|
||||
|
// infinite loop. We know it happens from time to time -- seldomly, but it
|
||||
|
// does. It would be nice to fix that! But in order to fix it we need to be
|
||||
|
// able to reproduce it, and for that we need test vectors. So print them
|
||||
|
// and hope that one day they circle back to the implementor.
|
||||
|
return Err(format!("Encountered infinite loop in babai_reduce of falcon-rust.\n\\
|
||||
|
Please help the developer(s) fix it! You can do this by sending them the inputs to the function that caused the behavior:\n\\
|
||||
|
f: {:?}\n\\
|
||||
|
g: {:?}\n\\
|
||||
|
capital_f: {:?}\n\\
|
||||
|
capital_g: {:?}\n", f.coefficients, g.coefficients, capital_f.coefficients, capital_g.coefficients));
|
||||
|
}
|
||||
|
}
|
||||
|
Ok(())
|
||||
|
}
|
||||
|
|
||||
|
/// Extended Euclidean algorithm for computing the greatest common divisor (g) and
|
||||
|
/// Bézout coefficients (u, v) for the relation
|
||||
|
///
|
||||
|
/// $$ u a + v b = g . $$
|
||||
|
///
|
||||
|
/// Implementation adapted from Wikipedia [1].
|
||||
|
///
|
||||
|
/// [1]: https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Pseudocode
|
||||
|
fn xgcd(a: &BigInt, b: &BigInt) -> (BigInt, BigInt, BigInt) {
|
||||
|
let (mut old_r, mut r) = (a.clone(), b.clone());
|
||||
|
let (mut old_s, mut s) = (BigInt::one(), BigInt::zero());
|
||||
|
let (mut old_t, mut t) = (BigInt::zero(), BigInt::one());
|
||||
|
|
||||
|
while r != BigInt::zero() {
|
||||
|
let quotient = old_r.clone() / r.clone();
|
||||
|
(old_r, r) = (r.clone(), old_r.clone() - quotient.clone() * r);
|
||||
|
(old_s, s) = (s.clone(), old_s.clone() - quotient.clone() * s);
|
||||
|
(old_t, t) = (t.clone(), old_t.clone() - quotient * t);
|
||||
|
}
|
||||
|
|
||||
|
(old_r, old_s, old_t)
|
||||
|
}
|
@ -0,0 +1,616 @@ |
|||||
|
use super::{field::FalconFelt, Inverse};
|
||||
|
use crate::dsa::rpo_falcon512::{MODULUS, N};
|
||||
|
use crate::Felt;
|
||||
|
use alloc::vec::Vec;
|
||||
|
use core::default::Default;
|
||||
|
use core::fmt::Debug;
|
||||
|
use core::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign};
|
||||
|
use num::{One, Zero};
|
||||
|
|
||||
|
#[derive(Debug, Clone, Default)]
|
||||
|
pub struct Polynomial<F> {
|
||||
|
pub coefficients: Vec<F>,
|
||||
|
}
|
||||
|
|
||||
|
impl<F> Polynomial<F>
|
||||
|
where
|
||||
|
F: Clone,
|
||||
|
{
|
||||
|
pub fn new(coefficients: Vec<F>) -> Self {
|
||||
|
Self { coefficients }
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<
|
||||
|
F: Mul<Output = F> + Sub<Output = F> + AddAssign + Zero + Div<Output = F> + Clone + Inverse,
|
||||
|
> Polynomial<F>
|
||||
|
{
|
||||
|
pub fn hadamard_mul(&self, other: &Self) -> Self {
|
||||
|
Polynomial::new(
|
||||
|
self.coefficients
|
||||
|
.iter()
|
||||
|
.zip(other.coefficients.iter())
|
||||
|
.map(|(a, b)| *a * *b)
|
||||
|
.collect(),
|
||||
|
)
|
||||
|
}
|
||||
|
pub fn hadamard_div(&self, other: &Self) -> Self {
|
||||
|
let other_coefficients_inverse = F::batch_inverse_or_zero(&other.coefficients);
|
||||
|
Polynomial::new(
|
||||
|
self.coefficients
|
||||
|
.iter()
|
||||
|
.zip(other_coefficients_inverse.iter())
|
||||
|
.map(|(a, b)| *a * *b)
|
||||
|
.collect(),
|
||||
|
)
|
||||
|
}
|
||||
|
|
||||
|
pub fn hadamard_inv(&self) -> Self {
|
||||
|
let coefficients_inverse = F::batch_inverse_or_zero(&self.coefficients);
|
||||
|
Polynomial::new(coefficients_inverse)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F: Zero + PartialEq + Clone> Polynomial<F> {
|
||||
|
pub fn degree(&self) -> Option<usize> {
|
||||
|
if self.coefficients.is_empty() {
|
||||
|
return None;
|
||||
|
}
|
||||
|
let mut max_index = self.coefficients.len() - 1;
|
||||
|
while self.coefficients[max_index] == F::zero() {
|
||||
|
if let Some(new_index) = max_index.checked_sub(1) {
|
||||
|
max_index = new_index;
|
||||
|
} else {
|
||||
|
return None;
|
||||
|
}
|
||||
|
}
|
||||
|
Some(max_index)
|
||||
|
}
|
||||
|
|
||||
|
pub fn lc(&self) -> F {
|
||||
|
match self.degree() {
|
||||
|
Some(non_negative_degree) => self.coefficients[non_negative_degree].clone(),
|
||||
|
None => F::zero(),
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
/// The following implementations are specific to cyclotomic polynomial rings,
|
||||
|
/// i.e., F\[ X \] / <X^n + 1>, and are used extensively in Falcon.
|
||||
|
impl<
|
||||
|
F: One
|
||||
|
+ Zero
|
||||
|
+ Clone
|
||||
|
+ Neg<Output = F>
|
||||
|
+ MulAssign
|
||||
|
+ AddAssign
|
||||
|
+ Div<Output = F>
|
||||
|
+ Sub<Output = F>
|
||||
|
+ PartialEq,
|
||||
|
> Polynomial<F>
|
||||
|
{
|
||||
|
/// Reduce the polynomial by X^n + 1.
|
||||
|
pub fn reduce_by_cyclotomic(&self, n: usize) -> Self {
|
||||
|
let mut coefficients = vec![F::zero(); n];
|
||||
|
let mut sign = -F::one();
|
||||
|
for (i, c) in self.coefficients.iter().cloned().enumerate() {
|
||||
|
if i % n == 0 {
|
||||
|
sign *= -F::one();
|
||||
|
}
|
||||
|
coefficients[i % n] += sign.clone() * c;
|
||||
|
}
|
||||
|
Polynomial::new(coefficients)
|
||||
|
}
|
||||
|
|
||||
|
/// Computes the field norm of the polynomial as an element of the cyclotomic ring
|
||||
|
/// F\[ X \] / <X^n + 1 > relative to one of half the size, i.e., F\[ X \] / <X^(n/2) + 1> .
|
||||
|
///
|
||||
|
/// Corresponds to formula 3.25 in the spec [1, p.30].
|
||||
|
///
|
||||
|
/// [1]: https://falcon-sign.info/falcon.pdf
|
||||
|
pub fn field_norm(&self) -> Self {
|
||||
|
let n = self.coefficients.len();
|
||||
|
let mut f0_coefficients = vec![F::zero(); n / 2];
|
||||
|
let mut f1_coefficients = vec![F::zero(); n / 2];
|
||||
|
for i in 0..n / 2 {
|
||||
|
f0_coefficients[i] = self.coefficients[2 * i].clone();
|
||||
|
f1_coefficients[i] = self.coefficients[2 * i + 1].clone();
|
||||
|
}
|
||||
|
let f0 = Polynomial::new(f0_coefficients);
|
||||
|
let f1 = Polynomial::new(f1_coefficients);
|
||||
|
let f0_squared = (f0.clone() * f0).reduce_by_cyclotomic(n / 2);
|
||||
|
let f1_squared = (f1.clone() * f1).reduce_by_cyclotomic(n / 2);
|
||||
|
let x = Polynomial::new(vec![F::zero(), F::one()]);
|
||||
|
f0_squared - (x * f1_squared).reduce_by_cyclotomic(n / 2)
|
||||
|
}
|
||||
|
|
||||
|
/// Lifts an element from a cyclotomic polynomial ring to one of double the size.
|
||||
|
pub fn lift_next_cyclotomic(&self) -> Self {
|
||||
|
let n = self.coefficients.len();
|
||||
|
let mut coefficients = vec![F::zero(); n * 2];
|
||||
|
for i in 0..n {
|
||||
|
coefficients[2 * i] = self.coefficients[i].clone();
|
||||
|
}
|
||||
|
Self::new(coefficients)
|
||||
|
}
|
||||
|
|
||||
|
/// Computes the galois adjoint of the polynomial in the cyclotomic ring F\[ X \] / < X^n + 1 > ,
|
||||
|
/// which corresponds to f(x^2).
|
||||
|
pub fn galois_adjoint(&self) -> Self {
|
||||
|
Self::new(
|
||||
|
self.coefficients
|
||||
|
.iter()
|
||||
|
.enumerate()
|
||||
|
.map(|(i, c)| if i % 2 == 0 { c.clone() } else { c.clone().neg() })
|
||||
|
.collect(),
|
||||
|
)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F: Clone + Into<f64>> Polynomial<F> {
|
||||
|
pub(crate) fn l2_norm_squared(&self) -> f64 {
|
||||
|
self.coefficients
|
||||
|
.iter()
|
||||
|
.map(|i| Into::<f64>::into(i.clone()))
|
||||
|
.map(|i| i * i)
|
||||
|
.sum::<f64>()
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F> PartialEq for Polynomial<F>
|
||||
|
where
|
||||
|
F: Zero + PartialEq + Clone + AddAssign,
|
||||
|
{
|
||||
|
fn eq(&self, other: &Self) -> bool {
|
||||
|
if self.is_zero() && other.is_zero() {
|
||||
|
true
|
||||
|
} else if self.is_zero() || other.is_zero() {
|
||||
|
false
|
||||
|
} else {
|
||||
|
let self_degree = self.degree().unwrap();
|
||||
|
let other_degree = other.degree().unwrap();
|
||||
|
self.coefficients[0..=self_degree] == other.coefficients[0..=other_degree]
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F> Eq for Polynomial<F> where F: Zero + PartialEq + Clone + AddAssign {}
|
||||
|
|
||||
|
impl<F> Add for &Polynomial<F>
|
||||
|
where
|
||||
|
F: Add<Output = F> + AddAssign + Clone,
|
||||
|
{
|
||||
|
type Output = Polynomial<F>;
|
||||
|
|
||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||
|
let coefficients = if self.coefficients.len() >= rhs.coefficients.len() {
|
||||
|
let mut coefficients = self.coefficients.clone();
|
||||
|
for (i, c) in rhs.coefficients.iter().enumerate() {
|
||||
|
coefficients[i] += c.clone();
|
||||
|
}
|
||||
|
coefficients
|
||||
|
} else {
|
||||
|
let mut coefficients = rhs.coefficients.clone();
|
||||
|
for (i, c) in self.coefficients.iter().enumerate() {
|
||||
|
coefficients[i] += c.clone();
|
||||
|
}
|
||||
|
coefficients
|
||||
|
};
|
||||
|
Self::Output { coefficients }
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F> Add for Polynomial<F>
|
||||
|
where
|
||||
|
F: Add<Output = F> + AddAssign + Clone,
|
||||
|
{
|
||||
|
type Output = Polynomial<F>;
|
||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||
|
let coefficients = if self.coefficients.len() >= rhs.coefficients.len() {
|
||||
|
let mut coefficients = self.coefficients.clone();
|
||||
|
for (i, c) in rhs.coefficients.into_iter().enumerate() {
|
||||
|
coefficients[i] += c;
|
||||
|
}
|
||||
|
coefficients
|
||||
|
} else {
|
||||
|
let mut coefficients = rhs.coefficients.clone();
|
||||
|
for (i, c) in self.coefficients.into_iter().enumerate() {
|
||||
|
coefficients[i] += c;
|
||||
|
}
|
||||
|
coefficients
|
||||
|
};
|
||||
|
Self::Output { coefficients }
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F> AddAssign for Polynomial<F>
|
||||
|
where
|
||||
|
F: Add<Output = F> + AddAssign + Clone,
|
||||
|
{
|
||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||
|
if self.coefficients.len() >= rhs.coefficients.len() {
|
||||
|
for (i, c) in rhs.coefficients.into_iter().enumerate() {
|
||||
|
self.coefficients[i] += c;
|
||||
|
}
|
||||
|
} else {
|
||||
|
let mut coefficients = rhs.coefficients.clone();
|
||||
|
for (i, c) in self.coefficients.iter().enumerate() {
|
||||
|
coefficients[i] += c.clone();
|
||||
|
}
|
||||
|
self.coefficients = coefficients;
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F> Sub for &Polynomial<F>
|
||||
|
where
|
||||
|
F: Sub<Output = F> + Clone + Neg<Output = F> + Add<Output = F> + AddAssign,
|
||||
|
{
|
||||
|
type Output = Polynomial<F>;
|
||||
|
|
||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||
|
self + &(-rhs)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F> Sub for Polynomial<F>
|
||||
|
where
|
||||
|
F: Sub<Output = F> + Clone + Neg<Output = F> + Add<Output = F> + AddAssign,
|
||||
|
{
|
||||
|
type Output = Polynomial<F>;
|
||||
|
|
||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||
|
self + (-rhs)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F> SubAssign for Polynomial<F>
|
||||
|
where
|
||||
|
F: Add<Output = F> + Neg<Output = F> + AddAssign + Clone + Sub<Output = F>,
|
||||
|
{
|
||||
|
fn sub_assign(&mut self, rhs: Self) {
|
||||
|
self.coefficients = self.clone().sub(rhs).coefficients;
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F: Neg<Output = F> + Clone> Neg for &Polynomial<F> {
|
||||
|
type Output = Polynomial<F>;
|
||||
|
|
||||
|
fn neg(self) -> Self::Output {
|
||||
|
Self::Output {
|
||||
|
coefficients: self.coefficients.iter().cloned().map(|a| -a).collect(),
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F: Neg<Output = F> + Clone> Neg for Polynomial<F> {
|
||||
|
type Output = Self;
|
||||
|
|
||||
|
fn neg(self) -> Self::Output {
|
||||
|
Self::Output {
|
||||
|
coefficients: self.coefficients.iter().cloned().map(|a| -a).collect(),
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F> Mul for &Polynomial<F>
|
||||
|
where
|
||||
|
F: Add + AddAssign + Mul<Output = F> + Sub<Output = F> + Zero + PartialEq + Clone,
|
||||
|
{
|
||||
|
type Output = Polynomial<F>;
|
||||
|
|
||||
|
fn mul(self, other: Self) -> Self::Output {
|
||||
|
if self.is_zero() || other.is_zero() {
|
||||
|
return Polynomial::<F>::zero();
|
||||
|
}
|
||||
|
let mut coefficients =
|
||||
|
vec![F::zero(); self.coefficients.len() + other.coefficients.len() - 1];
|
||||
|
for i in 0..self.coefficients.len() {
|
||||
|
for j in 0..other.coefficients.len() {
|
||||
|
coefficients[i + j] += self.coefficients[i].clone() * other.coefficients[j].clone();
|
||||
|
}
|
||||
|
}
|
||||
|
Polynomial { coefficients }
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F> Mul for Polynomial<F>
|
||||
|
where
|
||||
|
F: Add + AddAssign + Mul<Output = F> + Zero + PartialEq + Clone,
|
||||
|
{
|
||||
|
type Output = Self;
|
||||
|
|
||||
|
fn mul(self, other: Self) -> Self::Output {
|
||||
|
if self.is_zero() || other.is_zero() {
|
||||
|
return Self::zero();
|
||||
|
}
|
||||
|
let mut coefficients =
|
||||
|
vec![F::zero(); self.coefficients.len() + other.coefficients.len() - 1];
|
||||
|
for i in 0..self.coefficients.len() {
|
||||
|
for j in 0..other.coefficients.len() {
|
||||
|
coefficients[i + j] += self.coefficients[i].clone() * other.coefficients[j].clone();
|
||||
|
}
|
||||
|
}
|
||||
|
Self { coefficients }
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F: Add + Mul<Output = F> + Zero + Clone> Mul<F> for &Polynomial<F> {
|
||||
|
type Output = Polynomial<F>;
|
||||
|
|
||||
|
fn mul(self, other: F) -> Self::Output {
|
||||
|
Polynomial {
|
||||
|
coefficients: self.coefficients.iter().cloned().map(|i| i * other.clone()).collect(),
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F: Add + Mul<Output = F> + Zero + Clone> Mul<F> for Polynomial<F> {
|
||||
|
type Output = Polynomial<F>;
|
||||
|
|
||||
|
fn mul(self, other: F) -> Self::Output {
|
||||
|
Polynomial {
|
||||
|
coefficients: self.coefficients.iter().cloned().map(|i| i * other.clone()).collect(),
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F: Mul<Output = F> + Sub<Output = F> + AddAssign + Zero + Div<Output = F> + Clone>
|
||||
|
Polynomial<F>
|
||||
|
{
|
||||
|
/// Multiply two polynomials using Karatsuba's divide-and-conquer algorithm.
|
||||
|
pub fn karatsuba(&self, other: &Self) -> Self {
|
||||
|
Polynomial::new(vector_karatsuba(&self.coefficients, &other.coefficients))
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F> One for Polynomial<F>
|
||||
|
where
|
||||
|
F: Clone + One + PartialEq + Zero + AddAssign,
|
||||
|
{
|
||||
|
fn one() -> Self {
|
||||
|
Self { coefficients: vec![F::one()] }
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F> Zero for Polynomial<F>
|
||||
|
where
|
||||
|
F: Zero + PartialEq + Clone + AddAssign,
|
||||
|
{
|
||||
|
fn zero() -> Self {
|
||||
|
Self { coefficients: vec![] }
|
||||
|
}
|
||||
|
|
||||
|
fn is_zero(&self) -> bool {
|
||||
|
self.degree().is_none()
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F: Zero + Clone> Polynomial<F> {
|
||||
|
pub fn shift(&self, shamt: usize) -> Self {
|
||||
|
Self {
|
||||
|
coefficients: [vec![F::zero(); shamt], self.coefficients.clone()].concat(),
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
pub fn constant(f: F) -> Self {
|
||||
|
Self { coefficients: vec![f] }
|
||||
|
}
|
||||
|
|
||||
|
pub fn map<G: Clone, C: FnMut(&F) -> G>(&self, closure: C) -> Polynomial<G> {
|
||||
|
Polynomial::<G>::new(self.coefficients.iter().map(closure).collect())
|
||||
|
}
|
||||
|
|
||||
|
pub fn fold<G, C: FnMut(G, &F) -> G + Clone>(&self, mut initial_value: G, closure: C) -> G {
|
||||
|
for c in self.coefficients.iter() {
|
||||
|
initial_value = (closure.clone())(initial_value, c);
|
||||
|
}
|
||||
|
initial_value
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<F> Div<Polynomial<F>> for Polynomial<F>
|
||||
|
where
|
||||
|
F: Zero
|
||||
|
+ One
|
||||
|
+ PartialEq
|
||||
|
+ AddAssign
|
||||
|
+ Clone
|
||||
|
+ Mul<Output = F>
|
||||
|
+ MulAssign
|
||||
|
+ Div<Output = F>
|
||||
|
+ Neg<Output = F>
|
||||
|
+ Sub<Output = F>,
|
||||
|
{
|
||||
|
type Output = Polynomial<F>;
|
||||
|
|
||||
|
fn div(self, denominator: Self) -> Self::Output {
|
||||
|
if denominator.is_zero() {
|
||||
|
panic!();
|
||||
|
}
|
||||
|
if self.is_zero() {
|
||||
|
Self::zero();
|
||||
|
}
|
||||
|
let mut remainder = self.clone();
|
||||
|
let mut quotient = Polynomial::<F>::zero();
|
||||
|
while remainder.degree().unwrap() >= denominator.degree().unwrap() {
|
||||
|
let shift = remainder.degree().unwrap() - denominator.degree().unwrap();
|
||||
|
let quotient_coefficient = remainder.lc() / denominator.lc();
|
||||
|
let monomial = Self::constant(quotient_coefficient).shift(shift);
|
||||
|
quotient += monomial.clone();
|
||||
|
remainder -= monomial * denominator.clone();
|
||||
|
if remainder.is_zero() {
|
||||
|
break;
|
||||
|
}
|
||||
|
}
|
||||
|
quotient
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
fn vector_karatsuba<
|
||||
|
F: Zero + AddAssign + Mul<Output = F> + Sub<Output = F> + Div<Output = F> + Clone,
|
||||
|
>(
|
||||
|
left: &[F],
|
||||
|
right: &[F],
|
||||
|
) -> Vec<F> {
|
||||
|
let n = left.len();
|
||||
|
if n <= 8 {
|
||||
|
let mut product = vec![F::zero(); left.len() + right.len() - 1];
|
||||
|
for (i, l) in left.iter().enumerate() {
|
||||
|
for (j, r) in right.iter().enumerate() {
|
||||
|
product[i + j] += l.clone() * r.clone();
|
||||
|
}
|
||||
|
}
|
||||
|
return product;
|
||||
|
}
|
||||
|
let n_over_2 = n / 2;
|
||||
|
let mut product = vec![F::zero(); 2 * n - 1];
|
||||
|
let left_lo = &left[0..n_over_2];
|
||||
|
let right_lo = &right[0..n_over_2];
|
||||
|
let left_hi = &left[n_over_2..];
|
||||
|
let right_hi = &right[n_over_2..];
|
||||
|
let left_sum: Vec<F> =
|
||||
|
left_lo.iter().zip(left_hi).map(|(a, b)| a.clone() + b.clone()).collect();
|
||||
|
let right_sum: Vec<F> =
|
||||
|
right_lo.iter().zip(right_hi).map(|(a, b)| a.clone() + b.clone()).collect();
|
||||
|
|
||||
|
let prod_lo = vector_karatsuba(left_lo, right_lo);
|
||||
|
let prod_hi = vector_karatsuba(left_hi, right_hi);
|
||||
|
let prod_mid: Vec<F> = vector_karatsuba(&left_sum, &right_sum)
|
||||
|
.iter()
|
||||
|
.zip(prod_lo.iter().zip(prod_hi.iter()))
|
||||
|
.map(|(s, (l, h))| s.clone() - (l.clone() + h.clone()))
|
||||
|
.collect();
|
||||
|
|
||||
|
for (i, l) in prod_lo.into_iter().enumerate() {
|
||||
|
product[i] = l;
|
||||
|
}
|
||||
|
for (i, m) in prod_mid.into_iter().enumerate() {
|
||||
|
product[i + n_over_2] += m;
|
||||
|
}
|
||||
|
for (i, h) in prod_hi.into_iter().enumerate() {
|
||||
|
product[i + n] += h
|
||||
|
}
|
||||
|
product
|
||||
|
}
|
||||
|
|
||||
|
impl From<Polynomial<FalconFelt>> for Polynomial<Felt> {
|
||||
|
fn from(item: Polynomial<FalconFelt>) -> Self {
|
||||
|
let res: Vec<Felt> =
|
||||
|
item.coefficients.iter().map(|a| Felt::from(a.value() as u16)).collect();
|
||||
|
Polynomial::new(res)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl From<&Polynomial<FalconFelt>> for Polynomial<Felt> {
|
||||
|
fn from(item: &Polynomial<FalconFelt>) -> Self {
|
||||
|
let res: Vec<Felt> =
|
||||
|
item.coefficients.iter().map(|a| Felt::from(a.value() as u16)).collect();
|
||||
|
Polynomial::new(res)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl From<Polynomial<i16>> for Polynomial<FalconFelt> {
|
||||
|
fn from(item: Polynomial<i16>) -> Self {
|
||||
|
let res: Vec<FalconFelt> = item.coefficients.iter().map(|&a| FalconFelt::new(a)).collect();
|
||||
|
Polynomial::new(res)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl From<&Polynomial<i16>> for Polynomial<FalconFelt> {
|
||||
|
fn from(item: &Polynomial<i16>) -> Self {
|
||||
|
let res: Vec<FalconFelt> = item.coefficients.iter().map(|&a| FalconFelt::new(a)).collect();
|
||||
|
Polynomial::new(res)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl From<Vec<i16>> for Polynomial<FalconFelt> {
|
||||
|
fn from(item: Vec<i16>) -> Self {
|
||||
|
let res: Vec<FalconFelt> = item.iter().map(|&a| FalconFelt::new(a)).collect();
|
||||
|
Polynomial::new(res)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl From<&Vec<i16>> for Polynomial<FalconFelt> {
|
||||
|
fn from(item: &Vec<i16>) -> Self {
|
||||
|
let res: Vec<FalconFelt> = item.iter().map(|&a| FalconFelt::new(a)).collect();
|
||||
|
Polynomial::new(res)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl Polynomial<FalconFelt> {
|
||||
|
pub fn norm_squared(&self) -> u64 {
|
||||
|
self.coefficients
|
||||
|
.iter()
|
||||
|
.map(|&i| i.balanced_value() as i64)
|
||||
|
.map(|i| (i * i) as u64)
|
||||
|
.sum::<u64>()
|
||||
|
}
|
||||
|
|
||||
|
// PUBLIC ACCESSORS
|
||||
|
// --------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/// Returns the coefficients of this polynomial as field elements.
|
||||
|
pub fn to_elements(&self) -> Vec<Felt> {
|
||||
|
self.coefficients.iter().map(|&a| Felt::from(a.value() as u16)).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.coefficients[i].value() as u64 * b.coefficients[j].value() 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 = [FalconFelt::zero(); N];
|
||||
|
let modulus = MODULUS as u16;
|
||||
|
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] = FalconFelt::new(((neg_ai + bi) % modulus) as i16);
|
||||
|
}
|
||||
|
|
||||
|
Self::new(c.to_vec())
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
// TESTS
|
||||
|
// ================================================================================================
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
mod tests {
|
||||
|
use super::{FalconFelt, Polynomial, N};
|
||||
|
|
||||
|
#[test]
|
||||
|
fn test_negacyclic_reduction() {
|
||||
|
let coef1: [u8; N] = rand_utils::rand_array();
|
||||
|
let coef2: [u8; N] = rand_utils::rand_array();
|
||||
|
|
||||
|
let poly1 = Polynomial::new(coef1.iter().map(|&a| FalconFelt::new(a as i16)).collect());
|
||||
|
let poly2 = Polynomial::new(coef2.iter().map(|&a| FalconFelt::new(a as i16)).collect());
|
||||
|
let prod = poly1.clone() * poly2.clone();
|
||||
|
|
||||
|
assert_eq!(
|
||||
|
prod.reduce_by_cyclotomic(N),
|
||||
|
Polynomial::reduce_negacyclic(&Polynomial::mul_modulo_p(&poly1, &poly2))
|
||||
|
);
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,298 @@ |
|||||
|
use core::f64::consts::LN_2;
|
||||
|
use rand::Rng;
|
||||
|
|
||||
|
#[cfg(not(feature = "std"))]
|
||||
|
use num::Float;
|
||||
|
|
||||
|
/// Samples an integer from {0, ..., 18} according to the distribution χ, which is close to
|
||||
|
/// the half-Gaussian distribution on the natural numbers with mean 0 and standard deviation
|
||||
|
/// equal to sigma_max.
|
||||
|
fn base_sampler(bytes: [u8; 9]) -> i16 {
|
||||
|
const RCDT: [u128; 18] = [
|
||||
|
3024686241123004913666,
|
||||
|
1564742784480091954050,
|
||||
|
636254429462080897535,
|
||||
|
199560484645026482916,
|
||||
|
47667343854657281903,
|
||||
|
8595902006365044063,
|
||||
|
1163297957344668388,
|
||||
|
117656387352093658,
|
||||
|
8867391802663976,
|
||||
|
496969357462633,
|
||||
|
20680885154299,
|
||||
|
638331848991,
|
||||
|
14602316184,
|
||||
|
247426747,
|
||||
|
3104126,
|
||||
|
28824,
|
||||
|
198,
|
||||
|
1,
|
||||
|
];
|
||||
|
let u = u128::from_be_bytes([vec![0u8; 7], bytes.to_vec()].concat().try_into().unwrap());
|
||||
|
RCDT.into_iter().filter(|r| u < *r).count() as i16
|
||||
|
}
|
||||
|
|
||||
|
/// Computes an integer approximation of 2^63 * ccs * exp(-x).
|
||||
|
fn approx_exp(x: f64, ccs: f64) -> u64 {
|
||||
|
// The constants C are used to approximate exp(-x); these
|
||||
|
// constants are taken from FACCT (up to a scaling factor
|
||||
|
// of 2^63):
|
||||
|
// https://eprint.iacr.org/2018/1234
|
||||
|
// https://github.com/raykzhao/gaussian
|
||||
|
const C: [u64; 13] = [
|
||||
|
0x00000004741183A3u64,
|
||||
|
0x00000036548CFC06u64,
|
||||
|
0x0000024FDCBF140Au64,
|
||||
|
0x0000171D939DE045u64,
|
||||
|
0x0000D00CF58F6F84u64,
|
||||
|
0x000680681CF796E3u64,
|
||||
|
0x002D82D8305B0FEAu64,
|
||||
|
0x011111110E066FD0u64,
|
||||
|
0x0555555555070F00u64,
|
||||
|
0x155555555581FF00u64,
|
||||
|
0x400000000002B400u64,
|
||||
|
0x7FFFFFFFFFFF4800u64,
|
||||
|
0x8000000000000000u64,
|
||||
|
];
|
||||
|
|
||||
|
let mut z: u64;
|
||||
|
let mut y: u64;
|
||||
|
let twoe63 = 1u64 << 63;
|
||||
|
|
||||
|
y = C[0];
|
||||
|
z = f64::floor(x * (twoe63 as f64)) as u64;
|
||||
|
for cu in C.iter().skip(1) {
|
||||
|
let zy = (z as u128) * (y as u128);
|
||||
|
y = cu - ((zy >> 63) as u64);
|
||||
|
}
|
||||
|
|
||||
|
z = f64::floor((twoe63 as f64) * ccs) as u64;
|
||||
|
|
||||
|
(((z as u128) * (y as u128)) >> 63) as u64
|
||||
|
}
|
||||
|
|
||||
|
/// A random bool that is true with probability ≈ ccs · exp(-x).
|
||||
|
fn ber_exp(x: f64, ccs: f64, random_bytes: [u8; 7]) -> bool {
|
||||
|
// 0.69314718055994530941 = ln(2)
|
||||
|
let s = f64::floor(x / LN_2) as usize;
|
||||
|
let r = x - LN_2 * (s as f64);
|
||||
|
let shamt = usize::min(s, 63);
|
||||
|
let z = ((((approx_exp(r, ccs) as u128) << 1) - 1) >> shamt) as u64;
|
||||
|
let mut w = 0i16;
|
||||
|
for (index, i) in (0..64).step_by(8).rev().enumerate() {
|
||||
|
let byte = random_bytes[index];
|
||||
|
w = (byte as i16) - (((z >> i) & 0xff) as i16);
|
||||
|
if w != 0 {
|
||||
|
break;
|
||||
|
}
|
||||
|
}
|
||||
|
w < 0
|
||||
|
}
|
||||
|
|
||||
|
/// Samples an integer from the Gaussian distribution with given mean (mu) and standard deviation
|
||||
|
/// (sigma).
|
||||
|
pub(crate) fn sampler_z<R: Rng>(mu: f64, sigma: f64, sigma_min: f64, rng: &mut R) -> i16 {
|
||||
|
const SIGMA_MAX: f64 = 1.8205;
|
||||
|
const INV_2SIGMA_MAX_SQ: f64 = 1f64 / (2f64 * SIGMA_MAX * SIGMA_MAX);
|
||||
|
let isigma = 1f64 / sigma;
|
||||
|
let dss = 0.5f64 * isigma * isigma;
|
||||
|
let s = f64::floor(mu);
|
||||
|
let r = mu - s;
|
||||
|
let ccs = sigma_min * isigma;
|
||||
|
loop {
|
||||
|
let z0 = base_sampler(rng.gen());
|
||||
|
let random_byte: u8 = rng.gen();
|
||||
|
let b = (random_byte & 1) as i16;
|
||||
|
let z = b + ((b << 1) - 1) * z0;
|
||||
|
let zf_min_r = (z as f64) - r;
|
||||
|
// x = ((z-r)^2)/(2*sigma^2) - ((z-b)^2)/(2*sigma0^2)
|
||||
|
let x = zf_min_r * zf_min_r * dss - (z0 * z0) as f64 * INV_2SIGMA_MAX_SQ;
|
||||
|
if ber_exp(x, ccs, rng.gen()) {
|
||||
|
return z + (s as i16);
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(all(test, feature = "std"))]
|
||||
|
mod test {
|
||||
|
use alloc::vec::Vec;
|
||||
|
use rand::RngCore;
|
||||
|
use std::{thread::sleep, time::Duration};
|
||||
|
|
||||
|
use super::{approx_exp, ber_exp, sampler_z};
|
||||
|
|
||||
|
/// RNG used only for testing purposes, whereby the produced
|
||||
|
/// string of random bytes is equal to the one it is initialized
|
||||
|
/// with. Whatever you do, do not use this RNG in production.
|
||||
|
struct UnsafeBufferRng {
|
||||
|
buffer: Vec<u8>,
|
||||
|
index: usize,
|
||||
|
}
|
||||
|
|
||||
|
impl UnsafeBufferRng {
|
||||
|
fn new(buffer: &[u8]) -> Self {
|
||||
|
Self { buffer: buffer.to_vec(), index: 0 }
|
||||
|
}
|
||||
|
|
||||
|
fn next(&mut self) -> u8 {
|
||||
|
if self.buffer.len() <= self.index {
|
||||
|
// panic!("Ran out of buffer.");
|
||||
|
sleep(Duration::from_millis(10));
|
||||
|
0u8
|
||||
|
} else {
|
||||
|
let return_value = self.buffer[self.index];
|
||||
|
self.index += 1;
|
||||
|
return_value
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl RngCore for UnsafeBufferRng {
|
||||
|
fn next_u32(&mut self) -> u32 {
|
||||
|
// let bytes: [u8; 4] = (0..4)
|
||||
|
// .map(|_| self.next())
|
||||
|
// .collect_vec()
|
||||
|
// .try_into()
|
||||
|
// .unwrap();
|
||||
|
// u32::from_be_bytes(bytes)
|
||||
|
u32::from_le_bytes([self.next(), 0, 0, 0])
|
||||
|
}
|
||||
|
|
||||
|
fn next_u64(&mut self) -> u64 {
|
||||
|
// let bytes: [u8; 8] = (0..8)
|
||||
|
// .map(|_| self.next())
|
||||
|
// .collect_vec()
|
||||
|
// .try_into()
|
||||
|
// .unwrap();
|
||||
|
// u64::from_be_bytes(bytes)
|
||||
|
u64::from_le_bytes([self.next(), 0, 0, 0, 0, 0, 0, 0])
|
||||
|
}
|
||||
|
|
||||
|
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
|
for d in dest.iter_mut() {
|
||||
|
*d = self.next();
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
|
||||
|
for d in dest.iter_mut() {
|
||||
|
*d = self.next();
|
||||
|
}
|
||||
|
Ok(())
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn test_unsafe_buffer_rng() {
|
||||
|
let seed_bytes = hex::decode("7FFECD162AE2").unwrap();
|
||||
|
let mut rng = UnsafeBufferRng::new(&seed_bytes);
|
||||
|
let generated_bytes: Vec<u8> = (0..seed_bytes.len()).map(|_| rng.next()).collect();
|
||||
|
assert_eq!(seed_bytes, generated_bytes);
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn test_approx_exp() {
|
||||
|
let precision = 1u64 << 14;
|
||||
|
// known answers were generated with the following sage script:
|
||||
|
//```sage
|
||||
|
// num_samples = 10
|
||||
|
// precision = 200
|
||||
|
// R = Reals(precision)
|
||||
|
//
|
||||
|
// print(f"let kats : [(f64, f64, u64);{num_samples}] = [")
|
||||
|
// for i in range(num_samples):
|
||||
|
// x = RDF.random_element(0.0, 0.693147180559945)
|
||||
|
// ccs = RDF.random_element(0.0, 1.0)
|
||||
|
// res = round(2^63 * R(ccs) * exp(R(-x)))
|
||||
|
// print(f"({x}, {ccs}, {res}),")
|
||||
|
// print("];")
|
||||
|
// ```
|
||||
|
let kats: [(f64, f64, u64); 10] = [
|
||||
|
(0.2314993926072656, 0.8148006314615972, 5962140072160879737),
|
||||
|
(0.2648875572812225, 0.12769669655309035, 903712282351034505),
|
||||
|
(0.11251957513682391, 0.9264611470305881, 7635725498677341553),
|
||||
|
(0.04353439307256617, 0.5306497137523327, 4685877322232397936),
|
||||
|
(0.41834495299784347, 0.879438856118578, 5338392138535350986),
|
||||
|
(0.32579398973228557, 0.16513412873289002, 1099603299296456803),
|
||||
|
(0.5939508073919817, 0.029776019144967303, 151637565622779016),
|
||||
|
(0.2932367999399056, 0.37123847662857923, 2553827649386670452),
|
||||
|
(0.5005699297417507, 0.31447208863888976, 1758235618083658825),
|
||||
|
(0.4876437338498085, 0.6159515298936868, 3488632981903743976),
|
||||
|
];
|
||||
|
for (x, ccs, answer) in kats {
|
||||
|
let difference = (answer as i128) - (approx_exp(x, ccs) as i128);
|
||||
|
assert!(
|
||||
|
(difference * difference) as u64 <= precision * precision,
|
||||
|
"answer: {answer} versus approximation: {}\ndifference: {} whereas precision: {}",
|
||||
|
approx_exp(x, ccs),
|
||||
|
difference,
|
||||
|
precision
|
||||
|
);
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn test_ber_exp() {
|
||||
|
let kats = [
|
||||
|
(
|
||||
|
1.268_314_048_020_498_4,
|
||||
|
0.749_990_853_267_664_9,
|
||||
|
hex::decode("ea000000000000").unwrap(),
|
||||
|
false,
|
||||
|
),
|
||||
|
(
|
||||
|
0.001_563_917_959_143_409_6,
|
||||
|
0.749_990_853_267_664_9,
|
||||
|
hex::decode("6c000000000000").unwrap(),
|
||||
|
true,
|
||||
|
),
|
||||
|
(
|
||||
|
0.017_921_215_753_999_235,
|
||||
|
0.749_990_853_267_664_9,
|
||||
|
hex::decode("c2000000000000").unwrap(),
|
||||
|
false,
|
||||
|
),
|
||||
|
(
|
||||
|
0.776_117_648_844_980_6,
|
||||
|
0.751_181_554_542_520_8,
|
||||
|
hex::decode("58000000000000").unwrap(),
|
||||
|
true,
|
||||
|
),
|
||||
|
];
|
||||
|
for (x, ccs, bytes, answer) in kats {
|
||||
|
assert_eq!(answer, ber_exp(x, ccs, bytes.try_into().unwrap()));
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn test_sampler_z() {
|
||||
|
let sigma_min = 1.277833697;
|
||||
|
// known answers from the doc, table 3.2, page 44
|
||||
|
// https://falcon-sign.info/falcon.pdf
|
||||
|
// The zeros were added to account for dropped bytes.
|
||||
|
let kats = [
|
||||
|
(-91.90471153063714,1.7037990414754918,hex::decode("0fc5442ff043d66e91d1ea000000000000cac64ea5450a22941edc6c").unwrap(),-92),
|
||||
|
(-8.322564895434937,1.7037990414754918,hex::decode("f4da0f8d8444d1a77265c2000000000000ef6f98bbbb4bee7db8d9b3").unwrap(),-8),
|
||||
|
(-19.096516109216804,1.7035823083824078,hex::decode("db47f6d7fb9b19f25c36d6000000000000b9334d477a8bc0be68145d").unwrap(),-20),
|
||||
|
(-11.335543982423326, 1.7035823083824078, hex::decode("ae41b4f5209665c74d00dc000000000000c1a8168a7bb516b3190cb42c1ded26cd52000000000000aed770eca7dd334e0547bcc3c163ce0b").unwrap(), -12),
|
||||
|
(7.9386734193997555, 1.6984647769450156, hex::decode("31054166c1012780c603ae0000000000009b833cec73f2f41ca5807c000000000000c89c92158834632f9b1555").unwrap(), 8),
|
||||
|
(-28.990850086867255, 1.6984647769450156, hex::decode("737e9d68a50a06dbbc6477").unwrap(), -30),
|
||||
|
(-9.071257914091655, 1.6980782114808988, hex::decode("a98ddd14bf0bf22061d632").unwrap(), -10),
|
||||
|
(-43.88754568839566, 1.6980782114808988, hex::decode("3cbf6818a68f7ab9991514").unwrap(), -41),
|
||||
|
(-58.17435547946095,1.7010983419195522,hex::decode("6f8633f5bfa5d26848668e0000000000003d5ddd46958e97630410587c").unwrap(),-61),
|
||||
|
(-43.58664906684732, 1.7010983419195522, hex::decode("272bc6c25f5c5ee53f83c40000000000003a361fbc7cc91dc783e20a").unwrap(), -46),
|
||||
|
(-34.70565203313315, 1.7009387219711465, hex::decode("45443c59574c2c3b07e2e1000000000000d9071e6d133dbe32754b0a").unwrap(), -34),
|
||||
|
(-44.36009577368896, 1.7009387219711465, hex::decode("6ac116ed60c258e2cbaeab000000000000728c4823e6da36e18d08da0000000000005d0cc104e21cc7fd1f5ca8000000000000d9dbb675266c928448059e").unwrap(), -44),
|
||||
|
(-21.783037079346236, 1.6958406126012802, hex::decode("68163bc1e2cbf3e18e7426").unwrap(), -23),
|
||||
|
(-39.68827784633828, 1.6958406126012802, hex::decode("d6a1b51d76222a705a0259").unwrap(), -40),
|
||||
|
(-18.488607061056847, 1.6955259305261838, hex::decode("f0523bfaa8a394bf4ea5c10000000000000f842366fde286d6a30803").unwrap(), -22),
|
||||
|
(-48.39610939101591, 1.6955259305261838, hex::decode("87bd87e63374cee62127fc0000000000006931104aab64f136a0485b").unwrap(), -50),
|
||||
|
];
|
||||
|
for (mu, sigma, random_bytes, answer) in kats {
|
||||
|
assert_eq!(
|
||||
|
sampler_z(mu, sigma, sigma_min, &mut UnsafeBufferRng::new(&random_bytes)),
|
||||
|
answer
|
||||
|
);
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
@ -1,279 +0,0 @@ |
|||||
use alloc::vec::Vec;
|
|
||||
use core::ops::{Add, Mul, Sub};
|
|
||||
|
|
||||
use super::{FalconError, Felt, LOG_N, MODULUS, MODULUS_MINUS_1_OVER_TWO, N, PK_LEN};
|
|
||||
|
|
||||
// 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<Self, FalconError> {
|
|
||||
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<Self, FalconError> {
|
|
||||
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<Felt> {
|
|
||||
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) -> <Self as Mul<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) -> <Self as Add<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) -> <Self as Add<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))
|
|
||||
);
|
|
||||
}
|
|
||||
}
|
|
@ -1,289 +1,373 @@ |
|||||
use alloc::string::ToString;
|
|
||||
use core::cell::OnceCell;
|
|
||||
|
use alloc::{string::ToString, vec::Vec};
|
||||
|
use core::ops::Deref;
|
||||
|
|
||||
use super::{
|
use super::{
|
||||
ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, NonceBytes, NonceElements,
|
|
||||
Polynomial, PublicKeyBytes, Rpo256, Serializable, SignatureBytes, Word, MODULUS, N,
|
|
||||
SIG_L2_BOUND, ZERO,
|
|
||||
|
hash_to_point::hash_to_point_rpo256,
|
||||
|
keys::PubKeyPoly,
|
||||
|
math::{FalconFelt, FastFft, Polynomial},
|
||||
|
ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, Nonce, Rpo256,
|
||||
|
Serializable, Word, LOG_N, MODULUS, N, SIG_L2_BOUND, SIG_POLY_BYTE_LEN,
|
||||
};
|
};
|
||||
|
use num::Zero;
|
||||
|
|
||||
// FALCON SIGNATURE
|
// FALCON SIGNATURE
|
||||
// ================================================================================================
|
// ================================================================================================
|
||||
|
|
||||
/// An RPO Falcon512 signature over a message.
|
/// An RPO Falcon512 signature over a message.
|
||||
///
|
///
|
||||
/// The signature is a pair of polynomials (s1, s2) in (Z_p\[x\]/(phi))^2, where:
|
|
||||
|
/// The signature is a pair of polynomials (s1, s2) in (Z_p\[x\]/(phi))^2 a nonce `r`, and a public
|
||||
|
/// key polynomial `h` where:
|
||||
/// - p := 12289
|
/// - p := 12289
|
||||
/// - phi := x^512 + 1
|
/// - 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:
|
|
||||
|
/// The signature verifies against a public key `pk` if and only if:
|
||||
/// 1. s1 = c - s2 * h
|
/// 1. s1 = c - s2 * h
|
||||
/// 2. |s1|^2 + |s2|^2 <= SIG_L2_BOUND
|
/// 2. |s1|^2 + |s2|^2 <= SIG_L2_BOUND
|
||||
///
|
///
|
||||
/// where |.| is the norm.
|
|
||||
|
/// where |.| is the norm and:
|
||||
|
/// - c = HashToPoint(r || message)
|
||||
|
/// - pk = Rpo256::hash(h)
|
||||
///
|
///
|
||||
/// [Signature] also includes the extended public key which is serialized as:
|
|
||||
|
/// Here h is a polynomial representing the public key and pk is its digest using the Rpo256 hash
|
||||
|
/// function. c is a polynomial that is the hash-to-point of the message being signed.
|
||||
|
///
|
||||
|
/// The polynomial h is serialized as:
|
||||
/// 1. 1 byte representing the log2(512) i.e., 9.
|
/// 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.
|
|
||||
|
/// 2. 896 bytes for the public key itself.
|
||||
///
|
///
|
||||
/// The actual signature is serialized as:
|
|
||||
|
/// The signature is serialized as:
|
||||
/// 1. A header byte specifying the algorithm used to encode the coefficients of the `s2` polynomial
|
/// 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.
|
|
||||
|
/// together with the degree of the irreducible polynomial phi. For RPO Falcon512, the header
|
||||
|
/// byte is set to `10111001` which differentiates it from the standardized instantiation of
|
||||
|
/// the Falcon signature.
|
||||
/// 2. 40 bytes for the nonce.
|
/// 2. 40 bytes for the nonce.
|
||||
/// 3. 625 bytes encoding the `s2` polynomial above.
|
|
||||
|
/// 4. 625 bytes encoding the `s2` polynomial above.
|
||||
///
|
///
|
||||
/// The total size of the signature (including the extended public key) is 1563 bytes.
|
|
||||
#[derive(Debug, Clone)]
|
|
||||
|
/// The total size of the signature is (including the extended public key) is 1563 bytes.
|
||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Signature {
|
pub struct Signature {
|
||||
pub(super) pk: PublicKeyBytes,
|
|
||||
pub(super) sig: SignatureBytes,
|
|
||||
|
|
||||
// Cached polynomial decoding for public key and signatures
|
|
||||
pub(super) pk_polynomial: OnceCell<Polynomial>,
|
|
||||
pub(super) sig_polynomial: OnceCell<Polynomial>,
|
|
||||
|
header: SignatureHeader,
|
||||
|
nonce: Nonce,
|
||||
|
s2: SignaturePoly,
|
||||
|
h: PubKeyPoly,
|
||||
}
|
}
|
||||
|
|
||||
impl Signature {
|
impl Signature {
|
||||
|
// CONSTRUCTOR
|
||||
|
// --------------------------------------------------------------------------------------------
|
||||
|
pub fn new(nonce: Nonce, h: PubKeyPoly, s2: SignaturePoly) -> Signature {
|
||||
|
Self {
|
||||
|
header: SignatureHeader::default(),
|
||||
|
nonce,
|
||||
|
s2,
|
||||
|
h,
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
// PUBLIC ACCESSORS
|
// PUBLIC ACCESSORS
|
||||
// --------------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------------
|
||||
|
|
||||
/// Returns the public key polynomial h.
|
/// Returns the public key polynomial h.
|
||||
pub fn pub_key_poly(&self) -> Polynomial {
|
|
||||
*self.pk_polynomial.get_or_init(|| {
|
|
||||
// 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)
|
|
||||
|
pub fn pk_poly(&self) -> &PubKeyPoly {
|
||||
|
&self.h
|
||||
}
|
}
|
||||
|
|
||||
// Returns the polynomial representation of the signature in Z_p[x]/(phi).
|
// Returns the polynomial representation of the signature in Z_p[x]/(phi).
|
||||
pub fn sig_poly(&self) -> Polynomial {
|
|
||||
*self.sig_polynomial.get_or_init(|| {
|
|
||||
// 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")
|
|
||||
})
|
|
||||
|
pub fn sig_poly(&self) -> &Polynomial<FalconFelt> {
|
||||
|
&self.s2
|
||||
}
|
}
|
||||
|
|
||||
// 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())
|
|
||||
|
/// Returns the nonce component of the signature.
|
||||
|
pub fn nonce(&self) -> &Nonce {
|
||||
|
&self.nonce
|
||||
}
|
}
|
||||
|
|
||||
// SIGNATURE VERIFICATION
|
// SIGNATURE VERIFICATION
|
||||
// --------------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------------
|
||||
|
|
||||
/// Returns true if this signature is a valid signature for the specified message generated
|
/// Returns true if this signature is a valid signature for the specified message generated
|
||||
/// against key pair matching the specified public key commitment.
|
|
||||
|
/// against the secret key matching the specified public key commitment.
|
||||
pub fn verify(&self, message: Word, pubkey_com: Word) -> bool {
|
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();
|
|
||||
|
// compute the hash of the public key polynomial
|
||||
|
let h_felt: Polynomial<Felt> = (&**self.pk_poly()).into();
|
||||
|
let h_digest: Word = Rpo256::hash_elements(&h_felt.coefficients).into();
|
||||
if h_digest != pubkey_com {
|
if h_digest != pubkey_com {
|
||||
return false;
|
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
|
|
||||
|
let c = hash_to_point_rpo256(message, &self.nonce);
|
||||
|
h_digest == pubkey_com && verify_helper(&c, &self.s2, self.pk_poly())
|
||||
}
|
}
|
||||
}
|
}
|
||||
|
|
||||
// SERIALIZATION / DESERIALIZATION
|
|
||||
// ================================================================================================
|
|
||||
|
|
||||
impl Serializable for Signature {
|
impl Serializable for Signature {
|
||||
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
target.write_bytes(&self.pk);
|
|
||||
target.write_bytes(&self.sig);
|
|
||||
|
target.write(&self.header);
|
||||
|
target.write(&self.nonce);
|
||||
|
target.write(&self.s2);
|
||||
|
target.write(&self.h);
|
||||
}
|
}
|
||||
}
|
}
|
||||
|
|
||||
impl Deserializable for Signature {
|
impl Deserializable for Signature {
|
||||
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||
let pk: PublicKeyBytes = source.read_array()?;
|
|
||||
let sig: SignatureBytes = source.read_array()?;
|
|
||||
|
|
||||
// make sure public key and signature can be decoded correctly
|
|
||||
let pk_polynomial = Polynomial::from_pub_key(&pk)
|
|
||||
.map_err(|err| DeserializationError::InvalidValue(err.to_string()))?
|
|
||||
.into();
|
|
||||
let sig_polynomial = Polynomial::from_signature(&sig)
|
|
||||
.map_err(|err| DeserializationError::InvalidValue(err.to_string()))?
|
|
||||
.into();
|
|
||||
|
|
||||
Ok(Self { pk, sig, pk_polynomial, sig_polynomial })
|
|
||||
|
let header = source.read()?;
|
||||
|
let nonce = source.read()?;
|
||||
|
let s2 = source.read()?;
|
||||
|
let h = source.read()?;
|
||||
|
|
||||
|
Ok(Self { header, nonce, s2, h })
|
||||
}
|
}
|
||||
}
|
}
|
||||
|
|
||||
// HELPER FUNCTIONS
|
|
||||
|
// SIGNATURE HEADER
|
||||
// ================================================================================================
|
// ================================================================================================
|
||||
|
|
||||
/// 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];
|
|
||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
pub struct SignatureHeader(u8);
|
||||
|
|
||||
// absorb the nonce into the state
|
|
||||
for (&n, s) in nonce.iter().zip(state[Rpo256::RATE_RANGE].iter_mut()) {
|
|
||||
*s = n;
|
|
||||
|
impl Default for SignatureHeader {
|
||||
|
/// According to section 3.11.3 in the specification [1], the signature header has the format
|
||||
|
/// `0cc1nnnn` where:
|
||||
|
///
|
||||
|
/// 1. `cc` signifies the encoding method. `01` denotes using the compression encoding method
|
||||
|
/// and `10` denotes encoding using the uncompressed method.
|
||||
|
/// 2. `nnnn` encodes `LOG_N`.
|
||||
|
///
|
||||
|
/// For RPO Falcon 512 we use compression encoding and N = 512. Moreover, to differentiate the
|
||||
|
/// RPO Falcon variant from the reference variant using SHAKE256, we flip the first bit in the
|
||||
|
/// header. Thus, for RPO Falcon 512 the header is `10111001`
|
||||
|
///
|
||||
|
/// [1]: https://falcon-sign.info/falcon.pdf
|
||||
|
fn default() -> Self {
|
||||
|
Self(0b1011_1001)
|
||||
}
|
}
|
||||
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;
|
|
||||
|
impl Serializable for &SignatureHeader {
|
||||
|
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
|
target.write_u8(self.0)
|
||||
}
|
}
|
||||
|
}
|
||||
|
|
||||
// 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;
|
|
||||
|
impl Deserializable for SignatureHeader {
|
||||
|
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||
|
let header = source.read_u8()?;
|
||||
|
let (encoding, log_n) = (header >> 4, header & 0b00001111);
|
||||
|
if encoding != 0b1011 {
|
||||
|
return Err(DeserializationError::InvalidValue(
|
||||
|
"Failed to decode signature: not supported encoding algorithm".to_string(),
|
||||
|
));
|
||||
|
}
|
||||
|
|
||||
|
if log_n != LOG_N {
|
||||
|
return Err(DeserializationError::InvalidValue(
|
||||
|
format!("Failed to decode signature: only supported irreducible polynomial degree is 512, 2^{log_n} was provided")
|
||||
|
));
|
||||
}
|
}
|
||||
|
|
||||
|
Ok(Self(header))
|
||||
}
|
}
|
||||
|
}
|
||||
|
|
||||
|
// SIGNATURE POLYNOMIAL
|
||||
|
// ================================================================================================
|
||||
|
|
||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
pub struct SignaturePoly(pub Polynomial<FalconFelt>);
|
||||
|
|
||||
|
impl Deref for SignaturePoly {
|
||||
|
type Target = Polynomial<FalconFelt>;
|
||||
|
|
||||
// using the raw constructor is OK here because we reduce all coefficients by the modulus above
|
|
||||
unsafe { Polynomial::new(res) }
|
|
||||
|
fn deref(&self) -> &Self::Target {
|
||||
|
&self.0
|
||||
|
}
|
||||
}
|
}
|
||||
|
|
||||
/// 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);
|
|
||||
// we can safely (without overflow) create a new Felt from u64 value here since this value
|
|
||||
// contains at most 5 bytes
|
|
||||
result[i] = Felt::new(u64::from_le_bytes(buffer));
|
|
||||
|
impl From<Polynomial<FalconFelt>> for SignaturePoly {
|
||||
|
fn from(pk_poly: Polynomial<FalconFelt>) -> Self {
|
||||
|
Self(pk_poly)
|
||||
}
|
}
|
||||
|
}
|
||||
|
|
||||
result
|
|
||||
|
impl TryFrom<&[i16; N]> for SignaturePoly {
|
||||
|
type Error = ();
|
||||
|
|
||||
|
fn try_from(coefficients: &[i16; N]) -> Result<Self, Self::Error> {
|
||||
|
if are_coefficients_valid(coefficients) {
|
||||
|
Ok(Self(coefficients.to_vec().into()))
|
||||
|
} else {
|
||||
|
Err(())
|
||||
|
}
|
||||
|
}
|
||||
}
|
}
|
||||
|
|
||||
// TESTS
|
|
||||
// ================================================================================================
|
|
||||
|
impl Serializable for &SignaturePoly {
|
||||
|
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
|
let sig_coeff: Vec<i16> = self.0.coefficients.iter().map(|a| a.balanced_value()).collect();
|
||||
|
let mut sk_bytes = vec![0_u8; SIG_POLY_BYTE_LEN];
|
||||
|
|
||||
|
let mut acc = 0;
|
||||
|
let mut acc_len = 0;
|
||||
|
let mut v = 0;
|
||||
|
let mut t;
|
||||
|
let mut w;
|
||||
|
|
||||
|
// For each coefficient of x:
|
||||
|
// - the sign is encoded on 1 bit
|
||||
|
// - the 7 lower bits are encoded naively (binary)
|
||||
|
// - the high bits are encoded in unary encoding
|
||||
|
//
|
||||
|
// Algorithm 17 p. 47 of the specification [1].
|
||||
|
//
|
||||
|
// [1]: https://falcon-sign.info/falcon.pdf
|
||||
|
for &c in sig_coeff.iter() {
|
||||
|
acc <<= 1;
|
||||
|
t = c;
|
||||
|
|
||||
|
if t < 0 {
|
||||
|
t = -t;
|
||||
|
acc |= 1;
|
||||
|
}
|
||||
|
w = t as u16;
|
||||
|
|
||||
#[cfg(all(test, feature = "std"))]
|
|
||||
mod tests {
|
|
||||
use core::ffi::c_void;
|
|
||||
use rand_utils::rand_vector;
|
|
||||
|
|
||||
use super::{
|
|
||||
super::{ffi::*, KeyPair},
|
|
||||
*,
|
|
||||
};
|
|
||||
|
|
||||
// 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);
|
|
||||
|
acc <<= 7;
|
||||
|
let mask = 127_u32;
|
||||
|
acc |= (w as u32) & mask;
|
||||
|
w >>= 7;
|
||||
|
|
||||
|
acc_len += 8;
|
||||
|
|
||||
|
acc <<= w + 1;
|
||||
|
acc |= 1;
|
||||
|
acc_len += w + 1;
|
||||
|
|
||||
|
while acc_len >= 8 {
|
||||
|
acc_len -= 8;
|
||||
|
|
||||
|
sk_bytes[v] = (acc >> acc_len) as u8;
|
||||
|
v += 1;
|
||||
}
|
}
|
||||
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(),
|
|
||||
)
|
|
||||
|
if acc_len > 0 {
|
||||
|
sk_bytes[v] = (acc << (8 - acc_len)) as u8;
|
||||
|
}
|
||||
|
target.write_bytes(&sk_bytes);
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl Deserializable for SignaturePoly {
|
||||
|
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||
|
let input = source.read_array::<SIG_POLY_BYTE_LEN>()?;
|
||||
|
|
||||
|
let mut input_idx = 0;
|
||||
|
let mut acc = 0u32;
|
||||
|
let mut acc_len = 0;
|
||||
|
let mut coefficients = [FalconFelt::zero(); N];
|
||||
|
|
||||
|
// Algorithm 18 p. 48 of the specification [1].
|
||||
|
//
|
||||
|
// [1]: https://falcon-sign.info/falcon.pdf
|
||||
|
for c in coefficients.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(DeserializationError::InvalidValue(
|
||||
|
"Failed to decode signature: high bits {m} exceed 2048".to_string(),
|
||||
|
));
|
||||
|
}
|
||||
|
}
|
||||
|
if s != 0 && m == 0 {
|
||||
|
return Err(DeserializationError::InvalidValue(
|
||||
|
"Failed to decode signature: -0 is forbidden".to_string(),
|
||||
|
));
|
||||
}
|
}
|
||||
|
|
||||
|
let felt = if s != 0 { (MODULUS as u32 - m) as u16 } else { m as u16 };
|
||||
|
*c = FalconFelt::new(felt as i16);
|
||||
}
|
}
|
||||
|
|
||||
/// Finalizes the RPO state to prepare for squeezing.
|
|
||||
pub fn finalize(&mut self) {
|
|
||||
unsafe { rpo128_finalize(self as *mut Rpo128Context) }
|
|
||||
|
if (acc & ((1 << acc_len) - 1)) != 0 {
|
||||
|
return Err(DeserializationError::InvalidValue(
|
||||
|
"Failed to decode signature: Non-zero unused bits in the last byte".to_string(),
|
||||
|
));
|
||||
}
|
}
|
||||
|
Ok(Polynomial::new(coefficients.to_vec()).into())
|
||||
}
|
}
|
||||
|
}
|
||||
|
|
||||
#[test]
|
|
||||
fn test_hash_to_point() {
|
|
||||
// Create a random message and transform it into a u8 vector
|
|
||||
let msg_felts: Word = rand_vector::<Felt>(4).try_into().unwrap();
|
|
||||
let msg_bytes = msg_felts
|
|
||||
.iter()
|
|
||||
.flat_map(|e| e.as_int().to_le_bytes())
|
|
||||
.collect::<alloc::vec::Vec<_>>();
|
|
||||
|
|
||||
// Create a nonce i.e. a [u8; 40] array and pack into a [Felt; 8] array.
|
|
||||
let nonce: [u8; 40] = rand_vector::<u8>(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];
|
|
||||
}
|
|
||||
|
// HELPER FUNCTIONS
|
||||
|
// ================================================================================================
|
||||
|
|
||||
// Initialize the RPO state
|
|
||||
let mut rng = Rpo128Context::init();
|
|
||||
|
/// Takes the hash-to-point polynomial `c` of a message, the signature polynomial over
|
||||
|
/// the message `s2` and a public key polynomial and returns `true` is the signature is a valid
|
||||
|
/// signature for the given parameters, otherwise it returns `false`.
|
||||
|
fn verify_helper(c: &Polynomial<FalconFelt>, s2: &SignaturePoly, h: &PubKeyPoly) -> bool {
|
||||
|
let h_fft = h.fft();
|
||||
|
let s2_fft = s2.fft();
|
||||
|
let c_fft = c.fft();
|
||||
|
|
||||
// Absorb the nonce and message into the RPO state
|
|
||||
rng.absorb(&buffer);
|
|
||||
rng.absorb(&msg_bytes);
|
|
||||
rng.finalize();
|
|
||||
|
// compute the signature polynomial s1 using s1 = c - s2 * h
|
||||
|
let s1_fft = c_fft - s2_fft.hadamard_mul(&h_fft);
|
||||
|
let s1 = s1_fft.ifft();
|
||||
|
|
||||
// Generate the coefficients of the hash-to-point polynomial.
|
|
||||
let mut res: [u16; N] = [0; N];
|
|
||||
|
// compute the norm squared of (s1, s2)
|
||||
|
let length_squared_s1 = s1.norm_squared();
|
||||
|
let length_squared_s2 = s2.norm_squared();
|
||||
|
let length_squared = length_squared_s1 + length_squared_s2;
|
||||
|
|
||||
unsafe {
|
|
||||
PQCLEAN_FALCON512_CLEAN_hash_to_point_rpo(
|
|
||||
&mut rng as *mut Rpo128Context,
|
|
||||
res.as_mut_ptr(),
|
|
||||
9,
|
|
||||
);
|
|
||||
}
|
|
||||
|
length_squared < SIG_L2_BOUND
|
||||
|
}
|
||||
|
|
||||
|
/// Checks whether a set of coefficients is a valid one for a signature polynomial.
|
||||
|
fn are_coefficients_valid(x: &[i16]) -> bool {
|
||||
|
if x.len() != N {
|
||||
|
return false;
|
||||
|
}
|
||||
|
|
||||
// Check that the coefficients are correct
|
|
||||
let nonce = decode_nonce(&nonce);
|
|
||||
assert_eq!(res, hash_to_point(msg_felts, &nonce).inner());
|
|
||||
|
for &c in x {
|
||||
|
if !(-2047..=2047).contains(&c) {
|
||||
|
return false;
|
||||
|
}
|
||||
}
|
}
|
||||
|
|
||||
|
true
|
||||
|
}
|
||||
|
|
||||
|
// TESTS
|
||||
|
// ================================================================================================
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
mod tests {
|
||||
|
use super::{super::SecretKey, *};
|
||||
|
use rand::SeedableRng;
|
||||
|
use rand_chacha::ChaCha20Rng;
|
||||
|
|
||||
#[test]
|
#[test]
|
||||
fn test_serialization_round_trip() {
|
fn test_serialization_round_trip() {
|
||||
let key = KeyPair::new().unwrap();
|
|
||||
let signature = key.sign(Word::default()).unwrap();
|
|
||||
|
let seed = [0_u8; 32];
|
||||
|
let mut rng = ChaCha20Rng::from_seed(seed);
|
||||
|
|
||||
|
let sk = SecretKey::with_rng(&mut rng);
|
||||
|
let signature = sk.sign_with_rng(Word::default(), &mut rng);
|
||||
let serialized = signature.to_bytes();
|
let serialized = signature.to_bytes();
|
||||
let deserialized = Signature::read_from_bytes(&serialized).unwrap();
|
let deserialized = Signature::read_from_bytes(&serialized).unwrap();
|
||||
assert_eq!(signature.sig_poly(), deserialized.sig_poly());
|
assert_eq!(signature.sig_poly(), deserialized.sig_poly());
|
||||
assert_eq!(signature.pub_key_poly(), deserialized.pub_key_poly());
|
|
||||
}
|
}
|
||||
}
|
}
|