sdk/python: Perform full code cleanup and make everything work.

This commit is contained in:
parazyd
2023-08-13 15:31:08 +02:00
committed by parazyd
parent 66e399fd61
commit 988455f79b
18 changed files with 691 additions and 902 deletions

View File

@@ -1,6 +1,6 @@
[package]
name = "darkfi-sdk-py"
description = "Python bindings for Darkfi SDK"
description = "Python bindings for the DarkFi SDK"
version = "0.4.1"
edition = "2021"
authors = ["Dyne.org foundation <foundation@dyne.org>"]
@@ -8,14 +8,13 @@ license = "AGPL-3.0-only"
homepage = "https://dark.fi"
repository = "https://github.com/darkrenaissance/darkfi"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "darkfi_sdk_py"
name = "darkfi_sdk"
crate-type = ["cdylib"]
[dependencies]
darkfi = { path = "../../../", features = ["zk", "zkas"] }
darkfi-sdk = { path = "../" }
darkfi = {path = "../../../", features = ["zk", "zkas"]}
darkfi-sdk = {path = "../"}
halo2_gadgets = "0.3.0"
pyo3 = "0.19.2"
rand = "0.8.5"

View File

@@ -9,5 +9,4 @@ all:
dev:
$(MATURIN) develop --release
.PHONY: all

View File

@@ -22,14 +22,14 @@ $ source venv/bin/activate
```
$ python
>>> import darkfi_sdk_py
>>> from darkfi_sdk_py.base import Base
>>> a = Base.from_u64(42)
>>> b = Base.from_u64(69)
>>> a + b == Base.from_u64(111)
>>> import darkfi_sdk
>>> from darkfi_sdk.pasta import Fp
>>> a = Fp.from_u64(42)
>>> b = Fp.from_u64(69)
>>> a + b == Fp.from_u64(111)
```
### Randomness
## Randomness
Note that the `random` methods take randomness
from the OS on the Rust side.

View File

@@ -3,14 +3,13 @@ requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"
[project]
name = "darkfi-sdk-py"
requires-python = ">=3.8"
name = "darkfi-sdk"
requires-python = ">=3.9"
classifiers = [
"Programming Language :: Rust",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
[tool.maturin]
features = ["pyo3/extension-module"]

View File

@@ -1,44 +0,0 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
use darkfi_sdk::pasta::{arithmetic::CurveAffine, pallas};
use pyo3::prelude::*;
use super::base::Base;
/// 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)
}

View File

@@ -1,206 +0,0 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
use std::ops::Deref;
use darkfi_sdk::{
bridgetree::Hashable,
crypto::{poseidon_hash, MerkleNode},
pasta::{
group::ff::{Field, FromUniformBytes, PrimeField},
pallas,
},
};
use pyo3::{basic::CompareOp, prelude::*};
use rand::rngs::OsRng;
/// The base field of the Pallas and iso-Pallas curves.
/// Randomness is provided by the OS and on the Rust side.
#[pyclass]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Base(pub(crate) pallas::Base);
#[pymethods]
impl Base {
// Why is this not callable?
#[staticmethod]
fn from_u64(v: u64) -> Self {
Self(pallas::Base::from(v))
}
#[staticmethod]
fn from_u128(v: u128) -> Self {
Self(pallas::Base::from_u128(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)
}
fn __str__(&self) -> String {
format!("{:?}", self.0)
}
fn __repr__(slf: &PyCell<Self>) -> PyResult<String> {
let class_name: &str = slf.get_type().name()?;
Ok(format!("{}({:?})", class_name, slf.borrow().0))
}
fn __add__(&self, other: &Self) -> Self {
Self(self.0 + other.0)
}
fn __sub__(&self, other: &Self) -> Self {
Self(self.0 - other.0)
}
fn __mul__(&self, other: &Self) -> Self {
Self(self.0 * other.0)
}
fn __neg__(&self) -> Self {
Self(self.0.neg())
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Lt => Ok(self.0 < other.0),
CompareOp::Le => Ok(self.0 <= other.0),
CompareOp::Eq => Ok(self.0 == other.0),
CompareOp::Ne => Ok(self.0 != other.0),
CompareOp::Gt => Ok(self.0 > other.0),
CompareOp::Ge => Ok(self.0 >= other.0),
}
}
fn double(&self) -> Self {
Self(self.0.double())
}
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)
}

View File

@@ -0,0 +1,73 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
use std::ops::Deref;
use darkfi_sdk::{crypto, pasta::pallas};
use pyo3::{pyfunction, types::PyModule, wrap_pyfunction, PyCell, PyResult, Python};
use super::pasta::{Ep, Fp, Fq};
/// Calculate the Poseidon hash of given `Fp` elements.
#[pyfunction]
pub fn poseidon_hash(messages: Vec<&PyCell<Fp>>) -> Fp {
let messages: Vec<pallas::Base> = messages.iter().map(|x| x.borrow().deref().0).collect();
match messages.len() {
1 => Fp(crypto::util::poseidon_hash::<1>(messages.try_into().unwrap())),
2 => Fp(crypto::util::poseidon_hash::<2>(messages.try_into().unwrap())),
3 => Fp(crypto::util::poseidon_hash::<3>(messages.try_into().unwrap())),
4 => Fp(crypto::util::poseidon_hash::<4>(messages.try_into().unwrap())),
5 => Fp(crypto::util::poseidon_hash::<5>(messages.try_into().unwrap())),
6 => Fp(crypto::util::poseidon_hash::<6>(messages.try_into().unwrap())),
7 => Fp(crypto::util::poseidon_hash::<7>(messages.try_into().unwrap())),
8 => Fp(crypto::util::poseidon_hash::<8>(messages.try_into().unwrap())),
9 => Fp(crypto::util::poseidon_hash::<9>(messages.try_into().unwrap())),
10 => Fp(crypto::util::poseidon_hash::<10>(messages.try_into().unwrap())),
11 => Fp(crypto::util::poseidon_hash::<11>(messages.try_into().unwrap())),
12 => Fp(crypto::util::poseidon_hash::<12>(messages.try_into().unwrap())),
13 => Fp(crypto::util::poseidon_hash::<13>(messages.try_into().unwrap())),
14 => Fp(crypto::util::poseidon_hash::<14>(messages.try_into().unwrap())),
15 => Fp(crypto::util::poseidon_hash::<15>(messages.try_into().unwrap())),
16 => Fp(crypto::util::poseidon_hash::<16>(messages.try_into().unwrap())),
_ => unimplemented!(),
}
}
/// Calculate a Pedersen commitment with an u64 value.
#[pyfunction]
pub fn pedersen_commitment_u64(value: u64, blind: &PyCell<Fq>) -> Ep {
Ep(crypto::pedersen::pedersen_commitment_u64(value, blind.borrow().deref().0))
}
/// Calculate a Pedersen commitment with an Fp value.
#[pyfunction]
pub fn pedersen_commitment_base(value: &PyCell<Fp>, blind: &PyCell<Fq>) -> Ep {
Ep(crypto::pedersen::pedersen_commitment_base(
value.borrow().deref().0,
blind.borrow().deref().0,
))
}
/// Wrapper function for creating this Python module.
pub(crate) fn create_module(py: Python<'_>) -> PyResult<&PyModule> {
let submod = PyModule::new(py, "crypto")?;
submod.add_function(wrap_pyfunction!(poseidon_hash, submod)?)?;
submod.add_function(wrap_pyfunction!(pedersen_commitment_u64, submod)?)?;
submod.add_function(wrap_pyfunction!(pedersen_commitment_base, submod)?)?;
Ok(submod)
}

View File

@@ -16,71 +16,35 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/// Pallas point in affine space
mod affine;
/// Pallas base field element
mod base;
/// Pallas point in projective space
mod point;
/// Pallas scalar field element
mod scalar;
/// Pallas and Vesta curves
mod pasta;
/// ZK proof creation
mod proof;
/// Proving key creation
mod proving_key;
/// Verifying key creation
mod verifying_key;
/// zkas ZkBinary wrappers
mod zk_binary;
/// zkvm wrappers
mod zk_circuit;
/// Merkle tree utilities
mod merkle;
/// Cryptographic utilities
mod crypto;
/// zkas definitions
mod zkas;
#[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");
fn darkfi_sdk(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()> {
let submodule = pasta::create_module(py)?;
pyo3::py_run!(py, submodule, "import sys; sys.modules['darkfi_sdk.pasta'] = 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");
let submodule = merkle::create_module(py)?;
pyo3::py_run!(py, submodule, "import sys; sys.modules['darkfi_sdk.merkle'] = 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 = crypto::create_module(py)?;
pyo3::py_run!(py, submodule, "import sys; sys.modules['darkfi_sdk.crypto'] = submodule");
m.add_submodule(submodule)?;
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)?)?;
let submodule = zkas::create_module(py)?;
pyo3::py_run!(py, submodule, "import sys; sys.modules['darkfi_sdk.zkas'] = submodule");
m.add_submodule(submodule)?;
Ok(())
}

View File

@@ -0,0 +1,60 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
use std::ops::Deref;
use darkfi_sdk::crypto::{merkle_node, MerkleNode};
use pyo3::{pyclass, pymethods, types::PyModule, PyCell, PyResult};
use super::pasta::Fp;
#[pyclass]
/// Class representing a bridgetree
pub struct MerkleTree(merkle_node::MerkleTree);
#[pymethods]
impl MerkleTree {
#[new]
fn new() -> Self {
Self(merkle_node::MerkleTree::new(100))
}
fn append(&mut self, node: &PyCell<Fp>) -> PyResult<bool> {
Ok(self.0.append(MerkleNode::from(node.borrow().deref().0)))
}
fn mark(&mut self) -> PyResult<u32> {
Ok(u64::from(self.0.mark().unwrap()) as u32)
}
fn root(&self, checkpoint_depth: usize) -> PyResult<Fp> {
let root = self.0.root(checkpoint_depth).unwrap();
Ok(Fp(root.inner()))
}
fn witness(&self, position: u32, checkpoint_depth: usize) -> PyResult<Vec<Fp>> {
let path = self.0.witness((position as u64).into(), checkpoint_depth).unwrap();
Ok(path.iter().map(|x| Fp(x.inner())).collect())
}
}
/// Wrapper function for creating this Python module.
pub(crate) fn create_module(py: pyo3::Python<'_>) -> PyResult<&PyModule> {
let submod = PyModule::new(py, "merkle")?;
submod.add_class::<MerkleTree>()?;
Ok(submod)
}

316
src/sdk/python/src/pasta.rs Normal file
View File

@@ -0,0 +1,316 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
use std::ops::Deref;
use darkfi_sdk::{
crypto::{constants::NullifierK, pasta_prelude::*, util},
pasta::{group::ff::FromUniformBytes, pallas, vesta},
};
use halo2_gadgets::ecc::chip::FixedPoint;
use pyo3::{
basic::CompareOp, pyclass, pyfunction, pymethods, types::PyModule, wrap_pyfunction, PyCell,
PyResult,
};
use rand::rngs::OsRng;
macro_rules! impl_elem {
($x:ty, $inner:ty) => {
#[pymethods]
impl $x {
#[new]
fn new(v: &str) -> PyResult<Self> {
assert!(v.starts_with("0x") && v.len() == 66);
let v = v.trim_start_matches("0x");
let (a, b) = v.split_at(32);
let (le_1, le_0) = b.split_at(16);
let (le_3, le_2) = a.split_at(16);
let le_0 = u64::from_str_radix(le_0, 16)?;
let le_1 = u64::from_str_radix(le_1, 16)?;
let le_2 = u64::from_str_radix(le_2, 16)?;
let le_3 = u64::from_str_radix(le_3, 16)?;
Ok(Self(<$inner>::from_raw([le_0, le_1, le_2, le_3])))
}
#[staticmethod]
fn from_u64(v: u64) -> Self {
Self(<$inner>::from(v))
}
#[staticmethod]
fn from_u128(v: u128) -> Self {
Self(<$inner>::from_u128(v))
}
#[staticmethod]
const fn from_raw(v: [u64; 4]) -> Self {
Self(<$inner>::from_raw(v))
}
#[staticmethod]
fn from_uniform_bytes(bytes: [u8; 64]) -> Self {
Self(<$inner>::from_uniform_bytes(&bytes))
}
#[staticmethod]
fn random() -> Self {
Self(<$inner>::random(&mut OsRng))
}
#[staticmethod]
fn modulus() -> &'static str {
<$inner>::MODULUS
}
#[staticmethod]
fn zero() -> Self {
Self(<$inner>::ZERO)
}
#[staticmethod]
fn one() -> Self {
Self(<$inner>::ONE)
}
fn double(&self) -> Self {
Self(self.0.double())
}
fn square(&self) -> Self {
Self(self.0.square())
}
fn __str__(&self) -> PyResult<String> {
Ok(format!("{:?}", self.0))
}
fn __repr__(slf: &PyCell<Self>) -> PyResult<String> {
let class_name: &str = slf.get_type().name()?;
Ok(format!("{}({:?})", class_name, slf.borrow().0))
}
fn __add__(&self, other: &Self) -> Self {
Self(self.0 + other.0)
}
fn __sub__(&self, other: &Self) -> Self {
Self(self.0 - other.0)
}
fn __mul__(&self, other: &Self) -> Self {
Self(self.0 * other.0)
}
fn __neg__(&self) -> Self {
Self(self.0.neg())
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Lt => Ok(self.0 < other.0),
CompareOp::Le => Ok(self.0 <= other.0),
CompareOp::Eq => Ok(self.0 == other.0),
CompareOp::Ne => Ok(self.0 != other.0),
CompareOp::Gt => Ok(self.0 > other.0),
CompareOp::Ge => Ok(self.0 >= other.0),
}
}
fn to_json(&self) -> PyResult<String> {
self.__str__()
}
}
};
}
macro_rules! impl_affine {
($x:ty, $inner:ty, $base:ident, $projective:ty) => {
#[pymethods]
impl $x {
fn coordinates(&self) -> ($base, $base) {
let coords = self.0.coordinates().unwrap();
($base(*coords.x()), $base(*coords.y()))
}
fn coordinates_str(&self) -> PyResult<Vec<String>> {
let coords = self.0.coordinates().unwrap();
let x = $base(*coords.x()).__str__()?;
let y = $base(*coords.y()).__str__()?;
Ok(vec![x, y])
}
#[staticmethod]
fn from_xy(x: &PyCell<$base>, y: &PyCell<$base>) -> PyResult<Self> {
let affine_point =
<$inner>::from_xy(x.borrow().deref().0, y.borrow().deref().0).unwrap();
Ok(Self(affine_point))
}
#[staticmethod]
fn from_projective(x: &PyCell<$projective>) -> Self {
Self(<$inner>::from(x.borrow().deref().0))
}
fn __str__(&self) -> String {
format!("{:?}", self.0)
}
fn __repr__(slf: &PyCell<Self>) -> PyResult<String> {
let class_name: &str = slf.get_type().name()?;
Ok(format!("{}({:?})", class_name, slf.borrow().0))
}
}
};
}
macro_rules! impl_point {
($x:ty, $inner:ty, $base:ty, $scalar:ty, $affine:ty) => {
#[pymethods]
impl $x {
#[new]
fn new(x: &PyCell<$base>, y: &PyCell<$base>) -> PyResult<Self> {
let affine_point = <$affine>::from_xy(x, y).unwrap();
Ok(Self::from_affine(affine_point))
}
#[staticmethod]
fn identity() -> Self {
Self(<$inner>::identity())
}
#[staticmethod]
fn generator() -> Self {
Self(<$inner>::generator())
}
#[staticmethod]
fn random() -> Self {
Self(<$inner>::random(&mut OsRng))
}
#[staticmethod]
fn from_affine(p: $affine) -> Self {
Self(<$inner>::from(p.0))
}
fn __str__(slf: &PyCell<Self>) -> PyResult<String> {
let affine = <$affine>::from_projective(slf);
let (x, y) = affine.coordinates();
Ok(format!("[{}, {}]", x.__str__()?, y.__str__()?))
}
fn __repr__(slf: &PyCell<Self>) -> PyResult<String> {
let class_name: &str = slf.get_type().name()?;
Ok(format!("{}({:?})", class_name, slf.borrow().0))
}
fn __add__(&self, rhs: &Self) -> Self {
Self(self.0 + rhs.0)
}
fn __sub__(&self, rhs: &Self) -> Self {
Self(self.0 - rhs.0)
}
fn __mul__(&self, scalar: &$scalar) -> Self {
Self(self.0 * scalar.0)
}
fn __neg__(&self) -> Self {
Self(-self.0)
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Eq => Ok(self.0 == other.0),
CompareOp::Ne => Ok(self.0 != other.0),
CompareOp::Lt => unimplemented!(),
CompareOp::Le => unimplemented!(),
CompareOp::Gt => unimplemented!(),
CompareOp::Ge => unimplemented!(),
}
}
}
};
}
/// The base field of the Pallas curve and the scalar field of the Vesta curve.
#[pyclass(dict)]
#[derive(Copy, Clone, PartialEq, std::cmp::Eq, Ord, PartialOrd, Debug)]
pub struct Fp(pub(crate) pallas::Base);
impl_elem!(Fp, pallas::Base);
/// The scalar field of the Pallas curve and the base field of the Vesta curve.
#[pyclass]
#[derive(Copy, Clone, PartialEq, std::cmp::Eq, Ord, PartialOrd, Debug)]
pub struct Fq(pub(crate) pallas::Scalar);
impl_elem!(Fq, pallas::Scalar);
/// A Pallas curve point in the projective space
#[pyclass]
#[derive(Copy, Clone, PartialEq, std::cmp::Eq, Debug)]
pub struct Ep(pub(crate) pallas::Point);
impl_point!(Ep, pallas::Point, Fp, Fq, EpAffine);
/// A Pallas curve point in the affine space
#[pyclass]
#[derive(Copy, Clone, PartialEq, std::cmp::Eq, Debug)]
pub struct EpAffine(pub(crate) pallas::Affine);
impl_affine!(EpAffine, pallas::Affine, Fp, Ep);
/// A Vesta curve point in the projective space
#[pyclass]
#[derive(Copy, Clone, PartialEq, std::cmp::Eq, Debug)]
pub struct Eq(pub(crate) vesta::Point);
impl_point!(Eq, vesta::Point, Fq, Fp, EqAffine);
/// A Vesta curve point in the affine space
#[pyclass]
#[derive(Copy, Clone, PartialEq, std::cmp::Eq, Debug)]
pub struct EqAffine(pub(crate) vesta::Affine);
impl_affine!(EqAffine, vesta::Affine, Fq, Eq);
#[pyfunction]
/// Return the NullifierK generator point as EpAffine.
pub fn nullifier_k() -> EpAffine {
EpAffine(NullifierK.generator())
}
#[pyfunction]
/// Convert Fp to Fq safely.
pub fn mod_r_p(x: &PyCell<Fp>) -> PyResult<Fq> {
Ok(Fq(util::mod_r_p(x.borrow().deref().0)))
}
pub fn create_module(py: pyo3::Python<'_>) -> PyResult<&PyModule> {
let submod = PyModule::new(py, "pasta")?;
submod.add_class::<Fp>()?;
submod.add_class::<Fq>()?;
submod.add_class::<Ep>()?;
submod.add_class::<EpAffine>()?;
submod.add_class::<Eq>()?;
submod.add_class::<EqAffine>()?;
submod.add_function(wrap_pyfunction!(nullifier_k, submod)?)?;
submod.add_function(wrap_pyfunction!(mod_r_p, submod)?)?;
Ok(submod)
}

View File

@@ -1,128 +0,0 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
use darkfi_sdk::{
crypto::{
constants::fixed_bases::{
NullifierK, VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_R_BYTES,
VALUE_COMMITMENT_V_BYTES,
},
util::mod_r_p,
},
pasta::{
arithmetic::CurveExt,
group::{Curve, Group},
pallas,
},
};
use halo2_gadgets::ecc::chip::FixedPoint;
use pyo3::{basic::CompareOp, prelude::*};
use rand::rngs::OsRng;
use super::{affine::Affine, base::Base, scalar::Scalar};
/// 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 {
let hasher = pallas::Point::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 = pallas::Point::hash_to_curve(VALUE_COMMITMENT_PERSONALIZATION);
let r = hasher(&VALUE_COMMITMENT_R_BYTES);
let r = Self(r);
Self(r.0 * blind.0)
}
#[staticmethod]
fn random() -> Self {
Self(pallas::Point::random(&mut OsRng))
}
fn __str__(&self) -> String {
format!("{:?}", self.0)
}
fn __repr__(slf: &PyCell<Self>) -> PyResult<String> {
let class_name: &str = slf.get_type().name()?;
Ok(format!("{}({:?})", class_name, slf.borrow().0))
}
fn to_affine(&self) -> Affine {
Affine(self.0.to_affine())
}
fn __add__(&self, rhs: &Self) -> Self {
Self(self.0 + rhs.0)
}
fn __sub__(&self, rhs: &Self) -> Self {
Self(self.0 - rhs.0)
}
fn __mul__(&self, scalar: &Scalar) -> Self {
Self(self.0 * scalar.0)
}
fn __neg__(&self) -> Self {
Self(-self.0)
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Eq => Ok(self.0 == other.0),
CompareOp::Ne => Ok(self.0 != other.0),
CompareOp::Lt => todo!(),
CompareOp::Le => todo!(),
CompareOp::Gt => todo!(),
CompareOp::Ge => todo!(),
}
}
}
pub fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&PyModule> {
let submod = PyModule::new(py, "point")?;
submod.add_class::<Point>()?;
Ok(submod)
}

