Address Contract Partially Verified
Address
0x2bB76Cb5EaB856ffb548320509266c5BfeD46f82
Balance
0 ETH
Nonce
1
Code Size
5794 bytes
Creator
0x5622612b...b442 at tx 0x1bc84ef9...9f5f9c
Indexed Transactions
0 (1 on-chain, 1.5% indexed)
Contract Bytecode
5794 bytes
0x6080806040526004361015610012575f80fd5b5f905f3560e01c9081632a51043614610cca5750806341493c6014610aa157806344f6369214610a065780636b61d8e7146109c1578063eddf243c146105d3578063f11817b21461013e5763ffa1ad741461006b575f80fd5b3461013b578060031936011261013b576040516040810181811067ffffffffffffffff82111761010e57906040918252600681527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602083017f76352e302e30000000000000000000000000000000000000000000000000000081528451958694602086525180928160208801528787015e85828601015201168101030190f35b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b80fd5b503461013b5760c060031936011261013b573660841161013b573660c41161013b57604051906103006101718184610d30565b803684376101806004356111e2565b610191602495929535604435611286565b919392906101a06064356111e2565b9390926040519660408801967f26091e1cafb0ad8a4ea0a694cd3743ebf524779233db734c451d28b58aa9758e895288600160208201997e9ff50a6b8b11c3ca6fdb2690a124f8ce25489fefa65a3e782e7ba70b66690e8b527f061c3fd0fd3da25d2607c227d090cca750ed36c6ec878755e537c1c48951fb4c81527f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001604060608501927f0fa17ae9c2033379df7b5c65eff0e107055e9a273e6119a212dd09eb5170721984527f07236256d21c60d02f0bdbf95cff83e03ea9e16fca56b18d5544b0889a65c1f560843596836080820198808a5286828660608160075afa9110169160808160065afa16947f04eab241388a79817fe0e0e2ead0b2ec4ffdec51a16028dee020634fd129e71c83525260a43580965260608160075afa931016161660408a60808160065afa169851975198156105ab5760209a9b9c8a528a8a015260408901526060880152608087015260a086015260c085015260e08401527f1cc7cb8de715675f21f01ecc9b46d236e0865e0cc020024521998269845f74e66101008401527f03ff41f4ba0c37fe2caf27354d28e4b8f83d3b76777a63b327d736bffb0122ed6101208401527f01909cd7827e0278e6b60843a4abc7b111d7f8b2725cd5902a6b20da7a2938fb6101408401527f192bd3274441670227b4f69a44005b8711266e474227c6439ca25ca8e1ec1fc26101608401527f2d4d9aa7e302d9df41749d5507949d05dbea33fbb16c643b22f599a2be6df2e26101808401527f14bedd503c37ceb061d8ec60209fe345ce89830a19230301f076caff004d19266101a08401527f0967032fcbf776d1afc985f88877f182d38480a653f2decaa9794cbc3bf3060c6101c08401527f0e187847ad4c798374d0d6732bf501847dd68bc0e071241e0213bc7fc13db7ab6101e08401527e1752a100a72fdf1e5a5d6ea841cc20ec838bccfcf7bd559e79f1c9c759b6a06102008401527f192a8cc13cd9f762871f21e43451c6ca9eeab2cb2987c4e366a185c25dac2e7f6102208401526102408301526102608201527f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c26102808201527f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed6102a08201527f275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec6102c08201527f1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d6102e082015260405192839161055d8484610d30565b8336843760085afa1590811561059e575b506105765780f35b807f7fcdd1f40000000000000000000000000000000000000000000000000000000060049252fd5b600191505114155f61056e565b60048c7fa54f8e27000000000000000000000000000000000000000000000000000000008152fd5b503461013b5761014060031936011261013b57366101041161013b57366101441161013b5760405160408101907f26091e1cafb0ad8a4ea0a694cd3743ebf524779233db734c451d28b58aa9758e815260208101917e9ff50a6b8b11c3ca6fdb2690a124f8ce25489fefa65a3e782e7ba70b66690e83527f061c3fd0fd3da25d2607c227d090cca750ed36c6ec878755e537c1c48951fb4c81526001606083017f0fa17ae9c2033379df7b5c65eff0e107055e9a273e6119a212dd09eb5170721981527f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001604061010435947f07236256d21c60d02f0bdbf95cff83e03ea9e16fca56b18d5544b0889a65c1f5608088019680885284848460608160075afa911016838960808160065afa16947f04eab241388a79817fe0e0e2ead0b2ec4ffdec51a16028dee020634fd129e71c8352526101243580965260608160075afa931016161660408260808160065afa169051915190156109995760405191610100600484377f1cc7cb8de715675f21f01ecc9b46d236e0865e0cc020024521998269845f74e66101008401527f03ff41f4ba0c37fe2caf27354d28e4b8f83d3b76777a63b327d736bffb0122ed6101208401527f01909cd7827e0278e6b60843a4abc7b111d7f8b2725cd5902a6b20da7a2938fb6101408401527f192bd3274441670227b4f69a44005b8711266e474227c6439ca25ca8e1ec1fc26101608401527f2d4d9aa7e302d9df41749d5507949d05dbea33fbb16c643b22f599a2be6df2e26101808401527f14bedd503c37ceb061d8ec60209fe345ce89830a19230301f076caff004d19266101a08401527f0967032fcbf776d1afc985f88877f182d38480a653f2decaa9794cbc3bf3060c6101c08401527f0e187847ad4c798374d0d6732bf501847dd68bc0e071241e0213bc7fc13db7ab6101e08401527e1752a100a72fdf1e5a5d6ea841cc20ec838bccfcf7bd559e79f1c9c759b6a06102008401527f192a8cc13cd9f762871f21e43451c6ca9eeab2cb2987c4e366a185c25dac2e7f6102208401526102408301526102608201527f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c26102808201527f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed6102a08201527f275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec6102c08201527f1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d6102e08201526020816103008160085afa905116156105765780f35b6004837fa54f8e27000000000000000000000000000000000000000000000000000000008152fd5b503461013b57602060031936011261013b576004359067ffffffffffffffff821161013b5760206109fe6109f83660048601610d02565b90610d9e565b604051908152f35b503461013b5761010060031936011261013b57366101041161013b5760405190610a31608083610d30565b6080368337610a44602435600435610dee565b8252610a5a60843560a435604435606435610f03565b60208401526040830152610a7260e43560c435610dee565b60608301526040519190825b60048210610a8b57608084f35b6020806001928551815201930191019091610a7e565b5034610c67576060600319360112610c675760243567ffffffffffffffff8111610c6757610ad3903690600401610d02565b60443567ffffffffffffffff8111610c6757610af3903690600401610d02565b91909282600411610c67577fffffffff000000000000000000000000000000000000000000000000000000008435167fa4594c59000000000000000000000000000000000000000000000000000000008103610c7b575090610b5491610d9e565b604091825191610b648484610d30565b833684376004358352602083015283016101006003198583030112610c675780602385011215610c6757825193610b9d61010086610d30565b84906101048101928311610c6757600401905b828210610c6b57505050303b15610c675781517feddf243c000000000000000000000000000000000000000000000000000000008152925f600485015b60088210610c515750505061010483015f905b60028210610c3b575050505f8261014481305afa908115610c325750610c24575080f35b610c3091505f90610d30565b005b513d5f823e3d90fd5b6020806001928551815201930191019091610c00565b6020806001928551815201930191019091610bed565b5f80fd5b8135815260209182019101610bb0565b7f988066a1000000000000000000000000000000000000000000000000000000005f526004527fa4594c590000000000000000000000000000000000000000000000000000000060245260445ffd5b34610c67575f600319360112610c6757807fa4594c59bbc142f3b81c3ecb7f50a7c34bc9af7c4c444b5d48b795427e28591360209252f35b9181601f84011215610c675782359167ffffffffffffffff8311610c675760208381860195010111610c6757565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610d7157604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6020915f918160405192839283378101838152039060025afa15610de3577f1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5f511690565b6040513d5f823e3d90fd5b907f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478210801590610ed9575b610ea357811580610ed1575b610ecb57610e5d7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd476003818581818009090861140b565b818103610e6c57505060011b90565b7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47809106810306145f14610ea357600190811b1790565b7f7fcdd1f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b50505f90565b508015610e26565b507f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47811015610e1a565b919093927f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4783108015906111b8575b801561118e575b8015611164575b610ea3578082868517171715611159579082916110967f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4780808080888180808f9d7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd448f839290839109099d8e0981848181800909087f2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5089a09818c8181800909087f2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e7750806810306947f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd477f183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea48161105d81808b8009818780090861140b565b8408097f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4761108a8261161a565b80091415958691611441565b929080821480611150575b156110c85750505050905f146110c05760ff60025b169060021b179190565b60ff5f6110b6565b7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4780910681030614918261111e575b505015610ea357600191156111165760ff60025b169060021b17179190565b60ff5f61110b565b7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47919250819006810306145f806110f7565b508383146110a1565b50505090505f905f90565b507f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47811015610f40565b507f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47821015610f39565b507f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47851015610f32565b801561127f578060011c917f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47831015610ea35760018061124b7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd476003818881818009090861140b565b93161461125457565b907f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4780910681030690565b505f905f90565b801580611403575b6113f7578060021c92827f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4785108015906113cd575b610ea35784817f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4780808080808080807f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd44816113849d8d0909998a0981898181800909087f2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e7750806810306936002808a16149509818a8181800909087f2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e508611441565b80929160018082961614611396575050565b7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478093945080929550809106810306930681030690565b507f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478110156112c3565b50505f905f905f905f90565b50811561128e565b906114158261161a565b917f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4783800903610ea357565b917f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd477f183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4816114ac9396949661149e82808a8009818a80090861140b565b9061160e575b86080961140b565b927f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47600285096040519060208252602080830152602060408301528060608301527f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4560808301527f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4760a083015260208260c08160055afa91519115610ea3577f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47826001920903610ea3577f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47908209927f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4780808087800906810306818780090814908115916115dc575b50610ea357565b90507f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478084860960020914155f6115d5565b818091068103066114a4565b9060405191602083526020808401526020604084015260608301527f0c19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f5260808301527f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4760a083015260208260c08160055afa91519115610ea35756fea164736f6c634300081c000a
Verified Source Code Partial Match
Compiler: v0.8.28+commit.7893614a
EVM: cancun
Optimization: Yes (10000 runs)
SP1VerifierGroth16.sol 59 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ISP1Verifier, ISP1VerifierWithHash} from "../ISP1Verifier.sol";
import {Groth16Verifier} from "./Groth16Verifier.sol";
/// @title SP1 Verifier
/// @author Succinct Labs
/// @notice This contracts implements a solidity verifier for SP1.
contract SP1Verifier is Groth16Verifier, ISP1VerifierWithHash {
/// @notice Thrown when the verifier selector from this proof does not match the one in this
/// verifier. This indicates that this proof was sent to the wrong verifier.
/// @param received The verifier selector from the first 4 bytes of the proof.
/// @param expected The verifier selector from the first 4 bytes of the VERIFIER_HASH().
error WrongVerifierSelector(bytes4 received, bytes4 expected);
/// @notice Thrown when the proof is invalid.
error InvalidProof();
function VERSION() external pure returns (string memory) {
return "v5.0.0";
}
/// @inheritdoc ISP1VerifierWithHash
function VERIFIER_HASH() public pure returns (bytes32) {
return 0xa4594c59bbc142f3b81c3ecb7f50a7c34bc9af7c4c444b5d48b795427e285913;
}
/// @notice Hashes the public values to a field elements inside Bn254.
/// @param publicValues The public values.
function hashPublicValues(
bytes calldata publicValues
) public pure returns (bytes32) {
return sha256(publicValues) & bytes32(uint256((1 << 253) - 1));
}
/// @notice Verifies a proof with given public values and vkey.
/// @param programVKey The verification key for the RISC-V program.
/// @param publicValues The public values encoded as bytes.
/// @param proofBytes The proof of the program execution the SP1 zkVM encoded as bytes.
function verifyProof(
bytes32 programVKey,
bytes calldata publicValues,
bytes calldata proofBytes
) external view {
bytes4 receivedSelector = bytes4(proofBytes[:4]);
bytes4 expectedSelector = bytes4(VERIFIER_HASH());
if (receivedSelector != expectedSelector) {
revert WrongVerifierSelector(receivedSelector, expectedSelector);
}
bytes32 publicValuesDigest = hashPublicValues(publicValues);
uint256[2] memory inputs;
inputs[0] = uint256(programVKey);
inputs[1] = uint256(publicValuesDigest);
uint256[8] memory proof = abi.decode(proofBytes[4:], (uint256[8]));
this.Verify(proof, inputs);
}
}
ISP1Verifier.sol 24 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @title SP1 Verifier Interface
/// @author Succinct Labs
/// @notice This contract is the interface for the SP1 Verifier.
interface ISP1Verifier {
/// @notice Verifies a proof with given public values and vkey.
/// @dev It is expected that the first 4 bytes of proofBytes must match the first 4 bytes of
/// target verifier's VERIFIER_HASH.
/// @param programVKey The verification key for the RISC-V program.
/// @param publicValues The public values encoded as bytes.
/// @param proofBytes The proof of the program execution the SP1 zkVM encoded as bytes.
function verifyProof(
bytes32 programVKey,
bytes calldata publicValues,
bytes calldata proofBytes
) external view;
}
interface ISP1VerifierWithHash is ISP1Verifier {
/// @notice Returns the hash of the verifier.
function VERIFIER_HASH() external pure returns (bytes32);
}
Groth16Verifier.sol 540 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @title Groth16 verifier template.
/// @author Remco Bloemen
/// @notice Supports verifying Groth16 proofs. Proofs can be in uncompressed
/// (256 bytes) and compressed (128 bytes) format. A view function is provided
/// to compress proofs.
/// @notice See <https://2π.com/23/bn254-compression> for further explanation.
contract Groth16Verifier {
/// Some of the provided public input values are larger than the field modulus.
/// @dev Public input elements are not automatically reduced, as this is can be
/// a dangerous source of bugs.
error PublicInputNotInField();
/// The proof is invalid.
/// @dev This can mean that provided Groth16 proof points are not on their
/// curves, that pairing equation fails, or that the proof is not for the
/// provided public input.
error ProofInvalid();
// Addresses of precompiles
uint256 constant PRECOMPILE_MODEXP = 0x05;
uint256 constant PRECOMPILE_ADD = 0x06;
uint256 constant PRECOMPILE_MUL = 0x07;
uint256 constant PRECOMPILE_VERIFY = 0x08;
// Base field Fp order P and scalar field Fr order R.
// For BN254 these are computed as follows:
// t = 4965661367192848881
// P = 36⋅t⁴ + 36⋅t³ + 24⋅t² + 6⋅t + 1
// R = 36⋅t⁴ + 36⋅t³ + 18⋅t² + 6⋅t + 1
uint256 constant P = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47;
uint256 constant R = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001;
// Extension field Fp2 = Fp[i] / (i² + 1)
// Note: This is the complex extension field of Fp with i² = -1.
// Values in Fp2 are represented as a pair of Fp elements (a₀, a₁) as a₀ + a₁⋅i.
// Note: The order of Fp2 elements is *opposite* that of the pairing contract, which
// expects Fp2 elements in order (a₁, a₀). This is also the order in which
// Fp2 elements are encoded in the public interface as this became convention.
// Constants in Fp
uint256 constant FRACTION_1_2_FP = 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4;
uint256 constant FRACTION_27_82_FP = 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5;
uint256 constant FRACTION_3_82_FP = 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775;
// Exponents for inversions and square roots mod P
uint256 constant EXP_INVERSE_FP = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2
uint256 constant EXP_SQRT_FP = 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4;
// Groth16 alpha point in G1
uint256 constant ALPHA_X = 20491192805390485299153009773594534940189261866228447918068658471970481763042;
uint256 constant ALPHA_Y = 9383485363053290200918347156157836566562967994039712273449902621266178545958;
// Groth16 beta point in G2 in powers of i
uint256 constant BETA_NEG_X_0 = 6375614351688725206403948262868962793625744043794305715222011528459656738731;
uint256 constant BETA_NEG_X_1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132;
uint256 constant BETA_NEG_Y_0 = 11383000245469012944693504663162918391286475477077232690815866754273895001727;
uint256 constant BETA_NEG_Y_1 = 41207766310529818958173054109690360505148424997958324311878202295167071904;
// Groth16 gamma point in G2 in powers of i
uint256 constant GAMMA_NEG_X_0 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
uint256 constant GAMMA_NEG_X_1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
uint256 constant GAMMA_NEG_Y_0 = 13392588948715843804641432497768002650278120570034223513918757245338268106653;
uint256 constant GAMMA_NEG_Y_1 = 17805874995975841540914202342111839520379459829704422454583296818431106115052;
// Groth16 delta point in G2 in powers of i
uint256 constant DELTA_NEG_X_0 = 1807939758600928081661535078044266309701426477869595321608690071623627252461;
uint256 constant DELTA_NEG_X_1 = 13017767206419180294867239590191240882490168779777616723978810680471506089190;
uint256 constant DELTA_NEG_Y_0 = 11385252965472363874004017020523979267854101512663014352368174256411716100034;
uint256 constant DELTA_NEG_Y_1 = 707821308472421780425082520239282952693670279239989952629124761519869475067;
// Constant and public input points
uint256 constant CONSTANT_X = 17203997695518370725253383800612862082040222186834248316724952811913305748878;
uint256 constant CONSTANT_Y = 282619892079818506885924724237935832196325815176482254129420869757043108110;
uint256 constant PUB_0_X = 2763789253671512309630211343474627955637016507408470052385640371173442321228;
uint256 constant PUB_0_Y = 7070003421332099028511324531870215047017050364545890942981741487547942466073;
uint256 constant PUB_1_X = 2223923876691923064813371578678400285087400227347901303400514986210692294428;
uint256 constant PUB_1_Y = 3228708299174762375496115493137156328822199374794870011715145604387710550517;
/// Negation in Fp.
/// @notice Returns a number x such that a + x = 0 in Fp.
/// @notice The input does not need to be reduced.
/// @param a the base
/// @return x the result
function negate(uint256 a) internal pure returns (uint256 x) {
unchecked {
x = (P - (a % P)) % P; // Modulo is cheaper than branching
}
}
/// Exponentiation in Fp.
/// @notice Returns a number x such that a ^ e = x in Fp.
/// @notice The input does not need to be reduced.
/// @param a the base
/// @param e the exponent
/// @return x the result
function exp(uint256 a, uint256 e) internal view returns (uint256 x) {
bool success;
assembly ("memory-safe") {
let f := mload(0x40)
mstore(f, 0x20)
mstore(add(f, 0x20), 0x20)
mstore(add(f, 0x40), 0x20)
mstore(add(f, 0x60), a)
mstore(add(f, 0x80), e)
mstore(add(f, 0xa0), P)
success := staticcall(gas(), PRECOMPILE_MODEXP, f, 0xc0, f, 0x20)
x := mload(f)
}
if (!success) {
// Exponentiation failed.
// Should not happen.
revert ProofInvalid();
}
}
/// Invertsion in Fp.
/// @notice Returns a number x such that a * x = 1 in Fp.
/// @notice The input does not need to be reduced.
/// @notice Reverts with ProofInvalid() if the inverse does not exist
/// @param a the input
/// @return x the solution
function invert_Fp(uint256 a) internal view returns (uint256 x) {
x = exp(a, EXP_INVERSE_FP);
if (mulmod(a, x, P) != 1) {
// Inverse does not exist.
// Can only happen during G2 point decompression.
revert ProofInvalid();
}
}
/// Square root in Fp.
/// @notice Returns a number x such that x * x = a in Fp.
/// @notice Will revert with InvalidProof() if the input is not a square
/// or not reduced.
/// @param a the square
/// @return x the solution
function sqrt_Fp(uint256 a) internal view returns (uint256 x) {
x = exp(a, EXP_SQRT_FP);
if (mulmod(x, x, P) != a) {
// Square root does not exist or a is not reduced.
// Happens when G1 point is not on curve.
revert ProofInvalid();
}
}
/// Square test in Fp.
/// @notice Returns whether a number x exists such that x * x = a in Fp.
/// @notice Will revert with InvalidProof() if the input is not a square
/// or not reduced.
/// @param a the square
/// @return x the solution
function isSquare_Fp(uint256 a) internal view returns (bool) {
uint256 x = exp(a, EXP_SQRT_FP);
return mulmod(x, x, P) == a;
}
/// Square root in Fp2.
/// @notice Fp2 is the complex extension Fp[i]/(i^2 + 1). The input is
/// a0 + a1 ⋅ i and the result is x0 + x1 ⋅ i.
/// @notice Will revert with InvalidProof() if
/// * the input is not a square,
/// * the hint is incorrect, or
/// * the input coefficents are not reduced.
/// @param a0 The real part of the input.
/// @param a1 The imaginary part of the input.
/// @param hint A hint which of two possible signs to pick in the equation.
/// @return x0 The real part of the square root.
/// @return x1 The imaginary part of the square root.
function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) internal view returns (uint256 x0, uint256 x1) {
// If this square root reverts there is no solution in Fp2.
uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P));
if (hint) {
d = negate(d);
}
// If this square root reverts there is no solution in Fp2.
x0 = sqrt_Fp(mulmod(addmod(a0, d, P), FRACTION_1_2_FP, P));
x1 = mulmod(a1, invert_Fp(mulmod(x0, 2, P)), P);
// Check result to make sure we found a root.
// Note: this also fails if a0 or a1 is not reduced.
if (a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P)
|| a1 != mulmod(2, mulmod(x0, x1, P), P)) {
revert ProofInvalid();
}
}
/// Compress a G1 point.
/// @notice Reverts with InvalidProof if the coordinates are not reduced
/// or if the point is not on the curve.
/// @notice The point at infinity is encoded as (0,0) and compressed to 0.
/// @param x The X coordinate in Fp.
/// @param y The Y coordinate in Fp.
/// @return c The compresed point (x with one signal bit).
function compress_g1(uint256 x, uint256 y) internal view returns (uint256 c) {
if (x >= P || y >= P) {
// G1 point not in field.
revert ProofInvalid();
}
if (x == 0 && y == 0) {
// Point at infinity
return 0;
}
// Note: sqrt_Fp reverts if there is no solution, i.e. the x coordinate is invalid.
uint256 y_pos = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P));
if (y == y_pos) {
return (x << 1) | 0;
} else if (y == negate(y_pos)) {
return (x << 1) | 1;
} else {
// G1 point not on curve.
revert ProofInvalid();
}
}
/// Decompress a G1 point.
/// @notice Reverts with InvalidProof if the input does not represent a valid point.
/// @notice The point at infinity is encoded as (0,0) and compressed to 0.
/// @param c The compresed point (x with one signal bit).
/// @return x The X coordinate in Fp.
/// @return y The Y coordinate in Fp.
function decompress_g1(uint256 c) internal view returns (uint256 x, uint256 y) {
// Note that X = 0 is not on the curve since 0³ + 3 = 3 is not a square.
// so we can use it to represent the point at infinity.
if (c == 0) {
// Point at infinity as encoded in EIP196 and EIP197.
return (0, 0);
}
bool negate_point = c & 1 == 1;
x = c >> 1;
if (x >= P) {
// G1 x coordinate not in field.
revert ProofInvalid();
}
// Note: (x³ + 3) is irreducible in Fp, so it can not be zero and therefore
// y can not be zero.
// Note: sqrt_Fp reverts if there is no solution, i.e. the point is not on the curve.
y = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P));
if (negate_point) {
y = negate(y);
}
}
/// Compress a G2 point.
/// @notice Reverts with InvalidProof if the coefficients are not reduced
/// or if the point is not on the curve.
/// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1)
/// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i).
/// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0).
/// @param x0 The real part of the X coordinate.
/// @param x1 The imaginary poart of the X coordinate.
/// @param y0 The real part of the Y coordinate.
/// @param y1 The imaginary part of the Y coordinate.
/// @return c0 The first half of the compresed point (x0 with two signal bits).
/// @return c1 The second half of the compressed point (x1 unmodified).
function compress_g2(uint256 x0, uint256 x1, uint256 y0, uint256 y1)
internal view returns (uint256 c0, uint256 c1) {
if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) {
// G2 point not in field.
revert ProofInvalid();
}
if ((x0 | x1 | y0 | y1) == 0) {
// Point at infinity
return (0, 0);
}
// Compute y^2
// Note: shadowing variables and scoping to avoid stack-to-deep.
uint256 y0_pos;
uint256 y1_pos;
{
uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P);
uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P);
uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P);
y0_pos = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P);
y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P));
}
// Determine hint bit
// If this sqrt fails the x coordinate is not on the curve.
bool hint;
{
uint256 d = sqrt_Fp(addmod(mulmod(y0_pos, y0_pos, P), mulmod(y1_pos, y1_pos, P), P));
hint = !isSquare_Fp(mulmod(addmod(y0_pos, d, P), FRACTION_1_2_FP, P));
}
// Recover y
(y0_pos, y1_pos) = sqrt_Fp2(y0_pos, y1_pos, hint);
if (y0 == y0_pos && y1 == y1_pos) {
c0 = (x0 << 2) | (hint ? 2 : 0) | 0;
c1 = x1;
} else if (y0 == negate(y0_pos) && y1 == negate(y1_pos)) {
c0 = (x0 << 2) | (hint ? 2 : 0) | 1;
c1 = x1;
} else {
// G1 point not on curve.
revert ProofInvalid();
}
}
/// Decompress a G2 point.
/// @notice Reverts with InvalidProof if the input does not represent a valid point.
/// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1)
/// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i).
/// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0).
/// @param c0 The first half of the compresed point (x0 with two signal bits).
/// @param c1 The second half of the compressed point (x1 unmodified).
/// @return x0 The real part of the X coordinate.
/// @return x1 The imaginary poart of the X coordinate.
/// @return y0 The real part of the Y coordinate.
/// @return y1 The imaginary part of the Y coordinate.
function decompress_g2(uint256 c0, uint256 c1)
internal view returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) {
// Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square.
// so we can use it to represent the point at infinity.
if (c0 == 0 && c1 == 0) {
// Point at infinity as encoded in EIP197.
return (0, 0, 0, 0);
}
bool negate_point = c0 & 1 == 1;
bool hint = c0 & 2 == 2;
x0 = c0 >> 2;
x1 = c1;
if (x0 >= P || x1 >= P) {
// G2 x0 or x1 coefficient not in field.
revert ProofInvalid();
}
uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P);
uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P);
uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P);
y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P);
y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P));
// Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve.
// Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero.
// But y0 or y1 may still independently be zero.
(y0, y1) = sqrt_Fp2(y0, y1, hint);
if (negate_point) {
y0 = negate(y0);
y1 = negate(y1);
}
}
/// Compute the public input linear combination.
/// @notice Reverts with PublicInputNotInField if the input is not in the field.
/// @notice Computes the multi-scalar-multiplication of the public input
/// elements and the verification key including the constant term.
/// @param input The public inputs. These are elements of the scalar field Fr.
/// @return x The X coordinate of the resulting G1 point.
/// @return y The Y coordinate of the resulting G1 point.
function publicInputMSM(uint256[2] calldata input)
internal view returns (uint256 x, uint256 y) {
// Note: The ECMUL precompile does not reject unreduced values, so we check this.
// Note: Unrolling this loop does not cost much extra in code-size, the bulk of the
// code-size is in the PUB_ constants.
// ECMUL has input (x, y, scalar) and output (x', y').
// ECADD has input (x1, y1, x2, y2) and output (x', y').
// We reduce commitments(if any) with constants as the first point argument to ECADD.
// We call them such that ecmul output is already in the second point
// argument to ECADD so we can have a tight loop.
bool success = true;
assembly ("memory-safe") {
let f := mload(0x40)
let g := add(f, 0x40)
let s
mstore(f, CONSTANT_X)
mstore(add(f, 0x20), CONSTANT_Y)
mstore(g, PUB_0_X)
mstore(add(g, 0x20), PUB_0_Y)
s := calldataload(input)
mstore(add(g, 0x40), s)
success := and(success, lt(s, R))
success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40))
success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40))
mstore(g, PUB_1_X)
mstore(add(g, 0x20), PUB_1_Y)
s := calldataload(add(input, 32))
mstore(add(g, 0x40), s)
success := and(success, lt(s, R))
success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40))
success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40))
x := mload(f)
y := mload(add(f, 0x20))
}
if (!success) {
// Either Public input not in field, or verification key invalid.
// We assume the contract is correctly generated, so the verification key is valid.
revert PublicInputNotInField();
}
}
/// Compress a proof.
/// @notice Will revert with InvalidProof if the curve points are invalid,
/// but does not verify the proof itself.
/// @param proof The uncompressed Groth16 proof. Elements are in the same order as for
/// verifyProof. I.e. Groth16 points (A, B, C) encoded as in EIP-197.
/// @return compressed The compressed proof. Elements are in the same order as for
/// verifyCompressedProof. I.e. points (A, B, C) in compressed format.
function compressProof(uint256[8] calldata proof)
public view returns (uint256[4] memory compressed) {
compressed[0] = compress_g1(proof[0], proof[1]);
(compressed[2], compressed[1]) = compress_g2(proof[3], proof[2], proof[5], proof[4]);
compressed[3] = compress_g1(proof[6], proof[7]);
}
/// Verify a Groth16 proof with compressed points.
/// @notice Reverts with InvalidProof if the proof is invalid or
/// with PublicInputNotInField the public input is not reduced.
/// @notice There is no return value. If the function does not revert, the
/// proof was successfully verified.
/// @param compressedProof the points (A, B, C) in compressed format
/// matching the output of compressProof.
/// @param input the public input field elements in the scalar field Fr.
/// Elements must be reduced.
function verifyCompressedProof(
uint256[4] calldata compressedProof,
uint256[2] calldata input
) public view {
uint256[24] memory pairings;
{
(uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]);
(uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = decompress_g2(compressedProof[2], compressedProof[1]);
(uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]);
(uint256 Lx, uint256 Ly) = publicInputMSM(input);
// Verify the pairing
// Note: The precompile expects the F2 coefficients in big-endian order.
// Note: The pairing precompile rejects unreduced values, so we won't check that here.
// e(A, B)
pairings[ 0] = Ax;
pairings[ 1] = Ay;
pairings[ 2] = Bx1;
pairings[ 3] = Bx0;
pairings[ 4] = By1;
pairings[ 5] = By0;
// e(C, -δ)
pairings[ 6] = Cx;
pairings[ 7] = Cy;
pairings[ 8] = DELTA_NEG_X_1;
pairings[ 9] = DELTA_NEG_X_0;
pairings[10] = DELTA_NEG_Y_1;
pairings[11] = DELTA_NEG_Y_0;
// e(α, -β)
pairings[12] = ALPHA_X;
pairings[13] = ALPHA_Y;
pairings[14] = BETA_NEG_X_1;
pairings[15] = BETA_NEG_X_0;
pairings[16] = BETA_NEG_Y_1;
pairings[17] = BETA_NEG_Y_0;
// e(L_pub, -γ)
pairings[18] = Lx;
pairings[19] = Ly;
pairings[20] = GAMMA_NEG_X_1;
pairings[21] = GAMMA_NEG_X_0;
pairings[22] = GAMMA_NEG_Y_1;
pairings[23] = GAMMA_NEG_Y_0;
// Check pairing equation.
bool success;
uint256[1] memory output;
assembly ("memory-safe") {
success := staticcall(gas(), PRECOMPILE_VERIFY, pairings, 0x300, output, 0x20)
}
if (!success || output[0] != 1) {
// Either proof or verification key invalid.
// We assume the contract is correctly generated, so the verification key is valid.
revert ProofInvalid();
}
}
}
/// Verify an uncompressed Groth16 proof.
/// @notice Reverts with InvalidProof if the proof is invalid or
/// with PublicInputNotInField the public input is not reduced.
/// @notice There is no return value. If the function does not revert, the
/// proof was successfully verified.
/// @param proof the points (A, B, C) in EIP-197 format matching the output
/// of compressProof.
/// @param input the public input field elements in the scalar field Fr.
/// Elements must be reduced.
function Verify(
uint256[8] calldata proof,
uint256[2] calldata input
) public view {
(uint256 x, uint256 y) = publicInputMSM(input);
// Note: The precompile expects the F2 coefficients in big-endian order.
// Note: The pairing precompile rejects unreduced values, so we won't check that here.
bool success;
assembly ("memory-safe") {
let f := mload(0x40) // Free memory pointer.
// Copy points (A, B, C) to memory. They are already in correct encoding.
// This is pairing e(A, B) and G1 of e(C, -δ).
calldatacopy(f, proof, 0x100)
// Complete e(C, -δ) and write e(α, -β), e(L_pub, -γ) to memory.
// OPT: This could be better done using a single codecopy, but
// Solidity (unlike standalone Yul) doesn't provide a way to
// to do this.
mstore(add(f, 0x100), DELTA_NEG_X_1)
mstore(add(f, 0x120), DELTA_NEG_X_0)
mstore(add(f, 0x140), DELTA_NEG_Y_1)
mstore(add(f, 0x160), DELTA_NEG_Y_0)
mstore(add(f, 0x180), ALPHA_X)
mstore(add(f, 0x1a0), ALPHA_Y)
mstore(add(f, 0x1c0), BETA_NEG_X_1)
mstore(add(f, 0x1e0), BETA_NEG_X_0)
mstore(add(f, 0x200), BETA_NEG_Y_1)
mstore(add(f, 0x220), BETA_NEG_Y_0)
mstore(add(f, 0x240), x)
mstore(add(f, 0x260), y)
mstore(add(f, 0x280), GAMMA_NEG_X_1)
mstore(add(f, 0x2a0), GAMMA_NEG_X_0)
mstore(add(f, 0x2c0), GAMMA_NEG_Y_1)
mstore(add(f, 0x2e0), GAMMA_NEG_Y_0)
// Check pairing equation.
success := staticcall(gas(), PRECOMPILE_VERIFY, f, 0x300, f, 0x20)
// Also check returned value (both are either 1 or 0).
success := and(success, mload(f))
}
if (!success) {
// Either proof or verification key invalid.
// We assume the contract is correctly generated, so the verification key is valid.
revert ProofInvalid();
}
}
}
Read Contract
VERIFIER_HASH 0x2a510436 → bytes32
VERSION 0xffa1ad74 → string
Verify 0xeddf243c
compressProof 0x44f63692 → uint256[4]
hashPublicValues 0x6b61d8e7 → bytes32
verifyCompressedProof 0xf11817b2
verifyProof 0x41493c60
Recent Transactions
This address has 1 on-chain transactions, but only 1.5% of the chain is indexed. Transactions will appear as indexing progresses. View on Etherscan →