Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions ansible/host_vars/nightly_fake_prover.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ ansible_user: ubuntu
vlayer_release_channel: nightly
vlayer_prover_host: 127.0.0.1
vlayer_prover_port: 3000
vlayer_jwt_claims:
- "sub"
- "environment:test"
prover_nginx_ip_rate_limit_burst: 20
prover_nginx_ip_rate_limit_per_minute: 60
vlayer_alchemy_api_key: !vault |
Expand Down
3 changes: 3 additions & 0 deletions ansible/host_vars/stable_fake_prover.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ ansible_user: ubuntu
vlayer_release_channel: stable
vlayer_prover_host: 127.0.0.1
vlayer_prover_port: 3000
vlayer_jwt_claims:
- "sub"
- "environment:test"
prover_nginx_ip_rate_limit_burst: 20
prover_nginx_ip_rate_limit_per_minute: 60
vlayer_alchemy_api_key: !vault |
Expand Down
3 changes: 3 additions & 0 deletions ansible/host_vars/stable_prod_prover.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ vlayer_release_channel: stable
vlayer_prover_host: 127.0.0.1
vlayer_prover_port: 3000
vlayer_proof_type: groth16
vlayer_jwt_claims:
- "sub"
- "environment:production"
vlayer_bonsai_api_url: "https://api.bonsai.xyz/"
vlayer_bonsai_api_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
Expand Down
1 change: 1 addition & 0 deletions ansible/roles/prover/templates/vlayer.service.j2
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Environment=VLAYER_CHAIN_CLIENT__URL={{ vlayer_prover_chain_proof_url }}
{% endif %}
Environment=VLAYER_AUTH__JWT__PUBLIC_KEY={{ vlayer_jwt_public_key_location }}
Environment=VLAYER_AUTH__JWT__ALGORITHM={{ vlayer_jwt_algorithm }}
Environment='VLAYER_AUTH__JWT__CLAIMS={{ vlayer_jwt_claims | join(' ') }}'
Environment='VLAYER_RPC_URLS={{ vlayer_prover_rpc_urls | join(' ') }}'
ExecStart=/home/{{ ansible_user }}/.vlayer/bin/call_server

Expand Down
9 changes: 9 additions & 0 deletions book/src/appendix/architecture/prover.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ timeout = 240 # Optional timeout in seconds
public_key = "/path/to/signing/key.pem"
algorithm = "rs256" # Signing algorithm to use, defaults to rs256

# Optional list of user-defined JWT claims that the server will validate
[[auth.jwt.claims]]
name = "sub" # Specifying just the name of the claim will assert the claims existence with any value

[[auth.jwt.claims]]
name = "environment"
values = ["test"] # Here, `environment` can only accept a single value of `test` which the server will assert

# Optional gas-meter integration for billing and usage tracking
[gas_meter]
url = "http://localhost:3002"
Expand Down Expand Up @@ -118,6 +126,7 @@ We use `VLAYER_` prefix, and nesting of config options is represented using `__`
|`VLAYER_AUTH__JWT__ALGORITHM` |`auth.jwt.algorithm` |"rs256" |enum |"rs256","rs384","rs512" |
| | | | |"es256","es384","ps256" |
| | | | |"ps384","ps512","eddsa" |
|`VLAYER_AUTH__JWT__CLAIMS` |`auth.jwt.claims` |[] |list | |
|`VLAYER_GAS_METER__URL` |`gas_meter.url` |"http://localhost:3002" |string | |
|`VLAYER_GAS_METER__API_KEY` |`gas_meter.api_key` |"deadbeef" |string | |
|`VLAYER_GAS_METER__TIME_TO_LIVE` |`gas_meter.time_to_live` |3600 |usize | |
Expand Down
2 changes: 1 addition & 1 deletion book/src/appendix/contributing/extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Run the vlayer server:

```sh
cd rust
cargo run --bin call_server --proof fake
cargo run --bin call_server
```

