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

[WIP] Int - sign-and-magnitude #694

Closed
wants to merge 20 commits into from

Conversation

erik-3milabs
Copy link

What is this?
This PR provides basic signed integer support into crypto_bigint. This is a not-yet-implemented feature (see #1).
This basic implementation involves the following operations:

  • checked_add,
  • checked_sub,
  • checked_mul, and
  • checked_div

Related
Discussion started in #624.

About
This PR puts the first code on the board. The decision was made to implement Int as sign-and-magnitude (over, say, two's complement) since it is easier to scale to flexible-size implementations and, in my opinion, less error-prone. Moreover, multiplications yield hardly any overhead compared to operating on Uints; most of the overhead is incurred in the addition operation.

Open question:
It was mentioned in #624 to use Choice to represent the sign. Sadly, this struct does not seem to have a const constructor. If this is indeed the case, we will be unable to make the constants Int::ZERO, ::MINUS_ONE, ::ONE, ::MIN and ::MAX unless we resolve this.

Area for improvement:
The current addition/subtraction operations involve

  • one Uint-addition,
  • one Uint-subtraction,
  • three CtOption::ct_selects,
  • one Uint::eq call, and
  • one Uint::ct_gt call.

As such, this operation is ~7x more expensive than a Uint addition/subtraction. There is at least one clear area for improvement here: add an is_zero flag to the struct. Doing so will get allow us to get rid of the ct_eq call.
In my understanding, converting to and from two's complement will not be an option; the overhead in doing most likely exceeds the 6x performance penalty.

Any feedback is greatly appreciated!

@erik-3milabs erik-3milabs marked this pull request as draft October 14, 2024 15:58
@erik-3milabs erik-3milabs changed the title Basic Int Support [WIP] Basic Int Support Oct 14, 2024
@erik-3milabs erik-3milabs marked this pull request as ready for review October 14, 2024 16:22
@ycscaly
Copy link
Contributor

ycscaly commented Oct 14, 2024

@erik-3milabs crypto-bigint has their own const_choice, which is what I believe @tarcieri meant for you to use.

@lleoha
Copy link

lleoha commented Oct 14, 2024

The decision was made to implement Int as sign-and-magnitude

My impression was that in discussion it was suggested to rather use U2 not sign + absolute value. Constant operations would be easier in that representation and IMHO it's more suitable for fixed sized integers.
With that you can even make Int to just be a newtype of Uint if for some reason this is desired (provided you have accessor for the MSB).


impl<const LIMBS: usize> Int<LIMBS> {
/// Add two [`Int`]s, checking for overflow.
fn checked_adc(&self, rhs: &Self) -> CtOption<Self> {
Copy link

@lleoha lleoha Oct 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is unnecessary complex.
it's much easier to do it that way:

  • positive and negative never overflows
  • two positives (or two negatives) overflow if MSB of the result is different that MSB of operands (the result and operands of magnitude I mean)
    no need for min/max, sub etc.

use crate::int::Int;
use crate::{CheckedDiv, Uint};

impl<const LIMBS: usize> CheckedDiv for Int<LIMBS> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the zero has two representation, I think we should handle this as well, i.e "-0"/whatever should be "-0" not "+0".


impl<const LIMBS: usize, const RHS_LIMBS: usize> CheckedMul<Int<RHS_LIMBS>> for Int<LIMBS> {
#[inline]
fn checked_mul(&self, rhs: &Int<RHS_LIMBS>) -> CtOption<Self> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as for div, since there are two zero representation the sign should be preserved.
i.e.

  • -0 * -0 = +0
  • +0 * +0 = +0
  • -0 * +0 = -0
  • +0 * -0 = -0

use crate::int::Int;
use crate::WrappingNeg;

impl<const LIMBS: usize> Int<LIMBS> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, there are two representation of zero so sign should behave correctly i.e.:
-(-0) = +0
-(+0) = -0

Comment on lines +17 to +18
// TODO(erik): replace with Choice once I've figured how to const-construct those.
is_negative: bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bool anywhere in constant-time code is worrisome because it's an invitation for e.g. LLVM's optimizer to insert branches based on the value, even in cases where the original code deliberately avoids them

@lleoha
Copy link

lleoha commented Oct 14, 2024

There is at least one clear area for improvement here: add an is_zero

I am strongly against that for the following reasons. Currently zero has two representations (-0 and +0) adding is_zero will make it from 2 to ~2^(limbs * limb_bits).


#[derive(Copy, Clone, Hash, Debug, PartialEq)]
pub struct Int<const LIMBS: usize> {
// TODO(erik): replace with Choice once I've figured how to const-construct those.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no problem here, worst case scenario, just use u8, CtChoice is more or less that.

@tarcieri
Copy link
Member

The decision was made to implement Int as sign-and-magnitude (over, say, two's complement) since it is easier to scale to flexible-size implementations

I'd like to reiterate my previous comments that the heap-allocated Boxed* types are generally a much lower priority than the const-generic stack-allocated types.

The Boxed* types largely exist to support RSA and DSA, which are algorithms where it might be necessary to support a very high cardinality of potential key sizes.

Pretty much all other algorithms use a very small, fixed number of key sizes.


#[cfg(target_pointer_width = "64")]
#[allow(dead_code)]
type I128 = Int<2>;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is where it's tricky, if we want to relate this to builtin i128 type, this is actually closer to I129 because of using sign and magnitude instead of two's complements.

@erik-3milabs
Copy link
Author

@tarcieri @lleoha thank you for your feedback! It became clear that sign-and-magnitude is not as great a solution as I thought it to be. To investigate whether two's complement might be a better approach, I took some time to whip up a quick implementation; you can find it as an open PR at #695. I would greatly appreciate your feedback!

@erik-3milabs erik-3milabs changed the title [WIP] Basic Int Support [WIP] Int - sign-and-magnitude Oct 16, 2024
@lleoha
Copy link

lleoha commented Oct 16, 2024

U2 looks much better than sign magnitude and is more consistent.
I'll take a look at it tomorrow evening, and if you willing to accept help, I would be happy to help. I did implement it that before (I even have this implemented for this repo for my internal purposes but on in the state I want to publish it as of yet).

@lleoha
Copy link

lleoha commented Oct 16, 2024

@erik-3milabs forgot to mention you in my response :).

@erik-3milabs
Copy link
Author

@lleoha I'm definitely open to collab on this! Given that there is still enough to code, let's discuss with @tarcieri how we're going to structure this PR-wise; ideally, we don't make reviewing it a nightmare.

@erik-3milabs
Copy link
Author

Closing for now, due to "overwhelming" support for U2 #695.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants