zkas: Rename "stack" to "heap".

"stack" is a misnomer, and the behavior of the zkVM is more heap-like
than stack-like when doing an analogy with general computing.
This commit is contained in:
parazyd
2023-05-10 14:35:51 +02:00
parent fa260e822c
commit bd6a6bfaf2
19 changed files with 187 additions and 189 deletions

View File

@@ -21,7 +21,7 @@ use anyhow::{anyhow, Result};
use darkfi::{
tx::Transaction,
util::parse::encode_base10,
zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_stack::empty_witnesses, Proof},
zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses, Proof},
zkas::ZkBinary,
};
use darkfi_money_contract::{

View File

@@ -20,7 +20,7 @@ use anyhow::{anyhow, Result};
use darkfi::{
tx::Transaction,
util::parse::decode_base10,
zk::{proof::ProvingKey, vm::ZkCircuit, vm_stack::empty_witnesses},
zk::{proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses},
zkas::ZkBinary,
};
use darkfi_money_contract::{

View File

@@ -20,7 +20,7 @@ use anyhow::{anyhow, Result};
use darkfi::{
tx::Transaction,
util::parse::{decode_base10, encode_base10},
zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_stack::empty_witnesses},
zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses},
zkas::ZkBinary,
};
use darkfi_dao_contract::dao_model::DaoBulla;

View File

