-
Notifications
You must be signed in to change notification settings - Fork 157
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
feat: switch from num-bigint-dig to crypto-bigint #394
base: master
Are you sure you want to change the base?
Conversation
CI is finally green again, time for lots of review and cleanup |
@zeerooth |
src/algorithms/generate.rs
Outdated
let mut pi = prime_limit / (prime_limit.ln() - 1f64); | ||
#[cfg(not(feature = "std"))] | ||
let mut pi = prime_limit / (libm::logf(prime_limit as f32) as f64 - 1f64); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not entirely correct or optimal, but I am not sure what other way there is
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fjarri is there anything in crypto-primes
for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, nothing like that currently
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dignifiedquire so if I understand correctly, the purpose of this calculation is ultimately for a check that keygen terminates in a reasonable amount of time?
@dignifiedquire seems like you can remove "Very, very WIP" now |
🤣 yeah, finally |
@dignifiedquire how it's going ? |
He's waiting on review, which I'll try to do soon |
digest = { version = "=0.11.0-pre.9", default-features = false, features = ["alloc", "oid"] } | ||
pkcs1 = { version = "0.8.0-rc.0", default-features = false, features = ["alloc", "pkcs8"] } | ||
pkcs8 = { version = "0.11.0-rc.0", default-features = false, features = ["alloc"] } | ||
signature = { version = "=2.3.0-pre.4", default-features = false, features = ["alloc", "digest", "rand_core"] } | ||
spki = { version = "0.8.0-rc.1", default-features = false, features = ["alloc"] } | ||
zeroize = { version = "1.5", features = ["alloc"] } | ||
crypto-bigint = { version = "0.6.0-rc.6", default-features = false, features = ["zeroize", "alloc"] } | ||
crypto-primes = { version = "0.6.0-pre.2", default-features = false } | ||
libm = "0.2" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO it would be nice to avoid this, especially if its only used for no_std
prime counting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If all you need from there is logf(x)
, you can probably just approximate it with x.bits() / log2(e)
, pre-calculating log2(e)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or even take log2(e) == 1
if the approximation allows it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bits()
seems to not be around either..
README.md
Outdated
@@ -81,7 +81,7 @@ You can follow our work on mitigating this issue in [#390]. | |||
|
|||
## Minimum Supported Rust Version (MSRV) | |||
|
|||
This crate supports Rust 1.72 or higher. | |||
This crate supports Rust 1.73 or higher. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is crypto-bigint
's latest MSRV:
This crate supports Rust 1.73 or higher. | |
This crate supports Rust 1.83 or higher. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed all around
@@ -222,7 +223,6 @@ extern crate alloc; | |||
#[cfg(feature = "std")] | |||
extern crate std; | |||
|
|||
pub use num_bigint::BigUint; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps consider a BoxedUint
re-export here, possibly using a shorter name:
pub use crypto_bigint::BoxedUint as Uint;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added
// TODO: reenable, currently slow | ||
// key_generation!(key_generation_multi_16_1024, 16, 1024); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😢
src/algorithms/rsa.rs
Outdated
let dp = priv_key.dp().expect("precomputed"); | ||
let dq = priv_key.dq().expect("precomputed"); | ||
let qinv = priv_key.qinv().expect("precomputed"); | ||
let p_params = priv_key.p_params().expect("precomputed"); | ||
let q_params = priv_key.q_params().expect("precomputed"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The previous pattern-based approach seemed a little more elegant than this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
let two = NonZero::new(BoxedUint::from(2u64)) | ||
.expect("2 is non zero") | ||
.widen(bits); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something which makes this pattern better would be nice. We have various NonZero::new_unwrap
constructors which allow you to infallibly construct a NonZero
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
278 | let two = NonZero::new_unwrap(2).widen(bits);
| ^^^^^^^^^^ multiple `new_unwrap` found
|
= note: candidate #1 is defined in an impl for the type `crypto_bigint::NonZero<Limb>`
= note: candidate #2 is defined in an impl for the type `crypto_bigint::NonZero<crypto_bigint::Uint<LIMBS>>`
seems to not exist for theBoxedUint
version
src/algorithms/generate.rs
Outdated
@@ -93,14 +95,14 @@ pub(crate) fn generate_multi_prime_key_with_exp<R: CryptoRngCore + ?Sized>( | |||
|
|||
let n = compute_modulus(&primes); | |||
|
|||
if n.bits() != bit_size { | |||
if n.bits() as usize != bit_size { | |||
// This should never happen for nprimes == 2 because | |||
// gen_prime should set the top two bits in each prime. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment should be updated for the new name of the function.
Also I don't quite understand "gen_prime should set the top two bits in each prime" - what was that behavior for? generate_prime_with_rng()
only sets one top bit, to ensure that the returned primes are of the requested bit size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was a bit curious about that myself. I saw @FiloSottile mention the same thing in this post:
https://words.filippo.io/dispatches/rsa-keygen-bench/
There is almost nothing special to selecting prime candidates. You draw an appropriately sized random number from a CSPRNG, and to avoid wasting time, you set the least significant bit and the two most significant bits: large even numbers are not prime, and setting the top two guarantees N won’t come out too small.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you set only the top bit of a n/2 bits number, you get a value >= 2^(n/2-1). If you multiply two of them, you get a value >= 2^(n-2), which is at a minimum n-1 bits long, one too short for a n-bit modulus.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see, that makes sense. Do you think it should be the default behavior in crypto-primes
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you mainly need the primes for RSA, yes, you are only wasting CPU rejecting small moduli after you found the primes. If you're using the primes for something else, it might depend on what they are for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fjarri are you planning on changing the behaviour?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about it, and I don't think it should be the default - too specialized. It can be achieved with custom sieves, but I need to make a release first
src/encoding.rs
Outdated
let n = BigUint::from_bytes_be(pkcs1_key.modulus.as_bytes()); | ||
let e = BigUint::from_bytes_be(pkcs1_key.public_exponent.as_bytes()); | ||
let d = BigUint::from_bytes_be(pkcs1_key.private_exponent.as_bytes()); | ||
let prime1 = BigUint::from_bytes_be(pkcs1_key.prime1.as_bytes()); | ||
let prime2 = BigUint::from_bytes_be(pkcs1_key.prime2.as_bytes()); | ||
let key_malformed = pkcs8::Error::KeyMalformed; | ||
|
||
let bits = | ||
u32::try_from(pkcs1_key.modulus.as_bytes().len()).map_err(|_| key_malformed)? * 8; | ||
|
||
let n = BoxedUint::from_be_slice(pkcs1_key.modulus.as_bytes(), bits) | ||
.map_err(|_| key_malformed)?; | ||
let n = Option::from(Odd::new(n)).ok_or(key_malformed)?; | ||
|
||
let bits_e = u32::try_from(pkcs1_key.public_exponent.as_bytes().len()) | ||
.map_err(|_| key_malformed)? | ||
* 8; | ||
let e = BoxedUint::from_be_slice(pkcs1_key.public_exponent.as_bytes(), bits_e) | ||
.map_err(|_| key_malformed)?; | ||
let e = Option::from(e).ok_or(key_malformed)?; | ||
|
||
let d = BoxedUint::from_be_slice(pkcs1_key.private_exponent.as_bytes(), bits) | ||
.map_err(|_| key_malformed)?; | ||
|
||
let prime1 = BoxedUint::from_be_slice(pkcs1_key.prime1.as_bytes(), bits) | ||
.map_err(|_| key_malformed)?; | ||
let prime2 = BoxedUint::from_be_slice(pkcs1_key.prime2.as_bytes(), bits) | ||
.map_err(|_| key_malformed)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I quote understand why these conversions got more convoluted but perhaps there are upstream APIs which could be added to assist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving this to a method would clean it up a bunch. Call out to it and map_err from the call
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cleaned up a bit
Very, very WIPNot anymore, this is ready for review.
Replaces all usage of
num-bigint-dig
basedBigInt
usage with the newcrypto-bigint
crate, usingBoxedUint
Current known issue is that we do have a performance regression, which will be able to get rid of over time:
TODOs
RsaPrivateKey
RsaPublicKey
decrypt
implementationBigUint
to return owned versions