Skip to content

Commit 7f89c2a

Browse files
committed
feat(openac-sdk): add TypeScript SDK for ZK credential proving
Introduces a new TypeScript/JavaScript SDK that provides a high-level API for the zkID credential proving system: - Core SDK classes: OpenAC, Prover, Verifier, Credential - Input builders: buildJwtCircuitInputs, buildShowCircuitInputs - NativeBackend wrapper for ecdsa-spartan2 Rust CLI - WitnessCalculator for JS-based witness generation - Comprehensive test suite using SDK public API with self-contained ES256 SD-JWT generation (no external JWT library dependencies) e2e tests validate the actual integration surface users will consume, generating test credentials programmatically and exercising the full Prepare -> Show proving pipeline.
1 parent 3e0ba6f commit 7f89c2a

35 files changed

Lines changed: 11577 additions & 128 deletions

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ pub mod utils;
2121
pub use circuits::{prepare_circuit::PrepareCircuit, show_circuit::ShowCircuit};
2222
pub use paths::PathConfig;
2323
pub use prover::{
24-
generate_prepare_witness, generate_shared_blinds, prove_circuit, prove_circuit_with_pk,
25-
reblind, reblind_with_loaded_data, run_circuit, verify_circuit,
26-
verify_circuit_with_loaded_data,
24+
generate_prepare_witness, generate_shared_blinds, prove_circuit, prove_circuit_in_memory,
25+
prove_circuit_with_pk, reblind, reblind_in_memory, reblind_with_loaded_data, run_circuit,
26+
verify_circuit, verify_circuit_with_loaded_data,
2727
};
2828
pub use setup::{
2929
load_instance, load_proof, load_proving_key, load_shared_blinds, load_verifying_key,

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

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,109 @@ pub fn reblind_with_loaded_data<C: SpartanCircuit<E>>(
250250
}
251251
}
252252

253+
/// Prove a circuit and return results in memory (no file I/O).
254+
/// This is the building block for the WASM `precompute()` API.
255+
pub fn prove_circuit_in_memory<C: SpartanCircuit<E> + Clone + std::fmt::Debug>(
256+
circuit: C,
257+
pk: &<R1CSSNARK<E> as R1CSSNARKTrait<E>>::ProverKey,
258+
) -> Result<
259+
(
260+
R1CSSNARK<E>,
261+
spartan2::r1cs::SplitR1CSInstance<E>,
262+
spartan2::r1cs::R1CSWitness<E>,
263+
),
264+
SpartanError,
265+
> {
266+
let t0 = Instant::now();
267+
let mut prep_snark = R1CSSNARK::<E>::prep_prove(&pk, circuit.clone(), false)?;
268+
let prep_ms = t0.elapsed().as_millis();
269+
info!("ZK-Spartan prep_prove (in-memory): {} ms", prep_ms);
270+
271+
let t0 = Instant::now();
272+
let mut transcript = <E as Engine>::TE::new(b"R1CSSNARK");
273+
transcript.absorb(b"vk", &pk.vk_digest);
274+
275+
let public_values = SpartanCircuit::<E>::public_values(&circuit).map_err(|e| {
276+
SpartanError::SynthesisError {
277+
reason: format!("Circuit does not provide public IO: {e}"),
278+
}
279+
})?;
280+
281+
transcript.absorb(b"public_values", &public_values.as_slice());
282+
283+
let (instance, witness) = SatisfyingAssignment::r1cs_instance_and_witness(
284+
&mut prep_snark.ps,
285+
&pk.S,
286+
&pk.ck,
287+
&circuit,
288+
false,
289+
&mut transcript,
290+
)
291+
.map_err(|e| SpartanError::SynthesisError {
292+
reason: format!("Instance/witness generation failed: {e}"),
293+
})?;
294+
295+
let proof = R1CSSNARK::<E>::prove_inner(&pk, &instance, &witness, &mut transcript)?;
296+
let prove_ms = t0.elapsed().as_millis();
297+
298+
info!(
299+
"ZK-Spartan prove (in-memory): prep={} ms, prove={} ms, total={} ms",
300+
prep_ms,
301+
prove_ms,
302+
prep_ms + prove_ms
303+
);
304+
305+
Ok((proof, instance, witness))
306+
}
307+
308+
/// Reblind a proof with shared randomness and return results in memory (no file I/O).
309+
/// This is the building block for the WASM `present()` API.
310+
pub fn reblind_in_memory<C: SpartanCircuit<E>>(
311+
circuit: C,
312+
pk: &<R1CSSNARK<E> as R1CSSNARKTrait<E>>::ProverKey,
313+
instance: spartan2::r1cs::SplitR1CSInstance<E>,
314+
witness: spartan2::r1cs::R1CSWitness<E>,
315+
randomness: &[<E as Engine>::Scalar],
316+
) -> Result<
317+
(
318+
R1CSSNARK<E>,
319+
spartan2::r1cs::SplitR1CSInstance<E>,
320+
spartan2::r1cs::R1CSWitness<E>,
321+
),
322+
SpartanError,
323+
> {
324+
assert_eq!(randomness.len(), instance.num_shared_rows());
325+
326+
let mut reblind_transcript = <E as Engine>::TE::new(b"R1CSSNARK");
327+
reblind_transcript.absorb(b"vk", &pk.vk_digest);
328+
329+
let public_values = SpartanCircuit::<E>::public_values(&circuit).map_err(|e| {
330+
SpartanError::SynthesisError {
331+
reason: format!("Circuit does not provide public IO: {e}"),
332+
}
333+
})?;
334+
335+
reblind_transcript.absorb(b"public_values", &public_values.as_slice());
336+
337+
let (new_instance, new_witness) = SatisfyingAssignment::reblind_r1cs_instance_and_witness(
338+
&randomness,
339+
instance,
340+
witness,
341+
&pk.ck,
342+
&mut reblind_transcript,
343+
)
344+
.map_err(|e| SpartanError::SynthesisError {
345+
reason: format!("Reblind failed: {e}"),
346+
})?;
347+
348+
let proof =
349+
R1CSSNARK::<E>::prove_inner(&pk, &new_instance, &new_witness, &mut reblind_transcript)?;
350+
351+
info!("ZK-Spartan reblind (in-memory): complete");
352+
353+
Ok((proof, new_instance, new_witness))
354+
}
355+
253356
/// Only run the verification part using ZK-Spartan.
254357
/// Returns the public values embedded in the proof.
255358
pub fn verify_circuit(proof_path: impl AsRef<Path>, vk_path: impl AsRef<Path>) -> Vec<Scalar> {

0 commit comments

Comments
 (0)