Skip to content

Commit

Permalink
integrate Jolt as secondary prover (nexus-xyz#159)
Browse files Browse the repository at this point in the history
* implement jolt wrapper

* integrate jolt into tools

* clippy

* cleanup

* consts

* bump upstream

* switch to nexus fork

* default to nova

* update docs
  • Loading branch information
slumber authored May 29, 2024
1 parent 5820a3d commit bc517fe
Show file tree
Hide file tree
Showing 34 changed files with 977 additions and 58 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ members = [
"nova",
"spartan",
"api",
"jolt",
]
default-members = [
"riscv",
Expand Down
10 changes: 7 additions & 3 deletions api/examples/prover_run.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
// An example of loading and running the NVM.

use nexus_api::{
config::vm::{NovaImpl, VmConfig},
config::vm::{ProverImpl, VmConfig},
nvm::{self, memory::MerkleTrie, NexusVM},
prover::{self},
riscv::{self},
};
use nexus_config::vm::NovaImpl;
use std::path::PathBuf;

const CONFIG: VmConfig = VmConfig { k: 1, nova_impl: NovaImpl::Sequential };
const CONFIG: VmConfig = VmConfig {
k: 1,
prover: ProverImpl::Nova(NovaImpl::Sequential),
};

fn main() {
// expects example programs (`nexus-zkvm/examples`) to have been built with `cargo build -r`
Expand All @@ -29,7 +33,7 @@ fn main() {
let trace = nvm::interactive::trace(
&mut vm,
CONFIG.k,
matches!(CONFIG.nova_impl, NovaImpl::Parallel),
matches!(CONFIG.prover, ProverImpl::Nova(NovaImpl::Parallel)),
)
.expect("error generating execution trace");
println!("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
Expand Down
2 changes: 2 additions & 0 deletions config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ categories = { workspace = true }
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_wrapper = { path = "./serde_wrapper" }
serde-untagged = "0.1.6"

config = { version = "0.13.4", default-features = false }

url = { version = "2.5.0", features = ["serde"] }
Expand Down
4 changes: 2 additions & 2 deletions config/bases/vm.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[vm]
k = 16 # number of NexusVM instructions proved per Nova folding step.
nova_impl = "seq" # IVC implementation to be used, should be one of "seq" (sequential), "par" (parallel).
k = 16 # number of NexusVM instructions proved per Nova folding step.
prover = "nova-seq" # IVC implementation to be used, should be one of "nova-seq" (sequential), "nova-par" (parallel).
4 changes: 3 additions & 1 deletion config/serde_wrapper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ pub fn derive_deserialize(input: TokenStream) -> TokenStream {
#[::serde_wrapper::black_hole]
}
.into();
derive_input.attrs = parse_macro_input!(attrs with syn::Attribute::parse_outer);
let new_attrs = parse_macro_input!(attrs with syn::Attribute::parse_outer);
let old_attrs = &derive_input.attrs[..];
derive_input.attrs = [&new_attrs[..], old_attrs].concat();

let struct_item = match &mut derive_input.data {
syn::Data::Struct(item) => item,
Expand Down
95 changes: 84 additions & 11 deletions config/src/vm.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,108 @@
use std::fmt;

use serde::de;
use serde_untagged::UntaggedEnumVisitor;

use super::Config;

#[derive(serde_wrapper::Deserialize)]
pub struct VmConfig {
pub k: usize,
pub nova_impl: NovaImpl,
pub prover: ProverImpl,
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ProverImpl {
Jolt,

Nova(NovaImpl),
}

#[derive(Debug, Copy, Clone, PartialEq, serde_wrapper::Deserialize)]
#[cfg_attr(feature = "clap_derive", derive(clap::ValueEnum))]
pub enum NovaImpl {
#[serde(rename = "seq")]
#[cfg_attr(feature = "clap_derive", clap(name = "seq"))]
#[serde(rename = "nova-seq")]
#[cfg_attr(feature = "clap_derive", clap(name = "nova-seq"))]
Sequential,

#[serde(rename = "par")]
#[cfg_attr(feature = "clap_derive", clap(name = "par"))]
#[serde(rename = "nova-par")]
#[cfg_attr(feature = "clap_derive", clap(name = "nova-par"))]
Parallel,

#[serde(rename = "par-com")]
#[cfg_attr(feature = "clap_derive", clap(name = "par-com"))]
#[serde(rename = "nova-par-com")]
#[cfg_attr(feature = "clap_derive", clap(name = "nova-par-com"))]
ParallelCompressible,
}

// serde(untagged) errors with clap
impl<'de> de::Deserialize<'de> for ProverImpl {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
UntaggedEnumVisitor::new()
.string(|s| {
Ok(match s {
"jolt" => Self::Jolt,
"nova-seq" => Self::Nova(NovaImpl::Sequential),
"nova-par" => Self::Nova(NovaImpl::Parallel),
"nova-par-com" => Self::Nova(NovaImpl::ParallelCompressible),
_ => {
// the error message starts with "expected ..."
return Err(de::Error::invalid_value(
de::Unexpected::Str(s),
&r#"one of ["jolt", "nova-seq", "nova-par", "nova-par-com"]"#,
));
}
})
})
.deserialize(deserializer)
}
}

impl fmt::Display for ProverImpl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ProverImpl::Jolt => write!(f, "jolt"),
ProverImpl::Nova(nova_impl) => write!(f, "{nova_impl}"),
}
}
}

impl fmt::Display for NovaImpl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NovaImpl::Sequential => write!(f, "seq"),
NovaImpl::Parallel => write!(f, "par"),
NovaImpl::ParallelCompressible => write!(f, "par-com"),
NovaImpl::Sequential => write!(f, "nova-seq"),
NovaImpl::Parallel => write!(f, "nova-par"),
NovaImpl::ParallelCompressible => write!(f, "nova-par-com"),
}
}
}