Deploy `WebProofProver` and `WebProofVerifier` contracts on anvil:
Expand Down
2 changes: 1 addition & 1 deletion book/src/appendix/contributing/rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ For guides about the project structure, check out [architecture appendix](/appen
To profile execution of Guest code in zkVM, we leverage the profiling functionality [provided by RISC Zero](https://dev.risczero.com/api/zkvm/profiling). In order to run profiling, follow the steps in the [Usage](https://dev.risczero.com/api/zkvm/profiling#usage) section of the RISC Zero documentation, but in [Step 2](https://dev.risczero.com/api/zkvm/profiling#step-2-running) replace the command you run with:

```sh
RISC0_PPROF_OUT=./profile.pb cargo run --bin call_server --proof fake
RISC0_PPROF_OUT=./profile.pb cargo run --bin call_server
```

which will start the vlayer server. Then just call the JSON RPC API and the server will write the profiling output to `profile.pb`, which can be later [visualised as explained in the RISC Zero Profiling Guide](https://dev.risczero.com/api/zkvm/profiling#step-3-visualization). Please note that the profile only contains data about the Guest execution, i.e. the execution inside the zkVM.
Expand Down
3 changes: 2 additions & 1 deletion rust/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ anyhow = { workspace = true }
clap = { workspace = true, features = ["derive", "env", "string"] }
colored = { workspace = true }
derive-new = { workspace = true }
derive_builder = { workspace = true }
derive_more = { workspace = true }
flate2 = { workspace = true }
jwt = { workspace = true }
Expand All @@ -22,7 +23,7 @@ serde_json = { workspace = true }
serde_yml = { workspace = true }
soldeer-commands = { workspace = true }
soldeer-core = { workspace = true }
strum = { workspace = true }
strum = { workspace = true, features = ["derive"] }
tar = { workspace = true }
tempfile = { workspace = true }
test_runner = { workspace = true }
Expand Down
32 changes: 29 additions & 3 deletions rust/cli/src/commands/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ use std::{

use anyhow::Context;
use clap::{Args as ClapArgs, Parser, Subcommand};
use derive_builder::Builder;
use jwt::{
Claims, ClaimsBuilder, ClaimsBuilderError, DecodingKey, EncodingKey, Environment, Header,
JwtAlgorithm, JwtError, TokenData, Validation, decode, decode_header, encode,
get_current_timestamp,
DecodingKey, EncodingKey, Header, JwtAlgorithm, JwtError, TokenData, Validation, decode,
decode_header, encode, get_current_timestamp,
};
use serde::{Deserialize, Serialize};
use strum::{Display, EnumString};
use thiserror::Error;
use tracing::info;

Expand Down Expand Up @@ -82,6 +84,30 @@ struct Decode {
jwt: String,
}

#[derive(Debug, Clone, Copy, Default, EnumString, Display, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "lowercase")]
pub enum Environment {
#[default]
Test,
Production,
}

#[derive(Builder, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[builder(pattern = "owned")]
pub struct Claims {
#[builder(setter(into, strip_option), default)]
pub host: Option<String>,
#[builder(setter(into, strip_option), default)]
pub port: Option<u16>,
pub exp: u64,
pub sub: String,
#[builder(setter(into), default)]
pub environment: Option<Environment>,
}

pub fn run(args: Args) -> Result<()> {
match args.command {
Command::Encode(enc) => encode_jwt(enc)?,
Expand Down
1 change: 0 additions & 1 deletion rust/jwt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ version = "0.1.0"
edition = "2024"

[dependencies]
derive_builder = { workspace = true }
jsonwebtoken = { workspace = true }
serde = { workspace = true }
strum = { workspace = true, features = ["derive"] }
Expand Down
148 changes: 20 additions & 128 deletions rust/jwt/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::path::{Path, PathBuf};
use std::{
path::{Path, PathBuf},
str::FromStr,
};

use derive_builder::Builder;
pub use jsonwebtoken::{
Algorithm as JwtAlgorithm, DecodingKey, EncodingKey, Header, TokenData, Validation, decode,
decode_header, encode, errors::Error as JwtError, get_current_timestamp,
Expand All @@ -11,6 +13,8 @@ use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
#[error("empty string when parsing JWT claim")]
EmptyString,
#[error("JWT signing key not found: '{}'", .0.display())]
JwtSigningKeyNotFound(PathBuf),
#[error("JWT internal error: {0}")]
Expand Down Expand Up @@ -72,135 +76,23 @@ pub fn load_jwt_signing_key(
Ok(key)
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, EnumString, Display)]
#[serde(rename_all = "lowercase")]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "lowercase")]
pub enum Environment {
#[default]
Test,
Production,
}

#[derive(Builder, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[builder(pattern = "owned")]
pub struct Claims {
#[builder(setter(into, strip_option), default)]
pub host: Option<String>,
#[builder(setter(into, strip_option), default)]
pub port: Option<u16>,
pub exp: u64,
pub sub: String,
#[builder(setter(into), default)]
pub environment: Option<Environment>,
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct Claim {
pub name: String,
#[serde(default)]
pub values: Vec<String>,
}

#[allow(clippy::unwrap_used)]
pub mod test_helpers {
use super::*;
impl FromStr for Claim {
type Err = Error;

pub struct TokenArgs<'a> {
pub secret: &'a str,
pub invalid_after: i64,
pub subject: &'a str,
pub host: Option<&'a str>,
pub port: Option<u16>,
pub environment: Option<Environment>,
}

#[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
pub fn token(args: &TokenArgs) -> String {
let key = EncodingKey::from_secret(args.secret.as_bytes());
let ts = get_current_timestamp() as i64 + args.invalid_after;
let mut claims_builder = ClaimsBuilder::default()
.exp(ts as u64)
.sub(args.subject.to_string())
.environment(args.environment);
if let Some(host) = args.host {
claims_builder = claims_builder
.host(host.to_string())
.port(args.port.unwrap_or(DEFAULT_WEB_PROOF_PORT))
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
return Err(Error::EmptyString);
}

let claims = claims_builder.build().unwrap();

encode(&Header::default(), &claims, &key).unwrap()
}

pub const JWT_SECRET: &str = "deadbeef";
pub const DEFAULT_WEB_PROOF_PORT: u16 = 443;
}

#[cfg(test)]
mod tests {
use test_helpers::{JWT_SECRET, TokenArgs, token};

use super::*;

fn decoding_key() -> DecodingKey {
DecodingKey::from_secret(JWT_SECRET.as_bytes())
}

#[test]
fn decodes_without_web_proof_claims() {
let jwt = token(&TokenArgs {
secret: JWT_SECRET,
host: None,
port: None,
invalid_after: 0,
subject: "1234",
environment: None,
});
let claims: TokenData<Claims> =
decode(&jwt, &decoding_key(), &Validation::default()).unwrap();
assert!(claims.claims.host.is_none());
assert!(claims.claims.port.is_none());
}

#[test]
fn decodes_with_web_proof_claims() {
let jwt = token(&TokenArgs {
secret: JWT_SECRET,
host: Some("xyz.xyz"),
port: None,
invalid_after: 0,
subject: "1234",
environment: None,
});
let claims: TokenData<Claims> =
decode(&jwt, &decoding_key(), &Validation::default()).unwrap();
assert_eq!(claims.claims.host, Some("xyz.xyz".to_string()));
assert_eq!(claims.claims.port, Some(443));
}

#[test]
fn decodes_as_test() {
let jwt = token(&TokenArgs {
secret: JWT_SECRET,
host: None,
port: None,
invalid_after: 0,
subject: "1234",
environment: Some(Environment::Test),
});
let token_data: TokenData<Claims> =
decode(&jwt, &decoding_key(), &Validation::default()).unwrap();
assert_eq!(token_data.claims.environment, Some(Environment::Test));
}

#[test]
fn decodes_as_production() {
let jwt = token(&TokenArgs {
secret: JWT_SECRET,
host: None,
port: None,
invalid_after: 0,
subject: "1234",
environment: Some(Environment::Production),
});
let token_data: TokenData<Claims> =
decode(&jwt, &decoding_key(), &Validation::default()).unwrap();
assert_eq!(token_data.claims.environment, Some(Environment::Production));
let parts: Vec<&str> = s.split(':').collect();
let name = parts[0].to_string();
let values = parts[1..].iter().map(ToString::to_string).collect();
Ok(Self { name, values })
}
}
17 changes: 2 additions & 15 deletions rust/server_utils/src/jwt.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
pub mod axum;
pub mod cli;
pub mod config;

pub use jwt::{
Algorithm, Claims, ClaimsBuilder, ClaimsBuilderError, DecodingKey, EncodingKey, Environment,
Header, encode, get_current_timestamp,
};

pub mod test_helpers {
use cli::Config;
pub use jwt::test_helpers::{JWT_SECRET, TokenArgs, token};

use super::*;

pub fn default_config() -> Config {
Config::new(DecodingKey::from_secret(JWT_SECRET.as_bytes()), Default::default())
}
}
pub use jwt::{Algorithm, Claim, DecodingKey, EncodingKey, Header, encode, get_current_timestamp};
Loading
Loading