Преглед изворни кода

example: Add very basic hello-world contract

parazyd пре 2 година
родитељ
комит
aaef3705e5

+ 2 - 0
example/wasm-hello-world/.gitignore

@@ -0,0 +1,2 @@
+Cargo.lock
+target

+ 24 - 0
example/wasm-hello-world/Cargo.toml

@@ -0,0 +1,24 @@
+[package]
+name = "wasm_hello_world"
+version = "0.0.1"
+authors = ["Dyne.org foundation <foundation@dyne.org>"]
+license = "AGPL-3.0-only"
+edition = "2021"
+
+[workspace]
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[dependencies]
+darkfi-sdk = { path = "../../src/sdk" }
+darkfi-serial = { path = "../../src/serial", features = ["derive", "crypto"] }
+
+# We need to disable random using "custom" which makes the crate a noop
+# so the wasm32-unknown-unknown target is enabled.
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+getrandom = { version = "0.2.8", features = ["custom"] }
+
+[features]
+default = []
+no-entrypoint = []

+ 46 - 0
example/wasm-hello-world/Makefile

@@ -0,0 +1,46 @@
+.POSIX:
+
+# Cargo binary
+CARGO = cargo +nightly
+
+# wasm build target
+WASM_TARGET = wasm32-unknown-unknown
+
+# Cargo package name
+PKGNAME = $(shell grep '^name = ' Cargo.toml | cut -d' ' -f3 | tr -d '"')
+# wasm contract binary
+WASM_BIN = $(PKGNAME:=.wasm)
+
+# zkas compiler binary
+ZKAS = "../../zkas"
+
+# zkas circuits
+PROOFS_SRC = $(shell find proof -type f -name '*.zk')
+PROOFS_BIN = $(PROOFS_SRC:=.bin)
+
+# wasm source files
+WASM_SRC = \
+	Cargo.toml \
+	$(shell find src -type f -name '*.rs')
+
+all: $(WASM_BIN)
+
+$(PROOFS_BIN): $(PROOFS_SRC)
+	$(ZKAS) $(basename $@) -o $@
+
+$(WASM_BIN): $(WASM_SRC) $(PROOFS_BIN)
+	RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) build --target=$(WASM_TARGET) \
+		--release --package $(PKGNAME)
+	cp -f target/$(WASM_TARGET)/release/$@ $@
+	wasm-strip $@
+
+clippy: all
+	RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clippy --target=$(WASM_TARGET) \
+		--release --package $(PKGNAME)
+
+clean:
+	RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clean --target=$(WASM_TARGET) \
+		--release --package $(PKGNAME)
+	rm -f $(PROOFS_BIN) $(WASM_BIN)
+
+.PHONY: all clippy clean

+ 24 - 0
example/wasm-hello-world/proof/secret_commitment.zk

@@ -0,0 +1,24 @@
+# The k parameter defining the number of rows used in our circuit (2^k)
+k = 11;
+field = "pallas";
+
+# The constants we define for our circuit
+constant "SecretCommitment" {
+    EcFixedPointBase NULLIFIER_K,    
+}
+
+# The witness values we define in our circuit
+witness "SecretCommitment" {
+    # Secret key used to derive the public key
+    Base secret,
+}
+
+# The definition of our circuit
+circuit "SecretCommitment" {
+    # Derive the public key
+    pub = ec_mul_base(secret, NULLIFIER_K);
+
+    # Constrain the public key coordinates
+    constrain_instance(ec_get_x(pub));
+    constrain_instance(ec_get_y(pub));
+}

+ 142 - 0
example/wasm-hello-world/src/entrypoint.rs