// `derive(ValueEnum)` only works for enums with unit variants -- needs manual implementation.
#[cfg(feature = "clap_derive")]
mod clap_derive {
use super::{NovaImpl, ProverImpl};
use clap::{builder::PossibleValue, ValueEnum};

impl ValueEnum for ProverImpl {
fn value_variants<'a>() -> &'a [Self] {
&[
Self::Jolt,
Self::Nova(NovaImpl::Sequential),
Self::Nova(NovaImpl::Parallel),
Self::Nova(NovaImpl::ParallelCompressible),
]
}

fn to_possible_value(&self) -> Option<PossibleValue> {
let str = match self {
ProverImpl::Jolt => "jolt",
ProverImpl::Nova(NovaImpl::Sequential) => "nova-seq",
ProverImpl::Nova(NovaImpl::Parallel) => "nova-par",
ProverImpl::Nova(NovaImpl::ParallelCompressible) => "nova-par-com",
};
Some(PossibleValue::new(str))
}
}
}
Expand All @@ -45,7 +118,7 @@ mod tests {
#[test]
fn read_config() {
std::env::set_var("NEXUS_VM_K", "1");
std::env::set_var("NEXUS_VM_NOVAIMPL", "seq");
std::env::set_var("NEXUS_VM_PROVER", "nova-seq");

<VmConfig as Config>::from_env().unwrap();
}
Expand Down
6 changes: 3 additions & 3 deletions docs/pages/zkvm/configuring-prover.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ or distributed. It enables multiple provers to work on the same program in paral
The default one -- used in the quick start -- is sequential. To switch to parallel you can either pass it as a CLI argument:

```shell
cargo nexus prove --impl=par
cargo nexus verify --impl=par # don't forget to specify it for the verifier!
cargo nexus prove --impl=nova-par
cargo nexus verify --impl=nova-par # don't forget to specify it for the verifier!
```

Or simply set an environment variable:

```shell
export NEXUS_VM_NOVAIMPL=par # default is seq
export NEXUS_VM_PROVER=nova-par # default is nova-seq

cargo nexus prove
cargo nexus verify
Expand Down
19 changes: 19 additions & 0 deletions jolt/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "nexus-jolt"
version = "0.1.0"
edition = "2021"

[dependencies]
nexus-riscv = { path = "../riscv" }
nexus-vm = { path = "../vm" }

tracing = { version = "0.1", default-features = false }
thiserror = "1.0"
rayon = "1.8"
strum = "0.25.0"

ark-bn254 = "0.4"

# latest commit on slumber-arkworks-compat
jolt-core = { git = "https://github.com/nexus-xyz/jolt", rev = "dfb9f27" }
jolt-common = { git = "https://github.com/nexus-xyz/jolt", rev = "dfb9f27", package = "common" }
74 changes: 74 additions & 0 deletions jolt/src/convert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//! Conversion between NexusVM and Jolt types.
use nexus_riscv::rv32::{Inst, RV32};

use jolt_common::rv_trace as jolt_rv;

pub fn inst(inst: Inst) -> jolt_rv::ELFInstruction {
jolt_rv::ELFInstruction {
address: inst.pc as u64,
opcode: rv32_opcode(inst.inst),
rs1: inst.inst.rs1().map(Into::into),
rs2: inst.inst.rs2().map(Into::into),
rd: inst.inst.rd().map(Into::into),
imm: inst.inst.imm(),
// unsupported
virtual_sequence_index: None,
}
}

pub fn rv32_opcode(inst: RV32) -> jolt_rv::RV32IM {
use jolt_rv::RV32IM as JoltRV32IM;
use nexus_riscv::rv32::{AOP::*, BOP::*, LOP::*, RV32::*, SOP::*};

match inst {
LUI { .. } => JoltRV32IM::LUI,
AUIPC { .. } => JoltRV32IM::AUIPC,
JAL { .. } => JoltRV32IM::JAL,
JALR { .. } => JoltRV32IM::JALR,

BR { bop: BEQ, .. } => JoltRV32IM::BEQ,
BR { bop: BNE, .. } => JoltRV32IM::BNE,
BR { bop: BLT, .. } => JoltRV32IM::BLT,
BR { bop: BGE, .. } => JoltRV32IM::BGE,
BR { bop: BLTU, .. } => JoltRV32IM::BLTU,
BR { bop: BGEU, .. } => JoltRV32IM::BGEU,

LOAD { lop: LB, .. } => JoltRV32IM::LB,
LOAD { lop: LH, .. } => JoltRV32IM::LH,
LOAD { lop: LW, .. } => JoltRV32IM::LW,
LOAD { lop: LBU, .. } => JoltRV32IM::LBU,
LOAD { lop: LHU, .. } => JoltRV32IM::LHU,

STORE { sop: SB, .. } => JoltRV32IM::SB,
STORE { sop: SH, .. } => JoltRV32IM::SH,
STORE { sop: SW, .. } => JoltRV32IM::SW,

ALUI { aop: ADD, .. } => JoltRV32IM::ADDI,
ALUI { aop: SUB, .. } => JoltRV32IM::ADDI, // note: does not exist
ALUI { aop: SLL, .. } => JoltRV32IM::SLLI,
ALUI { aop: SLT, .. } => JoltRV32IM::SLTI,
ALUI { aop: SLTU, .. } => JoltRV32IM::SLTIU,
ALUI { aop: XOR, .. } => JoltRV32IM::XORI,
ALUI { aop: SRL, .. } => JoltRV32IM::SRLI,
ALUI { aop: SRA, .. } => JoltRV32IM::SRAI,
ALUI { aop: OR, .. } => JoltRV32IM::ORI,
ALUI { aop: AND, .. } => JoltRV32IM::ANDI,

ALU { aop: ADD, .. } => JoltRV32IM::ADD,
ALU { aop: SUB, .. } => JoltRV32IM::SUB,
ALU { aop: SLL, .. } => JoltRV32IM::SLL,
ALU { aop: SLT, .. } => JoltRV32IM::SLT,
ALU { aop: SLTU, .. } => JoltRV32IM::SLTU,
ALU { aop: XOR, .. } => JoltRV32IM::XOR,
ALU { aop: SRL, .. } => JoltRV32IM::SRL,
ALU { aop: SRA, .. } => JoltRV32IM::SRA,
ALU { aop: OR, .. } => JoltRV32IM::OR,
ALU { aop: AND, .. } => JoltRV32IM::AND,

FENCE => JoltRV32IM::FENCE,
ECALL => JoltRV32IM::ECALL,
EBREAK => JoltRV32IM::EBREAK,
UNIMP => JoltRV32IM::UNIMPL,
}
}
20 changes: 20 additions & 0 deletions jolt/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use nexus_riscv::{rv32::RV32, VMError};

use jolt_core::utils::errors::ProofVerifyError;

use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
VM(#[from] VMError),

#[error("Instruction isn't supported: {0}")]
Unsupported(RV32),

#[error(transparent)]
ProofVerify(#[from] ProofVerifyError),

#[error("memory access")]
Memory(#[from] nexus_vm::error::NexusVMError),
}
Loading

0 comments on commit bc517fe

Please sign in to comment.