Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
213 changes: 162 additions & 51 deletions Cargo.lock

Large diffs are not rendered by default.

21 changes: 16 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,23 @@ num = { version = "0.4", default-features = false, features = ["alloc", "libm"]
num-complex = { version = "0.4", default-features = false }
rand = { version = "0.8", default-features = false }
rand_core = { version = "0.6", default-features = false }
rand-utils = { version = "0.11", package = "winter-rand-utils", optional = true }
#rand-utils = { version = "0.11", package = "winter-rand-utils", optional = true }
rayon = { version = "1.10", optional = true }
serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] }
sha3 = { version = "0.10", default-features = false }
thiserror = { version = "2.0", default-features = false }
winter-crypto = { version = "0.11", default-features = false }
winter-math = { version = "0.11", default-features = false }
winter-utils = { version = "0.11", default-features = false }
winter-air = {git = 'https://github.com/Al-Kindi-0/winterfell', branch = 'al-zk' }
winter-crypto = {git = 'https://github.com/Al-Kindi-0/winterfell', branch = 'al-zk' }
winter-prover = {git = 'https://github.com/Al-Kindi-0/winterfell', branch = 'al-zk' }
winter-verifier = {git = 'https://github.com/Al-Kindi-0/winterfell', branch = 'al-zk' }
winter-math = {git = 'https://github.com/Al-Kindi-0/winterfell', branch = 'al-zk' }
winter-fri = {git = 'https://github.com/Al-Kindi-0/winterfell', branch = 'al-zk' }
winter-maybe-async = {git = 'https://github.com/Al-Kindi-0/winterfell', branch = 'al-zk' }
winter-utils = {git = 'https://github.com/Al-Kindi-0/winterfell', branch = 'al-zk', optional = true }
#winter-rand-utils = {git = 'https://github.com/Al-Kindi-0/winterfell', branch = 'al-zk', optional = true }
rand-utils = {git = 'https://github.com/Al-Kindi-0/winterfell', package = "winter-rand-utils" , branch = 'al-zk', optional = true }
winterfell = {git = 'https://github.com/Al-Kindi-0/winterfell/', branch = 'al-zk' }
rand_chacha = { version = "0.3", default-features = false }

[dev-dependencies]
assert_matches = { version = "1.5", default-features = false }
Expand All @@ -67,7 +77,8 @@ getrandom = { version = "0.2", features = ["js"] }
hex = { version = "0.4", default-features = false, features = ["alloc"] }
proptest = "1.5"
rand_chacha = { version = "0.3", default-features = false }
rand-utils = { version = "0.11", package = "winter-rand-utils" }
#rand-utils = { version = "0.10", package = "winter-rand-utils" }
rand-utils = {git = 'https://github.com/Al-Kindi-0/winterfell', package = "winter-rand-utils" , branch = 'al-zk' }
seq-macro = { version = "0.3" }

[build-dependencies]
Expand Down
2 changes: 2 additions & 0 deletions src/dsa/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Digital signature schemes supported by default in the Miden VM.

pub mod rpo_falcon512;

pub mod rpo_stark;
33 changes: 33 additions & 0 deletions src/dsa/rpo_stark/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
mod signature;
pub use signature::{PublicKey, SecretKey, Signature};

mod stark;
pub use stark::{PublicInputs, RescueAir};

// TESTS
// ================================================================================================

#[cfg(test)]
mod tests {
use std::println;

use rand::SeedableRng;
use rand_chacha::ChaCha20Rng;

use super::SecretKey;

#[test]
fn test_signature() {
use rand_utils::rand_array;

let seed = [0_u8; 32];
let mut rng = ChaCha20Rng::from_seed(seed);
let sk = SecretKey::with_rng(&mut rng);

let message = rand_array();
let signature = sk.sign(message);
let pk = sk.public_key();
println!("verify {:?}", pk.verify(message, &signature));
assert!(pk.verify(message, &signature))
}
}
174 changes: 174 additions & 0 deletions src/dsa/rpo_stark/signature/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use rand::{distributions::Uniform, prelude::Distribution, Rng};
use winter_math::{fields::f64::BaseElement, FieldElement, StarkField};
use winter_prover::Proof;
use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
use winterfell::{FieldExtension, ProofOptions};

