-
Notifications
You must be signed in to change notification settings - Fork 82
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
Function to increment pointer by one word in small fields. #1933
Conversation
It is most likely too complex for witgen to figure out going backwards.
@@ -0,0 +1,38 @@ | |||
/// Increments a pointer by one full 32-bit word. |
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.
Both this comment and the name of the function: it returns a constraint that forces a difference of one, it does not really increment it. This might be minor and it might also be more complicated to describe, but I was really wondering if this is a prover function that computes a witness, or if it adds/creates a constraint.
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.
On one hand, I agree this is a bi-directional set of constraints, on the other hand, I doubt I can use this function to decrement instead of increment, as it would be too hard for witgen to solve.
Do you have a suggestion for a better name?
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 fixed the comment.
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 ran the Gröbner Basis, and going on the opposite direction from post_low = 0
, I get that I can either get pre_low = 0xfffc
with overflow (the correct answer) or pre_low = -4
with no overflow (makes sense, but is forbidden by the invisible pre-condition that pre_low
is in range [0, 0xfffc]).
// Increment the address in every row but the ones it is set. | ||
std::array::map( | ||
word_increment_ptr(high_addr, low_addr, high_addr', low_addr'), | ||
|c| std::constraints::make_conditional(c, (1 - is_addr_set')) |
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.
Now I regret putting the condition as second parameter. Maybe we can change that in a future PR.
(because it doesn't fit the "if_else")
@@ -0,0 +1,35 @@ | |||
/// Creates constraints that increments a pointer by one full 32-bit word. |
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.
/// Creates constraints that increments a pointer by one full 32-bit word. | |
/// Creates constraints that increments a pointer. |
(you always increment by the abstract "one" which doesn't have a size, only the pointer has a 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.
Oh wait, it increments them by the size of a word?
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 increment by 4.
Could be an argument.
let word_increment_ptr: expr, expr, expr, expr -> Constr[] = constr |pre_high, pre_low, post_high, post_low| { | ||
// How far away from overflowing the low limb is: | ||
let low_diff = pre_low - (0x10000 - 4); | ||
// Is one if low limb is about to overflow: |
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 still don't understand what "about to overflow" means and what the two constraints in lines 20 and 24 do exactly.
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.
"about to overflow" in a sense that in the next step, the low limb will be set to zero and the carry will be added to the high limb.
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.
About lines 20;24-26, it can be seen as an is_not_zero
function (could be extracted, if useful somewhere else):
let is_not_zero = constr |value| {
// Assumes the inverse of "value" if it is not zero, otherwise undefined.
let inverse;
// Ensures that inverse * value is either 0 or 1:
(inverse * value - 1) * value = 0;
inverse * value
};
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 placed this is_not_zero in utils, and changed is_zero implementation to use it (because it is simpler). Now I am running the tests to see if it will break anything, since it returns a degree 2 expression.
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.
Renamed low_overflow
to carry
, so its role is more recognizable.
// Helper to allow low_overflow to be boolean | ||
let low_diff_inv; | ||
|
||
low_overflow = 1 - low_diff_inv * low_diff; |
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 constraints here ensure that low_overflow
is 1
if and only if low_diff
is zero, otherwise it is 0
, right? Should we also extract that into a helper function? Then we can use it for XIsZero
.
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.
Yes. Just found utils::is_zero()
. Will fix.
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.
Done.
std/machines/hash/poseidon_bb.asm
Outdated
) = 0; | ||
// Set high limb, incremented if low overflowed: | ||
(1 - is_addr_set') * (high_addr' - high_addr - low_overflow) = 0; | ||
// Increment the address in every row but the ones it is set. |
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.
pol commit low_addr[16];
pol commit high_addr[16];
std::utils::fold(16, |i| {
let constr_array = word_increment_ptr(high_addr[i], low_addr[i], high_addr[i]', low_addr[i]');
std::array::map(constr_array, |c| std::constraints::make_conditional(c, used))
}, [], |acc, constr_arr| acc + constr_arr);
It's nicely working like this so that we can increment address without overflow
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.
Looks a bit off. Didn't you mean:
let constr_array = word_increment_ptr(high_addr[i], low_addr[i], high_addr[i + 1], low_addr[i + 1]);
?
Co-authored-by: chriseth <[email protected]>
Co-authored-by: chriseth <[email protected]>
Has conflicts. |
Fixed. |
@@ -0,0 +1,38 @@ | |||
/// Creates constraints that increments a pointer by amount. | |||
/// |
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.
Should we document that post_high
does not wrap around?
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.
OK, it's actually obivous from the constraints in the docstring below.
/// - pre_low must be in range [0, 0xffff]. | ||
/// | ||
/// The pointers are given in 2 16-bit limbs. This function returns a set of constraints | ||
/// ensuring that the following are equal: |
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.
Should we also say something about the range of the post limbs?
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.
Should we? I don't think it is needed.
I think the description as 2 16-bit limbs makes it clear.
I'll add a precondition that the arithmetic should not overflow u32::MAX.
let inverse; | ||
|
||
// Ensures that "inverse * x" is either 0 or 1: | ||
(inverse * x - 1) * x = 0; |
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.
Is this a degree-3 constraint? Is that fine for our backends?
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 degree is 3 only if x is degree 1.
The final degree is, of course, 2*deg(x) + 1.
IIRC, 3 is the target degree we support. It works on plonky3.
Also, it seems very wrong that the user has to worry about this. It feels that the breakup of compatible polynomials should be a compilation step, possibly dependent on the constraints of the backend.
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.
Create an issue about this, but I admit the thoughts in there are convoluted and needs refining: #1971
Co-authored-by: chriseth <[email protected]>
Co-authored-by: chriseth <[email protected]>
| { | ||
// Is the low limb at its maximum value? | ||
let carry; | ||
carry = std::utils::is_zero(pre_low - (0x10000 - amount)); |
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.
Cool!
I would have done
force_bool(carry);
pre_low + amount = carry * 2**16 + post_low;
post_high = pre_high + carry;
Needs one less column (no need to store the inverse created in is_zero
), but would assume post_low
and post_high
to be range-constrained! Adding two range constraints would typically be more expensive, so I like this solution more.
Extracted from poseidon_bb, but can be used in poseidon2_bb, sha256bb, keccak16...