/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2026 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use darkfi_serial::{deserialize_partial, VarInt}; use super::{ compiler::MAGIC_BYTES, 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}; /// A ZkBinary decoded from compiled zkas code. /// This is used by the zkvm. /// /// The binary format consists of: /// - Header: magic bytes, version, k param, namespace /// - `.constant` section: constant types and names /// - `.literal` section: literal types and values /// - `.witness` section: witness types /// - `.circuit` section: opcoddes and their arguments /// - `.debug` section (optional): debug informatioon #[derive(Clone, Debug)] // ANCHOR: zkbinary-struct pub struct ZkBinary { pub namespace: String, pub k: u32, pub constants: Vec<(VarType, String)>, 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) } fn find_section(bytes: &[u8], section: &[u8]) -> Result { find_subslice(bytes, section).ok_or_else(|| { ZkasErr(format!("Could not find {} section", String::from_utf8_lossy(section))) }) } struct SectionOffsets { constant: usize, literal: usize, witness: usize, circuit: usize, debug: usize, } impl SectionOffsets { /// Find all section offsets in the binary and validate their order fn find(bytes: &[u8]) -> Result { let constant = find_section(bytes, SECTION_CONSTANT)?; let literal = find_section(bytes, SECTION_LITERAL)?; let witness = find_section(bytes, SECTION_WITNESS)?; let circuit = find_section(bytes, SECTION_CIRCUIT)?; // Debug section is optional, so use end of bytes if not present let debug = find_subslice(bytes, SECTION_DEBUG).unwrap_or(bytes.len()); // Validate section order let sections = [ (constant, ".constant"), (literal, ".literal"), (witness, ".witness"), (circuit, ".circuit"), (debug, "debug/EOF"), ]; for i in 0..sections.len() - 1 { if sections[i].0 > sections[i + 1].0 { return Err(ZkasErr(format!( "{} section appeared before {}", sections[i + 1].1, sections[i].1 ))); } } Ok(Self { constant, literal, witness, circuit, debug }) } /// Extract the bytes for the constant section fn constant_bytes<'a>(&self, bytes: &'a [u8]) -> &'a [u8] { &bytes[self.constant + SECTION_CONSTANT.len()..self.literal] } /// Extract the bytes for the literal section fn literal_bytes<'a>(&self, bytes: &'a [u8]) -> &'a [u8] { &bytes[self.literal + SECTION_LITERAL.len()..self.witness] } /// Extract the bytes for the witness section fn witness_bytes<'a>(&self, bytes: &'a [u8]) -> &'a [u8] { &bytes[self.witness + SECTION_WITNESS.len()..self.circuit] } /// Extract the bytes for the circuit section 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], 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 { return Err(ZkasErr("Not enough bytes".to_string())) } let magic_bytes = &bytes[0..4]; if magic_bytes != MAGIC_BYTES { return Err(ZkasErr("Magic bytes are incorrect".to_string())) } let _binary_version = &bytes[4]; // Deserialize the k param let (k, _): (u32, _) = deserialize_partial(&bytes[5..9])?; // For now, we'll limit k. if k > MAX_K { return Err(ZkasErr(format!("k param is too high, max allowed is {MAX_K}"))) } // After the binary version and k, we're supposed to have the witness namespace let (namespace, _): (String, _) = deserialize_partial(&bytes[9..])?; // Enforce a limit on the namespace string length if namespace.len() > MAX_NS_LEN { return Err(ZkasErr("Namespace too long".to_string())) } // =============== // Section parsing // =============== let offsets = SectionOffsets::find(bytes)?; let constants = Self::parse_constants(offsets.constant_bytes(bytes))?; let literals = Self::parse_literals(offsets.literal_bytes(bytes))?; let witnesses = Self::parse_witnesses(offsets.witness_bytes(bytes))?; let opcodes = Self::parse_circuit(offsets.circuit_bytes(bytes))?; 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, debug_info }) } fn parse_constants(bytes: &[u8]) -> Result> { let mut constants = vec![]; let mut offset = 0; while offset < bytes.len() { let c_type = VarType::from_repr(bytes[offset]).ok_or_else(|| { ZkasErr(format!("Could not decode constant VarType from {}", bytes[offset])) })?; offset += 1; let (name, len) = deserialize_partial::(&bytes[offset..])?; offset += len; constants.push((c_type, name)); } Ok(constants) } fn parse_literals(bytes: &[u8]) -> Result> { let mut literals = vec![]; let mut offset = 0; while offset < bytes.len() { let l_type = LitType::from_repr(bytes[offset]).ok_or_else(|| { ZkasErr(format!("Could not decode literal LitType from {}", bytes[offset])) })?; offset += 1; let (name, len) = deserialize_partial::(&bytes[offset..])?; offset += len; literals.push((l_type, name)); } Ok(literals) } fn parse_witnesses(bytes: &[u8]) -> Result> { let mut witnesses = vec![]; for &byte in bytes { let w_type = VarType::from_repr(byte).ok_or_else(|| { ZkasErr(format!("Could not decode witness VarType from {}", byte)) })?; witnesses.push(w_type); } Ok(witnesses) } #[allow(clippy::type_complexity)] fn parse_circuit(bytes: &[u8]) -> Result)>> { let mut opcodes = vec![]; let mut offset = 0; while offset < bytes.len() { let opcode = Opcode::from_repr(bytes[offset]).ok_or_else(|| { ZkasErr(format!("Could not decode Opcode from {}", bytes[offset])) })?; offset += 1; // TODO: Check that the types and arg number are correct // Parse argument count let (arg_count, len) = deserialize_partial::(&bytes[offset..])?; offset += len; // Parse arguments let mut args = vec![]; for _ in 0..arg_count.0 { // Check bounds to prevent panics if offset >= bytes.len() { return Err(ZkasErr(format!( "Bad offset for circuit: offset {} is >= circuit len {}", offset, bytes.len() ))); } let heap_type_byte = bytes[offset]; offset += 1; if offset >= bytes.len() { return Err(ZkasErr(format!( "Bad offset for circuit: offset {} is >= circuit len {}", offset, bytes.len() ))); } let (heap_index, len) = deserialize_partial::(&bytes[offset..])?; offset += len; let heap_type = HeapType::from_repr(heap_type_byte).ok_or_else(|| { ZkasErr(format!("Could not decode HeapType from {}", heap_type_byte)) })?; args.push((heap_type, heap_index.0 as usize)); } opcodes.push((opcode, args)); } 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)] mod tests { use crate::zkas::ZkBinary; #[test] fn panic_regression_001() { // 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, true); } #[test] fn panic_regression_002() { // Index out of bounds panic in parse_circuit(). // Read `doc/src/zkas/bincode.md` to understand the input. let data = vec![ 11u8, 1, 177, 53, 2, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 83, 105, 109, 112, 108, 101, 46, 99, 111, 110, 115, 116, 97, 110, 116, 3, 18, 86, 65, 76, 85, 69, 95, 67, 79, 77, 77, 73, 84, 95, 86, 65, 76, 85, 69, 2, 19, 86, 65, 76, 85, 69, 95, 67, 79, 77, 77, 73, 84, 95, 82, 65, 77, 68, 79, 77, 46, 108, 105, 116, 101, 114, 97, 108, 46, 119, 105, 116, 110, 101, 115, 115, 16, 18, 46, 99, 105, 114, 99, 117, 105, 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, true); } }