View File

@@ -1,63 +0,0 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
use std::ops::Deref;
use darkfi::zk::{proof, vm};
use darkfi_sdk::pasta::pallas;
use pyo3::prelude::*;
use rand::rngs::OsRng;
use super::{
base::Base, proving_key::ProvingKey, verifying_key::VerifyingKey, zk_circuit::ZkCircuit,
};
#[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)
}

View File

@@ -1,44 +0,0 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
use std::ops::Deref;
use darkfi::zk::{proof, vm};
use pyo3::prelude::*;
use super::zk_circuit::ZkCircuit;
#[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)
}

View File

@@ -1,121 +0,0 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
use darkfi_sdk::{
crypto::pasta_prelude::{Field, PrimeField},
pasta::{group::ff::FromUniformBytes, pallas},
};
use pyo3::{basic::CompareOp, prelude::*};
use rand::rngs::OsRng;
/// The scalar field of the Pallas and iso-Pallas curves.
#[pyclass]
pub struct Scalar(pub(crate) pallas::Scalar);
#[pymethods]
impl Scalar {
#[staticmethod]
fn from_u64(v: u64) -> Self {
Self(pallas::Scalar::from(v))
}
#[staticmethod]
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 from_uniform_bytes(bytes: [u8; 64]) -> Self {
Self(pallas::Scalar::from_uniform_bytes(&bytes))
}
#[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!("{:?}", self.0)
}
fn __repr__(slf: &PyCell<Self>) -> PyResult<String> {
let class_name: &str = slf.get_type().name()?;
Ok(format!("{}({:?})", class_name, slf.borrow().0))
}
fn __add__(&self, other: &Self) -> Self {
Self(self.0 + other.0)
}
fn __sub__(&self, other: &Self) -> Self {
Self(self.0 - other.0)
}
fn __mul__(&self, other: &Self) -> Self {
Self(self.0 * other.0)
}
fn __neg__(&self) -> Self {
Self(self.0.neg())
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Lt => Ok(self.0 < other.0),
CompareOp::Le => Ok(self.0 <= other.0),
CompareOp::Eq => Ok(self.0 == other.0),
CompareOp::Ne => Ok(self.0 != other.0),
CompareOp::Gt => Ok(self.0 > other.0),
CompareOp::Ge => Ok(self.0 >= other.0),
}
}
fn double(&self) -> Self {
Self(self.0.double())
}
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)
}