use crate::{
dsa::rpo_stark::stark::RpoSignatureScheme,
hash::{rpo::Rpo256, DIGEST_SIZE},
Word, ZERO,
};

// CONSTANTS
// ================================================================================================

/// Specifies the parameters of the STARK underlying the signature scheme. These parameters provide
/// at least 102 bits of security under the conjectured security of the toy protocol in
/// the ethSTARK paper [1].
///
/// [1]: https://eprint.iacr.org/2021/582
pub const PROOF_OPTIONS: ProofOptions =
ProofOptions::new(30, 8, 12, FieldExtension::Quadratic, 4, 7, true);

// 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 secret key.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PublicKey(Word);

impl PublicKey {
/// Returns the [Word] defining the public key.
pub fn inner(&self) -> Word {
self.0
}
}

impl PublicKey {
/// Verifies the provided signature against provided message and this public key.
pub fn verify(&self, message: Word, signature: &Signature) -> bool {
signature.verify(message, *self)
}
}

impl Serializable for PublicKey {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.0.write_into(target);
}
}

impl Deserializable for PublicKey {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let pk = <Word>::read_from(source)?;
Ok(Self(pk))
}
}

// SECRET KEY
// ================================================================================================

/// A secret key for generating signatures.
///
/// The secret key is a [Word] (i.e., 4 field elements).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SecretKey(Word);

impl SecretKey {
/// Generates a secret key from OS-provided randomness.
pub fn new(word: Word) -> Self {
Self(word)
}

/// Generates a secret key from a [Word].
#[cfg(feature = "std")]
pub fn random() -> 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`.
#[cfg(feature = "std")]
pub fn with_rng<R: Rng>(rng: &mut R) -> Self {
let mut sk = [ZERO; 4];
let uni_dist = Uniform::from(0..BaseElement::MODULUS);

for s in sk.iter_mut() {
let sampled_integer = uni_dist.sample(rng);
*s = BaseElement::new(sampled_integer);
}

Self(sk)
}

/// Computes the public key corresponding to this secret key.
pub fn public_key(&self) -> PublicKey {
let mut elements = [BaseElement::ZERO; 8];
elements[..DIGEST_SIZE].copy_from_slice(&self.0);
let pk = Rpo256::hash_elements(&elements);
PublicKey(pk.into())
}

/// Signs a message with this secret key.
pub fn sign(&self, message: Word) -> Signature {
let signature: RpoSignatureScheme<Rpo256> = RpoSignatureScheme::new(PROOF_OPTIONS);
let proof = signature.sign(self.0, message);
Signature { proof }
}
}

impl Serializable for SecretKey {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.0.write_into(target);
}
}

impl Deserializable for SecretKey {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let sk = <Word>::read_from(source)?;
Ok(Self(sk))
}
}

// SIGNATURE
// ================================================================================================

/// An RPO STARK-based signature over a message.
///
/// The signature is a STARK proof of knowledge of a pre-image given an image where the map is
/// the RPO permutation, the pre-image is the secret key and the image is the public key.
/// The current implementation follows the description in [1] but relies on the conjectured security
/// of the toy protocol in the ethSTARK paper [2], which gives us using the parameter set
/// given in `PROOF_OPTIONS` a signature with $102$ bits of average-case existential unforgeability
/// security against $2^{113}$-query bound adversaries that can obtain up to $2^{64}$ signatures
/// under the same public key.
///
/// [1]: https://eprint.iacr.org/2024/1553
/// [2]: https://eprint.iacr.org/2021/582
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Signature {
proof: Proof,
}

impl Signature {
/// Returns the STARK proof constituting the signature.
pub fn inner(&self) -> Proof {
self.proof.clone()
}

/// Returns true if this signature is a valid signature for the specified message generated
/// against the secret key matching the specified public key.
pub fn verify(&self, message: Word, pk: PublicKey) -> bool {
let signature: RpoSignatureScheme<Rpo256> = RpoSignatureScheme::new(PROOF_OPTIONS);

let res = signature.verify(pk.inner(), message, self.proof.clone());
res.is_ok()
}
}

impl Serializable for Signature {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.proof.write_into(target);
}
}

impl Deserializable for Signature {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let proof = Proof::read_from(source)?;
Ok(Self { proof })
}
}
Loading