zkas: Implement debug symbols in the compiled binary

This commit is contained in:
x
2026-01-06 17:21:44 +00:00
parent 43b9695884
commit 3aa0e3b722
27 changed files with 280 additions and 94 deletions

View File

@@ -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)?;

View File

@@ -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(

View File

@@ -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![];

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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:#?}");
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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));

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -188,7 +188,7 @@ impl ContractStore {
let (zkbin, vkbin): (Vec<u8>, Vec<u8>) = 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<u8>, Vec<u8>) = 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);

View File

@@ -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));

View File

@@ -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);

View File

@@ -890,7 +890,7 @@ pub(crate) fn zkas_db_set(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, 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!(

View File

@@ -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<u8>) -> 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<ZkOpcode> {
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);

View File

@@ -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)
}

View File

@@ -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";

View File

@@ -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<VarType>,
pub opcodes: Vec<(Opcode, Vec<(HeapType, usize)>)>,
pub debug_info: Option<DebugInfo>,
}
// 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<String>,
/// Literal values as strings
pub literal_names: Vec<String>,
}
// https://stackoverflow.com/questions/35901547/how-can-i-find-a-subsequence-in-a-u8-slice
fn find_subslice(haystack: &[u8], needle: &[u8]) -> Option<usize> {
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<Self> {
pub fn decode(bytes: &[u8], decode_debug_symbols: bool) -> Result<Self> {
// 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<Vec<(VarType, String)>> {
@@ -280,6 +304,70 @@ impl ZkBinary {
Ok(opcodes)
}
fn parse_debug(bytes: &[u8]) -> Result<DebugInfo> {
let mut offset = 0;
// Parse opcode source locations
let (num_opcodes, len) = deserialize_partial::<VarInt>(&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::<VarInt>(&bytes[offset..])?;
offset += len;
let (column, len) = deserialize_partial::<VarInt>(&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::<VarInt>(&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::<String>(&bytes[offset..])?;
offset += len;
heap_names.push(name);
}
// Parse literal names
let (num_literals, len) = deserialize_partial::<VarInt>(&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::<String>(&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);
}
}

View File

@@ -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)?;

View File

@@ -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();

View File

@@ -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;