View File

@@ -1,44 +0,0 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
use std::ops::Deref;
use darkfi::zk::proof;
use pyo3::prelude::*;
use super::zk_circuit::ZkCircuit;
#[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)
}

View File

@@ -1,77 +0,0 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
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))
.collect();
(opcode, args)
})
.collect()
}
fn k(&self) -> u32 {
self.0.k
}
}
pub fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&PyModule> {
let submod = PyModule::new(py, "zk_binary")?;
submod.add_class::<ZkBinary>()?;
Ok(submod)
}

View File

@@ -1,103 +0,0 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
use std::ops::Deref;
use darkfi::zk::{halo2::Value, vm, vm_heap::empty_witnesses};
use darkfi_sdk::crypto::MerkleNode;
use pyo3::prelude::*;
use super::{base::Base, point::Point, scalar::Scalar, zk_binary::ZkBinary};
#[pyclass]
pub struct ZkCircuit(pub(crate) vm::ZkCircuit, pub(crate) Vec<vm::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);
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);
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).unwrap(), &circuit_code);
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)
}

209
src/sdk/python/src/zkas.rs Normal file
View File

@@ -0,0 +1,209 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
*/
use std::ops::Deref;
use darkfi::{
zk::{self, empty_witnesses, halo2::Value},
zkas::decoder,
};
use darkfi_sdk::{crypto::MerkleNode, pasta::pallas};
use pyo3::{pyclass, pymethods, types::PyModule, PyCell, PyResult, Python};
use rand::rngs::OsRng;
use super::pasta::{Ep, Fp, Fq};
#[pyclass]
/// Decoded zkas bincode
pub struct ZkBinary(decoder::ZkBinary);
#[pymethods]
impl ZkBinary {
#[new]
fn new(bytes: Vec<u8>) -> Self {
Self::decode(bytes)
}
#[staticmethod]
fn decode(bytes: Vec<u8>) -> Self {
let bincode = decoder::ZkBinary::decode(bytes.as_slice()).unwrap();
Self(bincode)
}
fn k(&self) -> u32 {
self.0.k
}
}
#[pyclass]
/// Class representing a zkVM circuit, the witness values, and the zkas binary
/// defining the circuit code path.
pub struct ZkCircuit(zk::vm::ZkCircuit, Vec<zk::vm::Witness>, decoder::ZkBinary);
#[pymethods]
impl ZkCircuit {
#[new]
fn new(zkbin: &PyCell<ZkBinary>) -> Self {
let zkbin = zkbin.borrow().deref().0.clone();
let circuit = zk::vm::ZkCircuit::new(vec![], &zkbin);
Self(circuit, vec![], zkbin)
}
fn prover_build(&self) -> Self {
let circuit = zk::vm::ZkCircuit::new(self.1.clone(), &self.2);
Self(circuit, self.1.clone(), self.2.clone())
}
fn verifier_build(&self) -> Self {
let witnesses = empty_witnesses(&self.2).unwrap();
let circuit = zk::vm::ZkCircuit::new(witnesses.clone(), &self.2);
Self(circuit, witnesses, self.2.clone())
}
fn witness_ecpoint(&mut self, w: &PyCell<Ep>) {
let w = w.borrow();
let w = w.deref();
self.1.push(zk::vm::Witness::EcPoint(Value::known(w.0)));
}
fn witness_ecnipoint(&mut self, w: &PyCell<Ep>) {
let w = w.borrow();
let w = w.deref();
self.1.push(zk::vm::Witness::EcNiPoint(Value::known(w.0)));
}
fn witness_base(&mut self, w: &PyCell<Fp>) {
let w = w.borrow();
let w = w.deref();
self.1.push(zk::vm::Witness::Base(Value::known(w.0)));
}
fn witness_scalar(&mut self, w: &PyCell<Fq>) {
let w = w.borrow();
let w = w.deref();
self.1.push(zk::vm::Witness::Scalar(Value::known(w.0)));
}
fn witness_merklepath(&mut self, w: Vec<&PyCell<Fp>>) {
assert!(w.len() == 32);
let path: Vec<MerkleNode> =
w.iter().map(|x| MerkleNode::from(x.borrow().deref().0)).collect();
self.1.push(zk::vm::Witness::MerklePath(Value::known(path.try_into().unwrap())));
}
fn witness_uint32(&mut self, w: u32) {
self.1.push(zk::vm::Witness::Uint32(Value::known(w)));
}
fn witness_uint64(&mut self, w: u64) {
self.1.push(zk::vm::Witness::Uint64(Value::known(w)));
}
}
#[pyclass]
/// Verifying key for a zkVM proof
pub struct VerifyingKey(zk::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 vk = zk::proof::VerifyingKey::build(k, circuit);
Self(vk)
}
}
#[pyclass]
/// Proving key for a zkVM proof
pub struct ProvingKey(zk::proof::ProvingKey);
#[pymethods]
impl ProvingKey {
#[staticmethod]
fn build(k: u32, circuit: &PyCell<ZkCircuit>) -> Self {
let circuit_ref = circuit.borrow();
let circuit = &circuit_ref.deref().0;
let pk = zk::proof::ProvingKey::build(k, circuit);
Self(pk)
}
}
#[pyclass]
/// A zkVM proof
pub struct Proof(zk::proof::Proof);
#[pymethods]
impl Proof {
#[staticmethod]
fn create(
pk: &PyCell<ProvingKey>,
circuits: Vec<&PyCell<ZkCircuit>>,
instances: Vec<&PyCell<Fp>>,
) -> Self {
let pk = pk.borrow().deref().0.clone();
let circuits: Vec<zk::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 =
zk::proof::Proof::create(&pk, circuits.as_slice(), instances.as_slice(), &mut OsRng)
.unwrap();
Self(proof)
}
fn verify(&self, vk: &PyCell<VerifyingKey>, instances: Vec<&PyCell<Fp>>) {
let vk = vk.borrow().deref().0.clone();
let instances: Vec<pallas::Base> = instances.iter().map(|i| i.borrow().deref().0).collect();
self.0.verify(&vk, instances.as_slice()).unwrap();
}
}
#[pyclass]
/// MockProver class used for fast proof creation and verification.
/// Doesn't offer any security and should not be used in production.
pub struct MockProver(zk::halo2::dev::MockProver<pallas::Base>);
#[pymethods]
impl MockProver {
#[staticmethod]
fn run(k: u32, circuit: &PyCell<ZkCircuit>, instances: Vec<&PyCell<Fp>>) -> Self {
let circuit = circuit.borrow().deref().0.clone();
let instances: Vec<pallas::Base> = instances.iter().map(|i| i.borrow().deref().0).collect();
let prover = zk::halo2::dev::MockProver::run(k, &circuit, vec![instances]).unwrap();
Self(prover)
}
fn verify(&self) {
self.0.assert_satisfied();
}
}
pub fn create_module(py: Python<'_>) -> PyResult<&PyModule> {
let submod = PyModule::new(py, "zkas")?;
submod.add_class::<ZkBinary>()?;
submod.add_class::<ZkCircuit>()?;
submod.add_class::<VerifyingKey>()?;
submod.add_class::<ProvingKey>()?;
submod.add_class::<Proof>()?;
submod.add_class::<MockProver>()?;
Ok(submod)
}