Skip to content
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

STARK-based signature scheme #342

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
110 changes: 110 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ serde = { version = "1.0", default-features = false, optional = true, features =
sha3 = { version = "0.10", default-features = false }
winter-crypto = { version = "0.10", default-features = false }
winter-math = { version = "0.10", default-features = false }
winter-prover = { version = "0.10", default-features = false }
winter-utils = { version = "0.10", default-features = false }
winter-verifier = { version = "0.10", default-features = false }
winterfell = { version = "0.10", default-features = false }

[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
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;
30 changes: 30 additions & 0 deletions src/dsa/rpo_stark/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
mod signature;
pub use signature::{PublicKey, SecretKey, Signature};

mod stark;

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

#[cfg(test)]
mod tests {
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.compute_public_key();
assert!(pk.verify(message, &signature))
}
}
156 changes: 156 additions & 0 deletions src/dsa/rpo_stark/signature/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
use rand::Rng;
use winter_math::fields::f64::BaseElement;
use winter_prover::Proof;
use winter_utils::{
ByteReader, ByteWriter, Deserializable, DeserializationError, Randomizable, Serializable,
};
use winterfell::{FieldExtension, ProofOptions};

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

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

/// Specifies the parameters of the STARK underlying the signature scheme.
///
/// TODO: The number of queries needs to be updated so that it matches the one given in
/// the specification. The reason for choosing such a low number for the number of FRI queries is
/// due to the fact that the number of FRI queries should be smaller than the size of the LDE
/// domain. Since the PR enabling zero-knowledge in Winterfell is yet to be merged, this not
/// case.
pub const PROOF_OPTIONS: ProofOptions =
ProofOptions::new(63, 8, 0, FieldExtension::Quadratic, 8, 31);

// 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 {
/// 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)
}
}

// 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.
#[cfg(feature = "std")]
#[allow(clippy::new_without_default)]
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 mut sk = [ZERO; 4];

let mut dest = vec![0_u8; 8];
for s in sk.iter_mut() {
rng.fill_bytes(&mut dest);
*s = BaseElement::from_random_bytes(&dest).expect("");
}

Self(sk)
}

/// Computes the public key corresponding to this secret key.
pub fn compute_public_key(&self) -> PublicKey {
let pk = compute_rpo_image(self.0);
PublicKey(pk)
}

/// 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 }
}
}

// 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] in the unique decoding regime where
/// it is argued that for the parameter set `PROOF_OPTIONS` the signature achieves $113$ 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
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Signature {
proof: Proof,
}

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

signature.verify(pk, message, self.proof.clone()).is_ok()
}
}

// SERIALIZATION / DESERIALIZATION
// ================================================================================================

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))
}
}

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))
}
}

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
Loading