Skip to content

Commit 291575a

Browse files
authored
Merge pull request #58 from moven0831/feat/mobile-app-migration
feat: mobile app migration
2 parents ee0f4d2 + ed344c4 commit 291575a

File tree

284 files changed

+25355
-253
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

284 files changed

+25355
-253
lines changed

wallet-unit-poc/README.md

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ cargo run --release -- benchmark
112112

113113
### Mobile Benchmarks
114114

115-
For the reproduction of mobile benchmarks, please check this repo: https://github.com/moven0831/spartan2-hyrax-mopro
115+
For the reproduction of mobile benchmarks, please check the [OpenAC mobile app directory](/wallet-unit-poc/mobile/)
116116

117117
#### Prepare Circuit (Mobile)
118118

@@ -121,8 +121,8 @@ For the reproduction of mobile benchmarks, please check this repo: https://githu
121121

122122
| Device | Setup (ms) | Prove (ms) | Reblind (ms) | Verify (ms) |
123123
|:------------:|:----------:|:----------:|:------------:|:-----------:|
124-
| iPhone 17 | 3499 | 2987 | 856 | 151 |
125-
| Pixel 10 Pro | 9233 | 7318 | 1750 | 318 |
124+
| iPhone 17 | 3254 | 2102 | 884 | 137 |
125+
| Pixel 10 Pro | 9282 | 5161 | 1732 | 318 |
126126

127127
### Show Circuit Timing
128128

@@ -131,9 +131,5 @@ The Show circuit has constant performance regardless of JWT payload size.
131131

132132
| Device | Setup (ms) | Prove (ms) | Reblind (ms) | Verify (ms) |
133133
|:------------:|:----------:|:----------:|:------------:|:-----------:|
134-
| iPhone 17 | 47 | 99 | 30 | 13 |
135-
| Pixel 10 Pro | 122 | 340 | 125 | 61 |
136-
137-
| iPhone 17 | Pixel 10 Pro |
138-
|-----------|--------------|
139-
| <img src="https://github.com/user-attachments/assets/45cabf07-66e8-446a-add6-bf036b2f40fa" width="300"> | <img src="https://github.com/user-attachments/assets/2d9499d4-e06e-4dc2-9a12-41fe9033245b" width="300"> |
134+
| iPhone 17 | 43 | 85 | 30 | 13 |
135+
| Pixel 10 Pro | 99 | 308 | 130 | 65 |

wallet-unit-poc/ecdsa-spartan2/README.md

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -65,35 +65,6 @@ The Show circuit has constant performance regardless of JWT payload size.
6565
| Reblind | ~25 |
6666
| Verify | ~9 |
6767

68-
### Timing Measurements (Mobile)
69-
70-
All timing measurements are in milliseconds (ms).
71-
72-
**Test Device:**
73-
- iOS: iPhone 17, A19 chip, 8GB RAM
74-
- Android: Pixel 10 Pro, Tensor G5, 16GB of RAM
75-
76-
#### Prepare Circuit Timing
77-
78-
- Payload Size: 1920 Bytes
79-
- Peak Memory Usage for Proving: 2.27 GiB
80-
81-
| Device | Setup (ms) | Prove (ms) | Reblind (ms) | Verify (ms) |
82-
|:------------:|:----------:|:----------:|:------------:|:-----------:|
83-
| iPhone 17 | 3499 | 2987 | 856 | 151 |
84-
| Pixel 10 Pro | 9233 | 7318 | 1750 | 318 |
85-
86-
87-
#### Show Circuit Timing
88-
89-
The Show circuit has constant performance regardless of JWT payload size.
90-
- Peak Memory Usage for Proving: 1.96 GiB
91-
92-
| Device | Setup (ms) | Prove (ms) | Reblind (ms) | Verify (ms) |
93-
|:------------:|:----------:|:----------:|:------------:|:-----------:|
94-
| iPhone 17 | 47 | 99 | 30 | 13 |
95-
| Pixel 10 Pro | 122 | 340 | 125 | 61 |
96-
9768
### Size Measurements
9869

