Skip to content

Commit

Permalink
test: add Falcon signature tests (#1257)
Browse files Browse the repository at this point in the history
  • Loading branch information
scottdieringer authored Apr 11, 2024
1 parent d5de231 commit d9dc097
Show file tree
Hide file tree
Showing 7 changed files with 421 additions and 40 deletions.
50 changes: 25 additions & 25 deletions assembly/src/assembler/instruction/u32_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub enum U32OpMode {
pub fn u32testw(span: &mut SpanBuilder) -> Result<Option<CodeBlock>, AssemblyError> {
#[rustfmt::skip]
let ops = [
// Test the fourth element
// Test the fourth element
Dup3, U32split, Swap, Drop, Eqz,

// Test the third element
Expand Down Expand Up @@ -481,23 +481,23 @@ fn calculate_clz(span: &mut SpanBuilder) -> Result<Option<CodeBlock>, AssemblyEr
let ops_group_2 = [
Push(Felt::new(u32::MAX as u64 + 1)), // [2^32, pow2(32 - clz), n, clz, ...]

Dup1, Neg, Add, // [2^32 - pow2(32 - clz), pow2(32 - clz), n, clz, ...]
// `2^32 - pow2(32 - clz)` is equal to `clz` leading ones and `32 - clz`
Dup1, Neg, Add, // [2^32 - pow2(32 - clz), pow2(32 - clz), n, clz, ...]
// `2^32 - pow2(32 - clz)` is equal to `clz` leading ones and `32 - clz`
// zeros:
// 1111111111...1110000...0
// └─ `clz` ones ─┘

Swap, Push(2u8.into()), U32div, Drop, // [pow2(32 - clz) / 2, 2^32 - pow2(32 - clz), n, clz, ...]
// pow2(32 - clz) / 2 is equal to `clz` leading
Swap, Push(2u8.into()), U32div, Drop, // [pow2(32 - clz) / 2, 2^32 - pow2(32 - clz), n, clz, ...]
// pow2(32 - clz) / 2 is equal to `clz` leading
// zeros, `1` one and all other zeros.

Swap, Dup1, Add, // [bit_mask, pow2(32 - clz) / 2, n, clz, ...]
Swap, Dup1, Add, // [bit_mask, pow2(32 - clz) / 2, n, clz, ...]
// 1111111111...111000...0 <-- bitmask
// └─ clz ones ─┘│
// └─ additional one

MovUp2, U32and, // [m, pow2(32 - clz) / 2, clz]
// If calcualtion of `clz` is correct, m should be equal to
MovUp2, U32and, // [m, pow2(32 - clz) / 2, clz]
// If calcualtion of `clz` is correct, m should be equal to
// pow2(32 - clz) / 2

Eq, Assert(0) // [clz, ...]
Expand Down Expand Up @@ -556,23 +556,23 @@ fn calculate_clo(span: &mut SpanBuilder) -> Result<Option<CodeBlock>, AssemblyEr
let ops_group_2 = [
Push(Felt::new(u32::MAX as u64 + 1)), // [2^32, pow2(32 - clo), n, clo, ...]

Dup1, Neg, Add, // [2^32 - pow2(32 - clo), pow2(32 - clo), n, clo, ...]
// `2^32 - pow2(32 - clo)` is equal to `clo` leading ones and `32 - clo`
Dup1, Neg, Add, // [2^32 - pow2(32 - clo), pow2(32 - clo), n, clo, ...]
// `2^32 - pow2(32 - clo)` is equal to `clo` leading ones and `32 - clo`
// zeros:
// 11111111...1110000...0
// └─ clo ones ─┘

Swap, Push(2u8.into()), U32div, Drop, // [pow2(32 - clo) / 2, 2^32 - pow2(32 - clo), n, clo, ...]
// pow2(32 - clo) / 2 is equal to `clo` leading
Swap, Push(2u8.into()), U32div, Drop, // [pow2(32 - clo) / 2, 2^32 - pow2(32 - clo), n, clo, ...]
// pow2(32 - clo) / 2 is equal to `clo` leading
// zeros, `1` one and all other zeros.

Dup1, Add, // [bit_mask, 2^32 - pow2(32 - clo), n, clo, ...]
Dup1, Add, // [bit_mask, 2^32 - pow2(32 - clo), n, clo, ...]
// 111111111...111000...0 <-- bitmask
// └─ clo ones ─┘│
// └─ additional one

MovUp2, U32and, // [m, 2^32 - pow2(32 - clo), clo]
// If calcualtion of `clo` is correct, m should be equal to
MovUp2, U32and, // [m, 2^32 - pow2(32 - clo), clo]
// If calcualtion of `clo` is correct, m should be equal to
// 2^32 - pow2(32 - clo)

Eq, Assert(0) // [clo, ...]
Expand Down Expand Up @@ -630,23 +630,23 @@ fn calculate_ctz(span: &mut SpanBuilder) -> Result<Option<CodeBlock>, AssemblyEr
#[rustfmt::skip]
let ops_group_2 = [
Dup0, // [pow2(ctz), pow2(ctz), n, ctz, ...]
// pow2(ctz) is equal to all zeros with only one on the `ctz`'th trailing position
// pow2(ctz) is equal to all zeros with only one on the `ctz`'th trailing position

Pad, Incr, Neg, Add, // [pow2(ctz) - 1, pow2(ctz), n, ctz, ...]

Swap, U32split, Drop, // [pow2(ctz), pow2(ctz) - 1, n, ctz, ...]
// We need to drop the high bits of `pow2(ctz)` because if `ctz`
// We need to drop the high bits of `pow2(ctz)` because if `ctz`
// equals 32 `pow2(ctz)` will exceed the u32. Also in that case there
// is no need to check the dividing one, since it is absent (value is
// all 0's).
// all 0's).

Dup0, MovUp2, Add, // [bit_mask, pow2(ctz), n, ctz]
// 00..001111111111...11 <-- bitmask
// │└─ ctz ones ─┘
// └─ additional one

MovUp2, U32and, // [m, pow2(ctz), ctz]
// If calcualtion of `ctz` is correct, m should be equal to
// If calcualtion of `ctz` is correct, m should be equal to
// pow2(ctz)

Eq, Assert(0), // [ctz, ...]
Expand Down Expand Up @@ -709,18 +709,18 @@ fn calculate_cto(span: &mut SpanBuilder) -> Result<Option<CodeBlock>, AssemblyEr
Pad, Incr, Neg, Add, // [pow2(cto) - 1, pow2(cto), n, cto, ...]

Swap, U32split, Drop, // [pow2(cto), pow2(cto) - 1, n, cto, ...]
// We need to drop the high bits of `pow2(cto)` because if `cto`
// We need to drop the high bits of `pow2(cto)` because if `cto`
// equals 32 `pow2(cto)` will exceed the u32. Also in that case there
// is no need to check the dividing zero, since it is absent (value
// is all 1's).
// is no need to check the dividing zero, since it is absent (value
// is all 1's).

Dup1, Add, // [bit_mask, pow2(cto) - 1, n, cto]
// 00..001111111111...11 <-- bitmask
// │└─ cto ones ─┘
// └─ additional one

MovUp2, U32and, // [m, pow2(cto) - 1, cto]
// If calcualtion of `cto` is correct, m should be equal to
// If calcualtion of `cto` is correct, m should be equal to
// pow2(cto) - 1

Eq, Assert(0), // [cto, ...]
Expand Down
1 change: 1 addition & 0 deletions miden/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,4 @@ predicates = "3.0"
test-utils = { package = "miden-test-utils", path = "../test-utils" }
vm-core = { package = "miden-core", path = "../core", version = "0.8" }
winter-fri = { package = "winter-fri", version = "0.8" }
rand_chacha = "0.3.1"
146 changes: 145 additions & 1 deletion miden/tests/integration/operations/decorators/advice.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
use miden_vm::{Digest, Word};
use processor::ExecutionError;
use rand_chacha::rand_core::SeedableRng;

use test_utils::crypto::rpo_falcon512::SecretKey;
use test_utils::rand::rand_array;
use test_utils::serde::Serializable;
use test_utils::{
build_test,
crypto::{MerkleStore, RpoDigest},
rand::rand_value,
Felt,
Felt, TestError,
};

const ADVICE_PUSH_SIG: &str = "
begin
# => [PK, MSG, ...]
# Calling adv.push_sig.rpo_falcon512 on its own gives an error:
# internal error: entered unreachable code: decorators in and empty SPAN block
# add stack calls to avoid this issue.
push.0
drop
adv.push_sig.rpo_falcon512
# => [PK, MSG, ...]
end";

// ADVICE INJECTION
// ================================================================================================

Expand Down Expand Up @@ -329,3 +351,125 @@ fn advice_insert_hdword() {
let test = build_test!(source, &stack_inputs);
test.expect_stack(&[1, 2, 3, 4, 5, 6, 7, 8]);
}

#[test]
fn advice_push_sig_rpo_falcon_512() {
// Generate random keys and message.
let seed: [u8; 32] = rand_array();
let mut rng = rand_chacha::ChaCha20Rng::from_seed(seed);

let secret_key = SecretKey::with_rng(&mut rng);

// let secret_key = SecretKey::new();
let public_key = secret_key.public_key();
let message: Word = rand_array();

let public_key_word: Word = public_key.into();
let public_key_digest: Digest = public_key_word.into();

// Place digest of the public key and the secret key into advice map as a key value pair.
let secret_key_bytes = secret_key.to_bytes();
let secret_key_adv_map =
secret_key_bytes.iter().map(|a| Felt::new(*a as u64)).collect::<Vec<Felt>>();
let advice_map: Vec<(Digest, Vec<Felt>)> = vec![(public_key_digest, secret_key_adv_map)];

// Lay the public key digest and message into the operation stack.
let mut op_stack = vec![];
let message = message.into_iter().map(|a| a.as_int()).collect::<Vec<u64>>();
op_stack.extend_from_slice(&message);
op_stack.extend_from_slice(
&public_key_digest.as_elements().iter().map(|a| a.as_int()).collect::<Vec<u64>>(),
);
let advice_stack = vec![];

let store = MerkleStore::new();
let mut expected_stack = op_stack.clone();
expected_stack.reverse();

let test =
build_test!(ADVICE_PUSH_SIG, &op_stack, &advice_stack, store, advice_map.into_iter());
test.expect_stack(&expected_stack);
}

#[test]
fn advice_push_sig_rpo_falcon_512_bad_key_value() {
// Generate random keys and message.
let seed: [u8; 32] = rand_array();
let mut rng = rand_chacha::ChaCha20Rng::from_seed(seed);

let secret_key = SecretKey::with_rng(&mut rng);
let public_key = secret_key.public_key();
let message: Word = rand_array();

let public_key_word: Word = public_key.into();
let public_key_digest: Digest = public_key_word.into();

// Place digest of the public key and the secret key into advice map as a key value pair.
let secret_key_bytes = secret_key.to_bytes();
let mut secret_key_adv_map =
secret_key_bytes.iter().map(|a| Felt::new(*a as u64)).collect::<Vec<Felt>>();

// Secret key as bytes must have values in the range 0 - 255.
secret_key_adv_map.pop();
secret_key_adv_map.push(Felt::new(257));

let advice_map: Vec<(Digest, Vec<Felt>)> = vec![(public_key_digest, secret_key_adv_map)];

// Lay the public key digest and message into the operation stack.
let mut op_stack = vec![];
let message = message.into_iter().map(|a| a.as_int()).collect::<Vec<u64>>();
op_stack.extend_from_slice(&message);
op_stack.extend_from_slice(
&public_key_digest.as_elements().iter().map(|a| a.as_int()).collect::<Vec<u64>>(),
);
let advice_stack = vec![];

let store = MerkleStore::new();

let test =
build_test!(ADVICE_PUSH_SIG, &op_stack, &advice_stack, store, advice_map.into_iter());
test.expect_error(TestError::ExecutionError(ExecutionError::MalformedSignatureKey(
"RPO Falcon512",
)));
}

#[test]
fn advice_push_sig_rpo_falcon_512_bad_key_length() {
// Generate random keys and message.
let seed: [u8; 32] = rand_array();
let mut rng = rand_chacha::ChaCha20Rng::from_seed(seed);

let secret_key = SecretKey::with_rng(&mut rng);
let public_key = secret_key.public_key();
let message: Word = rand_array();

let public_key_word: Word = public_key.into();
let public_key_digest: Digest = public_key_word.into();

// Place digest of the public key and the secret key into advice map as a key value pair.
let secret_key_bytes = secret_key.to_bytes();
let mut secret_key_adv_map =
secret_key_bytes.iter().map(|a| Felt::new(*a as u64)).collect::<Vec<Felt>>();

// Secret key as bytes must be at least the correct length.
secret_key_adv_map.pop();
let advice_map: Vec<(Digest, Vec<Felt>)> = vec![(public_key_digest, secret_key_adv_map)];

// Lay the public key digest and message into the operation stack.
let mut op_stack = vec![];
let message = message.into_iter().map(|a| a.as_int()).collect::<Vec<u64>>();
op_stack.extend_from_slice(&message);
op_stack.extend_from_slice(
&public_key_digest.as_elements().iter().map(|a| a.as_int()).collect::<Vec<u64>>(),
);
let advice_stack = vec![];

let store = MerkleStore::new();

let test =
build_test!(ADVICE_PUSH_SIG, &op_stack, &advice_stack, store, advice_map.into_iter());

test.expect_error(TestError::ExecutionError(ExecutionError::MalformedSignatureKey(
"RPO Falcon512",
)));
}
4 changes: 4 additions & 0 deletions stdlib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,18 @@ assembly = { package = "miden-assembly", path = "../assembly", version = "0.8",
[dev-dependencies]
blake3 = "1.5"
miden-air = { package = "miden-air", path = "../air", version = "0.8", default-features = false }
num = "0.4.1"
num-bigint = "0.4"
processor = { package = "miden-processor", path = "../processor", version = "0.8", features = ["internals"], default-features = false }
rand = { version = "0.8.5", default-features = false }
serde_json = "1.0"
sha2 = "0.10"
sha3 = "0.10"
test-utils = { package = "miden-test-utils", path = "../test-utils" }
winter-air = { package = "winter-air", version = "0.8" }
winter-fri = { package = "winter-fri", version = "0.8" }



[build-dependencies]
assembly = { package = "miden-assembly", path = "../assembly", version = "0.8" }
18 changes: 10 additions & 8 deletions stdlib/asm/crypto/dsa/rpo_falcon512.masm
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export.hash_to_point.2
# Absorb the message
swapw loc_loadw.1 swapw hperm

# Squeeze the coefficents and save them
# Squeeze the coefficients and save them
repeat.63
swapw dup.12
mem_storew
Expand Down Expand Up @@ -212,7 +212,7 @@ export.load_h_s2_and_product.1
movup.2 assert_eq
assert_eq

# 4) Load s2 (Due to the final norm test we do not need to range check the s2 coefficents)
# 4) Load s2 (Due to the final norm test we do not need to range check the s2 coefficients)
padw padw
repeat.64
adv_pipe hperm
Expand Down Expand Up @@ -242,7 +242,7 @@ end
#! Output: [...]
#!
#! Cycles: 2504
export.probablistic_product.4
export.probabilistic_product.4
# 1) Save the pointers
push.0 movdn.3
loc_storew.0
Expand Down Expand Up @@ -473,7 +473,7 @@ end
#! All of the above implies that we can compute s1_i with only one modular reduction at the end,
#! in addition to one modular reduction applied to c_i.
#! Moreover, since we are only interested in the square norm of s1_i, we do not have to store
#! s1_i and then load it at a later point, and instead we can immediatly follow the computation
#! s1_i and then load it at a later point, and instead we can immediately follow the computation
#! of s1_i with computing its square norm.
#! After computing the square norm of s1_i, we can accumulate into an accumulator to compute the
#! sum of the square norms of all the coefficients of polynomial c. Using the overflow stack, this
Expand Down Expand Up @@ -515,7 +515,7 @@ export.compute_s1_norm_sq
exec.norm_sq
#=> [norm(e)^2, ...]

# Move the result out of the way so that we can process the remaining coefficents
# Move the result out of the way so that we can process the remaining coefficients
movdn.10

# 3) Compute the squared norm of (i + 1)-th coefficient of s1
Expand Down Expand Up @@ -599,16 +599,18 @@ export.verify.1665

# 2) Load the NONCE from the advice provider. This is encoded as 8 field elements
padw adv_loadw padw adv_loadw
#=> [PK, MSG, NONCE1, NONCE0, ...]
#=> [NONCE1, NONCE0, PK, MSG...]

# 3) Load the public key polynomial h and the signature polynomial s2 and the product of
# the two polynomials pi := h * s2 in Z_Q[x]. This also checks that h hashes to the provided
# digest PK. While loading the polynomials, the hash of the three polynomials is computed
# and the first half of the digest is kept on the stack for later use by the
# `probablistic_product` procedure.
# `probabilistic_product` procedure.

swapdw
#=> [PK, MSG, NONCE1, NONCE0...]
locaddr.0
#=> [h_ptr, PK, MSG, NONCE1, NONCE0...]
exec.load_h_s2_and_product
#=> [tau1, tau0, tau_ptr, MSG, NONCE1, NONCE1, ...] (Cycles: 5050)

Expand All @@ -632,7 +634,7 @@ export.verify.1665
locaddr.0 # h ptr
#=> [h_ptr, zeros_ptr, tau_ptr, ...]

exec.probablistic_product
exec.probabilistic_product
#=> [...] (Cycles: 2504)

# 6) Compute the squared norm of s1 := c - h * s2 (in Z_q[x]/(phi))
Expand Down
Loading

0 comments on commit d9dc097

Please sign in to comment.