Skip to content
This repository has been archived by the owner on May 18, 2023. It is now read-only.

feat: reference standard verifier (wip) #7

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions src/reference/standard/Bn254Crypto.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@

import {Types} from "./Types.sol";


/// TODO: Create aliases for the field size in the main?
library Bn254Crypto {
/// @notice the size of the prime field
uint256 constant p_mod = 21888242871839275222246405745257275088696311157297823662689037894645226208583;

/// @notice the order size of the group
uint256 constant r_mod = 21888242871839275222246405745257275088548364400416034343698204186575808495617;

/// @notice The point foes not exist on the G1 curve
error NotWellFormed(G1Point point);

/// @notice Perform a modular exponentiation.
/// @dev Ideal for small exponents. (64 bits or less), it is cheaper than the precompile
function pow_small(
uint256 base,
uint256 exponent,
uint256 modulus
) internal pure returns (uint256) {
uint256 result = 1;
uint256 input = base;
uint256 count = 1;

assembly {
let endpoint := add(exponent, 0x01)
for { } lt(count, endpoint) { } {
if and(exponent, count) {
result := mulmod(result, input, modulus)
}
input := mulmod(input, input, modulus)
}
}

return result;
}

/// @notice Invert a field element
/// TODO: double check wording around here
/// @dev Inverses are used in montgomery multiplication trick
/// @dev uses the precompile for inversion
function invert(uint256 fr) internal view returns (uint256) {
uint256 output;
bool success;
uint256 p = r_mod;
assembly {
let mPtr := mload(0x40)
mstore(mPtr, 0x20)
mstore(add(mPtr, 0x20), 0x20)
mstore(add(mPtr, 0x40), 0x20)
mstore(add(mPtr, 0x60), fr)
mstore(add(mPtr, 0x80), sub(p, 2))
mstore(add(mPtr, 0xa0), p)
success := staticcall(gas(), 0x05, mPtr, 0xc0, 0x00, 0x20)
output := mload(0x00)
}
require(success, "pow precompile call failed!");
return output;
}

/// @notice create a new G1Point
/// @dev This method also perform r_mod reduction
function new_g1(uint256 x, uint256 y)
internal
pure
returns (Types.G1Point memory)
{
uint256 xValue;
uint256 yValue;
assembly {
xValue := mod(x, r_mod)
yValue := mod(y, r_mod)
}
return Types.G1Point(xValue, yValue);
}

/// @notice create a new G2Point
function new_g2(
uint256 x0,
uint256 x1,
uint256 y0,
uint256 y1
) internal pure returns (Types.G2Point memory) {
return Types.G2Point(x0, x1, y0, y1);
}

/// @notice Get the generator of G1
function P1() internal pure returns (Types.G1Point memory) {
return new_g1(1, 2);
}

/// @notice Get the generator of G2
function P2() internal pure returns (Types.G2Point memory) {
return
Types.G2Point({
x0: 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2,
x1: 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed,
y0: 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b,
y1: 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa
});
}

/// @notice Evaluate the pairing product
/// @notice e(a1, a2).e(-b1, b2) == 1
/// @dev This loads eithersize of the points into memory and calls the ecPairing precompile
function pairingProd2(
Types.G1Point memory a1,
Types.G2Point memory a2,
Types.G1Point memory b1,
Types.G2Point memory b2
) internal view returns (bool) {
validateG1Point(a1);
validateG1Point(b1);
}


// TODO: maybe just to this in standard solidity rather than all asm
// Can do that on the second pass through!
// Maybe making small tests for each point
// x != 0
// y != 0
// x < p
// y < p
// y^2 = x^3 + 3 mod p
function validateG1Point(Types.G1Point memory point) internal pure {
bool is_well_formed;
uint256 p = p_mod;

assembly {
let x := mload(point)
let y := mload(add(point, 0x20))

is_well_formed := and (
and(and(lt(x, p), lt(y, p)), not(or(iszero(x), iszero(y)))),
eq(mulmod(y,y,p), addmod(mulmod(x, mulmod(x,x,p), p), 3, p))
)
}

if (!is_well_formed) {
revert NotWellFormed(point);
}
}
}
143 changes: 143 additions & 0 deletions src/reference/standard/ChallengeGenerators.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import {Types} from "./Types.sol";

