mirror of
https://github.com/succinctlabs/sp1-project-template.git
synced 2026-01-09 15:48:05 -05:00
feat: initial commit
This commit is contained in:
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# Cargo build
|
||||
**/target
|
||||
|
||||
# Cargo config
|
||||
.cargo
|
||||
|
||||
# Profile-guided optimization
|
||||
/tmp
|
||||
pgo-data.profdata
|
||||
|
||||
# MacOS nuisances
|
||||
.DS_Store
|
||||
|
||||
# Proofs
|
||||
**/proof-with-pis.json
|
||||
**/proof-with-io.json
|
||||
|
||||
# Env
|
||||
.env
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "contracts/lib/forge-std"]
|
||||
path = contracts/lib/forge-std
|
||||
url = https://github.com/foundry-rs/forge-std
|
||||
37
.vscode/settings.json
vendored
Normal file
37
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"rust-analyzer.linkedProjects": [
|
||||
"program/Cargo.toml",
|
||||
"script/Cargo.toml"
|
||||
],
|
||||
"rust-analyzer.check.overrideCommand": [
|
||||
"cargo",
|
||||
"clippy",
|
||||
"--workspace",
|
||||
"--message-format=json",
|
||||
"--all-features",
|
||||
"--all-targets",
|
||||
"--",
|
||||
"-A",
|
||||
"incomplete-features"
|
||||
],
|
||||
"rust-analyzer.runnables.extraEnv": {
|
||||
"RUST_LOG": "debug",
|
||||
"RUSTFLAGS": "-Ctarget-cpu=native"
|
||||
},
|
||||
"rust-analyzer.runnables.extraArgs": [
|
||||
"--release",
|
||||
"+nightly"
|
||||
],
|
||||
"rust-analyzer.diagnostics.disabled": [
|
||||
"unresolved-proc-macro"
|
||||
],
|
||||
"editor.rulers": [
|
||||
100
|
||||
],
|
||||
"editor.inlineSuggest.enabled": true,
|
||||
"[rust]": {
|
||||
"editor.defaultFormatter": "rust-lang.rust-analyzer",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.hover.enabled": true
|
||||
},
|
||||
}
|
||||
7163
Cargo.lock
generated
Normal file
7163
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
4
Cargo.toml
Normal file
4
Cargo.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[workspace]
|
||||
exclude = ["program"]
|
||||
members = ["script"]
|
||||
resolver = "2"
|
||||
34
contracts/.github/workflows/test.yml
vendored
Normal file
34
contracts/.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: test
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
env:
|
||||
FOUNDRY_PROFILE: ci
|
||||
|
||||
jobs:
|
||||
check:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
|
||||
name: Foundry project
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Foundry
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
with:
|
||||
version: nightly
|
||||
|
||||
- name: Run Forge build
|
||||
run: |
|
||||
forge --version
|
||||
forge build --sizes
|
||||
id: build
|
||||
|
||||
- name: Run Forge tests
|
||||
run: |
|
||||
forge test -vvv
|
||||
id: test
|
||||
18
contracts/.gitignore
vendored
Normal file
18
contracts/.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# Compiler files
|
||||
cache/
|
||||
out/
|
||||
|
||||
# Ignores development broadcast logs
|
||||
/broadcast
|
||||
/broadcast/*/11155111/
|
||||
/broadcast/*/31337/
|
||||
/broadcast/**/dry-run/
|
||||
|
||||
# Docs
|
||||
docs/
|
||||
|
||||
# Dotenv file
|
||||
.env
|
||||
|
||||
# Libraries
|
||||
lib/
|
||||
5
contracts/README.md
Normal file
5
contracts/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
### Deploy
|
||||
|
||||
```shell
|
||||
$ forge script script/SP1Tendermint.s.sol --rpc-url $RPC_11155111 --private-key $PRIVATE_KEY --etherscan-api-key $ETHERSCAN_API_KEY_11155111 --broadcast --verify
|
||||
```
|
||||
7
contracts/foundry.toml
Normal file
7
contracts/foundry.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[profile.default]
|
||||
src = "src"
|
||||
out = "out"
|
||||
libs = ["lib"]
|
||||
fs_permissions = [{ access = "read-write", path = "./" }]
|
||||
|
||||
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
|
||||
66
contracts/src/Fibonacci.sol
Normal file
66
contracts/src/Fibonacci.sol
Normal file
@@ -0,0 +1,66 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import {SP1Verifier} from "./SP1Verifier.sol";
|
||||
|
||||
/// @title Fibonacci.
|
||||
/// @author Succinct Labs
|
||||
/// @notice This contract implements a simple example of verifying the proof of a computing a
|
||||
/// fibonacci number.
|
||||
contract Fibonacci is SP1Verifier {
|
||||
/// @notice The verification key for the fibonacci program.
|
||||
bytes32 public fibonacciProgramVkey;
|
||||
|
||||
constructor(bytes32 _fibonacciProgramVkey) {
|
||||
fibonacciProgramVkey = _fibonacciProgramVkey;
|
||||
}
|
||||
|
||||
/// @notice Deserializes the encoded public values into their uint32 components.
|
||||
/// @param publicValues The encoded public values.
|
||||
function deserializePublicValues(
|
||||
bytes memory publicValues
|
||||
) public pure returns (uint32 val1, uint32 val2, uint32 val3) {
|
||||
require(
|
||||
publicValues.length == 12,
|
||||
"public values must be exactly 12 bytes long"
|
||||
);
|
||||
val1 = readUint32(publicValues, 0);
|
||||
val2 = readUint32(publicValues, 4);
|
||||
val3 = readUint32(publicValues, 8);
|
||||
}
|
||||
|
||||
/// @notice Reads a uint32 from the given data.
|
||||
/// @param data The data to convert.
|
||||
/// @param startIndex The index to start converting from.
|
||||
function readUint32(
|
||||
bytes memory data,
|
||||
uint startIndex
|
||||
) internal pure returns (uint32) {
|
||||
uint32 value;
|
||||
assembly {
|
||||
value := mload(add(data, add(startIndex, 4)))
|
||||
}
|
||||
return
|
||||
uint32(
|
||||
(value & 0xFF) *
|
||||
0x1000000 +
|
||||
((value >> 8) & 0xFF) *
|
||||
0x10000 +
|
||||
((value >> 16) & 0xFF) *
|
||||
0x100 +
|
||||
((value >> 24) & 0xFF)
|
||||
);
|
||||
}
|
||||
|
||||
/// @notice The entrypoint for verifying the proof of a fibonacci number.
|
||||
/// @param proof The encoded proof.
|
||||
/// @param publicValues The encoded public values.
|
||||
function verifyFibonacciProof(
|
||||
bytes memory proof,
|
||||
bytes memory publicValues
|
||||
) public view returns (uint32, uint32, uint32) {
|
||||
this.verifyProof(fibonacciProgramVkey, publicValues, proof);
|
||||
(uint32 n, uint32 a, uint32 b) = deserializePublicValues(publicValues);
|
||||
return (n, a, b);
|
||||
}
|
||||
}
|
||||
682
contracts/src/Groth16Verifier.sol
Normal file
682
contracts/src/Groth16Verifier.sol
Normal file
@@ -0,0 +1,682 @@
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/// @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 Verifier {
|
||||
|
||||
/// 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();
|
||||
/// The commitment is invalid
|
||||
/// @dev This can mean that provided commitment points and/or proof of knowledge are not on their
|
||||
/// curves, that pairing equation fails, or that the commitment and/or proof of knowledge is not for the
|
||||
/// commitment key.
|
||||
error CommitmentInvalid();
|
||||
|
||||
// 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 = 4526104477327034896484230421755646887971286435023365757616534777900267317123;
|
||||
uint256 constant ALPHA_Y = 8738707337436048873623821578902282097761060888707272574781321194825761752618;
|
||||
|
||||
// Groth16 beta point in G2 in powers of i
|
||||
uint256 constant BETA_NEG_X_0 = 5577751547843425983375915620633188186279047588373270636642744753092896599070;
|
||||
uint256 constant BETA_NEG_X_1 = 7276760880400875404073798822403924640641812282506485429473323832691113687363;
|
||||
uint256 constant BETA_NEG_Y_0 = 2319701350835483992547815279611442893981145381201061417996315038305632718777;
|
||||
uint256 constant BETA_NEG_Y_1 = 1155377988896171079524332722685100560033340209672545558309068908824660704308;
|
||||
|
||||
// Groth16 gamma point in G2 in powers of i
|
||||
uint256 constant GAMMA_NEG_X_0 = 9534361694683421810142758454632405976804477541619096742703542699640904049581;
|
||||
uint256 constant GAMMA_NEG_X_1 = 13636085034085327719643001541944244445265579133983458912521871165901683201268;
|
||||
uint256 constant GAMMA_NEG_Y_0 = 2173335513169042360982704720609540731389857621420434146967231057399254491707;
|
||||
uint256 constant GAMMA_NEG_Y_1 = 16792269951642013666806808624437407208161783613324912411561093211308829325548;
|
||||
|
||||
// Groth16 delta point in G2 in powers of i
|
||||
uint256 constant DELTA_NEG_X_0 = 18826016791288733846356863223038153145494367027154460536958615970968521515831;
|
||||
uint256 constant DELTA_NEG_X_1 = 14041918728835196545933305527436346435091240279457788938479101101856595490950;
|
||||
uint256 constant DELTA_NEG_Y_0 = 18407065829347926860699462640092447426946928443255719130645016117732021664268;
|
||||
uint256 constant DELTA_NEG_Y_1 = 15649047877482352021526697104957479516321402601808232197956412786617566172577;
|
||||
// Pedersen G point in G2 in powers of i
|
||||
uint256 constant PEDERSEN_G_X_0 = 16653027608501292894286732588878430999721912413395444127609118783528400229739;
|
||||
uint256 constant PEDERSEN_G_X_1 = 275576631929374646227652924609750629035825834003109148344196168793245299496;
|
||||
uint256 constant PEDERSEN_G_Y_0 = 7872349479413906103084301913862614554361639524303124606540079655621729581922;
|
||||
uint256 constant PEDERSEN_G_Y_1 = 5106783638917278090498702579869382439582329843263006778631919947393659586894;
|
||||
|
||||
// Pedersen GRootSigmaNeg point in G2 in powers of i
|
||||
uint256 constant PEDERSEN_GROOTSIGMANEG_X_0 = 15620041586864285807440513638163691068463254874665486441504297219491652749451;
|
||||
uint256 constant PEDERSEN_GROOTSIGMANEG_X_1 = 2197360486514214480070964903857463192665111296964844891060264052345099911874;
|
||||
uint256 constant PEDERSEN_GROOTSIGMANEG_Y_0 = 19098984693004127042465125993821749592629958097037392454289422609157636945927;
|
||||
uint256 constant PEDERSEN_GROOTSIGMANEG_Y_1 = 765199440690474748070940423016336621514515421165752794424426386637791805593;
|
||||
|
||||
// Constant and public input points
|
||||
uint256 constant CONSTANT_X = 14897434611156867437338332704102475469362740729876210138145369333607884671552;
|
||||
uint256 constant CONSTANT_Y = 19273056782990880542169777128627146153567429363440087800275273812165673602284;
|
||||
uint256 constant PUB_0_X = 8461962802893801455386781180914735114764467566206598403092079982236188247330;
|
||||
uint256 constant PUB_0_Y = 16762808316599535562609939665924810213695287816764967979850576810572632364509;
|
||||
uint256 constant PUB_1_X = 21435975481417343315657036755139205119342684018415778752992998138443810448340;
|
||||
uint256 constant PUB_1_Y = 11289329903751865544784568948199587882077939390709622088675301590238060293189;
|
||||
uint256 constant PUB_2_X = 3484188080676166780086644825247272278115506935613373999583732390870907771509;
|
||||
uint256 constant PUB_2_Y = 14314336212845534479822464205329461077191620768471345878667000861148787329575;
|
||||
|
||||
/// 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 wheter 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.
|
||||
/// @param publicCommitments public inputs generated from pedersen commitments.
|
||||
/// @param commitments The Pedersen commitments from the proof.
|
||||
/// @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,
|
||||
uint256[1] memory publicCommitments,
|
||||
uint256[2] memory commitments
|
||||
)
|
||||
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)
|
||||
success := and(success, staticcall(gas(), PRECOMPILE_ADD, commitments, 64, g, 0x40))
|
||||
success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40))
|
||||
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))
|
||||
mstore(g, PUB_2_X)
|
||||
mstore(add(g, 0x20), PUB_2_Y)
|
||||
s := mload(publicCommitments)
|
||||
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.
|
||||
/// @param commitments Pedersen commitments from the proof.
|
||||
/// @param commitmentPok proof of knowledge for the Pedersen commitments.
|
||||
/// @return compressed The compressed proof. Elements are in the same order as for
|
||||
/// verifyCompressedProof. I.e. points (A, B, C) in compressed format.
|
||||
/// @return compressedCommitments compressed Pedersen commitments from the proof.
|
||||
/// @return compressedCommitmentPok compressed proof of knowledge for the Pedersen commitments.
|
||||
function compressProof(
|
||||
uint256[8] calldata proof,
|
||||
uint256[2] calldata commitments,
|
||||
uint256[2] calldata commitmentPok
|
||||
)
|
||||
public view returns (
|
||||
uint256[4] memory compressed,
|
||||
uint256[1] memory compressedCommitments,
|
||||
uint256 compressedCommitmentPok
|
||||
) {
|
||||
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]);
|
||||
compressedCommitments[0] = compress_g1(commitments[0], commitments[1]);
|
||||
compressedCommitmentPok = compress_g1(commitmentPok[0], commitmentPok[1]);
|
||||
}
|
||||
|
||||
/// 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 compressedCommitments compressed Pedersen commitments from the proof.
|
||||
/// @param compressedCommitmentPok compressed proof of knowledge for the Pedersen commitments.
|
||||
/// @param input the public input field elements in the scalar field Fr.
|
||||
/// Elements must be reduced.
|
||||
function verifyCompressedProof(
|
||||
uint256[4] calldata compressedProof,
|
||||
uint256[1] calldata compressedCommitments,
|
||||
uint256 compressedCommitmentPok,
|
||||
uint256[2] calldata input
|
||||
) public view {
|
||||
uint256[1] memory publicCommitments;
|
||||
uint256[2] memory commitments;
|
||||
uint256[24] memory pairings;
|
||||
{
|
||||
(commitments[0], commitments[1]) = decompress_g1(compressedCommitments[0]);
|
||||
(uint256 Px, uint256 Py) = decompress_g1(compressedCommitmentPok);
|
||||
|
||||
uint256[] memory publicAndCommitmentCommitted;
|
||||
|
||||
publicCommitments[0] = uint256(
|
||||
sha256(
|
||||
abi.encodePacked(
|
||||
commitments[0],
|
||||
commitments[1],
|
||||
publicAndCommitmentCommitted
|
||||
)
|
||||
)
|
||||
) % R;
|
||||
// Commitments
|
||||
pairings[ 0] = commitments[0];
|
||||
pairings[ 1] = commitments[1];
|
||||
pairings[ 2] = PEDERSEN_G_X_1;
|
||||
pairings[ 3] = PEDERSEN_G_X_0;
|
||||
pairings[ 4] = PEDERSEN_G_Y_1;
|
||||
pairings[ 5] = PEDERSEN_G_Y_0;
|
||||
pairings[ 6] = Px;
|
||||
pairings[ 7] = Py;
|
||||
pairings[ 8] = PEDERSEN_GROOTSIGMANEG_X_1;
|
||||
pairings[ 9] = PEDERSEN_GROOTSIGMANEG_X_0;
|
||||
pairings[10] = PEDERSEN_GROOTSIGMANEG_Y_1;
|
||||
pairings[11] = PEDERSEN_GROOTSIGMANEG_Y_0;
|
||||
|
||||
// Verify pedersen commitments
|
||||
bool success;
|
||||
assembly ("memory-safe") {
|
||||
let f := mload(0x40)
|
||||
|
||||
success := staticcall(gas(), PRECOMPILE_VERIFY, pairings, 0x180, f, 0x20)
|
||||
success := and(success, mload(f))
|
||||
}
|
||||
if (!success) {
|
||||
revert CommitmentInvalid();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
(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,
|
||||
publicCommitments,
|
||||
commitments
|
||||
);
|
||||
|
||||
// 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 commitments the Pedersen commitments from the proof.
|
||||
/// @param commitmentPok the proof of knowledge for the Pedersen commitments.
|
||||
/// @param input the public input field elements in the scalar field Fr.
|
||||
/// Elements must be reduced.
|
||||
function verifyProof(
|
||||
uint256[8] calldata proof,
|
||||
uint256[2] calldata commitments,
|
||||
uint256[2] calldata commitmentPok,
|
||||
uint256[2] calldata input
|
||||
) public view {
|
||||
// HashToField
|
||||
uint256[1] memory publicCommitments;
|
||||
uint256[] memory publicAndCommitmentCommitted;
|
||||
|
||||
publicCommitments[0] = uint256(
|
||||
sha256(
|
||||
abi.encodePacked(
|
||||
commitments[0],
|
||||
commitments[1],
|
||||
publicAndCommitmentCommitted
|
||||
)
|
||||
)
|
||||
) % R;
|
||||
|
||||
// Verify pedersen commitments
|
||||
bool success;
|
||||
assembly ("memory-safe") {
|
||||
let f := mload(0x40)
|
||||
|
||||
calldatacopy(f, commitments, 0x40) // Copy Commitments
|
||||
mstore(add(f, 0x40), PEDERSEN_G_X_1)
|
||||
mstore(add(f, 0x60), PEDERSEN_G_X_0)
|
||||
mstore(add(f, 0x80), PEDERSEN_G_Y_1)
|
||||
mstore(add(f, 0xa0), PEDERSEN_G_Y_0)
|
||||
calldatacopy(add(f, 0xc0), commitmentPok, 0x40)
|
||||
mstore(add(f, 0x100), PEDERSEN_GROOTSIGMANEG_X_1)
|
||||
mstore(add(f, 0x120), PEDERSEN_GROOTSIGMANEG_X_0)
|
||||
mstore(add(f, 0x140), PEDERSEN_GROOTSIGMANEG_Y_1)
|
||||
mstore(add(f, 0x160), PEDERSEN_GROOTSIGMANEG_Y_0)
|
||||
|
||||
success := staticcall(gas(), PRECOMPILE_VERIFY, f, 0x180, f, 0x20)
|
||||
success := and(success, mload(f))
|
||||
}
|
||||
if (!success) {
|
||||
revert CommitmentInvalid();
|
||||
}
|
||||
|
||||
(uint256 x, uint256 y) = publicInputMSM(
|
||||
input,
|
||||
publicCommitments,
|
||||
commitments
|
||||
);
|
||||
|
||||
// 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.
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
96
contracts/src/SP1Verifier.sol
Normal file
96
contracts/src/SP1Verifier.sol
Normal file
@@ -0,0 +1,96 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import {Verifier} from "./Groth16Verifier.sol";
|
||||
|
||||
/// @title SP1 Verifier
|
||||
/// @author Succinct Labs
|
||||
/// @notice This contracts implements a solidity verifier for SP1.
|
||||
contract SP1Verifier is Verifier {
|
||||
/// @notice Deserializes a proof from the given bytes.
|
||||
/// @param proofBytes The proof bytes.
|
||||
function deserializeProof(
|
||||
bytes memory proofBytes
|
||||
)
|
||||
public
|
||||
pure
|
||||
returns (
|
||||
uint256[8] memory proof,
|
||||
uint256[2] memory commitments,
|
||||
uint256[2] memory commitmentPok
|
||||
)
|
||||
{
|
||||
require(
|
||||
proofBytes.length == 8 * 32 + 4 + 2 * 32 + 2 * 32,
|
||||
"invalid proof bytes length"
|
||||
);
|
||||
|
||||
uint256 offset = 32;
|
||||
for (uint256 i = 0; i < 8; i++) {
|
||||
assembly {
|
||||
mstore(
|
||||
add(proof, add(0, mul(32, i))),
|
||||
mload(add(proofBytes, add(offset, mul(32, i))))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
uint32 commitmentCount;
|
||||
offset += 8 * 32;
|
||||
assembly {
|
||||
let dataLocation := add(proofBytes, offset)
|
||||
let loadedData := mload(dataLocation)
|
||||
commitmentCount := and(shr(224, loadedData), 0xFFFFFFFF)
|
||||
}
|
||||
|
||||
offset += 4;
|
||||
for (uint256 i = 0; i < 2; i++) {
|
||||
assembly {
|
||||
mstore(
|
||||
add(commitments, add(0, mul(32, i))),
|
||||
mload(add(proofBytes, add(offset, mul(32, i))))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
offset += 2 * 32;
|
||||
for (uint256 i = 0; i < 2; i++) {
|
||||
assembly {
|
||||
mstore(
|
||||
add(commitmentPok, add(0, mul(32, i))),
|
||||
mload(add(proofBytes, add(offset, mul(32, i))))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Hashes the public values to a field elements inside Bn254.
|
||||
/// @param publicValues The public values.
|
||||
function hashPublicValues(
|
||||
bytes memory publicValues
|
||||
) public pure returns (bytes32) {
|
||||
return sha256(publicValues) & bytes32(uint256((1 << 254) - 1));
|
||||
}
|
||||
|
||||
/// @notice Verifies a proof with given public values and vkey.
|
||||
/// @param vkey 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 vkey,
|
||||
bytes memory publicValues,
|
||||
bytes memory proofBytes
|
||||
) public view {
|
||||
(
|
||||
uint256[8] memory proof,
|
||||
uint256[2] memory commitments,
|
||||
uint256[2] memory commitmentPok
|
||||
) = deserializeProof(proofBytes);
|
||||
bytes32 publicValuesDigest = hashPublicValues(publicValues);
|
||||
uint256[2] memory inputs = [
|
||||
uint256(vkey),
|
||||
uint256(publicValuesDigest)
|
||||
];
|
||||
this.verifyProof(proof, commitments, commitmentPok, inputs);
|
||||
}
|
||||
}
|
||||
1
contracts/src/fixtures/fixture.json
Normal file
1
contracts/src/fixtures/fixture.json
Normal file
@@ -0,0 +1 @@
|
||||
{"a":1268,"b":1926,"n":500,"vkey":"0x008263b55c318397bcce885362e22d34b9134afdd7ec77fdf7ebf6083f8f8304","publicValues":"0xf4010000f404000086070000","proof":"0x09abe9e5e7a30ad0cbfaa0cf96179aa028ff1a3c7114fdad3f31e9be555efd870ec0c56f5ba197d74f51ccc7de0dbea5aeb215964aea5ff299240b1ceca870071f4ea92eb79425bd7f59152b0eabd8ec6205a90b07a46660fbdffc8f395b0bde2f5fc94afc46685f6c7882921b4d078e555c2ebb2a37f8927c6cde98e768e0180dc22ac09491112eef1fc52c75c4008dbedccb8694c67f6658fb141e05891f1508a1e24cb4211251a0b6c76ed0ceb2e9ad61978b6d4af85ad3899ba368856b1126840a0935e44b381f50239d768b89213247e17317dcd7f538b324534edf6795302fc358b7142b38e00811741383b67382ec156ac215b48d176f1882bdb0450400000001154f697b7cc5c5af96c13df5f8d8567a36a92af85ffe11377148af127967a1a7152019951c6a2c1a3471b17cb5f3d4cbb6ed5447adb24d82f36b041c27a021a91bb239ee03e68fe20a04c9698dba57c1142af5e03de4b523c5b5731d15d1e64a0695a8eac45df57ded8ec3b23519b9262246895bfb382afb2da828cab06ab87c"}
|
||||
54
contracts/test/Fibonacci.t.sol
Normal file
54
contracts/test/Fibonacci.t.sol
Normal file
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import {Test, console} from "forge-std/Test.sol";
|
||||
import {stdJson} from "forge-std/StdJson.sol";
|
||||
import {Fibonacci} from "../src/Fibonacci.sol";
|
||||
import {Verifier} from "../src/Groth16Verifier.sol";
|
||||
|
||||
struct SP1ProofFixtureJson {
|
||||
uint32 a;
|
||||
uint32 b;
|
||||
uint32 n;
|
||||
bytes proof;
|
||||
bytes publicValues;
|
||||
bytes32 vkey;
|
||||
}
|
||||
|
||||
contract FibonacciTest is Test {
|
||||
using stdJson for string;
|
||||
|
||||
Fibonacci public fibonacci;
|
||||
|
||||
function loadFixture() public view returns (SP1ProofFixtureJson memory) {
|
||||
string memory root = vm.projectRoot();
|
||||
string memory path = string.concat(root, "/src/fixtures/fixture.json");
|
||||
string memory json = vm.readFile(path);
|
||||
bytes memory jsonBytes = json.parseRaw(".");
|
||||
return abi.decode(jsonBytes, (SP1ProofFixtureJson));
|
||||
}
|
||||
|
||||
function setUp() public {
|
||||
SP1ProofFixtureJson memory fixture = loadFixture();
|
||||
fibonacci = new Fibonacci(fixture.vkey);
|
||||
}
|
||||
|
||||
function test_ValidFibonacciProof() public view {
|
||||
SP1ProofFixtureJson memory fixture = loadFixture();
|
||||
(uint32 n, uint32 a, uint32 b) = fibonacci.verifyFibonacciProof(
|
||||
fixture.proof,
|
||||
fixture.publicValues
|
||||
);
|
||||
assert(n == fixture.n);
|
||||
assert(a == fixture.a);
|
||||
assert(b == fixture.b);
|
||||
}
|
||||
|
||||
function testFail_InvalidFibonacciProof() public view {
|
||||
SP1ProofFixtureJson memory fixture = loadFixture();
|
||||
fibonacci.verifyFibonacciProof(
|
||||
fixture.publicValues,
|
||||
fixture.publicValues
|
||||
);
|
||||
}
|
||||
}
|
||||
1366
program/Cargo.lock
generated
Normal file
1366
program/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
program/Cargo.toml
Normal file
19
program/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[workspace]
|
||||
[package]
|
||||
version = "0.1.0"
|
||||
name = "fibonacci-program"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
sp1-zkvm = { git = "https://github.com/succinctlabs/sp1.git" }
|
||||
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
|
||||
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
tendermint-light-client-verifier = { version = "0.35.0", default-features = false, features = [
|
||||
"rust-crypto",
|
||||
] }
|
||||
serde_cbor = "0.11.2"
|
||||
|
||||
[patch.crates-io]
|
||||
sha2-v0-9-8 = { git = "https://github.com/sp1-patches/RustCrypto-hashes", package = "sha2", branch = "patch-v0.9.8" }
|
||||
sha2-v0-10-8 = { git = "https://github.com/sp1-patches/RustCrypto-hashes", package = "sha2", branch = "patch-v0.10.8" }
|
||||
ed25519-consensus = { git = "https://github.com/sp1-patches/ed25519-consensus", branch = "patch-v2.1.0" }
|
||||
BIN
program/elf/riscv32im-succinct-zkvm-elf
Executable file
BIN
program/elf/riscv32im-succinct-zkvm-elf
Executable file
Binary file not shown.
42
program/src/main.rs
Normal file
42
program/src/main.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
//! A simple program that takes a number `n` as input, and writes the `n-1`th and `n`th fibonacci
|
||||
//! number as an output.
|
||||
|
||||
// These two lines are necessary for the program to properly compile.
|
||||
//
|
||||
// Under the hood, we wrap your main function with some extra code so that it behaves properly
|
||||
// inside the zkVM.
|
||||
#![no_main]
|
||||
sp1_zkvm::entrypoint!(main);
|
||||
|
||||
pub fn main() {
|
||||
// Read an input to the program.
|
||||
//
|
||||
// Behind the scenes, this compiles down to a custom system call which handles reading inputs
|
||||
// from the prover.
|
||||
let n = sp1_zkvm::io::read::<u32>();
|
||||
|
||||
// Write n to public input
|
||||
sp1_zkvm::io::commit(&n);
|
||||
|
||||
// Compute the n'th fibonacci number, using normal Rust code.
|
||||
let mut a = 0u32;
|
||||
let mut b = 1u32;
|
||||
for _ in 0..n {
|
||||
let mut c = a + b;
|
||||
c %= 7919; // Modulus to prevent overflow.
|
||||
a = b;
|
||||
b = c;
|
||||
}
|
||||
|
||||
// Write the output of the program.
|
||||
//
|
||||
// Behind the scenes, this also compiles down to a custom system call which handles writing
|
||||
// outputs to the prover.
|
||||
sp1_zkvm::io::commit(&a);
|
||||
sp1_zkvm::io::commit(&b);
|
||||
|
||||
// Print out the commited values.
|
||||
println!("n: {}", n);
|
||||
println!("a: {}", a);
|
||||
println!("b: {}", b);
|
||||
}
|
||||
3
rust-toolchain
Normal file
3
rust-toolchain
Normal file
@@ -0,0 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2024-04-17"
|
||||
components = ["llvm-tools", "rustc-dev"]
|
||||
7049
script/Cargo.lock
generated
Normal file
7049
script/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
script/Cargo.toml
Normal file
40
script/Cargo.toml
Normal file
@@ -0,0 +1,40 @@
|
||||
[package]
|
||||
version = "0.1.0"
|
||||
name = "fibonacci-script"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "prove"
|
||||
path = "src/bin/prove.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "export"
|
||||
path = "src/bin/export.rs"
|
||||
|
||||
[dependencies]
|
||||
alloy = { git = "https://github.com/alloy-rs/alloy", features = ["sol-types"] }
|
||||
sp1-sdk = { path = "../../sp1/sdk" }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
|
||||
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
tendermint = { version = "0.35.0", default-features = false }
|
||||
tendermint-light-client-verifier = { version = "0.35.0", default-features = false, features = [
|
||||
"rust-crypto",
|
||||
] }
|
||||
bincode = "1.3.3"
|
||||
itertools = "0.12.1"
|
||||
serde_cbor = "0.11.2"
|
||||
sha2 = "0.10.8"
|
||||
dotenv = "0.15.0"
|
||||
subtle-encoding = "0.5.1"
|
||||
ethers = "2.0.14"
|
||||
anyhow = "1.0.82"
|
||||
clap = { version = "4.0", features = ["derive", "env"] }
|
||||
log = "0.4.21"
|
||||
async-trait = "0.1.80"
|
||||
hex = "0.4.3"
|
||||
tracing = "0.1.40"
|
||||
|
||||
[build-dependencies]
|
||||
sp1-helper = { git = "https://github.com/succinctlabs/sp1.git", rev = "277f1b4cfee5129bd40d74748f3d241cdfa56e63" }
|
||||
5
script/build.rs
Normal file
5
script/build.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use sp1_helper::build_program;
|
||||
|
||||
fn main() {
|
||||
build_program("../program")
|
||||
}
|
||||
3
script/rust-toolchain
Normal file
3
script/rust-toolchain
Normal file
@@ -0,0 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2024-04-17"
|
||||
components = ["llvm-tools", "rustc-dev"]
|
||||
17
script/src/bin/export.rs
Normal file
17
script/src/bin/export.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub const FIBONACCI_ELF: &[u8] = include_bytes!("../../../program/elf/riscv32im-succinct-zkvm-elf");
|
||||
|
||||
/// Builds the proving artifacts from scratch and exports the solidity verifier.
|
||||
fn main() {
|
||||
sp1_sdk::utils::setup_logger();
|
||||
|
||||
tracing::info!("building groth16 artifacts");
|
||||
let artifacts_dir = sp1_sdk::artifacts::get_groth16_artifacts_dir();
|
||||
sp1_sdk::artifacts::build_groth16_artifacts_with_dummy(artifacts_dir);
|
||||
|
||||
tracing::info!("exporting groth16 verifier");
|
||||
let contracts_src_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../contracts/src");
|
||||
sp1_sdk::artifacts::export_solidity_groth16_verifier(contracts_src_dir)
|
||||
.expect("failed to export verifier");
|
||||
}
|
||||
104
script/src/bin/prove.rs
Normal file
104
script/src/bin/prove.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
//! An end-to-end example of using the SP1 SDK to generate a proof of a program that can be verified
|
||||
//! on-chain.
|
||||
//!
|
||||
//! You can run this script using the following command:
|
||||
//! ```shell
|
||||
//! RUST_LOG=info cargo run --package fibonacci-script --bin prove --release
|
||||
//! ```
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp1_sdk::{HashableKey, ProverClient, SP1Stdin};
|
||||
|
||||
/// The ELF (executable and linkable format) file for the Succinct RISC-V zkVM.
|
||||
///
|
||||
/// This file is generated by running `cargo prove build` inside the `program` directory.
|
||||
pub const FIBONACCI_ELF: &[u8] = include_bytes!("../../../program/elf/riscv32im-succinct-zkvm-elf");
|
||||
|
||||
/// The arguments for the prove command.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
struct ProveArgs {
|
||||
#[clap(long, default_value = "500")]
|
||||
n: u32,
|
||||
|
||||
#[clap(long, default_value = "../contracts/fixtures")]
|
||||
fixture_path: String,
|
||||
}
|
||||
|
||||
/// A fixture that can be used to test the verification of SP1 zkVM proofs inside Solidity.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct SP1FibonacciProofFixture {
|
||||
a: u32,
|
||||
b: u32,
|
||||
n: u32,
|
||||
vkey: String,
|
||||
public_values: String,
|
||||
proof: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Setup the logger.
|
||||
sp1_sdk::utils::setup_logger();
|
||||
|
||||
// Parse the command line arguments.
|
||||
let args = ProveArgs::parse();
|
||||
|
||||
// Setup the prover client.
|
||||
let client = ProverClient::new();
|
||||
|
||||
// Setup the program.
|
||||
let (pk, vk) = client.setup(FIBONACCI_ELF);
|
||||
|
||||
// Setup the inputs.;
|
||||
let mut stdin = SP1Stdin::new();
|
||||
stdin.write(&args.n);
|
||||
|
||||
// Generate the proof.
|
||||
let mut proof = client
|
||||
.prove_groth16(&pk, stdin)
|
||||
.expect("failed to generate proof");
|
||||
|
||||
// Read the public values from the proof.
|
||||
let n: u32 = proof.public_values.read();
|
||||
let a: u32 = proof.public_values.read();
|
||||
let b: u32 = proof.public_values.read();
|
||||
|
||||
// Create the testing fixture so we can test things end-ot-end.
|
||||
let fixture = SP1FibonacciProofFixture {
|
||||
a,
|
||||
b,
|
||||
n,
|
||||
vkey: vk.bytes32().to_string(),
|
||||
public_values: proof.public_values.bytes().to_string(),
|
||||
proof: proof.bytes().to_string(),
|
||||
};
|
||||
|
||||
// The verification key is used to verify that the proof corresponds to the execution of the
|
||||
// program on the given input.
|
||||
//
|
||||
// Note that the verification key stays the same regardless of the input.
|
||||
println!("Verification Key: {}", fixture.vkey);
|
||||
|
||||
// The public values are the values whicha are publically commited to by the zkVM.
|
||||
//
|
||||
// If you need to expose the inputs or outputs of your program, you should commit them in
|
||||
// the public values.
|
||||
println!("Public Values: {}", fixture.public_values);
|
||||
|
||||
// The proof proves to the verifier that the program was executed with some inputs that led to
|
||||
// the give public values.
|
||||
println!("Proof Bytes: {}", fixture.proof);
|
||||
|
||||
// Save the fixture to a file.
|
||||
let fixture_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../contracts/src/fixtures");
|
||||
std::fs::create_dir_all(&fixture_path).expect("failed to create fixture path");
|
||||
std::fs::write(
|
||||
fixture_path.join("fixture.json"),
|
||||
serde_json::to_string_pretty(&fixture).unwrap(),
|
||||
)
|
||||
.expect("failed to write fixture");
|
||||
}
|
||||
Reference in New Issue
Block a user