diff --git a/bin/darkirc/src/crypto/rln.rs b/bin/darkirc/src/crypto/rln.rs index 616c8832c..856c26c32 100644 --- a/bin/darkirc/src/crypto/rln.rs +++ b/bin/darkirc/src/crypto/rln.rs @@ -133,7 +133,7 @@ impl RlnIdentity { vec![epoch, external_nullifier, x, y, internal_nullifier, identity_root.inner()]; info!(target: "crypto::rln::create_proof", "[RLN] Creating proof for event {}", event.id()); - let signal_zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN)?; + let signal_zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN, false)?; let signal_circuit = ZkCircuit::new(witnesses, &signal_zkbin); let proof = Proof::create(proving_key, &[signal_circuit], &public_inputs, &mut OsRng)?; diff --git a/bin/darkirc/src/irc/client.rs b/bin/darkirc/src/irc/client.rs index 5fc40cdf6..b830ee093 100644 --- a/bin/darkirc/src/irc/client.rs +++ b/bin/darkirc/src/irc/client.rs @@ -603,7 +603,7 @@ impl Client { let identity_tree: MerkleTree = deserialize_async(&identity_tree).await?; // Retrieve the ZK proving key from the db - let signal_zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN)?; + let signal_zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN, false)?; let signal_circuit = ZkCircuit::new(empty_witnesses(&signal_zkbin)?, &signal_zkbin); let Some(proving_key) = self.server.server_store.get("rlnv2-diff-signal-pk")? else { return Err(Error::DatabaseError( diff --git a/bin/darkirc/src/irc/server.rs b/bin/darkirc/src/irc/server.rs index 96b3f5196..068664de3 100644 --- a/bin/darkirc/src/irc/server.rs +++ b/bin/darkirc/src/irc/server.rs @@ -155,7 +155,7 @@ impl IrcServer { let rln_identity_store = darkirc.sled.open_tree("rln_identity_store")?; // Generate RLN proving and verifying keys, if needed - let rln_signal_zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN)?; + let rln_signal_zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN, false)?; let rln_signal_circuit = ZkCircuit::new(empty_witnesses(&rln_signal_zkbin)?, &rln_signal_zkbin); @@ -184,7 +184,7 @@ impl IrcServer { if server_store.get("rlnv2-diff-slash-pk")?.is_none() { info!(target: "irc::server", "[RLN] Creating RlnV2_Diff_Slash ProvingKey"); - let zkbin = ZkBinary::decode(RLN2_SLASH_ZKBIN)?; + let zkbin = ZkBinary::decode(RLN2_SLASH_ZKBIN, false)?; let circuit = ZkCircuit::new(empty_witnesses(&zkbin).unwrap(), &zkbin); let provingkey = ProvingKey::build(zkbin.k, &circuit); let mut buf = vec![]; @@ -194,7 +194,7 @@ impl IrcServer { if server_store.get("rlnv2-diff-slash-vk")?.is_none() { info!(target: "irc::server", "[RLN] Creating RlnV2_Diff_Slash VerifyingKey"); - let zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN)?; + let zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN, false)?; let circuit = ZkCircuit::new(empty_witnesses(&zkbin).unwrap(), &zkbin); let verifyingkey = VerifyingKey::build(zkbin.k, &circuit); let mut buf = vec![]; diff --git a/bin/drk/src/dao.rs b/bin/drk/src/dao.rs index b80d4cc3c..6b0ffef05 100644 --- a/bin/drk/src/dao.rs +++ b/bin/drk/src/dao.rs @@ -2126,7 +2126,7 @@ impl Drk { return Err(Error::Custom("Fee circuit not found".to_string())) }; - let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?; + let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?; let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin); @@ -2141,7 +2141,7 @@ impl Drk { return Err(Error::DatabaseError("[dao_mint] DAO Mint circuit not found".to_string())) }; - let dao_mint_zkbin = ZkBinary::decode(&dao_mint_zkbin.1)?; + let dao_mint_zkbin = ZkBinary::decode(&dao_mint_zkbin.1, false)?; let dao_mint_circuit = ZkCircuit::new(empty_witnesses(&dao_mint_zkbin)?, &dao_mint_zkbin); @@ -2464,7 +2464,7 @@ impl Drk { )) }; - let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?; + let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?; let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin); @@ -2490,8 +2490,8 @@ impl Drk { )) }; - let propose_burn_zkbin = ZkBinary::decode(&propose_burn_zkbin.1)?; - let propose_main_zkbin = ZkBinary::decode(&propose_main_zkbin.1)?; + let propose_burn_zkbin = ZkBinary::decode(&propose_burn_zkbin.1, false)?; + let propose_main_zkbin = ZkBinary::decode(&propose_main_zkbin.1, false)?; let propose_burn_circuit = ZkCircuit::new(empty_witnesses(&propose_burn_zkbin)?, &propose_burn_zkbin); @@ -2644,7 +2644,7 @@ impl Drk { return Err(Error::Custom("[dao_generic_proposal_tx] Fee circuit not found".to_string())) }; - let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?; + let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?; let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin); @@ -2670,8 +2670,8 @@ impl Drk { )) }; - let propose_burn_zkbin = ZkBinary::decode(&propose_burn_zkbin.1)?; - let propose_main_zkbin = ZkBinary::decode(&propose_main_zkbin.1)?; + let propose_burn_zkbin = ZkBinary::decode(&propose_burn_zkbin.1, false)?; + let propose_main_zkbin = ZkBinary::decode(&propose_main_zkbin.1, false)?; let propose_burn_circuit = ZkCircuit::new(empty_witnesses(&propose_burn_zkbin)?, &propose_burn_zkbin); @@ -2848,7 +2848,7 @@ impl Drk { return Err(Error::Custom("[dao_vote] Fee circuit not found".to_string())) }; - let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?; + let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?; let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin); @@ -2870,8 +2870,8 @@ impl Drk { return Err(Error::Custom("[dao_vote] DAO Vote Main circuit not found".to_string())) }; - let dao_vote_burn_zkbin = ZkBinary::decode(&dao_vote_burn_zkbin.1)?; - let dao_vote_main_zkbin = ZkBinary::decode(&dao_vote_main_zkbin.1)?; + let dao_vote_burn_zkbin = ZkBinary::decode(&dao_vote_burn_zkbin.1, false)?; + let dao_vote_main_zkbin = ZkBinary::decode(&dao_vote_main_zkbin.1, false)?; let dao_vote_burn_circuit = ZkCircuit::new(empty_witnesses(&dao_vote_burn_zkbin)?, &dao_vote_burn_zkbin); @@ -3110,9 +3110,9 @@ impl Drk { return Err(Error::Custom("[dao_exec_transfer] Fee circuit not found".to_string())) }; - let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?; - let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?; - let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?; + let mint_zkbin = ZkBinary::decode(&mint_zkbin.1, false)?; + let burn_zkbin = ZkBinary::decode(&burn_zkbin.1, false)?; + let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?; let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin); let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin); @@ -3155,10 +3155,10 @@ impl Drk { )) }; - let dao_exec_zkbin = ZkBinary::decode(&dao_exec_zkbin.1)?; - let dao_auth_transfer_zkbin = ZkBinary::decode(&dao_auth_transfer_zkbin.1)?; + let dao_exec_zkbin = ZkBinary::decode(&dao_exec_zkbin.1, false)?; + let dao_auth_transfer_zkbin = ZkBinary::decode(&dao_auth_transfer_zkbin.1, false)?; let dao_auth_transfer_enc_coin_zkbin = - ZkBinary::decode(&dao_auth_transfer_enc_coin_zkbin.1)?; + ZkBinary::decode(&dao_auth_transfer_enc_coin_zkbin.1, false)?; let dao_exec_circuit = ZkCircuit::new(empty_witnesses(&dao_exec_zkbin)?, &dao_exec_zkbin); let dao_auth_transfer_circuit = @@ -3403,7 +3403,7 @@ impl Drk { else { return Err(Error::Custom("[dao_exec_generic] Fee circuit not found".to_string())) }; - let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?; + let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?; let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin); let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit); @@ -3422,7 +3422,7 @@ impl Drk { "[dao_exec_generic] DAO {namespace} circuit not found" ))) }; - let dao_exec_zkbin = ZkBinary::decode(&dao_exec_zkbin.1)?; + let dao_exec_zkbin = ZkBinary::decode(&dao_exec_zkbin.1, false)?; let dao_exec_circuit = ZkCircuit::new(empty_witnesses(&dao_exec_zkbin)?, &dao_exec_zkbin); let dao_exec_pk = ProvingKey::build(dao_exec_zkbin.k, &dao_exec_circuit); diff --git a/bin/drk/src/deploy.rs b/bin/drk/src/deploy.rs index c79a14f8a..cb2aba4d4 100644 --- a/bin/drk/src/deploy.rs +++ b/bin/drk/src/deploy.rs @@ -540,7 +540,7 @@ impl Drk { return Err(Error::Custom("[deploy_contract] Fee circuit not found".to_string())) }; - let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?; + let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?; let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin); @@ -603,7 +603,7 @@ impl Drk { return Err(Error::Custom("[lock_contract] Fee circuit not found".to_string())) }; - let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?; + let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?; let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin); diff --git a/bin/drk/src/money.rs b/bin/drk/src/money.rs index 696e2d253..0049283b9 100644 --- a/bin/drk/src/money.rs +++ b/bin/drk/src/money.rs @@ -1400,7 +1400,7 @@ impl Drk { return Err(Error::Custom("Fee circuit not found".to_string())) }; - let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?; + let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?; let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin); diff --git a/bin/drk/src/swap.rs b/bin/drk/src/swap.rs index 8a5f0f884..f6412e892 100644 --- a/bin/drk/src/swap.rs +++ b/bin/drk/src/swap.rs @@ -125,8 +125,8 @@ impl Drk { return Err(Error::Custom("Burn circuit not found".to_string())) }; - let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?; - let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?; + let mint_zkbin = ZkBinary::decode(&mint_zkbin.1, false)?; + let burn_zkbin = ZkBinary::decode(&burn_zkbin.1, false)?; let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin); let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin); @@ -228,8 +228,8 @@ impl Drk { return Err(Error::Custom("Burn circuit not found".to_string())) }; - let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?; - let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?; + let mint_zkbin = ZkBinary::decode(&mint_zkbin.1, false)?; + let burn_zkbin = ZkBinary::decode(&burn_zkbin.1, false)?; let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin); let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin); diff --git a/bin/drk/src/token.rs b/bin/drk/src/token.rs index 2230054e6..d8e1010af 100644 --- a/bin/drk/src/token.rs +++ b/bin/drk/src/token.rs @@ -301,9 +301,9 @@ impl Drk { return Err(Error::Custom("Fee circuit not found".to_string())) }; - let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?; - let auth_mint_zkbin = ZkBinary::decode(&auth_mint_zkbin.1)?; - let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?; + let mint_zkbin = ZkBinary::decode(&mint_zkbin.1, false)?; + let auth_mint_zkbin = ZkBinary::decode(&auth_mint_zkbin.1, false)?; + let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?; let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin); let auth_mint_circuit = @@ -409,8 +409,8 @@ impl Drk { return Err(Error::Custom("Fee circuit not found".to_string())) }; - let auth_mint_zkbin = ZkBinary::decode(&auth_mint_zkbin.1)?; - let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?; + let auth_mint_zkbin = ZkBinary::decode(&auth_mint_zkbin.1, false)?; + let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?; let auth_mint_circuit = ZkCircuit::new(empty_witnesses(&auth_mint_zkbin)?, &auth_mint_zkbin); diff --git a/bin/drk/src/transfer.rs b/bin/drk/src/transfer.rs index 781326e10..92ecf9c6c 100644 --- a/bin/drk/src/transfer.rs +++ b/bin/drk/src/transfer.rs @@ -95,9 +95,9 @@ impl Drk { return Err(Error::Custom("Fee circuit not found".to_string())) }; - let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?; - let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?; - let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?; + let mint_zkbin = ZkBinary::decode(&mint_zkbin.1, false)?; + let burn_zkbin = ZkBinary::decode(&burn_zkbin.1, false)?; + let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?; let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin); let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin); diff --git a/bin/zkas/src/main.rs b/bin/zkas/src/main.rs index 471017fba..fe9de1c29 100644 --- a/bin/zkas/src/main.rs +++ b/bin/zkas/src/main.rs @@ -164,7 +164,7 @@ fn main() -> ExitCode { println!("Wrote output to {}", &output); if eflag { - let zkbin = ZkBinary::decode(&bincode).unwrap(); + let zkbin = ZkBinary::decode(&bincode, true).unwrap(); println!("{zkbin:#?}"); } diff --git a/bin/zkrunner/zkrunner.py b/bin/zkrunner/zkrunner.py index f3dffe760..79ced2ee3 100755 --- a/bin/zkrunner/zkrunner.py +++ b/bin/zkrunner/zkrunner.py @@ -28,17 +28,18 @@ from darkfi_sdk.zkas import (MockProver, ZkBinary, ZkCircuit, ProvingKey, def eprint(fstr, *args): print("error: " + fstr, *args, file=sys.stderr) -def show_trace(opcodes, trace): - print(f"{'Line':<4} {'Opcode':<22} {'Type':<10} {'Values'}") +def show_trace(zkbin, opcodes, trace): + print(f"{'Line':<6} {'Source':<12} {'Opcode':<22} {'Result':<20} {'Values'}") for i, (opcode, (optype, args)) in enumerate(zip(opcodes, trace)): - if args: - args = ", ".join([str(arg) for arg in args]) - args = f"[{args}]" - else: - args = "" - opcode = str(opcode) - optype = str(optype) - print(f"{i:<4} {opcode:<22} {optype:<10} {args}") + # Get source location from debug info + loc = zkbin.opcode_location(i) + source = f"L{loc[0]}:C{loc[1]}" if loc else "-" + + # Get result variable name for assignments + result = zkbin.heap_name(i) or "-" # Simplified - would need proper heap tracking + + args_str = f"[{', '.join(str(a) for a in args)}]" if args else "" + print(f"{i:<6} {source:<12} {str(opcode):<22} {result:<20} {args_str}") def load_circuit_witness(circuit, witness_file): # We attempt to decode the witnesses from the JSON file. @@ -147,7 +148,7 @@ def main(witness_file, source_file, mock=False, trace=False): return -3 if trace: - show_trace(zkbin.opcodes(), circuit.opvalues()) + show_trace(zkbin, zkbin.opcodes(), circuit.opvalues()) print("Verifying ZK proof...") verify_status = proof.verify(verifying_key, instances) diff --git a/doc/src/zkas/bincode.md b/doc/src/zkas/bincode.md index 364a46242..c6c4c34a5 100644 --- a/doc/src/zkas/bincode.md +++ b/doc/src/zkas/bincode.md @@ -25,8 +25,8 @@ CONSTANT_TYPE CONSTANT_NAME CONSTANT_TYPE CONSTANT_NAME ... .literal -LITERAL -LITERAL +LITERAL_TYPE LITERAL_VALUE +LITERAL_TYPE LITERAL_VALUE ... .witness WITNESS_TYPE @@ -36,8 +36,10 @@ WITNESS_TYPE OPCODE ARG_NUM HEAP_TYPE HEAP_INDEX ... HEAP_TYPE HEAP_INDEX OPCODE ARG_NUM HEAP_TYPE HEAP_INDEX ... HEAP_TYPE HEAP_INDEX ... -.debug -TBD +.debug (optional) +NUM_OPCODES [LINE COLUMN] ... +HEAP_SIZE [HEAP_NAME] ... +NUM_LITERALS [LITERAL_NAME] ... ``` Integers in the binary are encoded using variable-integer encoding. @@ -129,7 +131,40 @@ the heap and become available for later references. ### `.debug` -TBD +The `.debug` section is optional and contains debug information that +maps the compiled binary back to the original source code. This is +useful for debugging circuit failures when only the compiled binary +is available. + +> `NUM_OPCODES [LINE COLUMN] ... HEAP_SIZE [HEAP_NAME] ... NUM_LITERALS [LITERAL_NAME] ...` + +where: + +| Element | Description | +|-----------------|------------------------------------------------------------------| +| `NUM_OPCODES` | Number of opcodes in the `.circuit` section (VarInt) | +| `LINE` | Source line number for this opcode (VarInt) | +| `COLUMN` | Source column number for this opcode (VarInt) | +| `HEAP_SIZE` | Total number of entries on the heap (VarInt) | +| `HEAP_NAME` | Variable name for this heap entry (String) | +| `NUM_LITERALS` | Number of literals in the `.literal` section (VarInt) | +| `LITERAL_NAME` | The literal value as a string, e.g., "42" (String) | + +The heap names are serialized in heap order: constants first, then +witnesses, then assigned variables from circuit statements. This +ordering matches the order in which items are pushed onto the heap +during compilation. + +Using the debug info, a debugger or tracing tool can display output +such as: + +``` +Line Source Opcode Variable Value +0 L23:C5 EcMulShort token_commit [0x3a2f..., 0x91bc...] +1 L24:C5 EcMulBase rcpt_commit [0x7d1e..., 0x44fa...] +2 L25:C5 EcAdd commitment [0x8b3c..., 0x22de...] +3 L26:C5 ConstrainInstance - - +``` ## Syntax Reference diff --git a/script/research/gg/src/main.rs b/script/research/gg/src/main.rs index e250b262b..4ffeaa436 100644 --- a/script/research/gg/src/main.rs +++ b/script/research/gg/src/main.rs @@ -267,7 +267,7 @@ fn main() -> Result<()> { continue } let mut reader = Cursor::new(pk); - let zkbin = ZkBinary::decode(&bincode)?; + let zkbin = ZkBinary::decode(&bincode, false)?; let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin); let proving_key = ProvingKey::read(&mut reader, circuit)?; mint = Some((proving_key, zkbin)); diff --git a/script/research/rlnv2/src/main.rs b/script/research/rlnv2/src/main.rs index 9c0a5e6d1..e5038c068 100644 --- a/script/research/rlnv2/src/main.rs +++ b/script/research/rlnv2/src/main.rs @@ -97,7 +97,7 @@ fn main() { // Signalling // ========== let signal_zkbin = include_bytes!("../signal.zk.bin"); - let signal_zkbin = ZkBinary::decode(signal_zkbin).unwrap(); + let signal_zkbin = ZkBinary::decode(signal_zkbin, false).unwrap(); let signal_empty_circuit = ZkCircuit::new(empty_witnesses(&signal_zkbin).unwrap(), &signal_zkbin); diff --git a/script/research/zkvm-metering/generator/src/main.rs b/script/research/zkvm-metering/generator/src/main.rs index fce90bf42..ca9e02448 100644 --- a/script/research/zkvm-metering/generator/src/main.rs +++ b/script/research/zkvm-metering/generator/src/main.rs @@ -17,16 +17,16 @@ */ use std::{ - fs::{File, read_dir}, + fs::{read_dir, File}, io::{Read, Write}, path::Path, }; use darkfi::{ - zk::{Proof, ProvingKey, VerifyingKey, Witness, ZkCircuit, empty_witnesses}, + zk::{empty_witnesses, Proof, ProvingKey, VerifyingKey, Witness, ZkCircuit}, zkas::ZkBinary, }; -use darkfi_sdk::pasta::{Eq, Fp, pallas::Base}; +use darkfi_sdk::pasta::{pallas::Base, Eq, Fp}; use darkfi_serial::serialize; use halo2_proofs::dev::CircuitCost; use rand::rngs::OsRng; @@ -83,7 +83,7 @@ fn main() { let mut file = File::open(&path).unwrap(); let mut buf = vec![]; file.read_to_end(&mut buf).unwrap(); - let zkbin = ZkBinary::decode(&buf).unwrap(); + let zkbin = ZkBinary::decode(&buf, false).unwrap(); // Get witnesses and public inputs for that particular zk file let (witnesses, public_inputs) = retrieve_proof_inputs(name); diff --git a/script/research/zkvm-metering/verifier/src/main.rs b/script/research/zkvm-metering/verifier/src/main.rs index 156344e46..519d0aff6 100644 --- a/script/research/zkvm-metering/verifier/src/main.rs +++ b/script/research/zkvm-metering/verifier/src/main.rs @@ -24,9 +24,9 @@ use std::{ }; use darkfi::{ - Result, - zk::{Proof, VerifyingKey, ZkCircuit, empty_witnesses}, + zk::{empty_witnesses, Proof, VerifyingKey, ZkCircuit}, zkas::ZkBinary, + Result, }; use darkfi_sdk::pasta::pallas::Base; use darkfi_serial::deserialize; @@ -66,7 +66,7 @@ fn main() -> Result<()> { file.read_to_end(&mut public_inputs_bin)?; // Deserialize and Verify - let zkbin = ZkBinary::decode(&bincode)?; + let zkbin = ZkBinary::decode(&bincode, false)?; let verifier_witnesses = empty_witnesses(&zkbin)?; // Create the circuit diff --git a/src/blockchain/contract_store.rs b/src/blockchain/contract_store.rs index a7ee4340b..ece59a505 100644 --- a/src/blockchain/contract_store.rs +++ b/src/blockchain/contract_store.rs @@ -188,7 +188,7 @@ impl ContractStore { let (zkbin, vkbin): (Vec, Vec) = deserialize(&zkas_bytes).unwrap(); // The first vec is the compiled zkas binary - let zkbin = ZkBinary::decode(&zkbin).unwrap(); + let zkbin = ZkBinary::decode(&zkbin, false).unwrap(); // Construct the circuit to be able to read the VerifyingKey let circuit = ZkCircuit::new(empty_witnesses(&zkbin).unwrap(), &zkbin); @@ -484,7 +484,7 @@ impl ContractStoreOverlay { let (zkbin, vkbin): (Vec, Vec) = deserialize(&zkas_bytes).unwrap(); // The first vec is the compiled zkas binary - let zkbin = ZkBinary::decode(&zkbin).unwrap(); + let zkbin = ZkBinary::decode(&zkbin, false).unwrap(); // Construct the circuit to be able to read the VerifyingKey let circuit = ZkCircuit::new(empty_witnesses(&zkbin).unwrap(), &zkbin); diff --git a/src/contract/test-harness/src/lib.rs b/src/contract/test-harness/src/lib.rs index 85b1d26d3..8a8679c2f 100644 --- a/src/contract/test-harness/src/lib.rs +++ b/src/contract/test-harness/src/lib.rs @@ -266,7 +266,7 @@ impl TestHarness { let mut proving_keys = HashMap::new(); for (bincode, namespace, pk) in pks { let mut reader = Cursor::new(pk); - let zkbin = ZkBinary::decode(&bincode)?; + let zkbin = ZkBinary::decode(&bincode, false)?; let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin); let proving_key = ProvingKey::read(&mut reader, circuit)?; proving_keys.insert(namespace, (proving_key, zkbin)); diff --git a/src/contract/test-harness/src/vks.rs b/src/contract/test-harness/src/vks.rs index 86dbd5cc4..949b4fde6 100644 --- a/src/contract/test-harness/src/vks.rs +++ b/src/contract/test-harness/src/vks.rs @@ -143,7 +143,7 @@ pub fn get_cached_pks_and_vks() -> Result<(Pks, Vks)> { let mut vks = vec![]; for bincode in bins.iter() { - let zkbin = ZkBinary::decode(bincode)?; + let zkbin = ZkBinary::decode(bincode, false)?; debug!("Building PK for {}", zkbin.namespace); let witnesses = empty_witnesses(&zkbin)?; let circuit = ZkCircuit::new(witnesses, &zkbin); diff --git a/src/runtime/import/db.rs b/src/runtime/import/db.rs index 6aade927d..dd7830673 100644 --- a/src/runtime/import/db.rs +++ b/src/runtime/import/db.rs @@ -890,7 +890,7 @@ pub(crate) fn zkas_db_set(mut ctx: FunctionEnvMut, ptr: WasmPtr, ptr_le }; // Validate the bytes by decoding them into the ZkBinary format - let zkbin = match ZkBinary::decode(&zkbin_bytes) { + let zkbin = match ZkBinary::decode(&zkbin_bytes, false) { Ok(zkbin) => zkbin, Err(e) => { error!( diff --git a/src/sdk/python/src/zkas.rs b/src/sdk/python/src/zkas.rs index 45dbe212c..0a95726ad 100644 --- a/src/sdk/python/src/zkas.rs +++ b/src/sdk/python/src/zkas.rs @@ -67,7 +67,7 @@ impl ZkBinary { analyzer.witnesses, analyzer.statements, analyzer.literals, - true, + false, ); let bincode = compiler.compile().unwrap(); @@ -77,7 +77,7 @@ impl ZkBinary { #[staticmethod] fn decode(bytes: Vec) -> Self { - let bincode = decoder::ZkBinary::decode(bytes.as_slice()).unwrap(); + let bincode = decoder::ZkBinary::decode(bytes.as_slice(), true).unwrap(); Self(bincode) } @@ -88,6 +88,18 @@ impl ZkBinary { fn opcodes(&self) -> Vec { self.0.opcodes.iter().map(|op| ZkOpcode(op.0)).collect() } + + fn opcode_location(&self, opcode_idx: usize) -> Option<(usize, usize)> { + self.0.opcode_location(opcode_idx) + } + + fn heap_name(&self, heap_idx: usize) -> Option<&str> { + self.0.heap_name(heap_idx) + } + + fn literal_name(&self, literal_idx: usize) -> Option<&str> { + self.0.literal_name(literal_idx) + } } #[pyclass(eq, eq_int)] @@ -285,6 +297,7 @@ impl Proof { literals: Vec::new(), witnesses: Vec::new(), opcodes: Vec::new(), + debug_info: None, }; let empty_circuit = zk::vm::ZkCircuit::new(Vec::new(), &zkbin); let empty_py_circuit = ZkCircuit(empty_circuit, Vec::new(), zkbin); diff --git a/src/zkas/compiler.rs b/src/zkas/compiler.rs index a0c678df5..15baa3cb4 100644 --- a/src/zkas/compiler.rs +++ b/src/zkas/compiler.rs @@ -22,6 +22,9 @@ use darkfi_serial::{serialize, VarInt}; use super::{ ast::{Arg, Constant, Literal, Statement, StatementType, Witness}, + constants::{ + SECTION_CIRCUIT, SECTION_CONSTANT, SECTION_DEBUG, SECTION_LITERAL, SECTION_WITNESS, + }, error::ErrorEmitter, types::HeapType, }; @@ -81,7 +84,7 @@ impl Compiler { // In the .constant section of the binary, we write the constant's type, // and the name so the VM can look it up from `src/crypto/constants/`. - bincode.extend_from_slice(b".constant"); + bincode.extend_from_slice(SECTION_CONSTANT); for i in &self.constants { tmp_heap.push(i.name.as_str()); bincode.push(i.typ as u8); @@ -91,7 +94,7 @@ impl Compiler { // Currently, our literals are only Uint64 types, in the binary we'll // add them here in the .literal section. In the VM, they will be on // their own heap, used for reference by opcodes. - bincode.extend_from_slice(b".literal"); + bincode.extend_from_slice(SECTION_LITERAL); for i in &self.literals { bincode.push(i.typ as u8); bincode.extend_from_slice(&serialize(&i.name)); @@ -99,13 +102,13 @@ impl Compiler { // In the .witness section, we write all our witness types, on the heap // they're in order of appearance. - bincode.extend_from_slice(b".witness"); + bincode.extend_from_slice(SECTION_WITNESS); for i in &self.witnesses { tmp_heap.push(i.name.as_str()); bincode.push(i.typ as u8); } - bincode.extend_from_slice(b".circuit"); + bincode.extend_from_slice(SECTION_CIRCUIT); for i in &self.statements { match i.typ { StatementType::Assign => tmp_heap.push(&i.lhs.as_ref().unwrap().name), @@ -155,7 +158,46 @@ impl Compiler { return Ok(bincode) } - // TODO: Otherwise, we proceed appending debug info. + // Otherwise, we proceed appending debug info. + bincode.extend_from_slice(SECTION_DEBUG); + + // Write source locations for each opcode. + // This allows mapping runtime errors back to source lines. + bincode.extend_from_slice(&serialize(&VarInt(self.statements.len() as u64))); + for stmt in &self.statements { + bincode.extend_from_slice(&serialize(&VarInt(stmt.line as u64))); + // For column, use the lhs variable's column if available + let column = stmt.lhs.as_ref().map(|v| v.column).unwrap_or(0); + bincode.extend_from_slice(&serialize(&VarInt(column as u64))); + } + + // Write heap variable names. + // The heap contains constants, witnesses, assigned variables (in order). + // This allows showing meaningful names instead of heap indices. + let heap_size = self.constants.len() + + self.witnesses.len() + + self.statements.iter().filter(|s| s.typ == StatementType::Assign).count(); + bincode.extend_from_slice(&serialize(&VarInt(heap_size as u64))); + + for constant in &self.constants { + bincode.extend_from_slice(&serialize(&constant.name)); + } + + for witness in &self.witnesses { + bincode.extend_from_slice(&serialize(&witness.name)); + } + + for stmt in &self.statements { + if stmt.typ == StatementType::Assign { + bincode.extend_from_slice(&serialize(&stmt.lhs.as_ref().unwrap().name)); + } + } + + // Write literal names (the literal values as strings, e.g. "42") + bincode.extend_from_slice(&serialize(&VarInt(self.literals.len() as u64))); + for literal in &self.literals { + bincode.extend_from_slice(&serialize(&literal.name)); + } Ok(bincode) } diff --git a/src/zkas/constants.rs b/src/zkas/constants.rs index 12e1e256f..a02fc70c8 100644 --- a/src/zkas/constants.rs +++ b/src/zkas/constants.rs @@ -33,3 +33,10 @@ pub const ALLOWED_FIELDS: [&str; 1] = ["pallas"]; /// Maximum recursion depth for nested function calls pub const MAX_RECURSION_DEPTH: usize = 16; + +// Section markers in the binary format +pub(super) const SECTION_CONSTANT: &[u8] = b".constant"; +pub(super) const SECTION_LITERAL: &[u8] = b".literal"; +pub(super) const SECTION_WITNESS: &[u8] = b".witness"; +pub(super) const SECTION_CIRCUIT: &[u8] = b".circuit"; +pub(super) const SECTION_DEBUG: &[u8] = b".debug"; diff --git a/src/zkas/decoder.rs b/src/zkas/decoder.rs index 38baac932..d2f406ddc 100644 --- a/src/zkas/decoder.rs +++ b/src/zkas/decoder.rs @@ -20,23 +20,19 @@ use darkfi_serial::{deserialize_partial, VarInt}; use super::{ compiler::MAGIC_BYTES, - constants::{MAX_K, MAX_NS_LEN, MIN_BIN_SIZE}, + constants::{ + MAX_K, MAX_NS_LEN, MIN_BIN_SIZE, SECTION_CIRCUIT, SECTION_CONSTANT, SECTION_DEBUG, + SECTION_LITERAL, SECTION_WITNESS, + }, types::HeapType, LitType, Opcode, VarType, }; use crate::{Error::ZkasDecoderError as ZkasErr, Result}; -// Section markers in the binary format -const SECTION_CONSTANT: &[u8] = b".constant"; -const SECTION_LITERAL: &[u8] = b".literal"; -const SECTION_WITNESS: &[u8] = b".witness"; -const SECTION_CIRCUIT: &[u8] = b".circuit"; -const SECTION_DEBUG: &[u8] = b".debug"; - /// A ZkBinary decoded from compiled zkas code. /// This is used by the zkvm. /// -/// The binary format consits of: +/// The binary format consists of: /// - Header: magic bytes, version, k param, namespace /// - `.constant` section: constant types and names /// - `.literal` section: literal types and values @@ -52,9 +48,22 @@ pub struct ZkBinary { pub literals: Vec<(LitType, String)>, pub witnesses: Vec, pub opcodes: Vec<(Opcode, Vec<(HeapType, usize)>)>, + pub debug_info: Option, } // ANCHOR_END: zkbinary-struct +/// Debug information decoded from the optional .debug section +/// Contains source mappings to help debug circuit failures. +#[derive(Clone, Debug, Default)] +pub struct DebugInfo { + /// Source locations (line, col) for each opcode + pub opcode_locations: Vec<(usize, usize)>, + /// Variable names for each heap entry (constants, witnesses, assigned vars in order) + pub heap_names: Vec, + /// Literal values as strings + pub literal_names: Vec, +} + // https://stackoverflow.com/questions/35901547/how-can-i-find-a-subsequence-in-a-u8-slice fn find_subslice(haystack: &[u8], needle: &[u8]) -> Option { haystack.windows(needle.len()).position(|window| window == needle) @@ -125,11 +134,20 @@ impl SectionOffsets { fn circuit_bytes<'a>(&self, bytes: &'a [u8]) -> &'a [u8] { &bytes[self.circuit + SECTION_CIRCUIT.len()..self.debug] } + + /// Extract the bytes for the debug section if present + fn debug_bytes<'a>(&self, bytes: &'a [u8]) -> Option<&'a [u8]> { + if self.debug < bytes.len() { + Some(&bytes[self.debug + SECTION_DEBUG.len()..]) + } else { + None + } + } } impl ZkBinary { /// Decode a ZkBinary from compiled bytes - pub fn decode(bytes: &[u8]) -> Result { + pub fn decode(bytes: &[u8], decode_debug_symbols: bool) -> Result { // Ensure that bytes is a certain minimum length. Otherwise the code // below will panic due to an index out of bounds error. if bytes.len() < MIN_BIN_SIZE { @@ -168,9 +186,15 @@ impl ZkBinary { let witnesses = Self::parse_witnesses(offsets.witness_bytes(bytes))?; let opcodes = Self::parse_circuit(offsets.circuit_bytes(bytes))?; - // TODO: Debug info + let mut debug_info = None; + if decode_debug_symbols { + debug_info = match offsets.debug_bytes(bytes) { + Some(debug_bytes) => Some(Self::parse_debug(debug_bytes)?), + None => None, + }; + } - Ok(Self { namespace, k, constants, literals, witnesses, opcodes }) + Ok(Self { namespace, k, constants, literals, witnesses, opcodes, debug_info }) } fn parse_constants(bytes: &[u8]) -> Result> { @@ -280,6 +304,70 @@ impl ZkBinary { Ok(opcodes) } + + fn parse_debug(bytes: &[u8]) -> Result { + let mut offset = 0; + + // Parse opcode source locations + let (num_opcodes, len) = deserialize_partial::(&bytes[offset..])?; + offset += len; + + let mut opcode_locations = Vec::with_capacity(num_opcodes.0 as usize); + for _ in 0..num_opcodes.0 { + let (line, len) = deserialize_partial::(&bytes[offset..])?; + offset += len; + let (column, len) = deserialize_partial::(&bytes[offset..])?; + offset += len; + opcode_locations.push((line.0 as usize, column.0 as usize)); + } + + // Parse heap var names + let (heap_size, len) = deserialize_partial::(&bytes[offset..])?; + offset += len; + + let mut heap_names = Vec::with_capacity(heap_size.0 as usize); + for _ in 0..heap_size.0 { + let (name, len) = deserialize_partial::(&bytes[offset..])?; + offset += len; + heap_names.push(name); + } + + // Parse literal names + let (num_literals, len) = deserialize_partial::(&bytes[offset..])?; + offset += len; + + let mut literal_names = Vec::with_capacity(num_literals.0 as usize); + for _ in 0..num_literals.0 { + let (name, len) = deserialize_partial::(&bytes[offset..])?; + offset += len; + literal_names.push(name); + } + + Ok(DebugInfo { opcode_locations, heap_names, literal_names }) + } + + /// Get the source location (line, column) for a given opcode index. + /// Returns `None` if debug info is not present or index is OOB. + pub fn opcode_location(&self, opcode_idx: usize) -> Option<(usize, usize)> { + self.debug_info.as_ref()?.opcode_locations.get(opcode_idx).copied() + } + + /// Get the variable name for a given heap index. + /// Returns `None` if debug info is not present or index is OOB. + pub fn heap_name(&self, heap_idx: usize) -> Option<&str> { + self.debug_info.as_ref()?.heap_names.get(heap_idx).map(|s| s.as_str()) + } + + /// Get the literal name/value for a given literal index. + /// Returns `None` if debug info is not present or index is OOB. + pub fn literal_name(&self, literal_idx: usize) -> Option<&str> { + self.debug_info.as_ref()?.literal_names.get(literal_idx).map(|s| s.as_str()) + } + + /// Check if debug info is present + pub fn has_debug_info(&self) -> bool { + self.debug_info.is_some() + } } #[cfg(test)] @@ -291,7 +379,7 @@ mod tests { // Out-of-memory panic from string deserialization. // Read `doc/src/zkas/bincode.md` to understand the input. let data = vec![11u8, 1, 177, 53, 1, 0, 0, 0, 0, 255, 0, 204, 200, 72, 72, 72, 72, 1]; - let _dec = ZkBinary::decode(&data); + let _dec = ZkBinary::decode(&data, true); } #[test] @@ -307,6 +395,6 @@ mod tests { 116, 4, 2, 0, 2, 0, 0, 2, 2, 0, 3, 0, 1, 8, 2, 0, 4, 0, 5, 8, 1, 0, 6, 9, 1, 0, 6, 240, 1, 0, 7, 240, 41, 0, 0, 0, 1, 0, 8, ]; - let _dec = ZkBinary::decode(&data); + let _dec = ZkBinary::decode(&data, true); } } diff --git a/tests/halo2_vk_ser.rs b/tests/halo2_vk_ser.rs index 99bbd4ec1..99321b0c4 100644 --- a/tests/halo2_vk_ser.rs +++ b/tests/halo2_vk_ser.rs @@ -46,7 +46,7 @@ use darkfi::{ #[test] fn halo2_vk_ser() -> Result<()> { let bincode = include_bytes!("../proof/opcodes.zk.bin"); - let zkbin = ZkBinary::decode(bincode)?; + let zkbin = ZkBinary::decode(bincode, false)?; let verifier_witnesses = empty_witnesses(&zkbin)?; diff --git a/tests/smt.rs b/tests/smt.rs index 414a8d293..1d4ad1787 100644 --- a/tests/smt.rs +++ b/tests/smt.rs @@ -34,7 +34,7 @@ use darkfi::{ #[test] fn zkvm_smt() -> Result<()> { let bincode = include_bytes!("../proof/smt.zk.bin"); - let zkbin = ZkBinary::decode(bincode)?; + let zkbin = ZkBinary::decode(bincode, false)?; let hasher = PoseidonFp::new(); let store = MemoryStorageFp::new(); diff --git a/tests/zkvm_opcodes.rs b/tests/zkvm_opcodes.rs index dc3187eae..5df05ef93 100644 --- a/tests/zkvm_opcodes.rs +++ b/tests/zkvm_opcodes.rs @@ -46,7 +46,7 @@ use darkfi::{ #[test] fn zkvm_opcodes() -> Result<()> { let bincode = include_bytes!("../proof/opcodes.zk.bin"); - let zkbin = ZkBinary::decode(bincode)?; + let zkbin = ZkBinary::decode(bincode, false)?; // Values for the proof let value = 666_u64;