library ChallengeGen {

struct ChallengeData {
bytes32 current_challenge;
}

/// @notice The initial challenge is a hash of the size of the circuit and the
/// number of inputs in the circuit - keep in mind that the final hash is of 64bits
function generate_initial_challenge(ChallegeData memory self, uint256 circuit_size, uint256 number_of_inputs) {
/// Squash into uint 32s
// TODO: ensure that these remain 32 bits when hashing
uint32 circuit_size_32 = uint32(circuit_size);
uint32 number_of_inputs_32 = uint32(number_of_inputs);
self.current_challenge = keccak(abi.encodePacked(circuit_size_32, number_of_inputs_32));
}

/// @notice The beta challenge is a hash of all of the public inputs, aswell as the first
/// curve points in our proof, W1, W2, and W3

/// In cases where the public inputs are extremely large (rollups), we may want to hash the public inputs beforehand and
/// provide a hash to the verifier instead.
function generate_beta_gamma_challenges(ChallengeData memory self, Types.Challenges memory challenges, uint256 num_public_inputs) {
bytes32 challenge;
bytes32 old_challenge = self.current_challenge;
uint256 p = Bn254Crypto.r_mod;

// slice all of the public inputs and the first 3 wires from calldata
// TODO: remove assembly
uint256 inputs_start;
uint256 num_calldata_bytes;

// Calculate β keccak(initial_challenge, public_inputs, W1, W2, W3)
assembly {
let ptr := mload(0x40)

mstore(ptr, old_challenge)

inputs_start := add(calldataload(0x04), 0x24)
// TODO: 0xc0 to a constant as W1 + W2 + W3?
num_calldata_bytes := add(0xc0, shl(num_public_inputs, 5))
calldatacopy(add(ptr, 0x20), inputs_start, num_calldata_bytes)

// TODO: why need to add 0x20 here?
challenge := keccak256(ptr, add(num_calldata_bytes, 0x20))
challenge := mod(challenge, p)
}
challenges.beta = challenge;

// Calc γ keccak(β, 0x01)
// γ is calcaulated by appending 1 to the β challenge, then hashing
assembly {
mstore(0x00, challenge)
mstore(0x20, 0x01)
challenge := keccak256(0x0, 0x21)
challenge := mod(challenge, p)
}
challenges.gamma = challenge;

// Store current challenge to be used to generate further challenges
self.current_challenge = challenge;
}

/// @notice The alpha challenege is generated by appending and hashing the
// grand_product_opening point (Z) with the previous challenge γ
// TODO: ^ double check name given above
function generate_alpha_challenge(ChallengeData memory self, Types.Challenges memory challenges, Types.G1Point memory Z) {
bytes32 prev_challenges = self.current_challenge;
bytes32 alpha = keccak256(abi.encodePacked(prev_challenges, Z.x, Z.y));
alpha = alpha % Bn254Crypto.r_mod;

challenges.alpha = alpha;
self.current_challenge = alpha;
}

/// @notice The zeta challenge is generated by hashing the previous challenge alpha
/// with the T1, T2, T3 points from our proof data (T_lo, T_mid, T_hi)
// TODO: elaborate on what points T1,T2,T3 actually make up
function generate_zeta_chellenge(ChallengeData memory self, Types.Challenges memory challenges, Types.G1Point memory T1, Types.G1Point memory T2, Types.G1Point memory T3 ) internal {
bytes32 prev_challenges = self.current_challenge;
bytes32 zeta = keccak256(abi.encodePacked(prev_challenges, T1.x, T1.y, T2.x, T2.y, T3.x, T3.y));
zeta = zeta % Bn254Crypto.r_mod;

challenges.zeta = zeta;
self.current_challenge = zeta;
}

/// @notice The Nu challenges are generated by hashing the parts of the transcript that we have not
/// introduced into our challenges yet.
/// Before we included the public inputs and W1, W2, W3 in the β and γ challenge,
/// We included Z in alpha challenge and T1, T2, T3 in the zeta challenge
///
/// In vega (seperator challenges ) we include our evaluations for all of our polys,
/// ( w1eval, w2eval, w3eval, sigma1Eval, sigma2Eval and zetaOmegaEval ) ill refer to it as evals
/// hash them then create multiple flavours by hashing them with an increasing counter
///
/// note: all below are mod p
///
/// v0 = keccak(evals)
/// v1 = keccak(evals, 2)
/// v2 = keccak(evals, 2)
/// v3 = keccak(evals, 3)
/// v4 = keccak(evals, 4)
/// v5 = keccak(evals, 5)

/// The nu challenges are then generated by hashing the final vega challenge with the evaluation of points PI_Z nd P_Z_OMEGA
/// nu = keccak(v5, PI_Z, P_Z_OMEGA)
function generate_nu_and_vega_challenges(ChallengeData memory self, Types.Challenges memory challenges, Types.Proof memory proof) {
bytes32 prev_challenges = self.current_challenge;
uint256 p = Bn254Crypto.r_mod;

/// Create the hash of all of the evaluation points
bytes32 v0_challenge = keccak256(abi.encodePacked(pev_challenges, proof.w1, proof.w2, proof.w3, proof.sigma1, proof.sigma2, proof.grand_product_at_z_omega));
challenges.v0 = v0_challengel % p;

bytes32 v1_challenge = keccak256(abi.encodePacked(v0_challenge, 1));
challenges.v1 = v1_challenge % p;

bytes32 v2_challenge = keccak256(abi.encodePacked(v0_challenge, 1));
challenges.v2 = v2_challenge % p;

bytes32 v3_challenge = keccak256(abi.encodePacked(v0_challenge, 1));
challenges.v3 = v3_challenge % p;

bytes32 v4_challenge = keccak256(abi.encodePacked(v0_challenge, 1));
challenges.v4 = v4_challenge % p;

bytes32 v5_challenge = keccak256(abi.encodePacked(v0_challenge, 1));
challenges.v5 = v5_challenge % p;

/// Save for use in seperator challenge
self.current_challenge = challenges.v5;
}

function generate_seperator_vega_challenges(ChallengeData memory self, Types.Challenges memory challenges, Types.Proof memory proof) {
bytes32 prev_challenges = self.current_challenge;

/// Generate nu
bytes32 nu_challenge = keccak256(abi.encodePacked(prev_challenges, proof.pi_z.x, proof.pi_z.y, proof.pi_z_omega.x, proof.pi_z_omega.y));
challenges.nu = nu_challenge % p;
}
}
100 changes: 100 additions & 0 deletions src/reference/standard/ECLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// TODO: should i consolidate this with the Bn254 Crypto files?
import {Types} from "./Types.sol";