9970
#### Prepare Circuit Sizes
@@ -128,5 +99,4 @@ To generate benchmark data for a specific payload size:
12899
```sh
129100
# Run the complete benchmark pipeline
130101
cargo run --release -- benchmark
131-
132102
```
Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,41 @@
1+
use std::path::Path;
2+
13
fn main() {
2-
witnesscalc_adapter::build_and_link("../circom/build/cpp/");
4+
// Construct absolute path to circuits using CARGO_MANIFEST_DIR
5+
// This ensures the path resolves correctly regardless of working directory
6+
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
7+
let circuits_dir = std::path::PathBuf::from(&manifest_dir)
8+
.parent() // Go up from ecdsa-spartan2/ to wallet-unit-poc/
9+
.expect("Failed to get parent directory")
10+
.join("circom/build/cpp");
11+
let circuits_path = circuits_dir.to_str().unwrap();
12+
13+
// Check for pre-built witnesscalc cache from build_pod.sh
14+
if let Ok(witnesscalc_cache) = std::env::var("WITNESSCALC_PREBUILD_CACHE") {
15+
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set");
16+
let target = std::env::var("TARGET").unwrap_or_default();
17+
18+
// Only apply for iOS targets
19+
match target.as_str() {
20+
"aarch64-apple-ios-sim" | "aarch64-apple-ios" | "x86_64-apple-ios" => {
21+
let cache_src = Path::new(&witnesscalc_cache);
22+
let target_witnesscalc = Path::new(&out_dir).join("witnesscalc");
23+
24+
// Symlink entire witnesscalc directory if cache exists and target doesn't
25+
if cache_src.exists() && !target_witnesscalc.exists() {
26+
#[cfg(unix)]
27+
{
28+
println!(
29+
"cargo:warning=Using cached witnesscalc from: {}",
30+
cache_src.display()
31+
);
32+
std::os::unix::fs::symlink(&cache_src, &target_witnesscalc).ok();
33+
}
34+
}
35+
}
36+
_ => {}
37+
}
38+
}
39+
40+
witnesscalc_adapter::build_and_link(circuits_path);
341
}

wallet-unit-poc/ecdsa-spartan2/src/circuits/prepare_circuit.rs

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::{
2+
paths::PathConfig,
23
prover::generate_prepare_witness,
34
utils::{compute_prepare_shared_scalars, PrepareSharedScalars},
45
Scalar, E,
@@ -7,36 +8,57 @@ use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError};
78
use circom_scotia::{reader::load_r1cs, synthesize};
89
use serde_json::Value;
910
use spartan2::traits::circuit::SpartanCircuit;
10-
use std::{any::type_name, env::current_dir, fs::File, path::PathBuf};
11+
use std::{any::type_name, fs::File, path::PathBuf};
1112

1213
witnesscalc_adapter::witness!(jwt);
1314

1415
// jwt.circom
15-
#[derive(Debug, Clone, Default)]
16+
#[derive(Debug, Clone)]
1617
pub struct PrepareCircuit {
18+
/// Path configuration for resolving file paths
19+
path_config: PathConfig,
20+
/// Optional override for input JSON path
1721
input_path: Option<PathBuf>,
1822
}
1923

