Browse Source

sdk-py: Add ZkCircuit, Proof, ProvingKey, VerifiyingKey, ZkBinary

freerangedev 3 years ago
parent
commit
46ed0a24b0

+ 1 - 0
doc/src/zkas/bincode.md

@@ -180,6 +180,7 @@ TBD
 | `EcMul`               | `ec_mul(EcPoint a, EcPoint c)`                          | `(EcPoint c)` |
 | `EcMulBase`           | `ec_mul_base(Base a, EcFixedPointBase b)`               | `(EcPoint c)` |
 | `EcMulShort`          | `ec_mul_short(Base a, EcFixedPointShort b)`             | `(EcPoint c)` |
+| `EcMulVarBase`        | `ec_mul_var_base()`                                     | `()`          |
 | `EcGetX`              | `ec_get_x(EcPoint a)`                                   | `(Base x)`    |
 | `EcGetY`              | `ec_get_y(EcPoint a)`                                   | `(Base y)`    |
 | `PoseidonHash`        | `poseidon_hash(Base a, ..., Base n)`                    | `(Base h)`    |

+ 1 - 1
src/sdk-py/Cargo.toml

@@ -9,7 +9,7 @@ name = "darkfi_sdk_py"
 crate-type = ["cdylib"]
 
 [dependencies]
-pyo3 = "0.18.3"
+pyo3 = "0.19.0"
 darkfi-sdk = { path = "../sdk" }
 rand = "0.8.5"
 halo2_gadgets = "0.3.0"

+ 25 - 0
src/sdk-py/src/affine.rs

@@ -0,0 +1,25 @@
+use crate::base::Base;
+use darkfi_sdk::{crypto::pallas, pasta::arithmetic::CurveAffine};
+use pyo3::prelude::*;
+
+/// A Pallas point in the affine coordinate space (or the point at infinity).
+#[pyclass]
+pub struct Affine(pub(crate) pallas::Affine);
+
+#[pymethods]
+impl Affine {
+    fn __str__(&self) -> String {
+        format!("Affine({:?})", self.0)
+    }
+
+    fn coordinates(&self) -> (Base, Base) {
+        let coords = self.0.coordinates().unwrap();
+        (Base(*coords.x()), Base(*coords.y()))
+    }
+}
+
+pub fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&PyModule> {
+    let submod = PyModule::new(py, "affine")?;
+    submod.add_class::<Affine>()?;
+    Ok(submod)
+}

+ 178 - 0
src/sdk-py/src/base.rs