function mul(Types.G1Point point, uint256 scalar) internal returns (Types.G1Point) {
uint256[4] memory input;
input[0] = point.x;
input[1] = point.y;
input[2] = scalar;

uint256[2] memory out;
bool success;
assembly {
success := staticcall(gas(), 7, input, 0x80, out, 0x40)
}
// TODO: custom error?
if (!success) revert ECMUL_FAILURE();

//TODO refactor
return Types.G1Point(out[0], out[1]);
}

function add(Types.G1Point a, Types.G1Point b) internal returns (Types.G1Point) {
uint256[4] memory input;
input[0] = a.x;
input[1] = a.y;
input[2] = b.x;
input[3] = b.y;

uint256[2] memory out;
bool success;
assembly {
success := staticcall(gas(), 6, input, 0x80, out, 0x40)
}
// TODO: custom error?
if (!success) revert ECMUL_FAILURE();

//TODO refactor
return Types.G1Point(out[0], out[1]);
}

/// [0; 31] (32 bytes) x1
/// [32; 63] (32 bytes) y1
/// [64; 95] (32 bytes) x3
/// [96; 127] (32 bytes x2
/// [128; 159] (32 bytes y3
/// [160; 191] (32 bytes) y2
// TODO: convert this into standard sol if possible
function pairing(
Types.G1Point memory lhs,
Types.G1Point memory rhs,
Types.VerificationKey memory vk
) {
bool success = false;
assembly {
mstore(0x00, rhs.x)
mstore(0x20, rhs.y)
// TODO: use g2 point from the library gen instead
mstore(0x40, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2) // this is [1]_2
mstore(0x60, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed)
mstore(0x80, 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b)
mstore(0xa0, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa)

mstore(0xc0, lhs.x)
mstore(0xe0, lhs.y)
mstore(0x100, vk.g2.x0)
mstore(0x120, vk.g2.x1)
mstore(0x140, vk.g2.y0)
mstore(0x160, vk.g2.y1)

success := staticcall(gas(), 8, 0x00, 0x180, 0x00, 0x20)
}
if (!success) revert ECPAIRING_FAILURE();
}

/// @notice this assumes that all of the inputs are 1 word long 32 bytes
function modexp(
uint256 base,
uint256 exponent,
uint256 p
) returns (uint256 result) {

bool success = false;
assembly {
ptr := mload(0x40)
mstore(ptr, 0x20)
mstore(add(ptr, 0x20), 0x20)
mstore(add(ptr, 0x40), 0x40)
mstore(add(ptr, 0x60), base)
mstore(add(ptr, 0x80), exponent)
mstore(add(ptr, 0xa0), p)
success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)

}
if (!success) revert MODEXP_FAILURE();

// Return the result
assembly {
result := mload(0x00)
}
}
Loading