-
Notifications
You must be signed in to change notification settings - Fork 397
[PQ-Accounts] SHAKE hint and direct variants #1730
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
3b9ee6a
bdd723e
96110a6
ceea8d6
b266b9e
53aa1ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| // OpenZeppelin Contracts for Cairo v4.0.0-alpha.1 (account/src/falcon_512.cairo) | ||
|
|
||
| //! Falcon-512 SHAKE account contracts and their verification implementation. | ||
| //! | ||
| //! These accounts implement the legacy Falcon submission algorithm with SHAKE-256 | ||
| //! hash-to-point. Their packed felt public keys and signatures are contract-specific; | ||
| //! they are not the encoding or finalized behavior of a NIST FN-DSA standard. | ||
|
|
||
| pub(crate) mod account; | ||
| pub(crate) mod falcon; | ||
| pub mod falcon_512_shake; | ||
| pub mod falcon_512_shake_direct; | ||
| pub(crate) mod hashing; | ||
| pub(crate) mod ntt; | ||
| pub(crate) mod packing; | ||
| pub(crate) mod zq; | ||
| use account::Falcon512SignatureVerifier; | ||
|
|
||
| pub use falcon_512_shake::Falcon512ShakeAccount; | ||
| pub use falcon_512_shake_direct::Falcon512ShakeDirectAccount; | ||
| use hashing::hash_to_point::hash_to_point_shake_512; | ||
|
|
||
| /// Number of felts in a packed Falcon-512 public key. | ||
| pub(crate) const PUBLIC_KEY_FELTS: u32 = 29; | ||
|
|
||
| /// Number of felts in a Falcon-512 signature carrying a product hint. | ||
| pub(crate) const SIGNATURE_FELTS: u32 = 60; | ||
|
|
||
| /// Number of felts in a hint-free Falcon-512 signature. | ||
| pub(crate) const DIRECT_SIGNATURE_FELTS: u32 = 31; | ||
|
|
||
| /// Verifier for the 60-felt SHAKE-256 signature carrying a polynomial-product hint. | ||
| pub(crate) impl Falcon512ShakeVerifier of Falcon512SignatureVerifier { | ||
| fn verify(message_hash: felt252, public_key: Span<felt252>, signature: Span<felt252>) -> bool { | ||
| if public_key.len() != PUBLIC_KEY_FELTS || signature.len() != SIGNATURE_FELTS { | ||
| return false; | ||
| } | ||
| let h_ntt = match packing::unpack_512_u16(public_key) { | ||
| Some(value) => value, | ||
| None => { return false; }, | ||
| }; | ||
| let s1 = match packing::unpack_512_u16(signature.slice(0, 29)) { | ||
| Some(value) => value, | ||
| None => { return false; }, | ||
| }; | ||
| let salt_a = *signature.at(29); | ||
| let salt_b = *signature.at(30); | ||
| let mul_hint = match packing::unpack_512_u16(signature.slice(31, 29)) { | ||
| Some(value) => value, | ||
| None => { return false; }, | ||
| }; | ||
| let message_point = match hash_to_point_shake_512(message_hash, salt_a, salt_b) { | ||
| Some(value) => value, | ||
| None => { return false; }, | ||
| }; | ||
| falcon::verify_512_with_hint_u16( | ||
| s1.span(), h_ntt.span(), mul_hint.span(), message_point.span(), | ||
| ) | ||
| } | ||
|
|
||
| fn is_valid_public_key(public_key: Span<felt252>) -> bool { | ||
| if public_key.len() != PUBLIC_KEY_FELTS { | ||
| return false; | ||
| } | ||
| match packing::unpack_512_u16(public_key) { | ||
| Some(_) => true, | ||
| None => false, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Verifier for the 31-felt SHAKE-256 signature that recomputes the product on-chain. | ||
| pub(crate) impl Falcon512ShakeDirectVerifier of Falcon512SignatureVerifier { | ||
| fn verify(message_hash: felt252, public_key: Span<felt252>, signature: Span<felt252>) -> bool { | ||
| if public_key.len() != PUBLIC_KEY_FELTS || signature.len() != DIRECT_SIGNATURE_FELTS { | ||
| return false; | ||
| } | ||
| let h_ntt = match packing::unpack_512_u16(public_key) { | ||
| Some(value) => value, | ||
| None => { return false; }, | ||
| }; | ||
| let s1 = match packing::unpack_512_u16(signature.slice(0, 29)) { | ||
| Some(value) => value, | ||
| None => { return false; }, | ||
| }; | ||
| let salt_a = *signature.at(29); | ||
| let salt_b = *signature.at(30); | ||
| let message_point = match hash_to_point_shake_512(message_hash, salt_a, salt_b) { | ||
| Some(value) => value, | ||
| None => { return false; }, | ||
| }; | ||
| falcon::verify_512_direct_u16(s1.span(), h_ntt.span(), message_point.span()) | ||
| } | ||
|
|
||
| fn is_valid_public_key(public_key: Span<felt252>) -> bool { | ||
| Falcon512ShakeVerifier::is_valid_public_key(public_key) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| // OpenZeppelin Contracts for Cairo v4.0.0-alpha.1 (account/src/falcon_512/account.cairo) | ||
|
|
||
| //! Shared account component for the Falcon-512 SHAKE account contracts. | ||
|
|
||
| /// Verification surface implemented by each Falcon-512 account variant. | ||
| pub(crate) trait Falcon512SignatureVerifier { | ||
| /// Returns whether `signature` authenticates `message_hash` under `public_key`. | ||
| fn verify(message_hash: felt252, public_key: Span<felt252>, signature: Span<felt252>) -> bool; | ||
|
|
||
| /// Returns whether the packed public key has the required canonical encoding. | ||
| fn is_valid_public_key(public_key: Span<felt252>) -> bool; | ||
| } | ||
|
|
||
| /// Account component for immutable, array-encoded Falcon-512 public keys. | ||
| #[starknet::component] | ||
| pub(crate) mod Falcon512AccountComponent { | ||
| use core::num::traits::Zero; | ||
| use openzeppelin_interfaces::accounts as interface; | ||
| use openzeppelin_introspection::src5::SRC5Component; | ||
| use openzeppelin_introspection::src5::SRC5Component::InternalTrait as SRC5InternalTrait; | ||
| use openzeppelin_utils::execution::execute_single_call; | ||
| use starknet::account::Call; | ||
| use starknet::storage::{MutableVecTrait, StoragePointerReadAccess, Vec, VecTrait}; | ||
| use crate::utils::is_tx_version_valid; | ||
| use super::Falcon512SignatureVerifier; | ||
|
|
||
| #[storage] | ||
| pub struct Storage { | ||
| pub public_key: Vec<felt252>, | ||
| } | ||
|
|
||
| #[event] | ||
| #[derive(Drop, Debug, PartialEq, starknet::Event)] | ||
| pub enum Event {} | ||
|
|
||
| pub mod Errors { | ||
| pub const INVALID_CALLER: felt252 = 'Account: invalid caller'; | ||
| pub const INVALID_PUBLIC_KEY: felt252 = 'Account: invalid public key'; | ||
| pub const INVALID_SIGNATURE: felt252 = 'Account: invalid signature'; | ||
| pub const INVALID_TX_VERSION: felt252 = 'Account: invalid tx version'; | ||
| } | ||
|
|
||
| #[embeddable_as(SRC6Impl)] | ||
| impl SRC6< | ||
| TContractState, | ||
| impl Verifier: Falcon512SignatureVerifier, | ||
| +HasComponent<TContractState>, | ||
| +SRC5Component::HasComponent<TContractState>, | ||
| +Drop<TContractState>, | ||
| > of interface::ISRC6<ComponentState<TContractState>> { | ||
| /// Executes calls forwarded by the account after protocol validation succeeds. | ||
| fn __execute__(self: @ComponentState<TContractState>, calls: Array<Call>) { | ||
| let sender = starknet::get_caller_address(); | ||
| assert(sender.is_zero(), Errors::INVALID_CALLER); | ||
| assert(is_tx_version_valid(), Errors::INVALID_TX_VERSION); | ||
|
|
||
| for call in calls.span() { | ||
| execute_single_call(call); | ||
| } | ||
| } | ||
|
|
||
| /// Validates an invoke transaction with the current transaction signature. | ||
| fn __validate__(self: @ComponentState<TContractState>, calls: Array<Call>) -> felt252 { | ||
| let _ = calls; | ||
| self.validate_transaction::<Verifier>() | ||
| } | ||
|
|
||
| /// Verifies a signature for an arbitrary message hash. | ||
| fn is_valid_signature( | ||
| self: @ComponentState<TContractState>, hash: felt252, signature: Array<felt252>, | ||
| ) -> felt252 { | ||
| if Verifier::verify(hash, self.read_public_key().span(), signature.span()) { | ||
| starknet::VALIDATED | ||
| } else { | ||
| 0 | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[embeddable_as(DeclarerImpl)] | ||
| impl Declarer< | ||
| TContractState, | ||
| impl Verifier: Falcon512SignatureVerifier, | ||
| +HasComponent<TContractState>, | ||
| +SRC5Component::HasComponent<TContractState>, | ||
| +Drop<TContractState>, | ||
| > of interface::IDeclarer<ComponentState<TContractState>> { | ||
| /// Validates a declare transaction with the current transaction signature. | ||
| fn __validate_declare__( | ||
| self: @ComponentState<TContractState>, class_hash: felt252, | ||
| ) -> felt252 { | ||
| let _ = class_hash; | ||
| self.validate_transaction::<Verifier>() | ||
| } | ||
| } | ||
|
|
||
| #[embeddable_as(DeployableImpl)] | ||
| impl Deployable< | ||
| TContractState, | ||
| impl Verifier: Falcon512SignatureVerifier, | ||
| +HasComponent<TContractState>, | ||
| +SRC5Component::HasComponent<TContractState>, | ||
| +Drop<TContractState>, | ||
| > of interface::IFeltArrayDeployable<ComponentState<TContractState>> { | ||
| /// Validates a deploy-account transaction with the current transaction signature. | ||
| fn __validate_deploy__( | ||
| self: @ComponentState<TContractState>, | ||
| class_hash: felt252, | ||
| contract_address_salt: felt252, | ||
| public_key: Array<felt252>, | ||
| ) -> felt252 { | ||
| let _ = class_hash; | ||
| let _ = contract_address_salt; | ||
| let _ = public_key; | ||
| self.validate_transaction::<Verifier>() | ||
| } | ||
| } | ||
|
|
||
| #[embeddable_as(PublicKeyImpl)] | ||
| impl PublicKey< | ||
| TContractState, | ||
| +HasComponent<TContractState>, | ||
| +SRC5Component::HasComponent<TContractState>, | ||
| +Drop<TContractState>, | ||
| > of interface::IFeltArrayPublicKey<ComponentState<TContractState>> { | ||
| /// Returns the immutable packed Falcon-512 public key. | ||
| fn get_public_key(self: @ComponentState<TContractState>) -> Array<felt252> { | ||
| self.read_public_key() | ||
| } | ||
| } | ||
|
|
||
| #[generate_trait] | ||
| pub impl InternalImpl< | ||
| TContractState, | ||
| +HasComponent<TContractState>, | ||
| impl SRC5: SRC5Component::HasComponent<TContractState>, | ||
| +Drop<TContractState>, | ||
| > of InternalTrait<TContractState> { | ||
| /// Validates and stores the immutable packed public key and registers SRC6 support. | ||
| fn initializer<impl Verifier: Falcon512SignatureVerifier>( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Severity 2 — Low: the
Suggested fix: move Also: |
||
| ref self: ComponentState<TContractState>, public_key: Array<felt252>, | ||
| ) { | ||
| assert(Verifier::is_valid_public_key(public_key.span()), Errors::INVALID_PUBLIC_KEY); | ||
|
|
||
| let mut src5_component = get_dep_component_mut!(ref self, SRC5); | ||
| src5_component.register_interface(interface::ISRC6_ID); | ||
|
|
||
| for felt in public_key { | ||
| self.public_key.push(felt); | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| /// Validates the transaction hash against the transaction signature. | ||
| fn validate_transaction<impl Verifier: Falcon512SignatureVerifier>( | ||
| self: @ComponentState<TContractState>, | ||
| ) -> felt252 { | ||
| let tx_info = starknet::get_tx_info().unbox(); | ||
| assert( | ||
| Verifier::verify( | ||
| tx_info.transaction_hash, self.read_public_key().span(), tx_info.signature, | ||
| ), | ||
| Errors::INVALID_SIGNATURE, | ||
| ); | ||
| starknet::VALIDATED | ||
| } | ||
|
|
||
| /// Reads the stored public key into its 29-felt packed representation. | ||
| fn read_public_key(self: @ComponentState<TContractState>) -> Array<felt252> { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Severity 3 — Medium:
The length is fixed at 29 by construction ( Suggested fix: |
||
| let mut public_key = array![]; | ||
| let len = self.public_key.len(); | ||
| let mut index = 0; | ||
| while index != len { | ||
| public_key.append(self.public_key.at(index).read()); | ||
| index += 1; | ||
| } | ||
|
Comment on lines
+412
to
+416
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| public_key | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Severity 2 — Low: CI runs the expensive Falcon suite redundantly.
The 65 dev-profile Falcon tests run twice (here and in the coverage run at line 67), several above 1e9 L2 gas, serial in one job — and the coverage run doesn't enable
falcon_presets_tests, so the Falcon presets (SRC9 routing,upgrade'sassert_only_self) report as entirely untested in Codecov anyway. Two further cost sinks:test_fast_ntt_matches_generic_reference_for_every_basis_vectoralone costs 7.65B gas (~76M steps) and is single-handedly why the 100M step cap is needed (the boundary/pseudorandom test already covers the interesting cases — sample every 16th basis vector or feature-gate it); and the#[cfg(test)]felt wrapper re-inlines the 31k-statement NTT body, making ~26.7% of the test program duplicate code and a 44 MB test artifact (have it callntt_falcon512_fast_u16_uncheckedand convert instead).