@@ -0,0 +1,178 @@
+use darkfi_sdk::{
+    crypto::{
+        pallas,
+        pasta_prelude::{Field, PrimeField},
+        poseidon_hash, MerkleNode,
+    },
+    incrementalmerkletree::Hashable,
+    pasta::group::ff::FromUniformBytes,
+};
+use pyo3::prelude::*;
+use rand::rngs::OsRng;
+use std::ops::Deref;
+
+/// The base field of the Pallas and iso-Pallas curves.
+/// Randomness is provided by the OS and on the Rust side.
+#[pyclass]
+pub struct Base(pub(crate) pallas::Base);
+
+#[pymethods]
+impl Base {
+    // Why is this not callable?
+    #[new]
+    fn from_u64(v: u64) -> Self {
+        Self(pallas::Base::from(v))
+    }
+
+    #[staticmethod]
+    fn from_raw(v: [u64; 4]) -> Self {
+        Self(pallas::Base::from_raw(v))
+    }
+
+    #[staticmethod]
+    fn from_uniform_bytes(bytes: [u8; 64]) -> Self {
+        Self(pallas::Base::from_uniform_bytes(&bytes))
+    }
+
+    #[staticmethod]
+    fn random() -> Self {
+        Self(pallas::Base::random(&mut OsRng))
+    }
+
+    #[staticmethod]
+    fn modulus() -> String {
+        pallas::Base::MODULUS.to_string()
+    }
+
+    #[staticmethod]
+    fn zero() -> Self {
+        Self(pallas::Base::zero())
+    }
+
+    #[staticmethod]
+    fn one() -> Self {
+        Self(pallas::Base::one())
+    }
+
+    #[staticmethod]
+    fn poseidon_hash(messages: Vec<&PyCell<Self>>) -> Self {
+        let l = messages.len();
+        let messages: Vec<pallas::Base> = messages.iter().map(|m| m.borrow().deref().0).collect();
+        if l == 1 {
+            let m: [pallas::Base; 1] = messages.try_into().unwrap();
+            Self(poseidon_hash(m))
+        } else if l == 2 {
+            let m: [pallas::Base; 2] = messages.try_into().unwrap();
+            Self(poseidon_hash(m))
+        } else if l == 3 {
+            let m: [pallas::Base; 3] = messages.try_into().unwrap();
+            Self(poseidon_hash(m))
+        } else if l == 4 {
+            let m: [pallas::Base; 4] = messages.try_into().unwrap();
+            Self(poseidon_hash(m))
+        } else if l == 5 {
+            let m: [pallas::Base; 5] = messages.try_into().unwrap();
+            Self(poseidon_hash(m))
+        } else if l == 6 {
+            let m: [pallas::Base; 6] = messages.try_into().unwrap();
+            Self(poseidon_hash(m))
+        } else if l == 7 {
+            let m: [pallas::Base; 7] = messages.try_into().unwrap();
+            Self(poseidon_hash(m))
+        } else if l == 8 {
+            let m: [pallas::Base; 8] = messages.try_into().unwrap();
+            Self(poseidon_hash(m))
+        } else if l == 9 {
+            let m: [pallas::Base; 9] = messages.try_into().unwrap();
+            Self(poseidon_hash(m))
+        } else if l == 10 {
+            let m: [pallas::Base; 10] = messages.try_into().unwrap();
+            Self(poseidon_hash(m))
+        } else if l == 11 {
+            let m: [pallas::Base; 11] = messages.try_into().unwrap();
+            Self(poseidon_hash(m))
+        } else if l == 12 {
+            let m: [pallas::Base; 12] = messages.try_into().unwrap();
+            Self(poseidon_hash(m))
+        } else if l == 13 {
+            let m: [pallas::Base; 13] = messages.try_into().unwrap();
+            Self(poseidon_hash(m))
+        } else if l == 14 {
+            let m: [pallas::Base; 14] = messages.try_into().unwrap();
+            Self(poseidon_hash(m))
+        } else if l == 15 {
+            let m: [pallas::Base; 15] = messages.try_into().unwrap();
+            Self(poseidon_hash(m))
+        } else if l == 16 {
+            let m: [pallas::Base; 16] = messages.try_into().unwrap();
+            Self(poseidon_hash(m))
+        } else {
+            panic!("Messages length violation, must be: 1 <= len <= 16");
+        }
+    }
+
+    /// pos(ition) encodes the left/right position on each level
+    /// path is the the silbling on each level
+    #[staticmethod]
+    fn merkle_root(i: u64, p: Vec<&PyCell<Base>>, a: &Base) -> Self {
+        // TOOD: consider adding length check, for i and path, for extra defensiness
+        let mut current = MerkleNode::new(a.0);
+        for (level, sibling) in p.iter().enumerate() {
+            let level = level as u8;
+            let sibling = MerkleNode::new(sibling.borrow().deref().0);
+            current = if i & (1 << level) == 0 {
+                MerkleNode::combine(level.into(), &current, &sibling)
+            } else {
+                MerkleNode::combine(level.into(), &sibling, &current)
+            };
+        }
+        let root = current.inner();
+        Self(root)
+    }
+
+    // For some reason, the name needs to be explictely stated
+    // for Python to correctly implement
+    #[pyo3(name = "__str__")]
+    fn __str_(&self) -> String {
+        format!("Base({:?})", self.0)
+    }
+
+    #[pyo3(name = "__repr__")]
+    fn __repr_(&self) -> String {
+        format!("Base({:?})", self.0)
+    }
+
+    fn eq(&self, rhs: &Self) -> bool {
+        self.0.eq(&rhs.0)
+    }
+
+    fn add(&self, rhs: &Self) -> Self {
+        Self(self.0.add(&rhs.0))
+    }
+
+    fn sub(&self, rhs: &Self) -> Self {
+        Self(self.0.sub(&rhs.0))
+    }
+
+    fn double(&self) -> Self {
+        Self(self.0.double())
+    }
+
+    fn mul(&self, rhs: &Self) -> Self {
+        Self(self.0.mul(&rhs.0))
+    }
+
+    fn neg(&self) -> Self {
+        Self(self.0.neg())
+    }
+
+    fn square(&self) -> Self {
+        Self(self.0.square())
+    }
+}
+
+pub fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&PyModule> {
+    let submod = PyModule::new(py, "base")?;
+    submod.add_class::<Base>()?;
+    Ok(submod)
+}

+ 55 - 311
src/sdk-py/src/lib.rs

