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

feat: modexp precompile 384 bits #1513

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

tekkac
Copy link

@tekkac tekkac commented Oct 19, 2024

Time spent on this PR:

Pull request type

Please check the type of change your PR introduces:

  • Bugfix
  • Feature
  • Code style update (formatting, renaming)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • Documentation content changes
  • Other (please describe):

What is the current behavior?

Running 8 test(s) from src/
[PASS] evm::precompiles::modexp::tests::test_modexp_berlin_empty_input (gas: ~133)
[PASS] evm::precompiles::modexp::tests::test_modexp_eip198_example_1 (gas: ~635)
[PASS] evm::precompiles::modexp::tests::test_modexp_circuit (gas: ~544)
[PASS] evm::precompiles::modexp::tests::test_modexp_edge_case_circuit (gas: ~9)
[PASS] evm::precompiles::modexp::tests::test_modexp_eip198_example_2 (gas: ~312)
[PASS] evm::precompiles::modexp::tests::test_modexp_precompile_input_output_worst (gas: ~953)
[PASS] evm::precompiles::modexp::tests::test_modexp_circuit_worst_case (gas: ~953)
[PASS] evm::precompiles::modexp::tests::test_modexp_precompile_input_output_all_sizes (gas: ~22497)

Resolves #1496

What is the new behavior?

  • modexp precompiled implemented with circuit up to 384 bits (48 bytes)
  • passes basic tests and some EIP test vectors
  • Custom input output needed because of non-standard integer types from circuits.

This change is Reviewable

Copy link
Member

Choose a reason for hiding this comment

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

why are some tests commented?

Copy link
Author

Choose a reason for hiding this comment

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

Those tests won't pass as the scope of the issue is to impl modexp for up to 384 bits.
I kept the tests code for reference, let me know how best to handle it.

@@ -88,18 +87,158 @@ pub impl ModExp of Precompile {
let gas = calc_gas(base_len.into(), exp_len.into(), mod_len.into(), exp_highp);

// Padding is needed if the input does not contain all 3 values.
let base = input.slice_right_padded(0, base_len);
let exponent = input.slice_right_padded(base_len, exp_len);

let (mod_start_idx, _) = base_len.overflowing_add(exp_len);
Copy link
Collaborator

Choose a reason for hiding this comment

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

note (for us): this should probably not be overflowing_add here. if base_len + exp_len > u32 there's something wrong in the input (which will lead to an OOG prior to that anyway)

Copy link
Collaborator

@enitrat enitrat left a comment

Choose a reason for hiding this comment

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

great :)

cairo/kakarot-ssj/crates/evm/src/precompiles/modexp.cairo Outdated Show resolved Hide resolved
Comment on lines +202 to +240
let mut s_limb0: u128 = Into::<_, felt252>::into(s.limb0).try_into().unwrap();
while s_limb0 != 0 {
let (q, r) = core::traits::DivRem::div_rem(s_limb0, 2);
bits.append(r.into());
s_limb0 = q;
};
let mut s_limb1: u128 = Into::<_, felt252>::into(s.limb1).try_into().unwrap();
if s_limb1 != 0 {
while bits.len() != 96 {
bits.append(0);
}
}
while s_limb1 != 0 {
let (q, r) = core::traits::DivRem::div_rem(s_limb1, 2);
bits.append(r.into());
s_limb1 = q;
};
let mut s_limb2: u128 = Into::<_, felt252>::into(s.limb2).try_into().unwrap();
if s_limb2 != 0 {
while bits.len() != 192 {
bits.append(0);
}
}
while s_limb2 != 0 {
let (q, r) = core::traits::DivRem::div_rem(s_limb2, 2);
bits.append(r.into());
s_limb2 = q;
};
let mut s_limb3: u128 = Into::<_, felt252>::into(s.limb3).try_into().unwrap();
if s_limb3 != 0 {
while bits.len() != 288 {
bits.append(0);
}
}
while s_limb3 != 0 {
let (q, r) = core::traits::DivRem::div_rem(s_limb3, 2);
bits.append(r.into());
s_limb3 = q;
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should use https://github.com/tekkac/kakarot/blob/be550c5812338e8fa727be51efc480b1b6b403e4/cairo/kakarot-ssj/crates/utils/src/traits/integer.cairo#L153-L167

There's an generic impl BitsUsedImpl. Perhaps it can be used directly; otherwise, we can implement the missing traits for the limb type (u96)

Copy link
Author

Choose a reason for hiding this comment

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

I can try again, the main issue is that only TryInto felt252 is impl'd for u96.

Copy link
Author

Choose a reason for hiding this comment

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

Tried again without success, libfuncs are missing for u96

Comment on lines +129 to 145
impl U8SpanTryIntoU384 of TryInto<Span<u8>, u384> {
fn try_into(self: Span<u8>) -> Option<u384> {
if self.len() != 48 {
return Option::None;
}
let limb3_128: u128 = self.slice(0, 12).pad_left_with_zeroes(16).from_be_bytes().unwrap();
let limb2_128: u128 = self.slice(12, 12).pad_left_with_zeroes(16).from_be_bytes().unwrap();
let limb1_128: u128 = self.slice(24, 12).pad_left_with_zeroes(16).from_be_bytes().unwrap();
let limb0_128: u128 = self.slice(36, 12).pad_left_with_zeroes(16).from_be_bytes().unwrap();
let limb0: u96 = Into::<_, felt252>::into(limb0_128).try_into().unwrap();
let limb1: u96 = Into::<_, felt252>::into(limb1_128).try_into().unwrap();
let limb2: u96 = Into::<_, felt252>::into(limb2_128).try_into().unwrap();
let limb3: u96 = Into::<_, felt252>::into(limb3_128).try_into().unwrap();
Option::Some(u384 { limb0, limb1, limb2, limb3 })
}
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Result::Ok((gas.into(), return_data))
// Compute the modular exponentiation
// x^y mod n
pub fn modexp_circuit(x: u384, y: u384, n: u384) -> u384 {
Copy link
Collaborator

@obatirou obatirou Oct 22, 2024

Choose a reason for hiding this comment

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

Please explain the algorithm in high level + add resources to get up to speed in docstring.
For example (and from my understanding of your implementation) this one: Repeated square-and-multiply algorithm for exponentiation
from Handbook of Applied Cryptography - Alfred J. Menezes, Paul C. van Oorschot and Scott A. Vanstone (Algo 2.143)
Feel free to put the resource you used.
Explain the implementation details where necessary for example the exponent is taken in little endian to process bits from LSB to MSB
This algo/implementation is not trivial and other devs (or future auditors) need context.

Copy link
Author

Choose a reason for hiding this comment

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

Let me know if the docstring is correct and sufficiently explicit

Copy link
Collaborator

@obatirou obatirou Oct 25, 2024

Choose a reason for hiding this comment

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

Doesn't it miss the check Assert :: (modulus - 1) * (modulus - 1) does not overflow base in the implementation ?

Copy link
Author

Choose a reason for hiding this comment

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

It's not needed in our case since the circuit already does all the operations with modular arithmetic.

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.

dev: modexp
4 participants