Skip to content

Commit b273aeb

Browse files
feat(sol): sign_gadget
1 parent 64ec1a7 commit b273aeb

File tree

4 files changed

+319
-0
lines changed

4 files changed

+319
-0
lines changed

solidity/src/base/Constants.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ pragma solidity ^0.8.28;
44

55
/// @dev The modulus of the bn254 scalar field.
66
uint256 constant MODULUS = 0x30644e72_e131a029_b85045b6_8181585d_2833e848_79b97091_43e1f593_f0000001;
7+
/// @dev The max number of bits a sign expr supports. This is the floor of log base 2 of the max value of bn254.
8+
uint8 constant MAX_BITS = 253;
79
/// @dev The largest mask that can be applied to a 256-bit number in order to enforce that it is less than the modulus.
810
uint256 constant MODULUS_MASK = 0x1FFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF;
911
/// @dev MODULUS + 1. Needs to be explicit for Yul usage.

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 {
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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/SwitchUtil.pre.sol
50+
function case_const(lhs, rhs) {
51+
revert(0, 0)
52+
}
53+
// IMPORT-YUL ../builder/VerificationBuilder.pre.sol
54+
function builder_consume_final_round_mle(builder_ptr) -> value {
55+
revert(0, 0)
56+
}
57+
// IMPORT-YUL ../base/Array.pre.sol
58+
function get_array_element(arr_ptr, index) -> value {
59+
revert(0, 0)
60+
}
61+
// IMPORT-YUL ../builder/VerificationBuilder.pre.sol
62+
function builder_consume_bit_distribution(builder_ptr) -> vary_mask, leading_bit_mask {
63+
revert(0, 0)
64+
}
65+
// IMPORT-YUL ../builder/VerificationBuilder.pre.sol
66+
function builder_produce_identity_constraint(builder_ptr, evaluation, degree) {
67+
revert(0, 0)
68+
}
69+
// IMPORT-YUL ../base/DataType.pre.sol
70+
function read_entry(result_ptr, data_type_variant) -> result_ptr_out, entry {
71+
revert(0, 0)
72+
}
73+
// IMPORT-YUL ../base/DataType.pre.sol
74+
function read_binary(result_ptr) -> result_ptr_out, entry {
75+
revert(0, 0)
76+
}
77+
// IMPORT-YUL ../base/DataType.pre.sol
78+
function read_data_type(ptr) -> ptr_out, data_type {
79+
revert(0, 0)
80+
}
81+
82+
function sign_expr_evaluate(expr_eval, builder_ptr, chi_eval) -> result_eval {
83+
let vary_mask
84+
let leading_bit_mask
85+
vary_mask, leading_bit_mask := builder_consume_bit_distribution(builder_ptr)
86+
let leading_bit_inverse_mask := shr(1, shl(1, xor(not(vary_mask), leading_bit_mask)))
87+
let sign_eval := shr(255, leading_bit_mask)
88+
let rhs_eval := 0
89+
90+
for { let i := 0 } lt(i, 256) { i := add(i, 1) } {
91+
if eq(and(vary_mask, shl(i, 1)), shl(i, 1)) {
92+
// For any varying bits...
93+
let bit_eval := builder_consume_final_round_mle(builder_ptr)
94+
builder_produce_identity_constraint(
95+
builder_ptr,
96+
addmod(
97+
bit_eval,
98+
mulmod(MODULUS_MINUS_ONE, mulmod(bit_eval, bit_eval, MODULUS), MODULUS),
99+
MODULUS
100+
),
101+
2
102+
)
103+
104+
if eq(i, 255) { sign_eval := bit_eval }
105+
if iszero(eq(i, 255)) {
106+
if gt(i, MAX_BITS) { err(ERR_INVALID_VARYING_BITS) }
107+
rhs_eval := addmod(rhs_eval, mulmod(bit_eval, shl(i, 1), MODULUS), MODULUS)
108+
}
109+
}
110+
}
111+
rhs_eval := addmod(rhs_eval, mulmod(sign_eval, leading_bit_mask, MODULUS), MODULUS)
112+
rhs_eval :=
113+
addmod(
114+
rhs_eval,
115+
mulmod(
116+
addmod(chi_eval, mulmod(MODULUS_MINUS_ONE, sign_eval, MODULUS), MODULUS),
117+
leading_bit_inverse_mask,
118+
MODULUS
119+
),
120+
MODULUS
121+
)
122+
rhs_eval :=
123+
addmod(rhs_eval, mulmod(mulmod(MODULUS_MINUS_ONE, chi_eval, MODULUS), shl(255, 1), MODULUS), MODULUS)
124+
if iszero(eq(rhs_eval, expr_eval)) { err(ERR_BIT_DECOMPOSITION_INVALID) }
125+
result_eval := sign_eval
126+
}
127+
128+
__eval := sign_expr_evaluate(__exprEval, __builder, __chiEval)
129+
}
130+
__builderOut = __builder;
131+
}
132+
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
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 {Test} from "forge-std/Test.sol";
6+
import "../../src/base/Constants.sol";
7+
import {Errors} from "../../src/base/Errors.sol";
8+
import {VerificationBuilder} from "../../src/builder/VerificationBuilder.pre.sol";
9+
import {SignExpr} from "../../src/proof_gadgets/SignExpr.pre.sol";
10+
import {F} from "../base/FieldUtil.sol";
11+
12+
contract SignExprTest is Test {
13+
function testSimpleAndExpr() public pure {
14+
VerificationBuilder.Builder memory builder;
15+
uint256[] memory bitDistribution = new uint256[](2);
16+
bitDistribution[0] = 0x800000000000000000000000000000000000000000000000000000000000007D;
17+
bitDistribution[1] = 0x8000000000000000000000000000000000000000000000000000000000000002;
18+
VerificationBuilder.__setBitDistributions(builder, bitDistribution);
19+
builder.maxDegree = 3;
20+
builder.constraintMultipliers = new uint256[](7);
21+
builder.constraintMultipliers[0] = 5;
22+
builder.constraintMultipliers[1] = 5;
23+
builder.constraintMultipliers[2] = 5;
24+
builder.constraintMultipliers[3] = 5;
25+
builder.constraintMultipliers[4] = 5;
26+
builder.constraintMultipliers[5] = 5;
27+
builder.constraintMultipliers[6] = 5;
28+
builder.aggregateEvaluation = 0;
29+
builder.rowMultipliersEvaluation = addmod(MODULUS, mulmod(MODULUS_MINUS_ONE, 2, MODULUS), MODULUS);
30+
31+
int64[4] memory evaluationVector = [int64(700), -6, 3007, 134562844];
32+
33+
int64[4][10] memory vectorsToEvaluate = [
34+
[int64(106), 23, -60, -76],
35+
[int64(1), 1, 1, 1],
36+
[int64(0), 1, 0, 0],
37+
[int64(0), 1, 1, 1],
38+
[int64(1), 0, 0, 0],
39+
[int64(0), 1, 0, 1],
40+
[int64(1), 0, 0, 1],
41+
[int64(1), 0, 1, 0],
42+
[int64(1), 1, 0, 0],
43+
[int64(1), 1, 0, 0]
44+
];
45+
46+
uint256[] memory evaluations = new uint256[](10);
47+
48+
for (uint8 i = 0; i < 10; ++i) {
49+
int64 evaluation = 0;
50+
for (uint8 j = 0; j < 4; ++j) {
51+
evaluation += evaluationVector[j] * vectorsToEvaluate[i][j];
52+
}
53+
evaluations[i] = F.from(evaluation).into();
54+
}
55+
56+
uint256[] memory finalRoundMles = new uint256[](7);
57+
for (uint8 i = 2; i < 9; ++i) {
58+
finalRoundMles[i - 2] = evaluations[i];
59+
}
60+
61+
VerificationBuilder.__setFinalRoundMLEs(builder, finalRoundMles);
62+
63+
uint256 signEval;
64+
(builder, signEval) = SignExpr.__signExprEvaluate(evaluations[0], builder, evaluations[1]);
65+
assert(signEval == evaluations[9]);
66+
}
67+
68+
/// forge-config: default.allow_internal_expect_revert = true
69+
function testIncorrectBitDecomposition() public {
70+
VerificationBuilder.Builder memory builder;
71+
uint256[] memory bitDistribution = new uint256[](2);
72+
bitDistribution[0] = 0x800000000000000000000000000000000000000000000000000000000000007D;
73+
bitDistribution[1] = 0x8000000000000000000000000000000000000000000000000000000000000002;
74+
VerificationBuilder.__setBitDistributions(builder, bitDistribution);
75+
builder.maxDegree = 3;
76+
builder.constraintMultipliers = new uint256[](7);
77+
builder.constraintMultipliers[0] = 5;
78+
builder.constraintMultipliers[1] = 5;
79+
builder.constraintMultipliers[2] = 5;
80+
builder.constraintMultipliers[3] = 5;
81+
builder.constraintMultipliers[4] = 5;
82+
builder.constraintMultipliers[5] = 5;
83+
builder.constraintMultipliers[6] = 5;
84+
builder.aggregateEvaluation = 0;
85+
builder.rowMultipliersEvaluation = addmod(MODULUS, mulmod(MODULUS_MINUS_ONE, 2, MODULUS), MODULUS);
86+
87+
int64[4] memory evaluationVector = [int64(700), -6, 3007, 134562844];
88+
89+
int64[4][10] memory vectorsToEvaluate = [
90+
[int64(106), 23, -60, -76],
91+
[int64(1), 1, 1, 1],
92+
[int64(0), 2, 0, 0],
93+
[int64(0), 1, 1, 1],
94+
[int64(1), 0, 0, 0],
95+
[int64(0), 1, 0, 1],
96+
[int64(1), 0, 0, 1],
97+
[int64(1), 0, 1, 0],
98+
[int64(1), 1, 0, 0],
99+
[int64(1), 1, 0, 0]
100+
];
101+
102+
uint256[] memory evaluations = new uint256[](10);
103+
104+
for (uint8 i = 0; i < 10; ++i) {
105+
int64 evaluation = 0;
106+
for (uint8 j = 0; j < 4; ++j) {
107+
evaluation += evaluationVector[j] * vectorsToEvaluate[i][j];
108+
}
109+
evaluations[i] = F.from(evaluation).into();
110+
}
111+
112+
uint256[] memory finalRoundMles = new uint256[](7);
113+
for (uint8 i = 2; i < 9; ++i) {
114+
finalRoundMles[i - 2] = evaluations[i];
115+
}
116+
117+
VerificationBuilder.__setFinalRoundMLEs(builder, finalRoundMles);
118+
119+
vm.expectRevert(Errors.BitDecompositionInvalid.selector);
120+
SignExpr.__signExprEvaluate(evaluations[0], builder, evaluations[1]);
121+
}
122+
123+
/// forge-config: default.allow_internal_expect_revert = true
124+
function testBitDecompositionWithInvalidBits() public {
125+
VerificationBuilder.Builder memory builder;
126+
uint256[] memory bitDistribution = new uint256[](2);
127+
bitDistribution[0] = 0xFF00000000000000000000000000000000000000000000000000000000000000;
128+
bitDistribution[1] = 0x8000000000000000000000000000000000000000000000000000000000000002;
129+
VerificationBuilder.__setBitDistributions(builder, bitDistribution);
130+
builder.maxDegree = 3;
131+
builder.constraintMultipliers = new uint256[](7);
132+
builder.constraintMultipliers[0] = 5;
133+
builder.constraintMultipliers[1] = 5;
134+
builder.constraintMultipliers[2] = 5;
135+
builder.constraintMultipliers[3] = 5;
136+
builder.constraintMultipliers[4] = 5;
137+
builder.constraintMultipliers[5] = 5;
138+
builder.constraintMultipliers[6] = 5;
139+
builder.aggregateEvaluation = 0;
140+
builder.rowMultipliersEvaluation = addmod(MODULUS, mulmod(MODULUS_MINUS_ONE, 2, MODULUS), MODULUS);
141+
142+
int64[4] memory evaluationVector = [int64(700), -6, 3007, 134562844];
143+
144+
int64[4][10] memory vectorsToEvaluate = [
145+
[int64(106), 23, -60, -76],
146+
[int64(1), 1, 1, 1],
147+
[int64(0), 2, 0, 0],
148+
[int64(0), 1, 1, 1],
149+
[int64(1), 0, 0, 0],
150+
[int64(0), 1, 0, 1],
151+
[int64(1), 0, 0, 1],
152+
[int64(1), 0, 1, 0],
153+
[int64(1), 1, 0, 0],
154+
[int64(1), 1, 0, 0]
155+
];
156+
157+
uint256[] memory evaluations = new uint256[](10);
158+
159+
for (uint8 i = 0; i < 10; ++i) {
160+
int64 evaluation = 0;
161+
for (uint8 j = 0; j < 4; ++j) {
162+
evaluation += evaluationVector[j] * vectorsToEvaluate[i][j];
163+
}
164+
evaluations[i] = F.from(evaluation).into();
165+
}
166+
167+
uint256[] memory finalRoundMles = new uint256[](7);
168+
for (uint8 i = 2; i < 9; ++i) {
169+
finalRoundMles[i - 2] = evaluations[i];
170+
}
171+
172+
VerificationBuilder.__setFinalRoundMLEs(builder, finalRoundMles);
173+
174+
vm.expectRevert(Errors.InvalidVaryingBits.selector);
175+
SignExpr.__signExprEvaluate(evaluations[0], builder, evaluations[1]);
176+
}
177+
}

0 commit comments

Comments
 (0)