@@ -0,0 +1,142 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2024 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::{poseidon_hash, ContractId, PublicKey},
+    dark_tree::DarkLeaf,
+    error::ContractResult,
+    msg,
+    pasta::pallas,
+    wasm::{
+        self,
+        db::{db_contains_key, db_del, db_init, db_lookup, db_set, zkas_db_set},
+    },
+    ContractCall, ContractError,
+};
+use darkfi_serial::{deserialize, serialize, Encodable};
+
+use crate::{
+    ContractFunction, HelloParams, HELLO_CONTRACT_MEMBER_TREE, HELLO_CONTRACT_ZKAS_SECRETCOMMIT_NS,
+};
+
+darkfi_sdk::define_contract!(
+    init: init_contract,
+    exec: process_instruction,
+    apply: process_update,
+    metadata: get_metadata
+);
+
+/// This entrypoint function runs when the contract is (re)deployed and initialized.
+/// We use this function to init all the necessary databases and prepare them with
+/// initial data if necessary.
+/// This is also the place where we bundle the zkas circuits that are to be used
+/// with functions provided by the contract.
+fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
+    // zkas circuits can simply be embedded in the wasm and set up by using
+    // respective db functions.
+    // The special `zkas db` operations exist in order to be able to verify
+    // the circuits being bundled and enforcing a specific tree inside sled,
+    // and dlso creation of VerifyingKey.
+    let circuit_bincode = include_bytes!("../proof/secret_commitment.zk.bin");
+
+    // For that, we use `zkas_db_set` and pass in the bincode.
+    zkas_db_set(&circuit_bincode[..])?;
+
+    // Now we also want to create our own database to hold things.
+    // This `lookup || init` method is a redeployment guard.
+    if db_lookup(cid, HELLO_CONTRACT_MEMBER_TREE).is_err() {
+        db_init(cid, HELLO_CONTRACT_MEMBER_TREE)?;
+    }
+
+    Ok(())
+}
+
+fn get_metadata(_cid: ContractId, ix: &[u8]) -> ContractResult {
+    let call_idx = wasm::util::get_call_index()? as usize;
+    let calls: Vec<DarkLeaf<ContractCall>> = deserialize(ix)?;
+    let self_ = &calls[call_idx].data;
+    let _func = ContractFunction::try_from(self_.data[0])?;
+
+    // Deserialize the call parameters
+    let params: HelloParams = deserialize(&self_.data[1..])?;
+
+    // Public inputs for the ZK proofs we have to verify
+    let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
+    // Public keys for the transaction signatures we have to verify
+    let signature_pubkeys: Vec<PublicKey> = vec![];
+
+    zk_public_inputs
+        .push((HELLO_CONTRACT_ZKAS_SECRETCOMMIT_NS.to_string(), vec![params.x, params.y]));
+
+    // Serialize everything gathered and return it
+    let mut metadata = vec![];
+    zk_public_inputs.encode(&mut metadata)?;
+    signature_pubkeys.encode(&mut metadata)?;
+
+    wasm::util::set_return_data(&metadata)
+}
+
+fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
+    let call_idx = wasm::util::get_call_index()? as usize;
+    let calls: Vec<DarkLeaf<ContractCall>> = deserialize(ix)?;
+    let self_ = &calls[call_idx].data;
+    let func = ContractFunction::try_from(self_.data[0])?;
+
+    // Deserialize the call parameters
+    let params: HelloParams = deserialize(&self_.data[1..])?;
+
+    // Open the db
+    let db_members = db_lookup(cid, HELLO_CONTRACT_MEMBER_TREE)?;
+
+    // Pubkey commitment
+    let commitment = poseidon_hash([params.x, params.y]);
+
+    match func {
+        ContractFunction::Register => {
+            if db_contains_key(db_members, &serialize(&commitment))? {
+                msg!("Error: Member already in database");
+                return Err(ContractError::Custom(1))
+            }
+        }
+
+        ContractFunction::Deregister => {
+            if !db_contains_key(db_members, &serialize(&commitment))? {
+                msg!("Error: Member not in database");
+                return Err(ContractError::Custom(2))
+            }
+        }
+    }
+
+    let mut update_data = vec![func as u8];
+    commitment.encode(&mut update_data)?;
+    wasm::util::set_return_data(&serialize(&update_data))
+}
+
+fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
+    let func = ContractFunction::try_from(update_data[0])?;
+    let db_members = db_lookup(cid, HELLO_CONTRACT_MEMBER_TREE)?;
+
+    let commitment = &update_data[1..];
+
+    match func {
+        ContractFunction::Register => db_set(db_members, commitment, &[])?,
+        ContractFunction::Deregister => db_del(db_members, commitment)?,
+    }
+
+    Ok(())
+}

+ 58 - 0
example/wasm-hello-world/src/lib.rs

@@ -0,0 +1,58 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2024 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::{error::ContractError, pasta::pallas};
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+
+/// Functions available in the contract
+#[repr(u8)]
+pub enum ContractFunction {
+    Register = 0x00,
+    Deregister = 0x01,
+}
+
+impl TryFrom<u8> for ContractFunction {
+    type Error = ContractError;
+
+    fn try_from(b: u8) -> Result<Self, Self::Error> {
+        match b {
+            0x00 => Ok(Self::Register),
+            0x01 => Ok(Self::Deregister),
+            _ => Err(ContractError::InvalidFunction),
+        }
+    }
+}
+
+/// Function parameters
+#[derive(Debug, Clone, Copy, SerialEncodable, SerialDecodable)]
+pub struct HelloParams {
+    /// X coordinate of the public key
+    pub x: pallas::Base,
+    /// Y coordinate of the public key
+    pub y: pallas::Base,
+}
+
+#[cfg(not(feature = "no-entrypoint"))]
+/// WASM entrypoint functions
+pub mod entrypoint;
+
+/// This is a sled tree that will be created
+pub const HELLO_CONTRACT_MEMBER_TREE: &str = "members";
+
+/// zkas circuit namespace
+pub const HELLO_CONTRACT_ZKAS_SECRETCOMMIT_NS: &str = "SecretCommitment";