24+
impl Default for PrepareCircuit {
25+
fn default() -> Self {
26+
Self {
27+
path_config: PathConfig::default(),
28+
input_path: None,
29+
}
30+
}
31+
}
32+
2033
impl PrepareCircuit {
21-
pub fn new<P: Into<Option<PathBuf>>>(path: P) -> Self {
34+
/// Create a new PrepareCircuit with PathConfig and optional input path override.
35+
pub fn new(path_config: PathConfig, input_path: Option<PathBuf>) -> Self {
36+
Self {
37+
path_config,
38+
input_path,
39+
}
40+
}
41+
42+
/// Create from just an input path (for backwards compatibility).
43+
/// Uses development PathConfig.
44+
pub fn with_input_path<P: Into<Option<PathBuf>>>(path: P) -> Self {
2245
Self {
46+
path_config: PathConfig::development(),
2347
input_path: path.into(),
2448
}
2549
}
2650

27-
fn input_path_absolute(&self, cwd: &PathBuf) -> Option<PathBuf> {
28-
self.input_path.as_ref().map(|p| {
29-
if p.is_absolute() {
30-
p.clone()
31-
} else {
32-
cwd.join(p)
33-
}
34-
})
51+
/// Resolve the input JSON path using PathConfig.
52+
fn resolve_input_json(&self) -> PathBuf {
53+
self.input_path
54+
.as_ref()
55+
.map(|p| self.path_config.resolve(p))
56+
.unwrap_or_else(|| self.path_config.input_json("jwt"))
3557
}
3658

37-
fn resolve_input_json(&self, cwd: &PathBuf) -> PathBuf {
38-
self.input_path_absolute(cwd)
39-
.unwrap_or_else(|| cwd.join("../circom/inputs/jwt/default.json"))
59+
/// Get the R1CS file path.
60+
fn r1cs_path(&self) -> PathBuf {
61+
self.path_config.r1cs_path("jwt")
4062
}
4163
}
4264

@@ -48,28 +70,27 @@ impl SpartanCircuit<E> for PrepareCircuit {
4870
_: &[AllocatedNum<Scalar>],
4971
_: Option<&[Scalar]>,
5072
) -> Result<(), SynthesisError> {
51-
let cwd = current_dir().unwrap();
52-
let root = cwd.join("../circom");
53-
let witness_dir = root.join("build/jwt/jwt_js");
54-
let r1cs = witness_dir.join("jwt.r1cs");
73+
let r1cs_path = self.r1cs_path();
5574

5675
// Detect if we're in setup phase (ShapeCS) or prove phase (SatisfyingAssignment)
5776
// During setup, we only need constraint structure instead of actual witness values
5877
let cs_type = type_name::<CS>();
5978
let is_setup_phase = cs_type.contains("ShapeCS");
6079

6180
if is_setup_phase {
62-
let r1cs = load_r1cs(r1cs);
81+
let r1cs = load_r1cs(&r1cs_path);
6382
// Pass None for witness during setup
6483
synthesize(cs, r1cs, None)?;
6584
return Ok(());
6685
}
6786

6887
// Generate witness using the dedicated function
69-
let input_path = self.input_path_absolute(&cwd);
70-
let witness = generate_prepare_witness(input_path.as_ref().map(|p| p.as_path()))?;
88+
let witness = generate_prepare_witness(
89+
&self.path_config,
90+
self.input_path.as_ref().map(|p| p.as_path()),
91+
)?;
7192

72-
let r1cs = load_r1cs(r1cs);
93+
let r1cs = load_r1cs(&r1cs_path);
7394
synthesize(cs, r1cs, Some(witness))?;
7495
Ok(())
7596
}
@@ -81,8 +102,7 @@ impl SpartanCircuit<E> for PrepareCircuit {
81102
&self,
82103
cs: &mut CS,
83104
) -> Result<Vec<AllocatedNum<Scalar>>, SynthesisError> {
84-
let cwd = current_dir().unwrap();
85-
let json_path = self.resolve_input_json(&cwd);
105+
let json_path = self.resolve_input_json();
86106

87107
let json_file = File::open(&json_path).map_err(|_| SynthesisError::AssignmentMissing)?;
88108

wallet-unit-poc/ecdsa-spartan2/src/circuits/show_circuit.rs

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,64 @@
1-
use crate::{utils::*, Scalar, E};
1+
use crate::{paths::PathConfig, utils::*, Scalar, E};
22
use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError};
33
use circom_scotia::{reader::load_r1cs, synthesize};
44
use serde_json::Value;
55
use spartan2::traits::circuit::SpartanCircuit;
6-
use std::{any::type_name, env::current_dir, fs::File, path::PathBuf, time::Instant};
6+
use std::{any::type_name, fs::File, path::PathBuf, time::Instant};
77
use tracing::info;
88

99
witnesscalc_adapter::witness!(show);
1010

1111
// show.circom
12-
#[derive(Debug, Clone, Default)]
12+
#[derive(Debug, Clone)]
1313
pub struct ShowCircuit {
14+
/// Path configuration for resolving file paths
15+
path_config: PathConfig,
16+
/// Optional override for input JSON path
1417
input_path: Option<PathBuf>,
1518
}
1619

20+
impl Default for ShowCircuit {
21+
fn default() -> Self {
22+
Self {
23+
path_config: PathConfig::default(),
24+
input_path: None,
25+
}
26+
}
27+
}
28+
1729
impl ShowCircuit {
18-
pub fn new<P: Into<Option<PathBuf>>>(path: P) -> Self {
30+
/// Create a new ShowCircuit with PathConfig and optional input path override.
31+
pub fn new(path_config: PathConfig, input_path: Option<PathBuf>) -> Self {
1932
Self {
33+
path_config,
34+
input_path,
35+
}
36+
}
37+
38+
/// Create from just an input path (for backwards compatibility).
39+
/// Uses development PathConfig.
40+
pub fn with_input_path<P: Into<Option<PathBuf>>>(path: P) -> Self {
41+
Self {
42+
path_config: PathConfig::development(),
2043
input_path: path.into(),
2144
}
2245
}
2346

24-
fn input_path_absolute(&self, cwd: &PathBuf) -> PathBuf {
47+
/// Resolve the input JSON path using PathConfig.
48+
fn resolve_input_json(&self) -> PathBuf {
2549
self.input_path
2650
.as_ref()
27-
.map(|p| {
28-
if p.is_absolute() {
29-
p.clone()
30-
} else {
31-
cwd.join(p)
32-
}
33-
})
34-
.unwrap_or_else(|| cwd.join("../circom/inputs/show/default.json"))
51+
.map(|p| self.path_config.resolve(p))
52+
.unwrap_or_else(|| self.path_config.input_json("show"))
53+
}
54+
55+
/// Get the R1CS file path.
56+
fn r1cs_path(&self) -> PathBuf {
57+
self.path_config.r1cs_path("show")
3558
}
3659

37-
fn load_inputs(&self, cwd: &PathBuf) -> Result<Value, SynthesisError> {
38-
let path = self.input_path_absolute(cwd);
60+
fn load_inputs(&self) -> Result<Value, SynthesisError> {
61+
let path = self.resolve_input_json();
3962
info!("Loading show inputs from {}", path.display());
4063
let file = File::open(&path).map_err(|_| SynthesisError::AssignmentMissing)?;
4164
serde_json::from_reader(file).map_err(|_| SynthesisError::AssignmentMissing)
@@ -50,11 +73,8 @@ impl SpartanCircuit<E> for ShowCircuit {
5073
_: &[AllocatedNum<Scalar>],
5174
_: Option<&[Scalar]>,
5275
) -> Result<(), SynthesisError> {
53-
let cwd = current_dir().unwrap();
54-
let root = cwd.join("../circom");
55-
let witness_dir = root.join("build/show/show_js");
56-
let r1cs = witness_dir.join("show.r1cs");
57-
let json_value = self.load_inputs(&cwd)?;
76+
let r1cs_path = self.r1cs_path();
77+
let json_value = self.load_inputs()?;
5878

5979
// Parse inputs using declarative field definitions
6080
let inputs = parse_show_inputs(&json_value)?;
@@ -65,7 +85,7 @@ impl SpartanCircuit<E> for ShowCircuit {
6585
let is_setup_phase = cs_type.contains("ShapeCS");
6686

6787
if is_setup_phase {
68-
let r1cs = load_r1cs(r1cs);
88+
let r1cs = load_r1cs(&r1cs_path);
6989
// Pass None for witness during setup
7090
synthesize(cs, r1cs, None)?;
7191
return Ok(());
@@ -78,15 +98,15 @@ impl SpartanCircuit<E> for ShowCircuit {
7898
let inputs_json = hashmap_to_json_string(&inputs)?;
7999

80100
// Generate raw witness bytes
81-
let witness_bytes = show_witness(&inputs_json)
82-
.map_err(|_| SynthesisError::Unsatisfiable)?;
101+
let witness_bytes =
102+
show_witness(&inputs_json).map_err(|_| SynthesisError::Unsatisfiable)?;
83103

84104
info!("witnesscalc time: {} ms", t0.elapsed().as_millis());
85105

86106
// Parse witness bytes directly to Scalar
87107
let witness = parse_witness(&witness_bytes)?;
88108

89-
let r1cs = load_r1cs(r1cs);
109+
let r1cs = load_r1cs(&r1cs_path);
90110
synthesize(cs, r1cs, Some(witness))?;
91111
Ok(())
92112
}
@@ -98,8 +118,7 @@ impl SpartanCircuit<E> for ShowCircuit {
98118
&self,
99119
cs: &mut CS,
100120
) -> Result<Vec<AllocatedNum<Scalar>>, SynthesisError> {
101-
let cwd = current_dir().unwrap();
102-
let json_value = self.load_inputs(&cwd)?;
121+
let json_value = self.load_inputs()?;
103122

104123
let inputs = parse_show_inputs(&json_value)?;
105124
let keybinding_x_bigint = inputs.get("deviceKeyX").unwrap()[0].clone();

wallet-unit-poc/ecdsa-spartan2/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,22 @@ pub type E = T256HyraxEngine;
1212
pub type Scalar = <E as Engine>::Scalar;
1313

1414
pub mod circuits;
15+
pub mod paths;
1516
pub mod prover;
1617
pub mod setup;
1718
pub mod utils;
1819

1920
// Re-export commonly used types and functions
2021
pub use circuits::{prepare_circuit::PrepareCircuit, show_circuit::ShowCircuit};
22+
pub use paths::PathConfig;
2123
pub use prover::{
2224
generate_prepare_witness, generate_shared_blinds, prove_circuit, prove_circuit_with_pk,
2325
reblind, reblind_with_loaded_data, run_circuit, verify_circuit,
2426
verify_circuit_with_loaded_data,
2527
};
2628
pub use setup::{
2729
load_instance, load_proof, load_proving_key, load_shared_blinds, load_verifying_key,
28-
load_witness, save_keys, setup_circuit_keys, setup_circuit_keys_no_save, PREPARE_PROVING_KEY,
29-
PREPARE_VERIFYING_KEY, SHOW_PROVING_KEY, SHOW_VERIFYING_KEY,
30+
load_witness, save_keys, setup_circuit_keys, setup_circuit_keys_no_save,
3031
};
3132
pub use utils::{
3233
bigint_to_scalar, calculate_jwt_output_indices, convert_bigint_to_scalar, parse_jwt_inputs,

0 commit comments

Comments
 (0)