Bläddra i källkod

sdk-py: Add darfi-sdk as dep

freerangedev 3 år sedan
förälder
incheckning
cfce6deccd

+ 1 - 2
Cargo.toml

@@ -43,7 +43,7 @@ members = [
     "bin/zktool",
 
     "src/sdk",
-    "src/darkfi-sdk-py",
+    "src/sdk-py",
     "src/serial",
     "src/serial/derive",
     "src/serial/derive-internal",
@@ -124,7 +124,6 @@ halo2_gadgets = {version = "0.3.0", optional = true}
 
 # Smart contract runtime
 darkfi-sdk = {path = "src/sdk", optional = true}
-darkfi-sdk-py = {path = "src/darkfi-sdk-py", optional = true}
 wasmer = {version = "3.3.0", optional = true}
 wasmer-compiler-singlepass = {version = "3.3.0", optional = true}
 wasmer-middlewares = {version = "3.3.0", optional = true}

+ 0 - 120
src/darkfi-sdk-py/.github/workflows/CI.yml

@@ -1,120 +0,0 @@
-# This file is autogenerated by maturin v1.0.0
-# To update, run
-#
-#    maturin generate-ci github
-#
-name: CI
-
-on:
-  push:
-    branches:
-      - main
-      - master
-    tags:
-      - '*'
-  pull_request:
-  workflow_dispatch:
-
-permissions:
-  contents: read
-
-jobs:
-  linux:
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        target: [x86_64, x86, aarch64, armv7, s390x, ppc64le]
-    steps:
-      - uses: actions/checkout@v3
-      - uses: actions/setup-python@v4
-        with:
-          python-version: '3.10'
-      - name: Build wheels
-        uses: PyO3/maturin-action@v1
-        with:
-          target: ${{ matrix.target }}
-          args: --release --out dist --find-interpreter
-          sccache: 'true'
-          manylinux: auto
-      - name: Upload wheels
-        uses: actions/upload-artifact@v3
-        with:
-          name: wheels
-          path: dist
-
-  windows:
-    runs-on: windows-latest
-    strategy:
-      matrix:
-        target: [x64, x86]
-    steps:
-      - uses: actions/checkout@v3
-      - uses: actions/setup-python@v4
-        with:
-          python-version: '3.10'
-          architecture: ${{ matrix.target }}
-      - name: Build wheels
-        uses: PyO3/maturin-action@v1
-        with:
-          target: ${{ matrix.target }}
-          args: --release --out dist --find-interpreter
-          sccache: 'true'
-      - name: Upload wheels
-        uses: actions/upload-artifact@v3
-        with:
-          name: wheels
-          path: dist
-
-  macos:
-    runs-on: macos-latest
-    strategy:
-      matrix:
-        target: [x86_64, aarch64]
-    steps:
-      - uses: actions/checkout@v3
-      - uses: actions/setup-python@v4
-        with:
-          python-version: '3.10'
-      - name: Build wheels
-        uses: PyO3/maturin-action@v1
-        with:
-          target: ${{ matrix.target }}
-          args: --release --out dist --find-interpreter
-          sccache: 'true'
-      - name: Upload wheels
-        uses: actions/upload-artifact@v3
-        with:
-          name: wheels
-          path: dist
-
-  sdist:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v3
-      - name: Build sdist
-        uses: PyO3/maturin-action@v1
-        with:
-          command: sdist
-          args: --out dist
-      - name: Upload sdist
-        uses: actions/upload-artifact@v3
-        with:
-          name: wheels
-          path: dist
-
-  release:
-    name: Release
-    runs-on: ubuntu-latest
-    if: "startsWith(github.ref, 'refs/tags/')"
-    needs: [linux, windows, macos, sdist]
-    steps:
-      - uses: actions/download-artifact@v3
-        with:
-          name: wheels
-      - name: Publish to PyPI
-        uses: PyO3/maturin-action@v1
-        env:
-          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
-        with:
-          command: upload
-          args: --skip-existing *

+ 0 - 14
src/darkfi-sdk-py/src/lib.rs

@@ -1,14 +0,0 @@
-use pyo3::prelude::*;
-
-/// Formats the sum of two numbers as string.
-#[pyfunction]
-fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
-    Ok((a + b).to_string())
-}
-
-/// A Python module implemented in Rust.
-#[pymodule]
-fn darkfi_sdk_py(_py: Python, m: &PyModule) -> PyResult<()> {
-    m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
-    Ok(())
-}

+ 0 - 0
src/darkfi-sdk-py/.gitignore → src/sdk-py/.gitignore


+ 4 - 0
src/darkfi-sdk-py/Cargo.toml → src/sdk-py/Cargo.toml

@@ -10,3 +10,7 @@ crate-type = ["cdylib"]
 
 [dependencies]
 pyo3 = "0.18.3"
+darkfi-sdk = { path = "../sdk" }
+rand = "0.8.5"
+halo2_gadgets = "0.3.0"
+darkfi = { path = "../../", features = ["zk", "zkas"] }

+ 5 - 0
src/sdk-py/README.md

@@ -0,0 +1,5 @@
+# Installation
+
+Follow virtualenv and pyo3 setup guide: https://pyo3.rs/v0.15.1/#using-rust-from-python
+
+(TODO: Makefile)

+ 0 - 0
src/darkfi-sdk-py/pyproject.toml → src/sdk-py/pyproject.toml


+ 314 - 0
src/sdk-py/src/lib.rs

@@ -0,0 +1,314 @@
+use std::ops::{Add, Deref, Mul};
+
+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(())
+}