Skip to content

Commit e088a27

Browse files
feat(sol): sign gadget (#860)
Please be sure to look over the pull request guidelines here: https://github.com/spaceandtimelabs/sxt-proof-of-sql/blob/main/CONTRIBUTING.md#submit-pr. # Please go through the following checklist - [ ] The PR title and commit messages adhere to guidelines here: https://github.com/spaceandtimelabs/sxt-proof-of-sql/blob/main/CONTRIBUTING.md. In particular `!` is used if and only if at least one breaking change has been introduced. - [ ] I have run the ci check script with `source scripts/run_ci_checks.sh`. - [ ] I have run the clean commit check script with `source scripts/check_commits.sh`, and the commit history is certified to follow clean commit guidelines as described here: https://github.com/spaceandtimelabs/sxt-proof-of-sql/blob/main/COMMIT_GUIDELINES.md - [ ] The latest changes from `main` have been incorporated to this PR by simple rebase if possible, if not, then conflicts are resolved appropriately. # Rationale for this change We need sign_expr for group by # What changes are included in this PR? New sign_expr in solidity. # Are these changes tested? Yes
1 parent c792c7a commit e088a27

File tree

9 files changed

+487
-10
lines changed

9 files changed

+487
-10
lines changed

solidity/src/base/Constants.sol

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ pragma solidity ^0.8.28;
66
uint256 constant MODULUS = 0x30644e72_e131a029_b85045b6_8181585d_2833e848_79b97091_43e1f593_f0000001;
77
/// @dev The largest mask that can be applied to a 256-bit number in order to enforce that it is less than the modulus.
88
uint256 constant MODULUS_MASK = 0x1FFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF;
9+
/// @dev A mask that can be applied to a bit distributions vary mask to see if it is valid, given the modulus.
10+
uint256 constant MODULUS_INVALID_VARY_MASK = 0x60000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000;
911
/// @dev MODULUS + 1. Needs to be explicit for Yul usage.
1012
uint256 constant MODULUS_PLUS_ONE = 0x30644e72_e131a029_b85045b6_8181585d_2833e848_79b97091_43e1f593_f0000002;
1113
/// @dev MODULUS - 1. Needs to be explicit for Yul usage.
@@ -174,7 +176,7 @@ uint256 constant VK_TAU_HY_REAL = 0x2bad9a374aec49d329ec66e8f530f68509313450580c
174176
uint256 constant VK_TAU_HY_IMAG = 0x219edfceee1723de674f5b2f6fdb69d9e32dd53b15844956a630d3c7cdaa6ed9;
175177

176178
/// @dev Size of the verification builder in bytes.
177-
uint256 constant VERIFICATION_BUILDER_SIZE = 0x20 * 14;
179+
uint256 constant VERIFICATION_BUILDER_SIZE = 0x20 * 15;
178180
/// @dev Offset of the pointer to the challenge queue in the verification builder.
179181
uint256 constant BUILDER_CHALLENGES_OFFSET = 0x20 * 0;
180182
/// @dev Offset of the pointer to the first round MLEs in the verification builder.
@@ -203,6 +205,8 @@ uint256 constant BUILDER_FIRST_ROUND_COMMITMENTS_OFFSET = 0x20 * 11;
203205
uint256 constant BUILDER_FINAL_ROUND_COMMITMENTS_OFFSET = 0x20 * 12;
204206
/// @dev Offset of the singleton chi evaluation in the verification builder.
205207
uint256 constant BUILDER_SINGLETON_CHI_EVALUATION_OFFSET = 0x20 * 13;
208+
/// @dev Offset of the pointer to the final round bit distributions in the verification builder.
209+
uint256 constant BUILDER_FINAL_ROUND_BIT_DISTRIBUTIONS_OFFSET = 0x20 * 14;
206210

207211
/// @dev The initial transcript state. This is the hash of the empty string.
208212
uint256 constant INITIAL_TRANSCRIPT_STATE = 0x7c26f909f37b2c61df0bb3b19f76296469cb4d07b582a215c4e2b1f7a05527c3;

solidity/src/base/Errors.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ uint32 constant ERR_UNSUPPORTED_PROOF_PLAN_VARIANT = 0xe5503cfa;
5555
uint32 constant ERR_UNSUPPORTED_DATA_TYPE_VARIANT = 0xbd12560e;
5656
/// @dev Error code for when the evaluation length is too large.
5757
uint32 constant ERR_EVALUATION_LENGTH_TOO_LARGE = 0xb65e7142;
58+
/// @dev Error code for when a bit decomposition is invalid.
59+
uint32 constant ERR_BIT_DECOMPOSITION_INVALID = 0xda443b2b;
60+
/// @dev Error code for when bits that shouldn't vary do vary.
61+
uint32 constant ERR_INVALID_VARYING_BITS = 0x76d56a3d;
5862

5963
library Errors {
6064
/// @notice Error thrown when the inputs to the ECADD precompile are invalid.
@@ -110,6 +114,10 @@ library Errors {
110114
error UnsupportedDataTypeVariant();
111115
/// @notice Error thrown when the evaluation length is too large.
112116
error EvaluationLengthTooLarge();
117+
/// @notice Error thrown when a bit decomposition is invalid.
118+
error BitDecompositionInvalid();
119+
/// @notice Error thrown when bits that shouldn't vary do vary.
120+
error InvalidVaryingBits();
113121

114122
function __err(uint32 __code) internal pure {
115123
assembly {

solidity/src/base/Queue.pre.sol

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,47 @@ library Queue {
4242
__value := dequeue(__queue)
4343
}
4444
}
45+
46+
/// @notice Dequeues two uint256 values from the front of the queue
47+
/// @custom:as-yul-wrapper
48+
/// #### Wrapped Yul Function
49+
/// ##### Signature
50+
/// ```yul
51+
/// function dequeue_uint512(queue_ptr) -> upper, lower
52+
/// ```
53+
/// ##### Parameters
54+
/// * `queue_ptr` - pointer to the array in memory. In Solidity memory layout,
55+
/// this points to where the array length is stored, followed by the array elements
56+
/// ##### Return Values
57+
/// * `upper` - the first word from the dequeued value from the front of the queue
58+
/// * `lower` - the second word from the dequeued value from the front of the queue
59+
/// @dev Removes and returns the first element from the queue.
60+
/// Reverts with Errors.EmptyQueue if the queue is empty.
61+
/// @param __queuePtr Single-element array containing the queue array
62+
/// @return __upper The first word from the dequeued value from the front of the queue
63+
/// @return __lower The second word from the dequeued value from the front of the queue
64+
function __dequeueUint512(uint256[][1] memory __queuePtr)
65+
internal
66+
pure
67+
returns (uint256 __upper, uint256 __lower)
68+
{
69+
assembly {
70+
// IMPORT-YUL Errors.sol
71+
function err(code) {
72+
revert(0, 0)
73+
}
74+
function dequeue_uint512(queue_ptr) -> upper, lower {
75+
let queue := mload(queue_ptr)
76+
let length := mload(queue)
77+
if iszero(length) { err(ERR_EMPTY_QUEUE) }
78+
queue := add(queue, WORD_SIZE)
79+
upper := mload(queue)
80+
queue := add(queue, WORD_SIZE)
81+
lower := mload(queue)
82+
mstore(queue, sub(length, 1))
83+
mstore(queue_ptr, queue)
84+
}
85+
__upper, __lower := dequeue_uint512(__queuePtr)
86+
}
87+
}
4588
}

solidity/src/builder/VerificationBuilder.pre.sol

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -778,12 +778,50 @@ library VerificationBuilder {
778778
revert(0, 0)
779779
}
780780
function builder_set_bit_distributions(builder_ptr, values_ptr) {
781-
if mload(values_ptr) { err(ERR_UNSUPPORTED_PROOF) }
781+
mstore(add(builder_ptr, BUILDER_FINAL_ROUND_BIT_DISTRIBUTIONS_OFFSET), values_ptr)
782782
}
783783
builder_set_bit_distributions(__builder, __values)
784784
}
785785
}
786786

787+
/// @notice Consumes a final round bit distribution from the verification builder
788+
/// @custom:as-yul-wrapper
789+
/// #### Wrapped Yul Function
790+
/// ##### Signature
791+
/// ```yul
792+
/// builder_consume_bit_distribution(builder_ptr) -> vary_mask, leading_bit_mask
793+
/// ```
794+
/// ##### Parameters
795+
/// * `builder_ptr` - memory pointer to the builder struct region
796+
/// ##### Return Values
797+
/// * `vary_mask` - the vary mask of the bit distribution
798+
/// * `leading_bit_mask` - the leading bit mask of the bit distribution
799+
/// @dev Dequeues and returns a final round bit distribution. Reverts with Errors.EmptyQueue if no values remain
800+
/// @param __builder The builder struct
801+
/// @return __varyMask the vary mask of the consumed bit distribution
802+
/// @return __leadingBitMask the leading bit mask of the consumed bit distribution
803+
function __consumeBitDistribution(Builder memory __builder)
804+
internal
805+
pure
806+
returns (uint256 __varyMask, uint256 __leadingBitMask)
807+
{
808+
assembly {
809+
// IMPORT-YUL ../base/Errors.sol
810+
function err(code) {
811+
revert(0, 0)
812+
}
813+
// IMPORT-YUL ../base/Queue.pre.sol
814+
function dequeue_uint512(queue_ptr) -> upper, lower {
815+
revert(0, 0)
816+
}
817+
function builder_consume_bit_distribution(builder_ptr) -> vary_mask, leading_bit_mask {
818+
let values_ptr := add(builder_ptr, BUILDER_FINAL_ROUND_BIT_DISTRIBUTIONS_OFFSET)
819+
vary_mask, leading_bit_mask := dequeue_uint512(values_ptr)
820+
}
821+
__varyMask, __leadingBitMask := builder_consume_bit_distribution(__builder)
822+
}
823+
}
824+
787825
/// @notice Gets the chi column evaluations array from the verification builder
788826
/// @custom:as-yul-wrapper
789827
/// #### Wrapped Yul Function
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
// This is licensed under the Cryptographic Open Software License 1.0
3+
pragma solidity ^0.8.28;
4+
5+
import "../base/Constants.sol";
6+
import "../base/Errors.sol";
7+
import {VerificationBuilder} from "../builder/VerificationBuilder.pre.sol";
8+
9+
/// @title SignExpr
10+
/// @dev Library for handling the sign of an evaluation
11+
library SignExpr {
12+
/// @notice Evaluates a sign expression by finding the sign of an expression evaluation
13+
/// @custom:as-yul-wrapper
14+
/// #### Wrapped Yul Function
15+
/// ##### Signature
16+
/// ```yul
17+
/// sign_expr_evaluate(expr_eval, builder_ptr, chi_eval) -> eval
18+
/// ```
19+
/// ##### Parameters
20+
/// * `expr_eval` - the expression evaluation
21+
/// * `builder_ptr` - memory pointer to the verification builder
22+
/// * `chi_eval` - the chi value for evaluation
23+
/// ##### Return Values
24+
/// * `eval` - the evaluation result from the builder's final round MLE (or bit distribution)
25+
/// @notice Evaluates the sign of an expression
26+
/// ##### Proof Plan Encoding
27+
/// The sign expression is encoded as follows:
28+
/// The expression
29+
/// @param __exprEval The expression evaluation
30+
/// @param __builder The verification builder
31+
/// @param __chiEval The chi value for evaluation
32+
/// @return __builderOut The verification builder result
33+
/// @return __eval The evaluated result
34+
function __signExprEvaluate( // solhint-disable-line gas-calldata-parameters
35+
uint256 __exprEval, VerificationBuilder.Builder memory __builder, uint256 __chiEval)
36+
internal
37+
pure
38+
returns (VerificationBuilder.Builder memory __builderOut, uint256 __eval)
39+
{
40+
assembly {
41+
// IMPORT-YUL ../base/Errors.sol
42+
function err(code) {
43+
revert(0, 0)
44+
}
45+
// IMPORT-YUL ../base/Queue.pre.sol
46+
function dequeue(queue_ptr) -> value {
47+
revert(0, 0)
48+
}
49+
// IMPORT-YUL ../base/Queue.pre.sol
50+
function dequeue_uint512(queue_ptr) -> value {
51+
revert(0, 0)
52+
}
53+
// IMPORT-YUL ../base/MathUtil.sol
54+
function addmod_bn254(lhs, rhs) -> sum {
55+
revert(0, 0)
56+
}
57+
// IMPORT-YUL ../base/MathUtil.sol
58+
function submod_bn254(lhs, rhs) -> difference {
59+
revert(0, 0)
60+
}
61+
// IMPORT-YUL ../base/MathUtil.sol
62+
function mulmod_bn254(lhs, rhs) -> product {
63+
revert(0, 0)
64+
}
65+
// IMPORT-YUL ../base/SwitchUtil.pre.sol
66+
function case_const(lhs, rhs) {
67+
revert(0, 0)
68+
}
69+
// IMPORT-YUL ../builder/VerificationBuilder.pre.sol
70+
function builder_consume_final_round_mle(builder_ptr) -> value {
71+
revert(0, 0)
72+
}
73+
// IMPORT-YUL ../base/Array.pre.sol
74+
function get_array_element(arr_ptr, index) -> value {
75+
revert(0, 0)
76+
}
77+
// IMPORT-YUL ../builder/VerificationBuilder.pre.sol
78+
function builder_consume_bit_distribution(builder_ptr) -> vary_mask, leading_bit_mask {
79+
revert(0, 0)
80+
}
81+
// IMPORT-YUL ../builder/VerificationBuilder.pre.sol
82+
function builder_produce_identity_constraint(builder_ptr, evaluation, degree) {
83+
revert(0, 0)
84+
}
85+
// IMPORT-YUL ../base/DataType.pre.sol
86+
function read_entry(result_ptr, data_type_variant) -> result_ptr_out, entry {
87+
revert(0, 0)
88+
}
89+
// IMPORT-YUL ../base/DataType.pre.sol
90+
function read_binary(result_ptr) -> result_ptr_out, entry {
91+
revert(0, 0)
92+
}
93+
// IMPORT-YUL ../base/DataType.pre.sol
94+
function read_data_type(ptr) -> ptr_out, data_type {
95+
revert(0, 0)
96+
}
97+
98+
function sign_expr_evaluate(expr_eval, builder_ptr, chi_eval) -> result_eval {
99+
let vary_mask
100+
let leading_bit_mask
101+
vary_mask, leading_bit_mask := builder_consume_bit_distribution(builder_ptr)
102+
103+
// Other than the lead bit, no bit should vary past some max bit position, depending on the field
104+
if and(vary_mask, MODULUS_INVALID_VARY_MASK) { err(ERR_INVALID_VARYING_BITS) }
105+
106+
// The lead bit of the leading_bit_mask dictates the sign, if it's constant sign.
107+
// So this will be the value if sign is constant. Otherwise, it will be overwritten
108+
let sign_eval := mulmod_bn254(shr(255, leading_bit_mask), chi_eval)
109+
110+
// For future computations, leading_bit_mask should have a 1 in the lead bit
111+
leading_bit_mask := or(leading_bit_mask, shl(255, 1))
112+
113+
// leading_bit_inverse_mask identifies columns that match the inverse of the lead bit column
114+
// So !vary_mask ^ leading_bit_mask, with a lead bit of zero.
115+
let leading_bit_inverse_mask := shr(1, shl(1, xor(not(vary_mask), leading_bit_mask)))
116+
117+
// sum_eval should ultimately add up to the original column of data
118+
// It will effectively be a recomposition of the bit decomposition
119+
let sum_eval := 0
120+
121+
for { let i := 0 } vary_mask {
122+
i := add(i, 1)
123+
vary_mask := shr(1, vary_mask)
124+
} {
125+
if and(vary_mask, 1) {
126+
// For any varying bits...
127+
let bit_eval := builder_consume_final_round_mle(builder_ptr)
128+
129+
// Verify that every eval is a bit
130+
// bit_eval - bit_eval * bit_eval = 0
131+
builder_produce_identity_constraint(
132+
builder_ptr, submod_bn254(bit_eval, mulmod_bn254(bit_eval, bit_eval)), 2
133+
)
134+
135+
switch i
136+
// If the lead bit varies, that we get the sign from the mles.
137+
case 255 { sign_eval := bit_eval }
138+
// For varying non lead bits,
139+
// we add bit_eval * 2ⁱ to the sum in order to recompose the original value of the column
140+
default { sum_eval := addmod_bn254(sum_eval, mulmod_bn254(bit_eval, shl(i, 1))) }
141+
}
142+
}
143+
144+
result_eval := submod_bn254(chi_eval, sign_eval)
145+
146+
// For constant and lead bits...
147+
// sum += sign_eval * leading_bit_mask + (sign_eval - chi_eval) * leading_bit_inverse_mask - chi_eval * (1 << 255)
148+
sum_eval :=
149+
submod_bn254(
150+
addmod_bn254(
151+
addmod_bn254(sum_eval, mulmod_bn254(sign_eval, leading_bit_mask)),
152+
mulmod_bn254(result_eval, leading_bit_inverse_mask)
153+
),
154+
mulmod_bn254(chi_eval, shl(255, 1))
155+
)
156+
157+
// Verify the bit recomposition matches the original column evaluation
158+
if sub(sum_eval, expr_eval) { err(ERR_BIT_DECOMPOSITION_INVALID) }
159+
}
160+
161+
__eval := sign_expr_evaluate(__exprEval, __builder, __chiEval)
162+
}
163+
__builderOut = __builder;
164+
}
165+
}

solidity/test/base/Constants.t.sol

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ contract ConstantsTest is Test {
2121
assert(mask == 0);
2222
}
2323

24+
function testModulusMaskAndModulusInvalidVaryMaskAgree() public pure {
25+
assert(MODULUS_MASK & MODULUS_INVALID_VARY_MASK == 0);
26+
assert(MODULUS_MASK | MODULUS_INVALID_VARY_MASK == (1 << 255) - 1);
27+
}
28+
2429
function testModulusPlusAndMinusOneAreCorrect() public pure {
2530
assert(MODULUS_PLUS_ONE == MODULUS + 1);
2631
assert(MODULUS_MINUS_ONE == MODULUS - 1);
@@ -85,7 +90,7 @@ contract ConstantsTest is Test {
8590
}
8691

8792
function testVerificationBuilderOffsetsAreValid() public pure {
88-
uint256[14] memory offsets = [
93+
uint256[15] memory offsets = [
8994
BUILDER_CHALLENGES_OFFSET,
9095
BUILDER_FIRST_ROUND_MLES_OFFSET,
9196
BUILDER_FINAL_ROUND_MLES_OFFSET,
@@ -99,7 +104,8 @@ contract ConstantsTest is Test {
99104
BUILDER_TABLE_CHI_EVALUATIONS_OFFSET,
100105
BUILDER_FIRST_ROUND_COMMITMENTS_OFFSET,
101106
BUILDER_FINAL_ROUND_COMMITMENTS_OFFSET,
102-
BUILDER_SINGLETON_CHI_EVALUATION_OFFSET
107+
BUILDER_SINGLETON_CHI_EVALUATION_OFFSET,
108+
BUILDER_FINAL_ROUND_BIT_DISTRIBUTIONS_OFFSET
103109
];
104110
uint256 offsetsLength = offsets.length;
105111
assert(VERIFICATION_BUILDER_SIZE == offsetsLength * WORD_SIZE);

solidity/test/base/Queue.t.pre.sol

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ contract ErrorsTest is Test {
2727
Queue.__dequeue(queue);
2828
}
2929

30+
/// forge-config: default.allow_internal_expect_revert = true
31+
function testDequeueUint512() public pure {
32+
uint256[][1] memory queue = [new uint256[](2)];
33+
queue[0][0] = 1001;
34+
queue[0][1] = 1002;
35+
(uint256 upper, uint256 lower) = Queue.__dequeueUint512(queue);
36+
assert(upper == 1001);
37+
assert(lower == 1002);
38+
// This is a little hacky. Even though we lost two elements, the length only drops by one,
39+
// because the dequeue function is interpreting it as a 512 array
40+
assert(queue[0].length == 1);
41+
}
42+
3043
/// forge-config: default.allow_internal_expect_revert = true
3144
function testFuzzDequeue(uint256[][1] memory queue) public {
3245
uint256 length = queue[0].length;

0 commit comments

Comments
 (0)