@@ -32,7 +32,7 @@ use chrono::Utc;
use darkfi::{
runtime::vm_runtime::SMART_CONTRACT_ZKAS_DB_NAME,
tx::Transaction,
zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_stack::empty_witnesses},
zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses},
zkas::ZkBinary,
};
use darkfi_money_contract::{

View File

@@ -9,9 +9,9 @@ Our programs consist of four sections: `constant`, `literal`,
same. Additionally, there is an optional section called `.debug`
which can hold debug info related to the binary.
We currently keep all variables on one stack, and literals on another
stack. Therefore before each `STACK_INDEX` we prepend `STACK_TYPE` so
the VM is able to know which stack it should do lookup from.
We currently keep all variables on one heap, and literals on another
heap. Therefore before each `HEAP_INDEX` we prepend `HEAP_TYPE` so
the VM is able to know which heap it should do lookup from.
The compiled binary blob has the following layout:
@@ -32,8 +32,8 @@ WITNESS_TYPE
WITNESS_TYPE
...
.circuit
OPCODE ARG_NUM STACK_TYPE STACK_INDEX ... STACK_TYPE STACK_INDEX
OPCODE ARG_NUM STACK_TYPE STACK_INDEX ... STACK_TYPE STACK_INDEX
OPCODE ARG_NUM HEAP_TYPE HEAP_INDEX ... HEAP_TYPE HEAP_INDEX
OPCODE ARG_NUM HEAP_TYPE HEAP_INDEX ... HEAP_TYPE HEAP_INDEX
...
.debug
TBD
@@ -78,7 +78,7 @@ The string is serialized with variable-integer encoding.
The constants in the `.constant` section are declared with their type
and name, so that the VM knows how to search for the builtin constant
and add it to the stack.
and add it to the heap.
### `.literal`
@@ -90,7 +90,7 @@ could be extended with signed integers, and strings.
### `.witness`
The `.witness` section holds the circuit witness values in the form
of `WITNESS_TYPE`. Their stack index is incremented for each witness
of `WITNESS_TYPE`. Their heap index is incremented for each witness
as they're kept in order like in the source file. The witnesses
that are of the same type as the circuit itself (typically `Base`)
will be loaded into the circuit as _private values_ using the Halo2
@@ -103,7 +103,7 @@ The `.circuit` section holds the procedural logic of the ZK proof.
In here we have statements with opcodes that are executed as
understood by the VM. The statements are in the form of:
> `OPCODE ARG_NUM STACK_TYPE STACK_INDEX ... STACK_TYPE STACK_INDEX`
> `OPCODE ARG_NUM HEAP_TYPE HEAP_INDEX ... HEAP_TYPE HEAP_INDEX`
where:
@@ -112,14 +112,14 @@ where:
| `OPCODE` | The opcode we wish to execute |
| `ARG_NUM` | The number of arguments given to this opcode |
| | (Note the VM should be checking the correctness of this as well) |
| `STACK_TYPE` | Type of the stack to do lookup from (variables or literals) |
| | (This is prepended to every `STACK_INDEX`) |
| `STACK_INDEX` | The location of the argument on the stack. |
| `HEAP_TYPE` | Type of the heap to do lookup from (variables or literals) |
| | (This is prepended to every `HEAP_INDEX`) |
| `HEAP_INDEX` | The location of the argument on the heap. |
| | (This is supposed to be repeated `ARG_NUM` times) |
In case an opcode has a return value, the value shall be pushed to
the stack and become available for later references.
the heap and become available for later references.
### `.debug`
@@ -168,8 +168,8 @@ TBD
| `LessThanStrict` | Strictly compare if `Base` a is lesser than `Base` b |
| `LessThanLoose` | Loosely compare if `Base` a is lesser than `Base` b |
| `BoolCheck` | Enforce that a `Base` fits in a boolean value (either 0 or 1) |
| `ConstrainEqualBase` | Constrain equality of two `Base` elements from the stack |
| `ConstrainEqualPoint`| Constrain equality of two `EcPoint` elements from the stack |
| `ConstrainEqualBase` | Constrain equality of two `Base` elements from the heap |
| `ConstrainEqualPoint`| Constrain equality of two `EcPoint` elements from the heap |
| `ConstrainInstance` | Constrain a `Base` to a Circuit's Public Input. |
### Built-in Opcode Wrappers

View File

@@ -22,7 +22,7 @@ use darkfi::{
zk::{
proof::{Proof, ProvingKey, VerifyingKey},
vm::{Witness, ZkCircuit},
vm_stack::empty_witnesses,
vm_heap::empty_witnesses,
},
zkas::decoder::ZkBinary,
Result,

View File

@@ -22,7 +22,7 @@ use darkfi::{
zk::{
proof::{Proof, ProvingKey, VerifyingKey},
vm::{Witness, ZkCircuit},
vm_stack::empty_witnesses,
vm_heap::empty_witnesses,
},
zkas::decoder::ZkBinary,
Result,

View File

@@ -37,7 +37,7 @@ use crate::{
zk::{
proof::{Proof, ProvingKey},
vm::ZkCircuit,
vm_stack::Witness,
vm_heap::Witness,
},
zkas::ZkBinary,
Result,

View File

@@ -53,7 +53,7 @@ use crate::{
zk::{
proof::{ProvingKey, VerifyingKey},
vm::ZkCircuit,
vm_stack::empty_witnesses,
vm_heap::empty_witnesses,
},
zkas::ZkBinary,
Error, Result,

View File

@@ -20,8 +20,9 @@
pub mod vm;
pub use vm::ZkCircuit;
pub mod vm_stack;
pub use vm_stack::{empty_witnesses, Witness};
/// VM heap variable definitions and utility functions
pub mod vm_heap;
pub use vm_heap::{empty_witnesses, Witness};
/// ZK gadget implementations
pub mod gadget;

View File

@@ -48,7 +48,7 @@ use halo2_proofs::{
};
use log::{error, trace};
pub use super::vm_stack::{StackVar, Witness};
pub use super::vm_heap::{HeapVar, Witness};
use super::{
assign_free_advice,
gadget::{
@@ -59,7 +59,7 @@ use super::{
},
};
use crate::zkas::{
types::{LitType, StackType},
types::{HeapType, LitType},
Opcode, ZkBinary,
};
@@ -111,7 +111,7 @@ pub struct ZkCircuit {
constants: Vec<String>,
witnesses: Vec<Witness>,
literals: Vec<(LitType, String)>,
opcodes: Vec<(Opcode, Vec<(StackType, usize)>)>,
opcodes: Vec<(Opcode, Vec<(HeapType, usize)>)>,
}
impl ZkCircuit {
@@ -291,12 +291,12 @@ impl Circuit<pallas::Base> for ZkCircuit {
// VM Setup
//====================
// Our stack which holds every variable we reference and create.
let mut stack: Vec<StackVar> = vec![];
// Our heap which holds every variable we reference and create.
let mut heap: Vec<HeapVar> = vec![];
// Our stack which holds all the literal values we have in the circuit.
// Our heap which holds all the literal values we have in the circuit.
// For now, we only support u64.
let mut litstack: Vec<u64> = vec![];
let mut litheap: Vec<u64> = vec![];
// Offset for public inputs
let mut public_inputs_offset = 0;
@@ -346,29 +346,29 @@ impl Circuit<pallas::Base> for ZkCircuit {
Value::known(pallas::Base::one()),
)?;
// Lookup and push constants onto the stack
// Lookup and push constants onto the heap
for constant in &self.constants {
trace!(
target: "zk::vm",
"Pushing constant `{}` to stack index {}",
"Pushing constant `{}` to heap address {}",
constant.as_str(),
stack.len()
heap.len()
);
match constant.as_str() {
"VALUE_COMMIT_VALUE" => {
let vcv = ValueCommitV;
let vcv = FixedPointShort::from_inner(ecc_chip.clone(), vcv);
stack.push(StackVar::EcFixedPointShort(vcv));
heap.push(HeapVar::EcFixedPointShort(vcv));
}
"VALUE_COMMIT_RANDOM" => {
let vcr = OrchardFixedBasesFull::ValueCommitR;
let vcr = FixedPoint::from_inner(ecc_chip.clone(), vcr);
stack.push(StackVar::EcFixedPoint(vcr));
heap.push(HeapVar::EcFixedPoint(vcr));
}
"NULLIFIER_K" => {
let nfk = NullifierK;
let nfk = FixedPointBaseField::from_inner(ecc_chip.clone(), nfk);
stack.push(StackVar::EcFixedPointBase(nfk));
heap.push(HeapVar::EcFixedPointBase(nfk));
}
_ => {
@@ -378,12 +378,12 @@ impl Circuit<pallas::Base> for ZkCircuit {
}
}
// Load the literals onto the literal stack.
// Load the literals onto the literal heap
// N.B. Only uint64 is supported right now.
for literal in &self.literals {
match literal.0 {
LitType::Uint64 => match literal.1.parse::<u64>() {
Ok(v) => litstack.push(v),
Ok(v) => litheap.push(v),
Err(e) => {
error!(target: "zk::vm", "Failed converting u64 literal: {}", e);
return Err(plonk::Error::Synthesis)
@@ -396,7 +396,7 @@ impl Circuit<pallas::Base> for ZkCircuit {
}
}
// Push the witnesses onto the stack, and potentially, if the witness
// Push the witnesses onto the heap, and potentially, if the witness
// is in the Base field (like the entire circuit is), load it into a
// table cell.
for witness in &self.witnesses {
@@ -409,8 +409,8 @@ impl Circuit<pallas::Base> for ZkCircuit {
w.as_ref().map(|cm| cm.to_affine()),
)?;
trace!(target: "zk::vm", "Pushing EcPoint to stack index {}", stack.len());
stack.push(StackVar::EcPoint(point));
trace!(target: "zk::vm", "Pushing EcPoint to heap address {}", heap.len());
heap.push(HeapVar::EcPoint(point));
}
Witness::EcNiPoint(w) => {
@@ -421,8 +421,8 @@ impl Circuit<pallas::Base> for ZkCircuit {
w.as_ref().map(|cm| cm.to_affine()),
)?;
trace!(target: "zk::vm", "Pushing EcNiPoint to stack index {}", stack.len());
stack.push(StackVar::EcNiPoint(point));
trace!(target: "zk::vm", "Pushing EcNiPoint to heap address {}", heap.len());
heap.push(HeapVar::EcNiPoint(point));
}
Witness::EcFixedPoint(_) => {
@@ -438,16 +438,16 @@ impl Circuit<pallas::Base> for ZkCircuit {
*w,
)?;
trace!(target: "zk::vm", "Pushing Base to stack index {}", stack.len());
stack.push(StackVar::Base(base));
trace!(target: "zk::vm", "Pushing Base to heap address {}", heap.len());
heap.push(HeapVar::Base(base));
}
Witness::Scalar(w) => {
// NOTE: Because the type in `halo2_gadgets` does not have a `Clone`
// impl, we push scalars as-is to the stack. They get witnessed
// impl, we push scalars as-is to the heap. They get witnessed
// when they get used.
trace!(target: "zk::vm", "Pushing Scalar to stack index {}", stack.len());
stack.push(StackVar::Scalar(*w));
trace!(target: "zk::vm", "Pushing Scalar to heap address {}", heap.len());
heap.push(HeapVar::Scalar(*w));
}
Witness::MerklePath(w) => {
@@ -455,18 +455,18 @@ impl Circuit<pallas::Base> for ZkCircuit {
let path: Value<[pallas::Base; MERKLE_DEPTH_ORCHARD]> =
w.map(|typed_path| gen_const_array(|i| typed_path[i].inner()));
trace!(target: "zk::vm", "Pushing MerklePath to stack index {}", stack.len());
stack.push(StackVar::MerklePath(path));
trace!(target: "zk::vm", "Pushing MerklePath to heap address {}", heap.len());
heap.push(HeapVar::MerklePath(path));
}
Witness::Uint32(w) => {
trace!(target: "zk::vm", "Pushing Uint32 to stack index {}", stack.len());
stack.push(StackVar::Uint32(*w));
trace!(target: "zk::vm", "Pushing Uint32 to heap address {}", heap.len());
heap.push(HeapVar::Uint32(*w));
}
Witness::Uint64(w) => {
trace!(target: "zk::vm", "Pushing Uint64 to stack index {}", stack.len());
stack.push(StackVar::Uint64(*w));
trace!(target: "zk::vm", "Pushing Uint64 to heap address {}", heap.len());
heap.push(HeapVar::Uint64(*w));
}
}
}
@@ -482,15 +482,15 @@ impl Circuit<pallas::Base> for ZkCircuit {
let args = &opcode.1;
let lhs: Point<pallas::Affine, EccChip<OrchardFixedBases>> =
stack[args[0].1].clone().into();
heap[args[0].1].clone().into();
let rhs: Point<pallas::Affine, EccChip<OrchardFixedBases>> =
stack[args[1].1].clone().into();
heap[args[1].1].clone().into();
let ret = lhs.add(layouter.namespace(|| "EcAdd()"), &rhs)?;
trace!(target: "zk::vm", "Pushing result to stack index {}", stack.len());
stack.push(StackVar::EcPoint(ret));
trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
heap.push(HeapVar::EcPoint(ret));
}
Opcode::EcMul => {
@@ -498,18 +498,18 @@ impl Circuit<pallas::Base> for ZkCircuit {
let args = &opcode.1;
let lhs: FixedPoint<pallas::Affine, EccChip<OrchardFixedBases>> =
stack[args[1].1].clone().into();
heap[args[1].1].clone().into();
let rhs = ScalarFixed::new(
ecc_chip.clone(),
layouter.namespace(|| "EcMul: ScalarFixed::new()"),
stack[args[0].1].clone().into(),
heap[args[0].1].clone().into(),
)?;
let (ret, _) = lhs.mul(layouter.namespace(|| "EcMul()"), rhs)?;
trace!(target: "zk::vm", "Pushing result to stack index {}", stack.len());
stack.push(StackVar::EcPoint(ret));
trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
heap.push(HeapVar::EcPoint(ret));
}
Opcode::EcMulVarBase => {
@@ -517,9 +517,9 @@ impl Circuit<pallas::Base> for ZkCircuit {
let args = &opcode.1;
let lhs: NonIdentityPoint<pallas::Affine, EccChip<OrchardFixedBases>> =
stack[args[1].1].clone().into();
heap[args[1].1].clone().into();
let rhs: AssignedCell<Fp, Fp> = stack[args[0].1].clone().into();
let rhs: AssignedCell<Fp, Fp> = heap[args[0].1].clone().into();
let rhs = ScalarVar::from_base(
ecc_chip.clone(),
layouter.namespace(|| "EcMulVarBase::from_base()"),
@@ -528,8 +528,8 @@ impl Circuit<pallas::Base> for ZkCircuit {
let (ret, _) = lhs.mul(layouter.namespace(|| "EcMulVarBase()"), rhs)?;
trace!(target: "zk::vm", "Pushing result to stack index {}", stack.len());
stack.push(StackVar::EcPoint(ret));
trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
heap.push(HeapVar::EcPoint(ret));
}
Opcode::EcMulBase => {
@@ -537,14 +537,14 @@ impl Circuit<pallas::Base> for ZkCircuit {
let args = &opcode.1;
let lhs: FixedPointBaseField<pallas::Affine, EccChip<OrchardFixedBases>> =
stack[args[1].1].clone().into();
heap[args[1].1].clone().into();
let rhs: AssignedCell<Fp, Fp> = stack[args[0].1].clone().into();
let rhs: AssignedCell<Fp, Fp> = heap[args[0].1].clone().into();
let ret = lhs.mul(layouter.namespace(|| "EcMulBase()"), rhs)?;
trace!(target: "zk::vm", "Pushing result to stack index {}", stack.len());
stack.push(StackVar::EcPoint(ret));
trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
heap.push(HeapVar::EcPoint(ret));
}
Opcode::EcMulShort => {
@@ -552,18 +552,18 @@ impl Circuit<pallas::Base> for ZkCircuit {
let args = &opcode.1;
let lhs: FixedPointShort<pallas::Affine, EccChip<OrchardFixedBases>> =
stack[args[1].1].clone().into();
heap[args[1].1].clone().into();
let rhs = ScalarFixedShort::new(
ecc_chip.clone(),
layouter.namespace(|| "EcMulShort: ScalarFixedShort::new()"),
(stack[args[0].1].clone().into(), one.clone()),
(heap[args[0].1].clone().into(), one.clone()),
)?;
let (ret, _) = lhs.mul(layouter.namespace(|| "EcMulShort()"), rhs)?;
trace!(target: "zk::vm", "Pushing result to stack index {}", stack.len());
stack.push(StackVar::EcPoint(ret));
trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
heap.push(HeapVar::EcPoint(ret));
}
Opcode::EcGetX => {
@@ -571,12 +571,12 @@ impl Circuit<pallas::Base> for ZkCircuit {
let args = &opcode.1;
let point: Point<pallas::Affine, EccChip<OrchardFixedBases>> =
stack[args[0].1].clone().into();
heap[args[0].1].clone().into();
let ret = point.inner().x();
trace!(target: "zk::vm", "Pushing result to stack index {}", stack.len());
stack.push(StackVar::Base(ret));
trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
heap.push(HeapVar::Base(ret));
}
Opcode::EcGetY => {
@@ -584,12 +584,12 @@ impl Circuit<pallas::Base> for ZkCircuit {
let args = &opcode.1;
let point: Point<pallas::Affine, EccChip<OrchardFixedBases>> =
stack[args[0].1].clone().into();
heap[args[0].1].clone().into();
let ret = point.inner().y();
trace!(target: "zk::vm", "Pushing result to stack index {}", stack.len());
stack.push(StackVar::Base(ret));
trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
heap.push(HeapVar::Base(ret));
}
Opcode::PoseidonHash => {
@@ -600,7 +600,7 @@ impl Circuit<pallas::Base> for ZkCircuit {
Vec::with_capacity(args.len());
for idx in args {
poseidon_message.push(stack[idx.1].clone().into());
poseidon_message.push(heap[idx.1].clone().into());
}
macro_rules! poseidon_hash {
@@ -624,8 +624,8 @@ impl Circuit<pallas::Base> for ZkCircuit {
let $cell: AssignedCell<Fp, Fp> = $output.into();
trace!(target: "zk::vm", "Pushing hash to stack index {}", stack.len());
stack.push(StackVar::Base($cell));
trace!(target: "zk::vm", "Pushing hash to heap address {}", heap.len());
heap.push(HeapVar::Base($cell));
};
}
@@ -650,9 +650,9 @@ impl Circuit<pallas::Base> for ZkCircuit {
trace!(target: "zk::vm", "Executing `MerkleRoot{:?}` opcode", opcode.1);
let args = &opcode.1;
let leaf_pos = stack[args[0].1].clone().into();
let merkle_path = stack[args[1].1].clone().into();
let leaf = stack[args[2].1].clone().into();
let leaf_pos = heap[args[0].1].clone().into();
let merkle_path = heap[args[1].1].clone().into();
let leaf = heap[args[2].1].clone().into();
let merkle_inputs = MerklePath::construct(
[config.merkle_chip_1(), config.merkle_chip_2()],
@@ -664,55 +664,55 @@ impl Circuit<pallas::Base> for ZkCircuit {
let root = merkle_inputs
.calculate_root(layouter.namespace(|| "MerkleRoot()"), leaf)?;
trace!(target: "zk::vm", "Pushing merkle root to stack index {}", stack.len());
stack.push(StackVar::Base(root));
trace!(target: "zk::vm", "Pushing merkle root to heap address {}", heap.len());
heap.push(HeapVar::Base(root));
}
Opcode::BaseAdd => {
trace!(target: "zk::vm", "Executing `BaseAdd{:?}` opcode", opcode.1);
let args = &opcode.1;
let lhs = &stack[args[0].1].clone().into();
let rhs = &stack[args[1].1].clone().into();
let lhs = &heap[args[0].1].clone().into();
let rhs = &heap[args[1].1].clone().into();
let sum = arith_chip.add(layouter.namespace(|| "BaseAdd()"), lhs, rhs)?;
trace!(target: "zk::vm", "Pushing sum to stack index {}", stack.len());
stack.push(StackVar::Base(sum));
trace!(target: "zk::vm", "Pushing sum to heap address {}", heap.len());
heap.push(HeapVar::Base(sum));
}
Opcode::BaseMul => {
trace!(target: "zk::vm", "Executing `BaseSub{:?}` opcode", opcode.1);
let args = &opcode.1;
let lhs = &stack[args[0].1].clone().into();
let rhs = &stack[args[1].1].clone().into();
let lhs = &heap[args[0].1].clone().into();
let rhs = &heap[args[1].1].clone().into();
let product = arith_chip.mul(layouter.namespace(|| "BaseMul()"), lhs, rhs)?;
trace!(target: "zk::vm", "Pushing product to stack index {}", stack.len());
stack.push(StackVar::Base(product));
trace!(target: "zk::vm", "Pushing product to heap address {}", heap.len());
heap.push(HeapVar::Base(product));
}
Opcode::BaseSub => {
trace!(target: "zk::vm", "Executing `BaseSub{:?}` opcode", opcode.1);
let args = &opcode.1;
let lhs = &stack[args[0].1].clone().into();
let rhs = &stack[args[1].1].clone().into();
let lhs = &heap[args[0].1].clone().into();
let rhs = &heap[args[1].1].clone().into();
let difference =
arith_chip.sub(layouter.namespace(|| "BaseSub()"), lhs, rhs)?;
trace!(target: "zk::vm", "Pushing difference to stack index {}", stack.len());
stack.push(StackVar::Base(difference));
trace!(target: "zk::vm", "Pushing difference to heap address {}", heap.len());
heap.push(HeapVar::Base(difference));
}
Opcode::WitnessBase => {
trace!(target: "zk::vm", "Executing `WitnessBase{:?}` opcode", opcode.1);
//let args = &opcode.1;
let lit = litstack[literals_offset];
let lit = litheap[literals_offset];
literals_offset += 1;
let witness = assign_free_advice(
@@ -721,18 +721,18 @@ impl Circuit<pallas::Base> for ZkCircuit {
Value::known(pallas::Base::from(lit)),
)?;
trace!(target: "zk::vm", "Pushing assignment to stack index {}", stack.len());
stack.push(StackVar::Base(witness));
trace!(target: "zk::vm", "Pushing assignment to heap address {}", heap.len());
heap.push(HeapVar::Base(witness));
}
Opcode::RangeCheck => {
trace!(target: "zk::vm", "Executing `RangeCheck{:?}` opcode", opcode.1);
let args = &opcode.1;
let lit = litstack[literals_offset];
let lit = litheap[literals_offset];
literals_offset += 1;
let arg = stack[args[1].1].clone();
let arg = heap[args[1].1].clone();
match lit {
64 => {
@@ -760,8 +760,8 @@ impl Circuit<pallas::Base> for ZkCircuit {
trace!(target: "zk::vm", "Executing `LessThanStrict{:?}` opcode", opcode.1);
let args = &opcode.1;
let a = stack[args[0].1].clone().into();
let b = stack[args[1].1].clone().into();
let a = heap[args[0].1].clone().into();
let b = heap[args[1].1].clone().into();
lessthan_chip.copy_less_than(
layouter.namespace(|| "copy a<b check"),
@@ -776,8 +776,8 @@ impl Circuit<pallas::Base> for ZkCircuit {
trace!(target: "zk::vm", "Executing `LessThanLoose{:?}` opcode", opcode.1);
let args = &opcode.1;
let a = stack[args[0].1].clone().into();
let b = stack[args[1].1].clone().into();
let a = heap[args[0].1].clone().into();
let b = heap[args[1].1].clone().into();
lessthan_chip.copy_less_than(
layouter.namespace(|| "copy a<b check"),
@@ -792,7 +792,7 @@ impl Circuit<pallas::Base> for ZkCircuit {
trace!(target: "zk::vm", "Executing `BoolCheck{:?}` opcode", opcode.1);
let args = &opcode.1;
let w = stack[args[0].1].clone().into();
let w = heap[args[0].1].clone().into();
boolcheck_chip
.small_range_check(layouter.namespace(|| "copy boolean check"), w)?;
@@ -802,8 +802,8 @@ impl Circuit<pallas::Base> for ZkCircuit {
trace!(target: "zk::vm", "Executing `ConstrainEqualBase{:?}` opcode", opcode.1);
let args = &opcode.1;
let lhs: AssignedCell<Fp, Fp> = stack[args[0].1].clone().into();
let rhs: AssignedCell<Fp, Fp> = stack[args[1].1].clone().into();
let lhs: AssignedCell<Fp, Fp> = heap[args[0].1].clone().into();
let rhs: AssignedCell<Fp, Fp> = heap[args[1].1].clone().into();
layouter.assign_region(
|| "constrain witnessed base equality",
@@ -816,10 +816,10 @@ impl Circuit<pallas::Base> for ZkCircuit {
let args = &opcode.1;
let lhs: Point<pallas::Affine, EccChip<OrchardFixedBases>> =
stack[args[0].1].clone().into();
heap[args[0].1].clone().into();
let rhs: Point<pallas::Affine, EccChip<OrchardFixedBases>> =
stack[args[1].1].clone().into();
heap[args[1].1].clone().into();
lhs.constrain_equal(
layouter.namespace(|| "constrain ec point equality"),
@@ -831,7 +831,7 @@ impl Circuit<pallas::Base> for ZkCircuit {
trace!(target: "zk::vm", "Executing `ConstrainInstance{:?}` opcode", opcode.1);
let args = &opcode.1;
let var: AssignedCell<Fp, Fp> = stack[args[0].1].clone().into();
let var: AssignedCell<Fp, Fp> = heap[args[0].1].clone().into();
layouter.constrain_instance(
var.cell(),

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//! VM stack type abstractions
//! VM heap type abstractions
use darkfi_sdk::crypto::{constants::OrchardFixedBases, MerkleNode};
use halo2_gadgets::ecc::{
chip::EccChip, FixedPoint, FixedPointBaseField, FixedPointShort, NonIdentityPoint, Point,
@@ -71,7 +71,7 @@ pub fn empty_witnesses(zkbin: &ZkBinary) -> Vec<Witness> {
/// These represent the witness types inside the circuit
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
pub enum StackVar {
pub enum HeapVar {
EcPoint(Point<pallas::Affine, EccChip<OrchardFixedBases>>),
EcNiPoint(NonIdentityPoint<pallas::Affine, EccChip<OrchardFixedBases>>),
EcFixedPoint(FixedPoint<pallas::Affine, EccChip<OrchardFixedBases>>),
@@ -87,10 +87,10 @@ pub enum StackVar {
// TODO: Make this not panic (try_from)
macro_rules! impl_from {
($variant:ident, $fortype:ty) => {
impl From<StackVar> for $fortype {
fn from(value: StackVar) -> Self {
impl From<HeapVar> for $fortype {
fn from(value: HeapVar) -> Self {
match value {
StackVar::$variant(v) => v,
HeapVar::$variant(v) => v,
_ => unreachable!(),
}
}

View File

@@ -32,7 +32,7 @@ pub struct Analyzer {
pub witnesses: Vec<Witness>,
pub statements: Vec<Statement>,
pub literals: Vec<Literal>,
pub stack: Vec<Variable>,
pub heap: Vec<Variable>,
error: ErrorEmitter,
}
@@ -49,15 +49,15 @@ impl Analyzer {
let lines: Vec<String> = source.as_str().lines().map(|x| x.to_string()).collect();
let error = ErrorEmitter::new("Semantic", filename, lines);
Self { constants, witnesses, statements, literals: vec![], stack: vec![], error }
Self { constants, witnesses, statements, literals: vec![], heap: vec![], error }
}
pub fn analyze_types(&mut self) {
// To work around the pedantic safety, we'll make new vectors and then
// replace the `statements` and `stack` vectors from the `Analyzer`
// replace the `statements` and `heap` vectors from the `Analyzer`
// object when we are done.
let mut statements = vec![];
let mut stack = vec![];
let mut heap = vec![];
for statement in &self.statements {
//println!("{:?}", statement);
@@ -120,7 +120,7 @@ impl Analyzer {
// convert it to another statement that will get executed
// before this one. An important assumption is that this
// opcode has a return value. When executed we will push
// this value onto the stack and use it as a reference to
// this value onto the heap and use it as a reference to
// the actual statement we're parsing at this moment.
// TODO: FIXME: This needs a recursive algorithm, as this
// only allows a single nested function.
@@ -238,16 +238,16 @@ impl Analyzer {
// Add this to the list of statements.
statements.push(s);
// We replace self.stack here so we can do proper stack lookups.
stack.push(v.clone());
self.stack = stack.clone();
// We replace self.heap here so we can do proper heap lookups.
heap.push(v.clone());
self.heap = heap.clone();
//println!("{:#?}", stack);
//println!("{:#?}", heap);
//println!("{:#?}", statements);
continue
} // <-- Arg::Func
// The literals get pushed on their own "stack", and
// The literals get pushed on their own "heap", and
// then the compiler will reference them by their own
// index when it comes to running the statement that
// requires the literal type.
@@ -336,27 +336,27 @@ impl Analyzer {
stmt.rhs = rhs;
// In case this statement is an assignment, we will push its
// result on the stack.
// result on the heap.
if statement.typ == StatementType::Assign {
let mut var = statement.lhs.clone().unwrap();
var.typ = return_types[0];
stmt.lhs = Some(var.clone());
stack.push(var.clone());
self.stack = stack.clone();
heap.push(var.clone());
self.heap = heap.clone();
}
//println!("{:#?}", stmt);
statements.push(stmt);
} // <-- for statement in &self.statements
// Here we replace the self.statements and self.stack with what we
// Here we replace the self.statements and self.heap with what we
// built so far. These can be used later on by the compiler after
// this function is finished.
self.statements = statements;
self.stack = stack;
self.heap = heap;
//println!("=================STATEMENTS===============\n{:#?}", self.statements);
//println!("===================STACK==================\n{:#?}", self.stack);
//println!("====================HEAP==================\n{:#?}", self.heap);
//println!("==================LITERALS================\n{:#?}", self.literals);
}
@@ -369,7 +369,7 @@ impl Analyzer {
return Some(Var::Witness(r))
}
if let Some(r) = self.lookup_stack(name) {
if let Some(r) = self.lookup_heap(name) {
return Some(Var::Variable(r))
}
@@ -396,8 +396,8 @@ impl Analyzer {
None
}
fn lookup_stack(&self, name: &str) -> Option<Variable> {
for i in &self.stack {
fn lookup_heap(&self, name: &str) -> Option<Variable> {
for i in &self.heap {
if i.name == name {
return Some(i.clone())
}
@@ -407,22 +407,22 @@ impl Analyzer {
}
pub fn analyze_semantic(&mut self) {
let mut stack = vec![];
let mut heap = vec![];
println!("Loading constants...\n-----");
for i in &self.constants {
println!("Adding `{}` to stack", i.name);
stack.push(&i.name);
println!("Adding `{}` to heap", i.name);
heap.push(&i.name);
Analyzer::pause();
}
println!("Stack:\n{:#?}\n-----", stack);
println!("Heap:\n{:#?}\n-----", heap);
println!("Loading witnesses...\n-----");
for i in &self.witnesses {
println!("Adding `{}` to stack", i.name);
stack.push(&i.name);
println!("Adding `{}` to heap", i.name);
heap.push(&i.name);
Analyzer::pause();
}
println!("Stack:\n{:#?}\n-----", stack);
println!("Heap:\n{:#?}\n-----", heap);
println!("Loading circuit...");
for i in &self.statements {
let mut argnames = vec![];
@@ -441,12 +441,12 @@ impl Analyzer {
for arg in &i.rhs {
if let Arg::Var(arg) = arg {
print!("Looking up `{}` on the stack... ", arg.name);
if let Some(index) = stack.iter().position(|&r| r == &arg.name) {
println!("Found at stack index {}", index);
print!("Looking up `{}` on the heap... ", arg.name);
if let Some(index) = heap.iter().position(|&r| r == &arg.name) {
println!("Found at heap index {}", index);
} else {
self.error.abort(
&format!("Could not find `{}` on the stack", arg.name),
&format!("Could not find `{}` on the heap", arg.name),
arg.line,
arg.column,
);
@@ -462,9 +462,9 @@ impl Analyzer {
}
match i.typ {
StatementType::Assign => {
println!("Pushing result as `{}` to stack", &i.lhs.as_ref().unwrap().name);
stack.push(&i.lhs.as_ref().unwrap().name);
println!("Stack:\n{:#?}\n-----", stack);
println!("Pushing result as `{}` to heap", &i.lhs.as_ref().unwrap().name);
heap.push(&i.lhs.as_ref().unwrap().name);
println!("Heap:\n{:#?}\n-----", heap);
}
StatementType::Call => {
println!("-----");

View File

@@ -23,7 +23,7 @@ use darkfi_serial::{serialize, VarInt};
use super::{
ast::{Arg, Constant, Literal, Statement, StatementType, Witness},
error::ErrorEmitter,
types::StackType,
types::HeapType,
};
/// Version of the binary
@@ -71,40 +71,40 @@ impl Compiler {
// Write the circuit's namespace
bincode.extend_from_slice(&serialize(&self.namespace));
// Temporaty stack vector for lookups
let mut tmp_stack = vec![];
// Temporary heap vector for lookups
let mut tmp_heap = vec![];
// 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");
for i in &self.constants {
tmp_stack.push(i.name.as_str());
tmp_heap.push(i.name.as_str());
bincode.push(i.typ as u8);
bincode.extend_from_slice(&serialize(&i.name));
}
// 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 stack, used for reference by opcodes.
// their own heap, used for reference by opcodes.
bincode.extend_from_slice(b".literal");
for i in &self.literals {
bincode.push(i.typ as u8);
bincode.extend_from_slice(&serialize(&i.name));
}
// In the .witness section, we write all our witness types, on the stack
// 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");
for i in &self.witnesses {
tmp_stack.push(i.name.as_str());
tmp_heap.push(i.name.as_str());
bincode.push(i.typ as u8);
}
bincode.extend_from_slice(b".circuit");
for i in &self.statements {
match i.typ {
StatementType::Assign => tmp_stack.push(&i.lhs.as_ref().unwrap().name),
// In case of a simple call, we don't append anything to the stack
StatementType::Assign => tmp_heap.push(&i.lhs.as_ref().unwrap().name),
// In case of a simple call, we don't append anything to the heap
StatementType::Call => {}
// TODO: FIXME: unreachable is reached with missing semicolons in the code
_ => unreachable!(),
@@ -116,21 +116,21 @@ impl Compiler {
for arg in &i.rhs {
match arg {
Arg::Var(arg) => {
if let Some(found) = Compiler::lookup_stack(&tmp_stack, &arg.name) {
bincode.push(StackType::Var as u8);
if let Some(found) = Compiler::lookup_heap(&tmp_heap, &arg.name) {
bincode.push(HeapType::Var as u8);
bincode.extend_from_slice(&serialize(&VarInt(found as u64)));
continue
}
self.error.abort(
&format!("Failed finding a stack reference for `{}`", arg.name),
&format!("Failed finding a heap reference for `{}`", arg.name),
arg.line,
arg.column,
);
}
Arg::Lit(lit) => {
if let Some(found) = Compiler::lookup_literal(&self.literals, &lit.name) {
bincode.push(StackType::Lit as u8);
bincode.push(HeapType::Lit as u8);
bincode.extend_from_slice(&serialize(&VarInt(found as u64)));
continue
}
@@ -156,8 +156,8 @@ impl Compiler {
bincode
}
fn lookup_stack(stack: &[&str], name: &str) -> Option<usize> {
for (idx, n) in stack.iter().enumerate() {
fn lookup_heap(heap: &[&str], name: &str) -> Option<usize> {
for (idx, n) in heap.iter().enumerate() {
if n == &name {
return Some(idx)
}

View File

@@ -18,7 +18,7 @@
use darkfi_serial::{deserialize_partial, VarInt};
use super::{compiler::MAGIC_BYTES, types::StackType, LitType, Opcode, VarType};
use super::{compiler::MAGIC_BYTES, types::HeapType, LitType, Opcode, VarType};
use crate::{Error::ZkasDecoderError as ZkasErr, Result};
/// A ZkBinary decoded from compiled zkas code.
@@ -29,7 +29,7 @@ pub struct ZkBinary {
pub constants: Vec<(VarType, String)>,
pub literals: Vec<(LitType, String)>,
pub witnesses: Vec<VarType>,
pub opcodes: Vec<(Opcode, Vec<(StackType, usize)>)>,
pub opcodes: Vec<(Opcode, Vec<(HeapType, usize)>)>,
}
// https://stackoverflow.com/questions/35901547/how-can-i-find-a-subsequence-in-a-u8-slice
@@ -177,7 +177,7 @@ impl ZkBinary {
}
#[allow(clippy::type_complexity)]
fn parse_circuit(bytes: &[u8]) -> Result<Vec<(Opcode, Vec<(StackType, usize)>)>> {
fn parse_circuit(bytes: &[u8]) -> Result<Vec<(Opcode, Vec<(HeapType, usize)>)>> {
let mut opcodes = vec![];
let mut iter_offset = 0;
@@ -198,20 +198,17 @@ impl ZkBinary {
let mut args = vec![];
for _ in 0..arg_num.0 {
let stack_type = bytes[iter_offset];
let heap_type = bytes[iter_offset];
iter_offset += 1;
let (stack_index, offset) = deserialize_partial::<VarInt>(&bytes[iter_offset..])?;
let (heap_index, offset) = deserialize_partial::<VarInt>(&bytes[iter_offset..])?;
iter_offset += offset;
let stack_type = match StackType::from_repr(stack_type) {
let heap_type = match HeapType::from_repr(heap_type) {
Some(v) => v,
None => {
return Err(ZkasErr(format!(
"Could not decode StackType from {}",
stack_type
)))
return Err(ZkasErr(format!("Could not decode HeapType from {}", heap_type)))
}
};
args.push((stack_type, stack_index.0 as usize)); // FIXME, why?
args.push((heap_type, heap_index.0 as usize)); // FIXME, why?
}
opcodes.push((opcode, args));

View File

@@ -263,7 +263,7 @@ impl Parser {
// Grab tokens for each statement
for i in circuit_tokens[2..circuit_tokens.len() - 1].iter() {
if i.token_type == TokenType::Semicolon {
// Push completed statement to the stack
// Push completed statement to the heap
circuit_stmts.push(circuit_stmt.clone());
circuit_stmt = vec![];
continue
@@ -610,7 +610,7 @@ impl Parser {
//
// constrain_instance(ec_get_x(token_commit));
//
// The inner call's result would still get pushed on the stack,
// The inner call's result would still get pushed on the heap,
// but it will not be accessible in any other scope.
//
// In certain opcodes, we also support literal types, and the
@@ -620,7 +620,7 @@ impl Parser {
// zero = witness_base(0);
//
// The literal type is used only in the function call's scope, but
// the result is then accessible on the stack to be used by further
// the result is then accessible on the heap to be used by further
// computation.
//
// Regarding multiple return values from opcodes, this is perhaps
@@ -788,7 +788,7 @@ impl Parser {
// Recurse this function to get the params of the nested one.
let args = self.parse_function_call(arg, iter);
// Then we assign a "fake" variable that serves as a stack
// Then we assign a "fake" variable that serves as a heap
// reference.
let var = Variable {
name: format!("_op_inner_{}_{}", arg.line, arg.column),

View File

@@ -16,15 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/// Stack types in bincode & vm
/// Heap types in bincode & vm
#[derive(Clone, Debug)]
#[repr(u8)]
pub enum StackType {
pub enum HeapType {
Var = 0x00,
Lit = 0x01,
}
impl StackType {
impl HeapType {
pub fn from_repr(b: u8) -> Option<Self> {
match b {
0x00 => Some(Self::Var),

View File

@@ -36,7 +36,7 @@ use darkfi::{
zk::{
proof::{ProvingKey, VerifyingKey},
vm::ZkCircuit,
vm_stack::{empty_witnesses, Witness},
vm_heap::{empty_witnesses, Witness},
Proof,
},
zkas::ZkBinary,

View File

@@ -36,7 +36,7 @@ use darkfi::{
zk::{
proof::{ProvingKey, VerifyingKey},
vm::ZkCircuit,
vm_stack::{empty_witnesses, Witness},
vm_heap::{empty_witnesses, Witness},
Proof,
},
zkas::ZkBinary,