diff --git a/Cargo.lock b/Cargo.lock index 1976d4de071..2dcdee89f8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,6 +185,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "asn1-rs" version = "0.7.1" @@ -552,6 +558,18 @@ dependencies = [ "serde", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2" version = "0.9.2" @@ -572,6 +590,27 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "blake2b_simd" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -581,6 +620,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "blst" version = "0.3.16" @@ -660,6 +705,12 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + [[package]] name = "bytecount" version = "0.6.9" @@ -1045,6 +1096,35 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" +[[package]] +name = "const_num_bigint" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ddae0ac5d546ee2446704bc529e011d584921305af4f0ae27590accc8cf9251" +dependencies = [ + "const_num_bigint_derive", + "const_std_vec", + "num-bigint", +] + +[[package]] +name = "const_num_bigint_derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1b18965eb9d280ddde32faf948cfe5c6e5bf6885cbf98fadacc28278b8f331f" +dependencies = [ + "num-bigint", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "const_std_vec" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee6a4701b947a8608d1991b1db0cf5bab74164bf2627e5cecc9aa69dd33f172" + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -1395,7 +1475,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.106", ] [[package]] @@ -1471,7 +1551,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "crypto-common", "subtle", ] @@ -1675,6 +1755,17 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -1807,6 +1898,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.31" @@ -2102,6 +2199,19 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand 0.8.5", + "rand_core 0.6.4", + "rand_xorshift 0.3.0", + "subtle", +] + [[package]] name = "h2" version = "0.4.12" @@ -2132,6 +2242,47 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "halo2curves" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d380afeef3f1d4d3245b76895172018cfb087d9976a7cabcd5597775b2933e07" +dependencies = [ + "blake2 0.10.6", + "digest 0.10.7", + "ff", + "group", + "halo2derive", + "lazy_static", + "num-bigint", + "num-integer", + "num-traits", + "pairing", + "pasta_curves", + "paste", + "rand 0.8.5", + "rand_core 0.6.4", + "rayon", + "sha2", + "static_assertions", + "subtle", + "unroll", +] + +[[package]] +name = "halo2derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb99e7492b4f5ff469d238db464131b86c2eaac814a78715acba369f64d2c76" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -2931,6 +3082,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libbz2-rs-sys" @@ -3143,7 +3297,7 @@ dependencies = [ "smallvec", "thiserror 2.0.17", "tracing", - "uint", + "uint 0.10.0", "web-time", ] @@ -3234,7 +3388,7 @@ dependencies = [ "pin-project", "rand 0.8.5", "salsa20", - "sha3", + "sha3 0.10.8", "tracing", ] @@ -3497,6 +3651,72 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "midnight-circuits" +version = "4.0.0" +source = "git+https://github.com/midnightntwrk/midnight-zk?rev=c88a50c2169f060120a52ad0980de90f08bc9535#c88a50c2169f060120a52ad0980de90f08bc9535" +dependencies = [ + "arrayvec", + "base64 0.13.1", + "bitvec", + "blake2b_simd", + "const_num_bigint", + "ff", + "group", + "halo2curves", + "lazy_static", + "midnight-curves", + "midnight-proofs", + "num-bigint", + "num-integer", + "num-traits", + "pasta_curves", + "rand 0.8.5", + "sha2", + "subtle", + "uint 0.9.5", +] + +[[package]] +name = "midnight-curves" +version = "0.1.0" +source = "git+https://github.com/midnightntwrk/midnight-zk?rev=c88a50c2169f060120a52ad0980de90f08bc9535#c88a50c2169f060120a52ad0980de90f08bc9535" +dependencies = [ + "bitvec", + "blst", + "byte-slice-cast", + "ff", + "getrandom 0.2.16", + "group", + "halo2curves", + "num-bigint", + "pairing", + "rand_core 0.6.4", + "serde", + "subtle", +] + +[[package]] +name = "midnight-proofs" +version = "0.4.0" +source = "git+https://github.com/midnightntwrk/midnight-zk?rev=c88a50c2169f060120a52ad0980de90f08bc9535#c88a50c2169f060120a52ad0980de90f08bc9535" +dependencies = [ + "blake2b_simd", + "ff", + "getrandom 0.2.16", + "group", + "halo2curves", + "midnight-curves", + "num-bigint", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "rayon", + "serde", + "serde_derive", + "sha3 0.9.1", + "tracing", +] + [[package]] name = "mime" version = "0.3.17" @@ -4147,10 +4367,15 @@ dependencies = [ name = "mithril-stm" version = "0.5.5" dependencies = [ + "anyhow", "blake2 0.10.6", "blst", "criterion", "digest 0.10.7", + "ff", + "group", + "midnight-circuits", + "midnight-curves", "num-bigint", "num-rational", "num-traits", @@ -4162,6 +4387,7 @@ dependencies = [ "rug", "serde", "serde_json", + "sha2", "thiserror 2.0.17", ] @@ -4658,6 +4884,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + [[package]] name = "pallas-addresses" version = "0.33.0" @@ -4822,6 +5057,21 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "pasta_curves" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "lazy_static", + "rand 0.8.5", + "static_assertions", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" @@ -5188,7 +5438,7 @@ dependencies = [ "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", - "rand_xorshift", + "rand_xorshift 0.4.0", "regex-syntax", "rusty-fork", "tempfile", @@ -5346,6 +5596,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -5411,6 +5667,15 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -6140,6 +6405,18 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug", +] + [[package]] name = "sha3" version = "0.10.8" @@ -6542,6 +6819,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tar" version = "0.4.44" @@ -7101,6 +7384,18 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "uint" version = "0.10.0" @@ -7159,6 +7454,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "unroll" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad948c1cb799b1a70f836077721a92a35ac177d4daddf4c20a633786d4cf618" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "unsigned-varint" version = "0.7.2" @@ -7533,7 +7838,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -7927,6 +8232,15 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x25519-dalek" version = "2.0.1" diff --git a/mithril-stm/Cargo.toml b/mithril-stm/Cargo.toml index 27248eb7503..84941e03c9b 100644 --- a/mithril-stm/Cargo.toml +++ b/mithril-stm/Cargo.toml @@ -19,15 +19,23 @@ rug-backend = ["rug/default"] num-integer-backend = ["num-bigint", "num-rational", "num-traits"] benchmark-internals = [] # For benchmarking multi_sig future_proof_system = [] # For activating future proof systems +future_snark = [] # For activating snark features [dependencies] +anyhow.workspace = true blake2 = "0.10.6" # Enforce blst portable feature for runtime detection of Intel ADX instruction set. blst = { version = "0.3.16", features = ["portable"] } digest = { workspace = true } +ff = "0.13.1" +group = "0.13.0" +midnight-circuits = { git = "https://github.com/midnightntwrk/midnight-zk", rev = "c88a50c2169f060120a52ad0980de90f08bc9535" } +midnight-curves = { git = "https://github.com/midnightntwrk/midnight-zk", rev = "c88a50c2169f060120a52ad0980de90f08bc9535" } +num-traits = "0.2.19" rand_core = { workspace = true } rayon = { workspace = true } serde = { workspace = true } +sha2 = "0.10.9" thiserror = { workspace = true } [target.'cfg(any(target_family = "wasm", target_env = "musl", windows))'.dependencies] diff --git a/mithril-stm/src/lib.rs b/mithril-stm/src/lib.rs index 2ca7313521b..f70a2d8cb26 100644 --- a/mithril-stm/src/lib.rs +++ b/mithril-stm/src/lib.rs @@ -117,6 +117,8 @@ mod key_registration; mod merkle_tree; mod parameters; mod participant; +#[cfg(feature = "future_snark")] +mod schnorr_signature; mod single_signature; pub use aggregate_signature::{ diff --git a/mithril-stm/src/schnorr_signature/mod.rs b/mithril-stm/src/schnorr_signature/mod.rs new file mode 100644 index 00000000000..25b0d6e9504 --- /dev/null +++ b/mithril-stm/src/schnorr_signature/mod.rs @@ -0,0 +1,168 @@ +#![allow(dead_code)] + +use midnight_circuits::{ + ecc::{hash_to_curve::HashToCurveGadget, native::EccChip}, + hash::poseidon::PoseidonChip, + types::AssignedNative, +}; +use midnight_curves::{ + Fq as JubjubBase, Fr as JubjubScalar, JubjubAffine, JubjubExtended, JubjubSubgroup, +}; +use sha2::{Digest, Sha256}; + +use anyhow::{Result, anyhow}; + +mod signature; +mod signing_key; +mod verification_key; + +/// A DST to distinguish between use of Poseidon hash +pub const DST_SIGNATURE: JubjubBase = JubjubBase::from_raw([0u64, 0, 0, 0]); +pub const DST_LOTTERY: JubjubBase = JubjubBase::from_raw([1u64, 0, 0, 0]); + +/// Defining a type for the CPU hash to curve gadget +type JubjubHashToCurve = HashToCurveGadget< + JubjubBase, + JubjubExtended, + AssignedNative, + PoseidonChip, + EccChip, +>; + +/// Convert an arbitrary array of bytes into a Jubjub scalar field element +/// First hash the message to 256 bits use Sha256 then perform the conversion +/// TODO: Handle the unwrap properly +pub(crate) fn hash_msg_to_jubjubbase(msg: &[u8]) -> Result { + let mut hash = Sha256::new(); + hash.update(msg); + let hmsg = hash.finalize(); + let mut output = [0u8; 32]; + // Adding a check here but this + if hmsg.len() == output.len() { + output.copy_from_slice(&hmsg); + } else { + return Err(anyhow!( + "Hash of the message does not have the correct lenght." + )); + } + Ok(JubjubBase::from_raw([ + u64::from_le_bytes(output[0..8].try_into()?), + u64::from_le_bytes(output[8..16].try_into()?), + u64::from_le_bytes(output[16..24].try_into()?), + u64::from_le_bytes(output[24..32].try_into()?), + ])) +} + +/// Extract the coordinates of a given point +/// This is mainly use to feed the Poseidon hash function +pub(crate) fn get_coordinates(point: JubjubSubgroup) -> (JubjubBase, JubjubBase) { + let extended = JubjubExtended::from(point); // Convert to JubjubExtended + let affine = JubjubAffine::from(extended); // Convert to JubjubAffine (affine coordinates) + let x = affine.get_u(); // Get x-coordinate + let y = affine.get_v(); // Get y-coordinate + (x, y) +} + +/// Convert an element of the BLS12-381 base field to +/// one of the Jubjub base field +pub fn jubjub_base_to_scalar(x: &JubjubBase) -> Result { + let bytes = x.to_bytes_le(); + Ok(JubjubScalar::from_raw([ + u64::from_le_bytes(bytes[0..8].try_into()?), + u64::from_le_bytes(bytes[8..16].try_into()?), + u64::from_le_bytes(bytes[16..24].try_into()?), + u64::from_le_bytes(bytes[24..32].try_into()?), + ])) +} + +#[cfg(test)] +mod tests { + + use super::*; + use group::Group; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + use crate::schnorr_signature::{ + signing_key::SchnorrSigningKey, verification_key::SchnorrVerificationKey, + }; + + // Testing conversion from arbitrary message to scalar field element + #[test] + fn test_hash_msg_to_jubjubbase() { + let msg = vec![0, 0, 0, 1]; + let h = hash_msg_to_jubjubbase(&msg).unwrap(); + // Correct value corresponding to the message [0,0,0,1] + let bytes_le = [ + 179, 7, 17, 168, 141, 112, 57, 117, 112, 92, 169, 56, 36, 70, 1, 217, 9, 13, 255, 42, + 100, 207, 166, 110, 188, 47, 35, 211, 35, 168, 100, 25, + ]; + let field_elem = JubjubBase::from_bytes_le(&bytes_le).unwrap(); + assert_eq!(h, field_elem) + } + + // Testing conversion from EC point to scalar coordinates + // For now only printing, next step is to try to generate a point + // from x and y values to check if they match with the result of the function + #[test] + fn test_get_coordinates() { + let seed = [0u8; 32]; + let mut rng = ChaCha20Rng::from_seed(seed); + let point = JubjubSubgroup::random(&mut rng); + let (x, y) = get_coordinates(point); + println!("{:?}", (x, y)); + } + + // Testing conversion from BLS12-381 base field to Jubjub base field + // TODO: Add randomness to val + #[test] + fn test_jubjub_ase_to_scalar() { + let val = vec![0, 0, 0, 1]; + let jjbase = JubjubBase::from_raw(val.clone().try_into().unwrap()); + let jjscalar = JubjubScalar::from_raw(val.try_into().unwrap()); + + let converted_base = jubjub_base_to_scalar(&jjbase).unwrap(); + + assert_eq!(jjscalar, converted_base); + } + + #[test] + fn test_sig_and_verify() { + let msg = vec![0, 0, 0, 1]; + let seed = [0u8; 32]; + let mut rng = ChaCha20Rng::from_seed(seed); + let sk = SchnorrSigningKey::generate(&mut rng); + let vk = SchnorrVerificationKey::from(&sk); + let sig = sk.sign(&msg, &mut rng).unwrap(); + sig.verify(&msg, &vk).unwrap(); + } + + // TODO: Change the errors + #[test] + fn test_invalid_sig() { + let msg = vec![0, 0, 0, 1]; + let msg2 = vec![0, 0, 0, 2]; + let seed = [0u8; 32]; + let mut rng = ChaCha20Rng::from_seed(seed); + + let sk = SchnorrSigningKey::generate(&mut rng); + let vk = SchnorrVerificationKey::from(&sk); + + let sk2 = SchnorrSigningKey::generate(&mut rng); + let vk2 = SchnorrVerificationKey::from(&sk2); + + let sig = sk.sign(&msg, &mut rng).unwrap(); + let sig2 = sk.sign(&msg2, &mut rng).unwrap(); + + // Wrong verification key is used + let result = sig.verify(&msg, &vk2); + assert!( + result.is_err(), + "Wrong verfication key used, test should fail." + ); + + // Wrong message is verified + let result = sig2.verify(&msg, &vk); + assert!(result.is_err(), "Wrong message used, test should fail."); + } +} diff --git a/mithril-stm/src/schnorr_signature/signature.rs b/mithril-stm/src/schnorr_signature/signature.rs new file mode 100644 index 00000000000..deb437c7f20 --- /dev/null +++ b/mithril-stm/src/schnorr_signature/signature.rs @@ -0,0 +1,88 @@ +use anyhow::{Result, anyhow}; +use midnight_circuits::hash::poseidon::PoseidonChip; +use midnight_circuits::instructions::HashToCurveCPU; +use midnight_circuits::instructions::hash::HashCPU; +use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, JubjubSubgroup}; + +use group::Group; + +use crate::{ + Index, + schnorr_signature::{ + DST_LOTTERY, DST_SIGNATURE, JubjubHashToCurve, get_coordinates, hash_msg_to_jubjubbase, + jubjub_base_to_scalar, verification_key::SchnorrVerificationKey, + }, +}; + +/// Structure of the Schnorr signature to use with the SNARK +/// This signature includes a value `sigma` which depends only on +/// the message and the signing key. +/// This value is used in the lottery process to determine the correct indices. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct SchnorrSignature { + pub(crate) sigma: JubjubSubgroup, + pub(crate) s: JubjubScalar, + pub(crate) c: JubjubBase, +} + +impl SchnorrSignature { + pub(crate) fn verify(&self, msg: &[u8], vk: &SchnorrVerificationKey) -> Result<()> { + let g = JubjubSubgroup::generator(); + + // First hashing the message to a scalar then hashing it to a curve point + let hash = JubjubHashToCurve::hash_to_curve(&[hash_msg_to_jubjubbase(msg)?]); + + // Computing R1 = H(msg) * s + sigma * c + let c_scalar = jubjub_base_to_scalar(&self.c)?; + let h_s = hash * self.s; + let sigma_c = self.sigma * c_scalar; + let r1_tilde = h_s + sigma_c; + + // Computing R2 = g * s + vk * c + let g_s = g * self.s; + let vk_c = vk.0 * c_scalar; + let r2_tilde = g_s + vk_c; + + let (hashx, hashy) = get_coordinates(hash); + let (vkx, vky) = get_coordinates(vk.0); + let (sigmax, sigmay) = get_coordinates(self.sigma); + let (r1x, r1y) = get_coordinates(r1_tilde); + let (r2x, r2y) = get_coordinates(r2_tilde); + + let c_tilde = PoseidonChip::::hash(&[ + DST_SIGNATURE, + hashx, + hashy, + vkx, + vky, + sigmax, + sigmay, + r1x, + r1y, + r2x, + r2y, + ]); + + if c_tilde != self.c { + // TODO: Wrong error for now, need to change that once the errors are added + return Err(anyhow!("Signature failed to verify.")); + } + + Ok(()) + } + + /// Dense mapping function indexed by the index to be evaluated + /// adapted to the Schnorr signature. + /// We need to convert the inputs to fit in a Poseidon hash. + /// The order of the hash input must be the same as the one in the SNARK circuit + /// `ev = H(DST || msg || index || σ) <- MSP.Eval(msg,index,σ)` given in paper. + fn evaluate_dense_mapping(&self, msg: &[u8], index: Index) -> Result<[u8; 32]> { + let msg = JubjubHashToCurve::hash_to_curve(&[hash_msg_to_jubjubbase(msg)?]); + let (msgx, msgy) = get_coordinates(msg); + // TODO: Check if this is the correct way to add the index + let idx = JubjubBase::from_raw([0, 0, 0, index]); + let (sigmax, sigmay) = get_coordinates(self.sigma); + let ev = PoseidonChip::::hash(&[DST_LOTTERY, msgx, msgy, idx, sigmax, sigmay]); + Ok(ev.to_bytes_le()) + } +} diff --git a/mithril-stm/src/schnorr_signature/signing_key.rs b/mithril-stm/src/schnorr_signature/signing_key.rs new file mode 100644 index 00000000000..ca03e57c955 --- /dev/null +++ b/mithril-stm/src/schnorr_signature/signing_key.rs @@ -0,0 +1,154 @@ +use anyhow::{Result, anyhow}; +use ff::Field; +use midnight_circuits::hash::poseidon::PoseidonChip; +use midnight_circuits::instructions::hash::HashCPU; +use midnight_curves::{Fq as JubjubBase, Fr as JubjubScalar, JubjubSubgroup}; +use rand_core::{CryptoRng, RngCore}; + +use midnight_circuits::instructions::HashToCurveCPU; + +use group::Group; + +use crate::schnorr_signature::{ + DST_SIGNATURE, JubjubHashToCurve, get_coordinates, hash_msg_to_jubjubbase, + jubjub_base_to_scalar, +}; +use crate::schnorr_signature::{ + signature::SchnorrSignature, verification_key::SchnorrVerificationKey, +}; + +/// Schnorr Signing key, it is essentially a random scalar of the Jubjub scalar field +#[derive(Debug, Clone)] +pub(crate) struct SchnorrSigningKey(pub(crate) JubjubScalar); + +impl SchnorrSigningKey { + pub(crate) fn generate(rng: &mut (impl RngCore + CryptoRng)) -> Self { + SchnorrSigningKey(JubjubScalar::random(rng)) + } + + // TODO: Check if we want the sign function to handle the randomness by itself + pub(crate) fn sign( + &self, + msg: &[u8], + rng: &mut (impl RngCore + CryptoRng), + ) -> Result { + // Use the subgroup generator to compute the curve points + let g = JubjubSubgroup::generator(); + let vk = SchnorrVerificationKey::from(self); + + // First hashing the message to a scalar then hashing it to a curve point + let hash = JubjubHashToCurve::hash_to_curve(&[hash_msg_to_jubjubbase(msg)?]); + + // sigma = H(msg) * sk + let sigma = hash * self.0; + + // Compute the random part of the signature with + // r1 = H(msg) * r + // r2 = g * r + let r = JubjubScalar::random(rng); + let r1 = hash * r; + let r2 = g * r; + + // Since the hash function takes as input scalar elements + // We need to convert the EC points to their coordinates + // I use gx and gy for now but maybe we can replace them by a DST? + let (hashx, hashy) = get_coordinates(hash); + let (vkx, vky) = get_coordinates(vk.0); + let (sigmax, sigmay) = get_coordinates(sigma); + let (r1x, r1y) = get_coordinates(r1); + let (r2x, r2y) = get_coordinates(r2); + + let c = PoseidonChip::::hash(&[ + DST_SIGNATURE, + hashx, + hashy, + vkx, + vky, + sigmax, + sigmay, + r1x, + r1y, + r2x, + r2y, + ]); + + // We want to use the from_raw function because the result of + // the poseidon hash might not fit into the smaller modulus + // the Fr scalar field + // TODO: Refactor this + let c_scalar = jubjub_base_to_scalar(&c)?; + let s = r - c_scalar * self.0; + + Ok(SchnorrSignature { sigma, s, c }) + } + + fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } + + /// Convert a string of bytes into a `SchnorrSigningKey`. + /// The bytes must represent a Jubjub scalar or the conversion will fail + // TODO: Maybe rework this function, do we want to allow any bytes representation + // to be convertible to a sk? + fn from_bytes(bytes: &[u8]) -> Result { + // This is a bit ugly, I'll try to find a better way to do it + let bytes = bytes + .get(..32) + .ok_or(anyhow!("Not enough bytes to create a signing key."))? + .try_into()?; + // Jubjub returs a CtChoice so I convert it to an option that looses the const time property + match JubjubScalar::from_bytes(bytes).into_option() { + Some(sk) => Ok(Self(sk)), + // the error should be updated + None => Err(anyhow!( + "Failed to create a Jubjub scalar from the given bytes." + )), + } + } +} + +// +#[cfg(test)] +mod tests { + use super::*; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + impl PartialEq for SchnorrSigningKey { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } + } + + #[test] + fn test_generate_signing_key() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let _sk = SchnorrSigningKey::generate(&mut rng); + } + + #[test] + fn test_to_from_bytes() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk = SchnorrSigningKey::generate(&mut rng); + let bytes = sk.to_bytes(); + let recovered_sk = SchnorrSigningKey::from_bytes(&bytes).unwrap(); + assert_eq!(sk, recovered_sk); + } + + // For now failing test, maybe change it later depending on what + // we want for from_bytes + #[test] + fn failing_test_from_bytes() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let mut sk = [0; 32]; + rng.fill_bytes(&mut sk); + // Setting the msb to 1 to make sk bigger than the modulus + sk[0] |= 0xff; + let result = SchnorrSigningKey::from_bytes(&sk); + + assert!( + result.is_err(), + "Value is not a proper sk, test should fail." + ); + } +} diff --git a/mithril-stm/src/schnorr_signature/verification_key.rs b/mithril-stm/src/schnorr_signature/verification_key.rs new file mode 100644 index 00000000000..911e503c5b7 --- /dev/null +++ b/mithril-stm/src/schnorr_signature/verification_key.rs @@ -0,0 +1,33 @@ +use group::Group; +pub use midnight_curves::JubjubSubgroup; + +use crate::schnorr_signature::signing_key::SchnorrSigningKey; + +/// Schnorr verification key, it consists of a point on the Jubjub curve +/// vk = g * sk, where g is a generator +#[derive(Debug, Clone, Copy, Default)] +pub struct SchnorrVerificationKey(pub(crate) JubjubSubgroup); + +impl From<&SchnorrSigningKey> for SchnorrVerificationKey { + /// Convert a Shnorr secret key into a verification key + /// This is done by computing `vk = g * sk` where g is the generator + /// of the subgroup and sk is the schnorr secret key + fn from(sk: &SchnorrSigningKey) -> Self { + let g = JubjubSubgroup::generator(); + SchnorrVerificationKey(g * sk.0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + #[test] + fn test_generate_signing_key() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let sk = SchnorrSigningKey::generate(&mut rng); + let _vk = SchnorrVerificationKey::from(&sk); + } +}