Skip to content
Merged
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
6 changes: 5 additions & 1 deletion GrothProofs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ julia --project=GrothProofs -e 'using Pkg; Pkg.test()'
```julia
using GrothProofs
r1cs = create_r1cs_example_sum_of_products()
qap = r1cs_to_qap(r1cs)
witness = create_witness_sum_of_products(3, 5, 7, 11)
artifacts = setup(r1cs; prepare_vk=true)
proof = prove(artifacts.pk, artifacts.qap, witness)
public_inputs = public_inputs_from_witness(r1cs, witness)
verify(artifacts.pvk, public_inputs, proof)
```

GrothProofs expects the pairing engine from GrothCurves and the algebra from
Expand Down
108 changes: 108 additions & 0 deletions GrothProofs/src/Groth16.jl
Original file line number Diff line number Diff line change
Expand Up @@ -403,3 +403,111 @@ function verify_with_prepared(pvk::PreparedVerificationKey{E}, proof::Groth16Pro
end

export PreparedVerificationKey, prepare_verifying_key, prepare_inputs, verify_with_prepared

"""
validate_witness_shape(r1cs::R1CS{F}, witness::Witness{F}) where F

Validate witness layout conventions:
- `length(witness.values) == r1cs.num_vars`
- `witness.values[1] == 1`

Throws `ArgumentError` with explicit diagnostics if validation fails.
"""
function validate_witness_shape(r1cs::R1CS{F}, witness::Witness{F}) where F
n = length(witness.values)
if n != r1cs.num_vars
throw(ArgumentError("Expected witness length $(r1cs.num_vars), got $(n)"))
end
if !isone(witness.values[1])
throw(ArgumentError("Witness convention violation: values[1] must be one($F)"))
end
return nothing
end

"""
public_inputs_from_witness(r1cs::R1CS{F}, witness::Witness{F}) where F

Extract arkworks-style public inputs from a witness, excluding the leading
constant `1` at index 1.
"""
function public_inputs_from_witness(r1cs::R1CS{F}, witness::Witness{F}) where F
validate_witness_shape(r1cs, witness)
expected = r1cs.num_public - 1
if expected <= 0
return F[]
end
return copy(witness.values[2:r1cs.num_public])
end

"""
setup(r1cs::R1CS{F}; rng=Random.GLOBAL_RNG, engine=BN254_ENGINE, prepare_vk::Bool=false) where F

High-level Groth16 setup wrapper:
1. Converts `r1cs` to `qap`.
2. Runs trusted setup.
3. Optionally prepares the verifying key.

Returns a named tuple with fields `pk`, `vk`, `qap`, and optionally `pvk`.
"""
function setup(r1cs::R1CS{F}; rng::AbstractRNG=Random.GLOBAL_RNG, engine::AbstractPairingEngine=BN254_ENGINE, prepare_vk::Bool=false) where F
qap = r1cs_to_qap(r1cs)
keypair = setup_full(qap; rng=rng, engine=engine)
if prepare_vk
pvk = prepare_verifying_key(keypair.vk)
return (pk=keypair.pk, vk=keypair.vk, qap=qap, pvk=pvk)
end
return (pk=keypair.pk, vk=keypair.vk, qap=qap)
end

"""
prove(pk::ProvingKey, qap::QAP{F}, witness::Witness{F}; rng=Random.GLOBAL_RNG, debug_no_random=false) where F

High-level proving wrapper over `prove_full`.
"""
function prove(pk::ProvingKey, qap::QAP{F}, witness::Witness{F}; rng::AbstractRNG=Random.GLOBAL_RNG, debug_no_random::Bool=false) where F
if length(witness.values) != qap.num_vars
throw(ArgumentError("Witness length $(length(witness.values)) does not match QAP num_vars $(qap.num_vars)"))
end
if !isone(witness.values[1])
throw(ArgumentError("Witness convention violation: values[1] must be one($F)"))
end
return prove_full(pk, qap, witness; rng=rng, debug_no_random=debug_no_random)
end

"""
process_vk(vk::VerificationKey)

Prepare a verification key for repeated verification.
"""
process_vk(vk::VerificationKey) = prepare_verifying_key(vk)

"""
verify(vk::VerificationKey, public_inputs::Vector{F}, proof::Groth16Proof) where F

High-level verifier wrapper for non-prepared verification keys.
"""
function verify(vk::VerificationKey, public_inputs::Vector{F}, proof::Groth16Proof) where F
return verify_full(vk, proof, public_inputs)
end

"""
verify(pvk::PreparedVerificationKey, public_inputs::Vector{F}, proof::Groth16Proof) where F

High-level verifier wrapper for prepared verification keys.
"""
function verify(pvk::PreparedVerificationKey, public_inputs::Vector{F}, proof::Groth16Proof) where F
prepared = prepare_inputs(pvk, public_inputs)
return verify_with_prepared(pvk, proof, prepared)
end

"""
verify_prepared(pvk::PreparedVerificationKey, prepared_inputs::G1Point, proof::Groth16Proof)

Verify a proof using already prepared public inputs.
"""
function verify_prepared(pvk::PreparedVerificationKey, prepared_inputs::G1Point, proof::Groth16Proof)
return verify_with_prepared(pvk, proof, prepared_inputs)
end

export validate_witness_shape, public_inputs_from_witness
export setup, prove, process_vk, verify, verify_prepared
3 changes: 3 additions & 0 deletions GrothProofs/src/GrothProofs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@ export QAP, r1cs_to_qap, evaluate_qap, compute_h_polynomial
export Groth16Proof
export ProvingKey, VerificationKey, Keypair
export setup_full, prove_full, verify_full
export PreparedVerificationKey, prepare_verifying_key, prepare_inputs, verify_with_prepared
export validate_witness_shape, public_inputs_from_witness
export setup, prove, process_vk, verify, verify_prepared

end
45 changes: 40 additions & 5 deletions GrothProofs/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ end
if !isempty(touched)
bad_inputs = copy(public_inputs)
idx = rand(rng, touched)
bad_inputs[idx - 1] += one(eltype(bad_inputs))
bad_inputs[idx-1] += one(eltype(bad_inputs))
@test !verify_full(keypair.vk, proof, bad_inputs)
end

Expand Down Expand Up @@ -279,10 +279,10 @@ end
w = builder === create_r1cs_example_multiplication ?
create_witness_multiplication(3, 5, 7, 11) :
builder === create_r1cs_example_sum_of_products ?
create_witness_sum_of_products(3, 5, 7, 11) :
builder === create_r1cs_example_affine_product ?
create_witness_affine_product(2, 3, 5, 7) :
create_witness_square_offset(2, 3, 5)
create_witness_sum_of_products(3, 5, 7, 11) :
builder === create_r1cs_example_affine_product ?
create_witness_affine_product(2, 3, 5, 7) :
create_witness_square_offset(2, 3, 5)

# Build combined polynomials
u_poly = zero(qap.u[1])
Expand Down Expand Up @@ -343,3 +343,38 @@ end

@test_throws ErrorException prove_full(keypair.pk, qap, bad_witness; rng=MersenneTwister(1234))
end

@testset "High-level wrapper API and conventions" begin
r1cs = create_r1cs_example_sum_of_products()
witness = create_witness_sum_of_products(3, 5, 7, 11)
@test validate_witness_shape(r1cs, witness) === nothing

public_inputs = public_inputs_from_witness(r1cs, witness)
@test length(public_inputs) == r1cs.num_public - 1

artifacts = setup(r1cs; rng=MersenneTwister(7001), prepare_vk=true)
proof = prove(artifacts.pk, artifacts.qap, witness; rng=MersenneTwister(7002))

@test verify(artifacts.vk, public_inputs, proof)
@test verify(artifacts.pvk, public_inputs, proof)

prepared_inputs = prepare_inputs(artifacts.pvk, public_inputs)
@test verify_prepared(artifacts.pvk, prepared_inputs, proof)

bad_inputs = copy(public_inputs)
bad_inputs[2] += one(eltype(bad_inputs))
@test !verify(artifacts.vk, bad_inputs, proof)
@test !verify(artifacts.pvk, bad_inputs, proof)
end

@testset "High-level wrapper convention failures" begin
r1cs = create_r1cs_example_multiplication()
witness = create_witness_multiplication(2, 3, 5, 7)

wrong_len = Witness(witness.values[1:end-1])
@test_throws ArgumentError validate_witness_shape(r1cs, wrong_len)
@test_throws ArgumentError public_inputs_from_witness(r1cs, wrong_len)

bad_first = Witness(vcat([zero(eltype(witness.values))], witness.values[2:end]))
@test_throws ArgumentError validate_witness_shape(r1cs, bad_first)
end
33 changes: 17 additions & 16 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ DocMeta.setdocmeta!(GrothProofs, :DocTestSetup, :(using GrothProofs); recursive=
const PAGES = [
"Home" => "index.md",
"Getting Started" => "getting-started.md",
"Groth16 End-to-End" => "groth16-e2e.md",
"Packages" => [
"Groth Algebra" => "algebra.md",
"Groth Curves" => "curves.md",
Expand All @@ -25,26 +26,26 @@ const PAGES = [
]

makedocs(
sitename = "Groth.jl",
format = Documenter.HTML(
prettyurls = true,
collapselevel = 1,
inventory_version = "dev",
sitename="Groth.jl",
format=Documenter.HTML(
prettyurls=true,
collapselevel=1,
inventory_version="dev",
),
modules = [GrothAlgebra, GrothCurves, GrothProofs],
pages = PAGES,
clean = true,
checkdocs = :none,
repo = Remotes.GitHub("0xpantera", "Groth.jl"),
meta = Dict(
modules=[GrothAlgebra, GrothCurves, GrothProofs],
pages=PAGES,
clean=true,
checkdocs=:none,
repo=Remotes.GitHub("0xpantera", "Groth.jl"),
meta=Dict(
:description => "Groth.jl provides algebra, curve, and Groth16 proof tooling in Julia.",
),
)

deploydocs(
repo = "github.com/0xpantera/Groth.jl.git",
target = "build",
branch = "gh-pages",
devbranch = "master",
push_preview = true,
repo="github.com/0xpantera/Groth.jl.git",
target="build",
branch="gh-pages",
devbranch="master",
push_preview=true,
)
6 changes: 6 additions & 0 deletions docs/src/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,9 @@ julia --project=. docs/make.jl
julia --project=. benchmarks/run.jl
julia --project=. benchmarks/plot.jl
```

## 6. Follow the end-to-end Groth16 tutorial

After building docs, open the `Groth16 End-to-End` page for the high-level
`setup`/`prove`/`verify` wrapper flow and explicit witness/public-input
conventions.
56 changes: 56 additions & 0 deletions docs/src/groth16-e2e.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Groth16 End-to-End

```@meta
CurrentModule = GrothProofs
DocTestSetup = :(using GrothProofs, GrothAlgebra, GrothCurves, Random)
```

This tutorial shows the high-level Groth16 flow in `GrothProofs`:

1. Build an R1CS and satisfying witness.
2. Run setup.
3. Generate a proof.
4. Verify with both raw and prepared verification keys.
5. Confirm failure on mutated public inputs.

## Conventions

- Witness layout is `[1, public..., private...]`.
- `public_inputs` excludes the leading `1` (arkworks-style).
- `validate_witness_shape` and `public_inputs_from_witness` enforce this contract.
- Current setup/prover randomness samplers are BN254Fr-oriented.
- For production usage, provide a CSPRNG-backed RNG.

## Complete Example

```@example
using GrothProofs
using Random

# 1) Build a circuit and witness.
r1cs = create_r1cs_example_sum_of_products() # r = x*y + z*u
witness = create_witness_sum_of_products(3, 5, 7, 11)
validate_witness_shape(r1cs, witness)

# 2) Setup keys and QAP from the R1CS.
artifacts = setup(r1cs; rng=MersenneTwister(42), prepare_vk=true)

# 3) Prove.
proof = prove(artifacts.pk, artifacts.qap, witness; rng=MersenneTwister(1337))

# 4) Extract public inputs (without the leading constant 1) and verify.
public_inputs = public_inputs_from_witness(r1cs, witness)
ok_unprepared = verify(artifacts.vk, public_inputs, proof)
ok_prepared = verify(artifacts.pvk, public_inputs, proof)

# 5) Mutate one public input and verify rejection.
bad_inputs = copy(public_inputs)
bad_inputs[2] += one(eltype(bad_inputs))
bad_ok = verify(artifacts.vk, bad_inputs, proof)

(ok_unprepared, ok_prepared, bad_ok)
```

Expected result:

- `(true, true, false)`
7 changes: 7 additions & 0 deletions docs/src/proofs.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ verify_full
prepare_verifying_key
prepare_inputs
verify_with_prepared
validate_witness_shape
public_inputs_from_witness
setup
prove
process_vk
verify
verify_prepared
```

### Example
Expand Down