@@ -1,314 +1,58 @@
-use std::ops::{Add, Deref, Mul};
+mod affine;
+mod base;
+mod point;
+mod proof;
+mod proving_key;
+mod scalar;
+mod verifying_key;
+mod zk_binary;
+mod zk_circuit;
+
+#[pyo3::prelude::pymodule]
+fn darkfi_sdk_py(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()> {
+    let submodule = affine::create_module(py)?;
+    pyo3::py_run!(py, submodule, "import sys; sys.modules['darkfi_sdk_py.affine'] = submodule");
+    m.add_submodule(submodule)?;
+
+    let submodule = base::create_module(py)?;
+    pyo3::py_run!(py, submodule, "import sys; sys.modules['darkfi_sdk_py.base'] = submodule");
+    m.add_submodule(submodule)?;
+
+    let submodule = scalar::create_module(py)?;
+    pyo3::py_run!(py, submodule, "import sys; sys.modules['darkfi_sdk_py.scalar'] = submodule");
+    m.add_submodule(scalar::create_module(py)?)?;
+
+    let submodule = point::create_module(py)?;
+    pyo3::py_run!(py, submodule, "import sys; sys.modules['darkfi_sdk_py.point'] = submodule");
+    m.add_submodule(point::create_module(py)?)?;
+
+    let submodule = proof::create_module(py)?;
+    pyo3::py_run!(py, submodule, "import sys; sys.modules['darkfi_sdk_py.proof'] = submodule");
+    m.add_submodule(proof::create_module(py)?)?;
+
+    let submodule = proving_key::create_module(py)?;
+    pyo3::py_run!(
+        py,
+        submodule,
+        "import sys; sys.modules['darkfi_sdk_py.proving_key'] = submodule"
+    );
+    m.add_submodule(proving_key::create_module(py)?)?;
+
+    let submodule = verifying_key::create_module(py)?;
+    pyo3::py_run!(
+        py,
+        submodule,
+        "import sys; sys.modules['darkfi_sdk_py.verifying_key'] = submodule"
+    );
+    m.add_submodule(verifying_key::create_module(py)?)?;
+
+    let submodule = zk_binary::create_module(py)?;
+    pyo3::py_run!(py, submodule, "import sys; sys.modules['darkfi_sdk_py.zk_binary'] = submodule");
+    m.add_submodule(zk_binary::create_module(py)?)?;
+
+    let submodule = zk_circuit::create_module(py)?;
+    pyo3::py_run!(py, submodule, "import sys; sys.modules['darkfi_sdk_py.zk_circuit'] = submodule");
+    m.add_submodule(zk_circuit::create_module(py)?)?;
 
-use darkfi_sdk::{
-    crypto::{
-        constants::{
-            fixed_bases::{VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_V_BYTES},
-            NullifierK,
-        },
-        pallas,
-        pasta_prelude::{Field, PrimeField},
-        poseidon_hash,
-        util::mod_r_p,
-        MerkleNode, ValueCommit,
-    },
-    incrementalmerkletree::Hashable,
-    pasta::{
-        arithmetic::{CurveAffine, CurveExt},
-        group::{ff::FromUniformBytes, Curve, Group},
-    },
-};
-use halo2_gadgets::ecc::chip::FixedPoint;
-use pyo3::prelude::*;
-use rand::rngs::OsRng;
-
-/// The base field of the Pallas and iso-Pallas curves.
-#[pyclass]
-#[derive(Clone, Debug)]
-struct Base(pallas::Base);
-
-#[pymethods]
-impl Base {
-    #[staticmethod]
-    fn from_raw(v: [u64; 4]) -> Self {
-        Self(pallas::Base::from_raw(v))
-    }
-
-    #[staticmethod]
-    fn from(v: u64) -> Self {
-        Self(pallas::Base::from(v))
-    }
-
-    #[staticmethod]
-    fn from_u128(v: u128) -> Self {
-        Self(pallas::Base::from_u128(v))
-    }
-
-    #[staticmethod]
-    fn from_uniform_bytes(bytes: [u8; 64]) -> Self {
-        Self(pallas::Base::from_uniform_bytes(&bytes))
-    }
-
-    #[staticmethod]
-    fn random() -> Self {
-        Self(pallas::Base::random(&mut OsRng))
-    }
-
-    #[staticmethod]
-    fn modulus() -> String {
-        pallas::Base::MODULUS.to_string()
-    }
-
-    #[staticmethod]
-    fn zero() -> Self {
-        Self(pallas::Base::zero())
-    }
-
-    #[staticmethod]
-    fn one() -> Self {
-        Self(pallas::Base::one())
-    }
-
-    #[staticmethod]
-    fn poseidon_hash(messages: Vec<&PyCell<Self>>) -> Self {
-        let l = messages.len();
-        let messages: Vec<pallas::Base> = messages.iter().map(|m| m.borrow().deref().0).collect();
-        // TODO: is there a more idomatic way?
-        if l == 1 {
-            let m: [pallas::Base; 1] = messages.try_into().unwrap();
-            Self(poseidon_hash(m))
-        } else if l == 2 {
-            let m: [pallas::Base; 2] = messages.try_into().unwrap();
-            Self(poseidon_hash(m))
-        } else if l == 3 {
-            let m: [pallas::Base; 3] = messages.try_into().unwrap();
-            Self(poseidon_hash(m))
-        } else if l == 4 {
-            let m: [pallas::Base; 4] = messages.try_into().unwrap();
-            Self(poseidon_hash(m))
-        } else if l == 5 {
-            let m: [pallas::Base; 5] = messages.try_into().unwrap();
-            Self(poseidon_hash(m))
-        } else if l == 6 {
-            let m: [pallas::Base; 6] = messages.try_into().unwrap();
-            Self(poseidon_hash(m))
-        } else if l == 7 {
-            let m: [pallas::Base; 7] = messages.try_into().unwrap();
-            Self(poseidon_hash(m))
-        } else if l == 8 {
-            let m: [pallas::Base; 8] = messages.try_into().unwrap();
-            Self(poseidon_hash(m))
-        } else if l == 9 {
-            let m: [pallas::Base; 9] = messages.try_into().unwrap();
-            Self(poseidon_hash(m))
-        } else if l == 10 {
-            let m: [pallas::Base; 10] = messages.try_into().unwrap();
-            Self(poseidon_hash(m))
-        } else if l == 11 {
-            let m: [pallas::Base; 11] = messages.try_into().unwrap();
-            Self(poseidon_hash(m))
-        } else if l == 12 {
-            let m: [pallas::Base; 12] = messages.try_into().unwrap();
-            Self(poseidon_hash(m))
-        } else if l == 13 {
-            let m: [pallas::Base; 13] = messages.try_into().unwrap();
-            Self(poseidon_hash(m))
-        } else if l == 14 {
-            let m: [pallas::Base; 14] = messages.try_into().unwrap();
-            Self(poseidon_hash(m))
-        } else if l == 15 {
-            let m: [pallas::Base; 15] = messages.try_into().unwrap();
-            Self(poseidon_hash(m))
-        } else if l == 16 {
-            let m: [pallas::Base; 16] = messages.try_into().unwrap();
-            Self(poseidon_hash(m))
-        } else {
-            panic!("Messages length violation, must be: 1 <= len <= 16");
-        }
-    }
-
-    fn __str_(&self) -> String {
-        format!("Base({:?})", self.0)
-    }
-
-    fn add(&self, rhs: &Self) -> Self {
-        Self(self.0.add(&rhs.0))
-    }
-
-    fn sub(&self, rhs: &Self) -> Self {
-        Self(self.0.sub(&rhs.0))
-    }
-
-    fn double(&self) -> Self {
-        Self(self.0.double())
-    }
-
-    fn mul(&self, rhs: &Self) -> Self {
-        Self(self.0.mul(&rhs.0))
-    }
-
-    fn neg(&self) -> Self {
-        Self(self.0.neg())
-    }
-
-    fn square(&self) -> Self {
-        Self(self.0.square())
-    }
-
-    /// pos(ition) encodes the left/right position on each level
-    /// path is the the silbling on each level
-    fn merkle_root(&self, pos: u64, path: Vec<&PyCell<Base>>) -> Self {
-        // TOOD: consider adding length check, for pos and path, for extra defensiness
-        let mut current = MerkleNode::new(self.0);
-        for (level, sibling) in path.iter().enumerate() {
-            let level = level as u8;
-            let sibling = MerkleNode::new(sibling.borrow().deref().0);
-            current = if pos & (1 << level) == 0 {
-                MerkleNode::combine(level.into(), &current, &sibling)
-            } else {
-                MerkleNode::combine(level.into(), &sibling, &current)
-            };
-        }
-        let root = current.inner();
-        Self(root)
-    }
-}
-
-// Why Scalar field is from the field vesta curve is defined over?
-
-/// The scalar field of the Pallas and iso-Pallas curves.
-#[pyclass]
-struct Scalar(pallas::Scalar);
-
-#[pymethods]
-impl Scalar {
-    #[staticmethod]
-    fn from_raw(v: [u64; 4]) -> Self {
-        Self(pallas::Scalar::from_raw(v))
-    }
-
-    #[staticmethod]
-    fn from_u128(v: u128) -> Self {
-        Self(pallas::Scalar::from_u128(v))
-    }
-
-    #[staticmethod]
-    fn random() -> Self {
-        Self(pallas::Scalar::random(&mut OsRng))
-    }
-
-    #[staticmethod]
-    fn modulus() -> String {
-        pallas::Scalar::MODULUS.to_string()
-    }
-
-    #[staticmethod]
-    fn zero() -> Self {
-        Self(pallas::Scalar::zero())
-    }
-
-    #[staticmethod]
-    fn one() -> Self {
-        Self(pallas::Scalar::one())
-    }
-
-    fn __str__(&self) -> String {
-        format!("Scalar({:?})", self.0)
-    }
-
-    fn add(&self, rhs: &Self) -> Self {
-        Self(self.0.add(&rhs.0))
-    }
-
-    fn sub(&self, rhs: &Self) -> Self {
-        Self(self.0.sub(&rhs.0))
-    }
-
-    fn double(&self) -> Self {
-        Self(self.0.double())
-    }
-
-    fn mul(&self, rhs: &Self) -> Self {
-        Self(self.0.mul(&rhs.0))
-    }
-
-    fn neg(&self) -> Self {
-        Self(self.0.neg())
-    }
-
-    fn square(&self) -> Self {
-        Self(self.0.square())
-    }
-}
-
-/// A Pallas point in the projective coordinate space.
-#[pyclass]
-struct Point(pallas::Point);
-
-#[pymethods]
-impl Point {
-    #[staticmethod]
-    fn identity() -> Self {
-        Self(pallas::Point::identity())
-    }
-
-    #[staticmethod]
-    fn generator() -> Self {
-        Self(pallas::Point::generator())
-    }
-
-    fn __str__(&self) -> String {
-        format!("Point({:?})", self.0)
-    }
-
-    fn to_affine(&self) -> Affine {
-        Affine(self.0.to_affine())
-    }
-
-    fn add(&self, rhs: &Self) -> Self {
-        Self(self.0.add(rhs.0))
-    }
-
-    fn mul(&self, scalar: &Scalar) -> Self {
-        Self(self.0.mul(scalar.0))
-    }
-
-    fn mul_base(&self, value: &Base) -> Self {
-        let v = NullifierK.generator();
-        Self(v * mod_r_p(value.0))
-    }
-
-    fn mul_short(&self, value: u64) -> Self {
-        // QUESTION: Why does v need to be a random element from EP?
-        // Why not NullifierK.generator() or some other pre-determined generator?
-        let hasher = ValueCommit::hash_to_curve(VALUE_COMMITMENT_PERSONALIZATION);
-        let v = hasher(&VALUE_COMMITMENT_V_BYTES);
-        Self(v * mod_r_p(pallas::Base::from(value)))
-    }
-}
-
-/// A Pallas point in the affine coordinate space (or the point at infinity).
-#[pyclass]
-struct Affine(pallas::Affine);
-
-#[pymethods]
-impl Affine {
-    fn __str__(&self) -> String {
-        format!("Affine({:?})", self.0)
-    }
-
-    fn coordinates(&self) -> (Base, Base) {
-        let coords = self.0.coordinates().unwrap();
-        (Base(*coords.x()), Base(*coords.y()))
-    }
-}
-
-/// This is where you define the classes and function be added to the module.
-#[pymodule]
-fn darkfi_sdk_py(_py: Python, m: &PyModule) -> PyResult<()> {
-    m.add_class::<Base>()?;
-    m.add_class::<Scalar>()?;
-    m.add_class::<Point>()?;
-    m.add_class::<Affine>()?;
     Ok(())
 }

+ 89 - 0
src/sdk-py/src/point.rs

@@ -0,0 +1,89 @@
+use crate::affine::Affine;
+use crate::base::Base;
+use crate::scalar::Scalar;
+use darkfi_sdk::{
+    crypto::{
+        constants::{
+            fixed_bases::{
+                VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_R_BYTES,
+                VALUE_COMMITMENT_V_BYTES,
+            },
+            NullifierK,
+        },
+        pallas,
+        util::mod_r_p,
+        ValueCommit,
+    },
+    pasta::{
+        arithmetic::CurveExt,
+        group::{Curve, Group},
+    },
+};
+use halo2_gadgets::ecc::chip::FixedPoint;
+use pyo3::prelude::*;
+use std::ops::{Add, Mul};
+
+/// A Pallas point in the projective coordinate space.
+#[pyclass]
+pub struct Point(pub(crate) pallas::Point);
+
+#[pymethods]
+impl Point {
+    #[staticmethod]
+    fn identity() -> Self {
+        Self(pallas::Point::identity())
+    }
+
+    #[staticmethod]
+    fn generator() -> Self {
+        Self(pallas::Point::generator())
+    }
+
+    #[staticmethod]
+    fn mul_short(value: &Base) -> Self {
+        // QUESTION: Why does v need to be a random element from EP?
+        // Why not NullifierK.generator() or some other pre-determined generator?
+        let hasher = ValueCommit::hash_to_curve(VALUE_COMMITMENT_PERSONALIZATION);
+        let v = hasher(&VALUE_COMMITMENT_V_BYTES);
+        Self(v * mod_r_p(value.0))
+    }
+
+    // why value doesn't need to be a Pycell
+    #[staticmethod]
+    fn mul_base(value: &Base) -> Self {
+        let v = NullifierK.generator();
+        Self(v * mod_r_p(value.0))
+    }
+
+    // why not a pycell
+    #[staticmethod]
+    fn mul_r_generator(blind: &Scalar) -> Self {
+        let hasher = ValueCommit::hash_to_curve(VALUE_COMMITMENT_PERSONALIZATION);
+        let r = hasher(&VALUE_COMMITMENT_R_BYTES);
+        let r = Self(r);
+        r.mul(blind)
+    }
+
+    #[pyo3(name = "__str__")]
+    fn __str__(&self) -> String {
+        format!("Point({:?})", self.0)
+    }
+
+    fn to_affine(&self) -> Affine {
+        Affine(self.0.to_affine())
+    }
+
+    fn add(&self, rhs: &Self) -> Self {
+        Self(self.0.add(rhs.0))
+    }
+
+    fn mul(&self, scalar: &Scalar) -> Self {
+        Self(self.0.mul(scalar.0))
+    }
+}
+
+pub fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&PyModule> {
+    let submod = PyModule::new(py, "point")?;
+    submod.add_class::<Point>()?;
+    Ok(submod)
+}

+ 44 - 0
src/sdk-py/src/proof.rs

@@ -0,0 +1,44 @@
+use crate::base::Base;
+use crate::proving_key::ProvingKey;
+use crate::verifying_key::VerifyingKey;
+use crate::zk_circuit::ZkCircuit;
+use darkfi::zk::{proof, vm};
+use darkfi_sdk::crypto::pallas;
+use pyo3::prelude::*;
+use rand::rngs::OsRng;
+use std::ops::Deref;
+
+#[pyclass]
+pub struct Proof(pub(crate) proof::Proof);
+
+#[pymethods]
+impl Proof {
+    #[staticmethod]
+    fn create(
+        pk: &PyCell<ProvingKey>,
+        circuits: Vec<&PyCell<ZkCircuit>>,
+        instances: Vec<&PyCell<Base>>,
+    ) -> Self {
+        let pk = pk.borrow().deref().0.clone();
+        let circuits: Vec<vm::ZkCircuit> =
+            circuits.iter().map(|c| c.borrow().deref().0.clone()).collect();
+        let instances: Vec<pallas::Base> = instances.iter().map(|i| i.borrow().deref().0).collect();
+        let proof =
+            proof::Proof::create(&pk, circuits.as_slice(), instances.as_slice(), &mut OsRng);
+        let proof = proof.unwrap();
+        Self(proof)
+    }
+
+    fn verify(&self, vk: &PyCell<VerifyingKey>, instances: Vec<&PyCell<Base>>) {
+        let vk = vk.borrow().deref().0.clone();
+        let proof = self.0.clone();
+        let instances: Vec<pallas::Base> = instances.iter().map(|i| i.borrow().deref().0).collect();
+        proof.verify(&vk, instances.as_slice()).unwrap();
+    }
+}
+
+pub fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&PyModule> {
+    let submod = PyModule::new(py, "proof")?;
+    submod.add_class::<Proof>()?;
+    Ok(submod)
+}

+ 24 - 0
src/sdk-py/src/proving_key.rs

@@ -0,0 +1,24 @@
+use crate::zk_circuit::ZkCircuit;
+use darkfi::zk::{proof, vm};
+use pyo3::prelude::*;
+use std::ops::Deref;
+
+#[pyclass]
+pub struct ProvingKey(pub(crate) proof::ProvingKey);
+
+#[pymethods]
+impl ProvingKey {
+    #[staticmethod]
+    fn build(k: u32, circuit: &PyCell<ZkCircuit>) -> Self {
+        let circuit_ref = circuit.borrow();
+        let circuit: &vm::ZkCircuit = &circuit_ref.deref().0;
+        let proving_key = proof::ProvingKey::build(k, circuit);
+        Self(proving_key)
+    }
+}
+
+pub fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&PyModule> {
+    let submod = PyModule::new(py, "proving_key")?;
+    submod.add_class::<ProvingKey>()?;
+    Ok(submod)
+}

+ 84 - 0
src/sdk-py/src/scalar.rs

@@ -0,0 +1,84 @@
+use darkfi_sdk::crypto::{
+    pallas,
+    pasta_prelude::{Field, PrimeField},
+};
+use pyo3::prelude::*;
+use rand::rngs::OsRng;
+
+/// Why does Vesta use Fq?
+/// The scalar field of the Pallas and iso-Pallas curves.
+#[pyclass]
+pub struct Scalar(pub(crate) pallas::Scalar);
+
+#[pymethods]
+impl Scalar {
+    #[new]
+    fn from_u128(v: u128) -> Self {
+        Self(pallas::Scalar::from_u128(v))
+    }
+
+    #[staticmethod]
+    fn from_raw(v: [u64; 4]) -> Self {
+        Self(pallas::Scalar::from_raw(v))
+    }
+
+    #[staticmethod]
+    fn random() -> Self {
+        Self(pallas::Scalar::random(&mut OsRng))
+    }
+
+    #[staticmethod]
+    fn modulus() -> String {
+        pallas::Scalar::MODULUS.to_string()
+    }
+
+    #[staticmethod]
+    fn zero() -> Self {
+        Self(pallas::Scalar::zero())
+    }
+
+    #[staticmethod]
+    fn one() -> Self {
+        Self(pallas::Scalar::one())
+    }
+
+    #[pyo3(name = "__str__")]
+    fn __str__(&self) -> String {
+        format!("Scalar({:?})", self.0)
+    }
+
+    #[pyo3(name = "__repr__")]
+    fn __repr__(&self) -> String {
+        format!("Scalar({:?})", self.0)
+    }
+
+    fn add(&self, rhs: &Self) -> Self {
+        Self(self.0.add(&rhs.0))
+    }
+
+    fn sub(&self, rhs: &Self) -> Self {
+        Self(self.0.sub(&rhs.0))
+    }
+
+    fn double(&self) -> Self {
+        Self(self.0.double())
+    }
+
+    fn mul(&self, rhs: &Self) -> Self {
+        Self(self.0.mul(&rhs.0))
+    }
+
+    fn neg(&self) -> Self {
+        Self(self.0.neg())
+    }
+
+    fn square(&self) -> Self {
+        Self(self.0.square())
+    }
+}
+
+pub fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&PyModule> {
+    let submod = PyModule::new(py, "scalar")?;
+    submod.add_class::<Scalar>()?;
+    Ok(submod)
+}

+ 24 - 0
src/sdk-py/src/verifying_key.rs

@@ -0,0 +1,24 @@
+use crate::zk_circuit::ZkCircuit;
+use darkfi::zk::proof;
+use pyo3::prelude::*;
+use std::ops::Deref;
+
+#[pyclass]
+pub struct VerifyingKey(pub(crate) proof::VerifyingKey);
+
+#[pymethods]
+impl VerifyingKey {
+    #[staticmethod]
+    fn build(k: u32, circuit: &PyCell<ZkCircuit>) -> Self {
+        let circuit_ref = circuit.borrow();
+        let circuit = &circuit_ref.deref().0;
+        let proving_key = proof::VerifyingKey::build(k, circuit);
+        Self(proving_key)
+    }
+}
+
+pub fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&PyModule> {
+    let submod = PyModule::new(py, "verifying_key")?;
+    submod.add_class::<VerifyingKey>()?;
+    Ok(submod)
+}

+ 55 - 0
src/sdk-py/src/zk_binary.rs

@@ -0,0 +1,55 @@
+use darkfi::zkas::decoder;
+use pyo3::prelude::*;
+
+#[pyclass]
+pub struct ZkBinary(pub(crate) decoder::ZkBinary);
+
+/// There is no need for constants, as bindings for ec_mul_short and ec_mul_base
+/// don't actually take the constants.
+/// The constants are hardcoded on the Rust side.
+#[pymethods]
+impl ZkBinary {
+    #[staticmethod]
+    fn decode(bytes: Vec<u8>) -> Self {
+        let bincode = decoder::ZkBinary::decode(bytes.as_slice()).unwrap();
+        Self(bincode)
+    }
+
+    fn namespace(&self) -> String {
+        self.0.namespace.clone()
+    }
+
+    fn literals(&self) -> Vec<(String, String)> {
+        let l = self.0.literals.clone();
+        l.iter().map(|(lit, value)| (format!("{lit:?}"), value.clone())).collect()
+    }
+
+    fn witnesses(&self) -> Vec<String> {
+        let w = self.0.witnesses.clone();
+        w.iter().map(|v| format!("{v:?}")).collect()
+    }
+
+    fn constant_count(&self) -> usize {
+        self.0.constants.len()
+    }
+
+    fn opcodes(&self) -> Vec<(String, Vec<(String, usize)>)> {
+        let o = self.0.opcodes.clone();
+        o.iter()
+            .map(|(opcode_, args_)| {
+                let opcode = format!("{opcode_:?}");
+                let args = args_
+                    .iter()
+                    .map(|(heap_type, heap_idx)| (format!("{heap_type:?}"), heap_idx.clone()))
+                    .collect();
+                (opcode, args)
+            })
+            .collect()
+    }
+}
+
+pub fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&PyModule> {
+    let submod = PyModule::new(py, "zk_binary")?;
+    submod.add_class::<ZkBinary>()?;
+    Ok(submod)
+}

+ 87 - 0
src/sdk-py/src/zk_circuit.rs

@@ -0,0 +1,87 @@
+use crate::base::Base;
+use crate::point::Point;
+use crate::scalar::Scalar;
+use crate::zk_binary::ZkBinary;
+use darkfi::zk::{halo2::Value, vm, vm_heap::empty_witnesses};
+use darkfi_sdk::crypto::MerkleNode;
+use pyo3::prelude::*;
+use std::ops::Deref;
+
+#[pyclass]
+pub struct ZkCircuit(pub(crate) vm::ZkCircuit, pub(crate) Vec<vm::Witness>);
+
+/// QUESTION: how to deal with witness?
+/// Like Builder Object
+#[pymethods]
+impl ZkCircuit {
+    #[new]
+    fn new(circuit_code: &PyCell<ZkBinary>) -> Self {
+        let circuit_code = circuit_code.borrow().deref().0.clone();
+        // DUMMY CIRCUIT
+        let circuit = vm::ZkCircuit::new(vec![], circuit_code.clone());
+        Self(circuit, vec![])
+    }
+
+    fn build(&self, circuit_code: &PyCell<ZkBinary>) -> Self {
+        let circuit_code = circuit_code.borrow().deref().0.clone();
+        let circuit = vm::ZkCircuit::new(self.1.clone(), circuit_code.clone());
+        Self(circuit, self.1.clone())
+    }
+
+    fn verifier_build(&self, circuit_code: &PyCell<ZkBinary>) -> Self {
+        let circuit_code = circuit_code.borrow().deref().0.clone();
+        let circuit = vm::ZkCircuit::new(empty_witnesses(&circuit_code), circuit_code.clone());
+        Self(circuit, self.1.clone())
+    }
+
+    fn witness_point(&mut self, v: &PyCell<Point>) {
+        let v = v.borrow();
+        let v = v.deref();
+        self.1.push(vm::Witness::EcPoint(Value::known(v.0)));
+    }
+
+    fn witness_ni_point(&mut self, v: &PyCell<Point>) {
+        let v = v.borrow();
+        let v = v.deref();
+        self.1.push(vm::Witness::EcNiPoint(Value::known(v.0)));
+    }
+
+    fn witness_fixed_point(&mut self, v: &PyCell<Point>) {
+        let v = v.borrow();
+        let v = v.deref();
+        self.1.push(vm::Witness::EcFixedPoint(Value::known(v.0)));
+    }
+
+    fn witness_scalar(&mut self, v: &PyCell<Scalar>) {
+        let v = v.borrow();
+        let v = v.deref();
+        self.1.push(vm::Witness::Scalar(Value::known(v.0)));
+    }
+
+    fn witness_base(&mut self, v: &PyCell<Base>) {
+        let v = v.borrow();
+        let v = v.deref();
+        self.1.push(vm::Witness::Base(Value::known(v.0)));
+    }
+
+    fn witness_merkle_path(&mut self, v: Vec<&PyCell<Base>>) {
+        let v: Vec<MerkleNode> = v.iter().map(|v| MerkleNode::new(v.borrow().deref().0)).collect();
+        let v: [MerkleNode; 32] = v.try_into().unwrap();
+        let v = Value::known(v);
+        self.1.push(vm::Witness::MerklePath(v));
+    }
+
+    fn witness_u32(&mut self, v: u32) {
+        self.1.push(vm::Witness::Uint32(Value::known(v)));
+    }
+
+    fn witness_u64(&mut self, v: u64) {
+        self.1.push(vm::Witness::Uint64(Value::known(v)));
+    }
+}
+
+pub fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&PyModule> {
+    let submod = PyModule::new(py, "zk_circuit")?;
+    submod.add_class::<ZkCircuit>()?;
+    Ok(submod)
+}

+ 7 - 9
src/zk/vm.rs

@@ -113,6 +113,7 @@ impl VmConfig {
     }
 }
 
+#[derive(Clone)]
 pub struct ZkCircuit {
     constants: Vec<String>,
     witnesses: Vec<Witness>,
@@ -388,7 +389,7 @@ impl Circuit<pallas::Base> for ZkCircuit {
 
                 _ => {
                     error!(target: "zk::vm", "Invalid constant name: {}", constant.as_str());
-                    return Err(plonk::Error::Synthesis)
+                    return Err(plonk::Error::Synthesis);
                 }
             }
         }
@@ -403,12 +404,12 @@ impl Circuit<pallas::Base> for ZkCircuit {
                     Ok(v) => litheap.push(v),
                     Err(e) => {
                         error!(target: "zk::vm", "Failed converting u64 literal: {}", e);
-                        return Err(plonk::Error::Synthesis)
+                        return Err(plonk::Error::Synthesis);
                     }
                 },
                 _ => {
                     error!(target: "zk::vm", "Invalid literal: {:?}", literal);
-                    return Err(plonk::Error::Synthesis)
+                    return Err(plonk::Error::Synthesis);
                 }
             }
         }
