Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 0 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,3 @@ debug = 2
debug-assertions = true
inherits = "release"
overflow-checks = true

# Local patch for faster iteration during development
[patch."https://github.com/0xMiden/rust-fn-dsa"]
fn-dsa-comm = { path = "/Users/al/Code/rust-fn-dsa/fn-dsa-comm" }
fn-dsa-kgen = { path = "/Users/al/Code/rust-fn-dsa/fn-dsa-kgen" }
fn-dsa-sign = { path = "/Users/al/Code/rust-fn-dsa/fn-dsa-sign" }
11 changes: 6 additions & 5 deletions miden-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,10 @@ clap = { features = ["derive"], optional = true, versio
curve25519-dalek = { default-features = false, version = "4" }
ed25519-dalek = { features = ["zeroize"], version = "2" }
flume = { version = "0.11" }
fn-dsa-comm = { default-features = false, git = "https://github.com/0xMiden/rust-fn-dsa", branch = "expose-internals" }
fn-dsa-kgen = { default-features = false, git = "https://github.com/0xMiden/rust-fn-dsa", branch = "expose-internals" }
fn-dsa-sign = { default-features = false, git = "https://github.com/0xMiden/rust-fn-dsa", branch = "expose-internals" }
fn-dsa-comm = { branch = "sign-prehash", default-features = false, git = "https://github.com/0xMiden/rust-fn-dsa" }
fn-dsa-kgen = { branch = "sign-prehash", default-features = false, git = "https://github.com/0xMiden/rust-fn-dsa" }
fn-dsa-sign = { branch = "sign-prehash", default-features = false, git = "https://github.com/0xMiden/rust-fn-dsa" }
fn-dsa-vrfy = { branch = "sign-prehash", default-features = false, git = "https://github.com/0xMiden/rust-fn-dsa" }
hashbrown = { features = ["serde"], optional = true, version = "0.16" }
hkdf = { default-features = false, version = "0.12" }
k256 = { features = ["ecdh", "ecdsa"], version = "0.13" }
Expand Down Expand Up @@ -135,8 +136,8 @@ p3-miden-prover = { default-features = false, version = "0.4.2" }
[dev-dependencies]
assert_matches = { default-features = false, version = "1.5" }
criterion = { features = ["html_reports"], version = "0.7" }
fn-dsa-sign = { git = "https://github.com/0xMiden/rust-fn-dsa", branch = "expose-internals", features = ["testing"] }
fn-dsa-vrfy = { git = "https://github.com/0xMiden/rust-fn-dsa", branch = "expose-internals" }
fn-dsa-sign = { branch = "sign-prehash", git = "https://github.com/0xMiden/rust-fn-dsa" }
fn-dsa-vrfy = { branch = "sign-prehash", git = "https://github.com/0xMiden/rust-fn-dsa" }
hex = { default-features = false, features = ["alloc"], version = "0.4" }
itertools = { version = "0.14" }
proptest = { default-features = false, features = ["alloc"], version = "1.7" }
Expand Down
3 changes: 1 addition & 2 deletions miden-crypto/src/dsa/falcon512_rpo/keys/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use super::{
ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, Serializable, Signature,
math::{FalconFelt, Polynomial},
};

mod public_key;
Expand Down Expand Up @@ -55,7 +54,7 @@ mod tests {
let mut buffer = vec![];
sk.write_into(&mut buffer);
let sk_deserialized = SecretKey::read_from_bytes(&buffer).unwrap();
assert_eq!(sk.short_lattice_basis(), sk_deserialized.short_lattice_basis());
assert_eq!(sk, sk_deserialized);

// sign a random message
let message = Word::new([ONE; 4]);
Expand Down
99 changes: 48 additions & 51 deletions miden-crypto/src/dsa/falcon512_rpo/keys/public_key.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
//! Public key types for the RPO Falcon 512 digital signature scheme used in Miden VM.

use alloc::{string::ToString, vec::Vec};
use core::ops::Deref;

use fn_dsa_vrfy::{VerifyingKey, VerifyingKey512};

use super::{
super::{LOG_N, N, PK_LEN},
ByteReader, ByteWriter, Deserializable, DeserializationError, FalconFelt, Felt, Polynomial,
Serializable, Signature,
super::{
LOG_N, N, PK_LEN,
math::{FalconFelt, Polynomial},
},
ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, Serializable, Signature,
};
use crate::{SequentialCommit, Word};

// PUBLIC KEY
// ================================================================================================

/// Public key represented as a polynomial with coefficients over the Falcon prime field.
/// Public key for Falcon-512 DSA.
///
/// Internally stores the encoded public key bytes in fn-dsa format. The bytes can be
/// decoded to a `VerifyingKey512` on demand for verification operations.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublicKey(Polynomial<FalconFelt>);
pub struct PublicKey {
encoded: [u8; PK_LEN],
}

impl PublicKey {
/// Verifies the provided signature against provided message and this public key.
Expand All @@ -33,78 +41,67 @@ impl PublicKey {
pub fn to_commitment(&self) -> Word {
<Self as SequentialCommit>::to_commitment(self)
}
}

impl SequentialCommit for PublicKey {
type Commitment = Word;
/// Returns the encoded public key bytes.
pub fn as_bytes(&self) -> &[u8; PK_LEN] {
&self.encoded
}

fn to_elements(&self) -> Vec<Felt> {
Into::<Polynomial<Felt>>::into(self.0.clone()).coefficients
/// Returns the public key as a polynomial over the Falcon prime field.
pub fn to_polynomial(&self) -> Polynomial<FalconFelt> {
let h = self.decode_coefficients();
let coefficients: Vec<FalconFelt> = h.iter().map(|&v| FalconFelt::new(v)).collect();
Polynomial::new(coefficients)
}
}

impl Deref for PublicKey {
type Target = Polynomial<FalconFelt>;
/// Decodes the stored bytes into a VerifyingKey512 for verification operations.
pub(crate) fn decode_verifying_key(&self) -> Option<VerifyingKey512> {
VerifyingKey512::decode(&self.encoded)
}

fn deref(&self) -> &Self::Target {
&self.0
/// Decodes the public key polynomial coefficients from the stored bytes.
fn decode_coefficients(&self) -> [u16; N] {
let mut h = [0u16; N];
// Skip the header byte (LOG_N)
fn_dsa_comm::codec::modq_decode(&self.encoded[1..], &mut h)
.expect("encoded key should be valid");
h
}
}

impl From<Polynomial<FalconFelt>> for PublicKey {
fn from(pk_poly: Polynomial<FalconFelt>) -> Self {
Self(pk_poly)
impl SequentialCommit for PublicKey {
type Commitment = Word;

fn to_elements(&self) -> Vec<Felt> {
let h = self.decode_coefficients();
h.iter().map(|&v| Felt::new(v as u64)).collect()
}
}

impl Serializable for &PublicKey {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
let mut buf = [0_u8; PK_LEN];
buf[0] = LOG_N;

// Convert FalconFelt coefficients to u16 external representation [0, q-1]
let h: Vec<u16> = self.0.coefficients.iter().map(|c| c.value()).collect();

// Use fn-dsa-comm's modq_encode to encode 512 coefficients at 14 bits each
// This encodes 4 coefficients per 7 bytes (512/4 = 128 groups = 896 bytes)
let written = fn_dsa_comm::codec::modq_encode(&h, &mut buf[1..]);
assert_eq!(written, PK_LEN - 1, "modq_encode should write exactly {} bytes", PK_LEN - 1);

target.write(buf);
target.write_bytes(&self.encoded);
}
}

impl Deserializable for PublicKey {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let buf = source.read_array::<PK_LEN>()?;
let encoded: [u8; PK_LEN] = source.read_array()?;

if buf[0] != LOG_N {
if encoded[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]
encoded[0]
)));
}

// Use fn-dsa-comm's modq_decode to decode 512 coefficients
let mut h = [0u16; N];
let read_bytes = fn_dsa_comm::codec::modq_decode(&buf[1..], &mut h).ok_or_else(|| {
// Validate by attempting to decode
VerifyingKey512::decode(&encoded).ok_or_else(|| {
DeserializationError::InvalidValue(
"Failed to decode public key: invalid modq encoding".to_string(),
"Failed to decode public key: invalid encoding".to_string(),
)
})?;

// Verify we consumed exactly the expected number of bytes
if read_bytes != PK_LEN - 1 {
return Err(DeserializationError::InvalidValue(format!(
"Failed to decode public key: expected {} bytes, read {}",
PK_LEN - 1,
read_bytes
)));
}

// Convert u16 values to FalconFelt (modq_decode already validates values are in [0, q-1])
let coefficients: Vec<FalconFelt> = h.iter().map(|&v| FalconFelt::new(v)).collect();

Ok(Polynomial::new(coefficients).into())
Ok(Self { encoded })
}
}
Loading
Loading