diff --git a/Cargo.lock b/Cargo.lock index c20e26f..5d82b6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,6 +25,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -70,6 +79,26 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bytemuck" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -116,6 +145,16 @@ dependencies = [ "libc", ] +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -367,9 +406,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -402,9 +441,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -468,7 +507,10 @@ name = "rsa" version = "0.9.6" dependencies = [ "base64ct", + "bytemuck", + "cfg-if", "const-oid", + "crypto-bigint", "digest", "hex-literal", "num-bigint-dig", @@ -487,6 +529,7 @@ dependencies = [ "sha2", "sha3", "signature", + "sp1-lib", "spki", "subtle", "zeroize", @@ -539,18 +582,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.192" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -614,6 +657,15 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +[[package]] +name = "sp1-lib" +version = "3.0.0" +source = "git+https://github.com/succinctlabs/sp1.git?branch=dev#0c08cafd4adeb0d246b21411d8422611b3861337" +dependencies = [ + "bincode", + "serde", +] + [[package]] name = "spin" version = "0.5.2" @@ -638,9 +690,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.39" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e4b2265..c427ce7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,14 @@ pkcs8 = { version = "0.10.2", default-features = false, features = ["alloc"] } signature = { version = ">2.0, <2.3", default-features = false , features = ["alloc", "digest", "rand_core"] } spki = { version = "0.7.3", default-features = false, features = ["alloc"] } zeroize = { version = "1.5", features = ["alloc"] } +crypto-bigint = "0.5.5" +cfg-if = "1.0.0" +bytemuck = { version = "1.16.1", features = ["derive"] } + +[target.'cfg(all(target_os = "zkvm", target_vendor = "succinct"))'.dependencies] +sp1-lib = { git = "https://github.com/succinctlabs/sp1.git", branch = "dev" } + + # optional dependencies sha1 = { version = "0.10.5", optional = true, default-features = false, features = ["oid"] } diff --git a/src/algorithms/rsa.rs b/src/algorithms/rsa.rs index 3510152..bb4d242 100644 --- a/src/algorithms/rsa.rs +++ b/src/algorithms/rsa.rs @@ -7,6 +7,11 @@ use num_integer::{sqrt, Integer}; use num_traits::{FromPrimitive, One, Pow, Signed, Zero}; use rand_core::CryptoRngCore; use zeroize::{Zeroize, Zeroizing}; +use bytemuck::cast_ref; +#[cfg(all(target_os = "zkvm", target_vendor = "succinct"))] +use sp1_lib::io::hint_slice; +use crypto_bigint::{Integer as CryptoInteger, NonZero, Encoding, U2048, U256, U4096}; +use core::convert::TryInto; use crate::errors::{Error, Result}; use crate::traits::{PrivateKeyParts, PublicKeyParts}; @@ -19,9 +24,125 @@ use crate::traits::{PrivateKeyParts, PublicKeyParts}; /// or signature scheme. See the [module-level documentation][crate::hazmat] for more information. #[inline] pub fn rsa_encrypt(key: &K, m: &BigUint) -> Result { + // Ok(m.modpow(key.e(), key.n())) + cfg_if::cfg_if! { + if #[cfg(all(target_os = "zkvm", target_vendor = "succinct"))] { + let m_u2048 = from_biguint_to_u2048(m); + let e_u2048 = from_biguint_to_u2048(key.e()); + let n_u2048 = from_biguint_to_u2048(key.n()); + return Ok(custom_modpow_u2048(&m_u2048, &e_u2048, &n_u2048)); + } + } Ok(m.modpow(key.e(), key.n())) } +cfg_if::cfg_if! { + if #[cfg(all(target_os = "zkvm", target_vendor = "succinct"))] { + /// Performs modular exponentiation of `base` to the power of `exp` modulo `modulus`. + /// This function takes in U2048 operands and returns the result as a BigUint. + fn custom_modpow_u2048(base: &U2048, exp: &U2048, modulus: &U2048) -> BigUint { + if *modulus == U2048::ONE { + return BigUint::zero(); + } + + let mut result = U2048::ONE; + let modulus_nonzero = NonZero::new(*modulus).unwrap(); // Convert modulus to NonZero + let mut base = base.rem(&modulus_nonzero); + + + let mut exp = *exp; + while exp > U2048::ZERO { + if exp.is_odd().into() { + result = mul_mod_u2048(&result, &base, &modulus_nonzero); + } + exp = exp.shr(1); + base = mul_mod_u2048(&base, &base, &modulus_nonzero); + } + + let result_biguint = BigUint::from_bytes_le(&result.to_le_bytes()); + result_biguint + + } + + + /// Performs modular multiplication of `a` and `b` with `modulus`. + /// It calculates the quotient and remainder in unconstrained. + fn mul_mod_u2048(a: &U2048, b: &U2048, modulus: &U2048) -> U2048 { + let prod = mul_u2048(*a, *b); + sp1_lib::unconstrained! { + let modulus_u4096 = U4096::from(modulus); + let modulus_u4096_nonzero = NonZero::new(modulus_u4096).unwrap(); // Convert modulus to NonZero + let (quotient, result) = prod.div_rem(&modulus_u4096_nonzero); + let result_bytes = result.to_le_bytes(); + let quotient_bytes = quotient.to_le_bytes(); + + hint_slice(&result_bytes); + hint_slice("ient_bytes[..256]); + } + + let result_bytes: [u8; 512] = sp1_lib::io::read_vec().try_into().unwrap(); + let quotient_bytes: [u8; 256] = sp1_lib::io::read_vec().try_into().unwrap(); + + let q_array = U2048::from_le_slice("ient_bytes); + let result_u4096 = U4096::from_le_slice(&result_bytes); + let result_u2048 = U2048::from_le_slice(&result_bytes[..256]); + + assert!(prod.wrapping_sub(&mul_u2048(q_array, *modulus)).wrapping_sub(&result_u4096) == U4096::ZERO); + result_u2048 + } + + + + /// Performs multiplication of `a` and `b`, which are both U2048, + /// and returns a U4096. + fn mul_u2048(a_array: U2048, b_array: U2048) -> U4096 { + let mut sum = U4096::ZERO; + let a_words = a_array.to_words(); + + for i in 0..8 { + let chunk = a_words[i*8..(i+1)*8].try_into().unwrap(); + let a_chunk: U256 = U256::from_words(chunk); + let mut prod = mul_array(a_chunk, b_array); + let mut shifted_words = [0u32; 128]; + shifted_words[i*8..].copy_from_slice(&prod.to_words()[..(128 - 8*i)]); + let shifted_prod = U4096::from_words(shifted_words); + sum = sum.wrapping_add(&shifted_prod); + } + + sum + } + + /// Performs multiplication of `a` a U256 and `b` which is a U2048. + fn mul_array(a: U256, b_array: U2048) -> U4096 { + let mut result_words = [0u32; 128]; + let result_ptr = result_words.as_mut_ptr(); + unsafe { + sp1_lib::syscall_u256x2048_mul( + cast_ref(&a.to_words()), + cast_ref(&b_array.to_words()), + result_ptr as *mut [u32; 64], + result_ptr.add(64) as *mut [u32; 8], + ); + } + + U4096::from_words(result_words) + } + + /// Converts a BigUint to a U2048. + fn from_biguint_to_u2048(value: &BigUint) -> U2048 { + let mut padded_bytes = [0u8; 256]; + let a_bytes = value.to_bytes_le(); + for (i, &byte) in a_bytes.iter().enumerate() { + if i >= 256 { break; } + padded_bytes[i] = byte; + } + + U2048::from_le_slice(&padded_bytes) + } + } +} + + /// ⚠️ Performs raw RSA decryption with no padding or error checking. /// /// Returns a plaintext `BigUint`. Performs RSA blinding if an `Rng` is passed.