@@ -446,7 +447,7 @@ impl Circuit<pallas::Base> for ZkCircuit {
 
                 Witness::EcFixedPoint(_) => {
                     error!(target: "zk::vm", "Unable to witness EcFixedPoint, this is unimplemented.");
-                    return Err(plonk::Error::Synthesis)
+                    return Err(plonk::Error::Synthesis);
                 }
 
                 Witness::Base(w) => {
@@ -571,16 +572,13 @@ impl Circuit<pallas::Base> for ZkCircuit {
                 Opcode::EcMulShort => {
                     trace!(target: "zk::vm", "Executing `EcMulShort{:?}` opcode", opcode.1);
                     let args = &opcode.1;
-
                     let lhs: FixedPointShort<pallas::Affine, EccChip<OrchardFixedBases>> =
                         heap[args[1].1].clone().into();
-
                     let rhs = ScalarFixedShort::new(
                         ecc_chip.clone(),
                         layouter.namespace(|| "EcMulShort: ScalarFixedShort::new()"),
                         (heap[args[0].1].clone().into(), one.clone()),
                     )?;
-
                     let (ret, _) = lhs.mul(layouter.namespace(|| "EcMulShort()"), rhs)?;
 
                     trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
@@ -772,7 +770,7 @@ impl Circuit<pallas::Base> for ZkCircuit {
                         }
                         x => {
                             error!(target: "zk::vm", "Unsupported bit-range {} for range_check", x);
-                            return Err(plonk::Error::Synthesis)
+                            return Err(plonk::Error::Synthesis);
                         }
                     }
                 }
@@ -892,7 +890,7 @@ impl Circuit<pallas::Base> for ZkCircuit {
 
                 _ => {
                     error!(target: "zk::vm", "Unsupported opcode");
-                    return Err(plonk::Error::Synthesis)
+                    return Err(plonk::Error::Synthesis);
                 }
             }
         }