x 3 anos atrás
commit
4a7c88325f
14 arquivos alterados com 933 adições e 0 exclusões
  1. 2 0
      .gitignore
  2. 48 0
      Cargo.toml
  3. 45 0
      Makefile
  4. 51 0
      README.md
  5. BIN
      darkmap_plan.png
  6. 26 0
      proof/set_v1.zk
  7. 31 0
      src/client/mod.rs
  8. 107 0
      src/client/set_v1.rs
  9. 204 0
      src/entrypoint.rs
  10. 34 0
      src/error.rs
  11. 50 0
      src/lib.rs
  12. 35 0
      src/model.rs
  13. 197 0
      tests/harness.rs
  14. 103 0
      tests/integration.rs

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+map_contract.wasm
+proof/*.zk.bin

+ 48 - 0
Cargo.toml

@@ -0,0 +1,48 @@
+[package]
+name = "darkfi-map-contract"
+version = "0.4.1"
+authors = ["Dyne.org foundation <foundation@dyne.org>"]
+license = "AGPL-3.0-only"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[dependencies]
+darkfi-sdk = { path = "../../sdk" }
+darkfi-serial = { path = "../../serial", features = ["derive", "crypto"] }
+thiserror = "1.0.40"
+
+# The following dependencies are used for the client API and
+# probably shouldn't be in WASM
+chacha20poly1305 = { version = "0.10.1", optional = true }
+# why is darkfi undeclared?
+darkfi = { path = "../../../", features = ["zk", "rpc", "blockchain"], optional = true }
+halo2_proofs = { version = "0.3.0", optional = true }
+log = { version = "0.4.17", optional = true }
+rand = { version = "0.8.5", optional = true }
+
+# These are used just for the integration tests
+[dev-dependencies]
+async-std = {version = "1.12.0", features = ["attributes"]}
+bs58 = "0.4.0"
+darkfi = {path = "../../../", features = ["tx", "blockchain"]}
+simplelog = "0.12.1"
+sled = "0.34.7"
+sqlx = {version = "0.6.3", features = ["runtime-async-std-rustls", "sqlite"]}
+
+# 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 = []
+client = [
+    "darkfi",
+    "rand",
+    "chacha20poly1305",
+    "log",
+    "halo2_proofs",
+]

+ 45 - 0
Makefile

@@ -0,0 +1,45 @@
+.POSIX:
+
+# Cargo binary
+CARGO = cargo
+
+# 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 = \
+	$(shell find src -type f) \
+	$(shell find ../../sdk -type f) \
+	$(shell find ../../serial -type f)
+
+# wasm contract binary
+WASM_BIN = map_contract.wasm
+
+all: $(WASM_BIN)
+
+$(WASM_BIN): $(WASM_SRC) $(PROOFS_BIN)
+	$(CARGO) build --release --package darkfi-map-contract --target wasm32-unknown-unknown
+	cp -f ../../../target/wasm32-unknown-unknown/release/darkfi_map_contract.wasm $@
+
+client:
+	$(CARGO) build --release --features=no-entrypoint,client \
+		--package darkfi-map-contract \
+
+$(PROOFS_BIN): $(ZKAS) $(PROOFS_SRC)
+	$(ZKAS) $(basename $@) -o $@
+
+test-integration: all
+	$(CARGO) test --release --features=no-entrypoint,client \
+		--package darkfi-map-contract \
+		--test integration
+
+test: test-integration
+
+clean:
+	rm -f $(PROOFS_BIN) $(WASM_BIN)
+
+.PHONY: all test-integration

+ 51 - 0
README.md

@@ -0,0 +1,51 @@
+# Darkmap
+
+`Darkmap` aims to be a permissionless name system.
+
+Anyone can have a number of pseudonyms, each pseudonym can own a number of
+namespaces.
+
+There is a strong gurantee of immutability, so values can be safely
+cached locally (even the secret is leaked).
+
+The main application is to enable a private and secure software supply chain.
+
+## Dpath
+
+Syntax example: ns1:ns2.key
+
+```
+# mutable example
+
+we want master to change, so we make it mutable
+
+                   mutable
+                      v
+darkrenaissance:darkfi.master -> 1fb851750a6b8bfadfe60ca362cff0fc89a9b2ed 
+      ^           ^
+   namespace    subnamespace
+
+
+# immutable example
+
+once we cut the release tag, we don't want the path to change, so we make it immutable
+
+	  immutable immutable
+               v      v
+darkrenaissance:darkfi:v0_4_1 -> 0793fe32a3d7e9bedef9c3c0767647c74db215e9 (tagged commit should never change)
+```
+
+```
+darkrenaissance:darkfi:v0_4_1 -> 0793fe32a3d7e9bedef9c3c0767647c74db215e9 (tagged commit should never change)
+                 ^
+       namespace is owned by bob 
+       suppose bob's secret is leaked
+       because v0_4_1 is permanently locked in the darkfi namespace,
+       adversary cannot change the path's value
+
+```
+
+# Credit
+
+Designed by someone else and with love.
+

BIN
darkmap_plan.png


+ 26 - 0
proof/set_v1.zk

@@ -0,0 +1,26 @@
+constant "Set_V1" {} 
+
+witness "Set_V1" {
+	Base secret,
+	Base lock,
+	# Whether set canonical root
+	# 
+	# Be nice, don't spam
+	# setting slots in the canonical root namespace will be paid eventually
+	# but people can always choose to use an alt root
+	Base car,
+	Base key,
+	Base value,
+}
+
+circuit "Set_V1" {
+	account = poseidon_hash(secret);
+	constrain_instance(account);
+	constrain_instance(lock);
+	constrain_instance(car);
+	constrain_instance(key);
+	constrain_instance(value);
+	bool_check(lock);
+	bool_check(car);
+}
+

+ 31 - 0
src/client/mod.rs

@@ -0,0 +1,31 @@
+/* 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/>.
+ */
+
+//! This module implements the client-side API for this contract's interaction.
+//! What we basically do here is implement an API that creates the necessary
+//! structures and is able to export them to create a DarkFi transaction
+//! object that can be broadcasted to the network when we want to make a
+//! payment with some coins in our wallet.
+//!
+//! Note that this API does not involve any wallet interaction, but only takes
+//! the necessary objects provided by the caller. This is intentional, so we
+//! are able to abstract away any wallet interfaces to client implementations.
+
+/// `Map::Set` API
+pub mod set_v1;
+

+ 107 - 0
src/client/set_v1.rs

@@ -0,0 +1,107 @@
+/* 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::{
+    zk::{
+        halo2::Value,
+        Proof,
+        ProvingKey,
+        ZkCircuit,
+        Witness
+    },
+    zkas::ZkBinary,
+    Result,
+};
+
+use darkfi_sdk::{
+    crypto::{
+        poseidon_hash,
+        SecretKey,
+    },
+    pasta::pallas,
+};
+
+use log::debug;
+
+use rand::rngs::OsRng;
+
+use crate::model::SetParamsV1;
+
+pub struct SetCallBuilder {
+    pub secret:     SecretKey,
+    pub lock:       pallas::Base,
+    pub car:        pallas::Base,
+    pub key:        pallas::Base,
+    pub value:      pallas::Base,
+    pub zkbin:      ZkBinary,
+    pub prove_key:  ProvingKey,
+}
+
+pub struct SetCallDebris {
+    pub params: SetParamsV1,
+    pub proofs: Vec<Proof>,
+    pub signature_secrets: Vec<SecretKey>,
+}
+
+impl SetCallBuilder {
+    pub fn build(&self) -> Result<SetCallDebris> {
+        debug!("Building Map::SetV1 contract call");
+
+        let params = SetParamsV1 { 
+            // !!!!private computation done in rust!!!!
+            account: poseidon_hash([self.secret.inner()]), 
+            lock :self.lock,
+            car :self.car,
+            key: self.key,
+            value: self.value,
+        };
+
+        Ok(
+            SetCallDebris {
+                params: params.clone(),
+                proofs: vec![self.create_set_proof(params.clone())?],
+                signature_secrets: vec![self.secret],
+        })
+    }
+
+    pub fn create_set_proof(
+        &self,
+        public_inputs: SetParamsV1
+    ) -> Result<Proof> {
+        debug!("Creating map set proof");
+
+        let witness       = vec![
+            Witness::Base(Value::known(self.secret.inner())),
+            Witness::Base(Value::known(self.car)),
+            Witness::Base(Value::known(self.lock)),
+            Witness::Base(Value::known(self.key)),
+            Witness::Base(Value::known(self.value)),
+        ];
+        let circuit       = ZkCircuit::new(witness, self.zkbin.clone());
+        let proof         = Proof::create(
+            &self.prove_key,
+            &[circuit],
+            &public_inputs.to_vec(),
+            &mut OsRng
+        )?;
+
+        Ok(proof)
+    }
+}
+

+ 204 - 0
src/entrypoint.rs

@@ -0,0 +1,204 @@
+/* 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 crate::{
+    ContractFunction,
+    MAP_CONTRACT_ENTRIES_TREE,
+    MAP_CONTRACT_ZKAS_SET_NS,
+    error::MapError
+};
+
+use darkfi_sdk::{
+    crypto::{ContractId, PublicKey, poseidon_hash},
+    error::{ContractError, ContractResult},
+    msg,
+    pasta::pallas,
+    ContractCall,
+    util::set_return_data,
+    db::{db_init, db_lookup, db_set, zkas_db_set, db_get},
+};
+
+use darkfi_serial::{
+    serialize,
+    deserialize,
+    Encodable,
+    WriteExt
+};
+
+use crate::model::{
+    SetParamsV1,
+    SetUpdateV1,
+};
+
+darkfi_sdk::define_contract!(
+    init:     init_contract,
+    exec:     process_instruction,
+    apply:    process_update,
+    metadata: get_metadata
+);
+
+fn init_contract(cid: ContractId, ix: &[u8]) -> ContractResult {
+    let set_v1_bincode = include_bytes!("../proof/set_v1.zk.bin");
+    zkas_db_set(&set_v1_bincode[..])?;
+
+    if db_lookup(cid, MAP_CONTRACT_ENTRIES_TREE).is_err() {
+        db_init(cid, MAP_CONTRACT_ENTRIES_TREE)?;
+    }
+
+    Ok(())
+}
+
+fn get_metadata(cid: ContractId, ix: &[u8]) -> ContractResult {
+    let (call_idx, calls): (u32, Vec<ContractCall>) = deserialize(ix)?;
+    if call_idx >= calls.len() as u32 {
+        msg!("Error: call_idx >= calls.len()");
+        return Err(ContractError::Internal);
+    }
+
+    let self_ = &calls[call_idx as usize];
+    match ContractFunction::try_from(self_.data[0])? {
+        ContractFunction::Set => {
+            let params: SetParamsV1 = deserialize(&self_.data[1..])?;
+            let signature_pubkeys: Vec<PublicKey> = vec![];
+            let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)>
+                = vec![];
+
+            zk_public_inputs.push((
+                MAP_CONTRACT_ZKAS_SET_NS.to_string(),
+                params.to_vec(),
+            ));
+    
+            let mut metadata = vec![];
+            zk_public_inputs.encode(&mut metadata)?;
+            signature_pubkeys.encode(&mut metadata)?;
+
+            set_return_data(&metadata)?;
+            Ok(())
+        }
+    }
+}
+
+/// the most imporatant things to the implementation:
+/// - there is 1 map, slot (number) -> value, so set and get are gas efficient
+/// - slot is function of a) namespace and b) key under the namespace
+///   - slot(root_namespace, darkrenaissance) = poseidon_hash(
+///                                                 0,
+///                                                 darkrenaissance
+///                                             )
+///                                           = alice_account
+///   - slot(darkrenaissance, darkfi)         = poseidon_hash(
+///                                                 alice_account,
+///                                                 darkfi
+///                                             )
+///                                           = bob_account
+///   - slot(darkfi, v0_4_1)                  = poseidon_hash(
+///                                                 bob_account,
+///                                                 v0_4_1
+///                                             )
+///                                           = value
+/// - 0 is the special account for the canonical root
+///
+///
+fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
+    let (call_idx, calls): (u32, Vec<ContractCall>) = deserialize(ix)?;
+    if call_idx >= calls.len() as u32 {
+        msg!("Error: call_idx >= calls.len()");
+        return Err(ContractError::Internal);
+    }
+
+    match ContractFunction::try_from(ix[0])? {
+        ContractFunction::Set => {
+            msg!("processing SET");
+            let params: SetParamsV1 = 
+                deserialize(&calls[call_idx as usize].data[1..])?;
+            let slot = if params.car == pallas::Base::one() {
+                poseidon_hash([pallas::Base::zero(), params.key])
+            } else {
+                poseidon_hash([params.account, params.key])
+            };
+
+            // Question being answered by this block of code:
+            // is this slot locked?
+            let db = db_lookup(cid, MAP_CONTRACT_ENTRIES_TREE)?;
+            match db_get(db, &serialize(&slot))? {
+                None => msg!("[SET] slot has no value"),
+                Some(lock) => {
+                    if deserialize(&lock)? {
+                        return Err(MapError::Locked.into())
+                    }
+                }
+            };
+            msg!("[SET] slot  = {:?}", slot);
+            msg!("[SET] car   = {:?}", params.car);
+            msg!("[SET] lock  = {:?}", params.lock);
+            msg!("[SET] value = {:?}", params.value);
+
+
+            let update = SetUpdateV1 {
+                slot,
+                lock: params.lock,
+                value: params.value,
+            };
+            let mut update_data = vec![];
+            update_data.write_u8(ContractFunction::Set as u8)?;
+            update.encode(&mut update_data);
+            set_return_data(&update_data)?;
+            msg!("[SET] State update set!");
+
+            Ok(())
+        }
+    }
+}
+
+fn process_update(
+    cid: ContractId,
+    update_data: &[u8]
+) -> ContractResult {
+    match ContractFunction::try_from(update_data[0])? {
+        ContractFunction::Set => {
+            let update: SetUpdateV1 = deserialize(&update_data[1..])?;
+
+            msg!("[SET] serialized_slot     = {:?}",
+                 &serialize(&update.slot));
+            msg!("[SET] serialized_slot + 1 = {:?}",
+                 &serialize(&(update.slot.add(&pallas::Base::one()))));
+            msg!("[SET] serialized_lock    = {:?}",
+                 &serialize(&update.lock));
+            msg!("[SET] serialized_value    = {:?}",
+                 &serialize(&update.value));
+
+            // key(slot)     = lock
+            // key(slot + 1) = value
+            let db = db_lookup(cid, MAP_CONTRACT_ENTRIES_TREE)?;
+            db_set(
+                db,
+                &serialize(&update.slot),
+                &serialize(&update.lock),
+            ).unwrap();
+            db_set(
+                db,
+                &serialize(&(update.slot.add(&pallas::Base::one()))),
+                &serialize(&update.value),
+            ).unwrap();
+
+            Ok(())
+        },
+    }
+}
+

+ 34 - 0
src/error.rs

@@ -0,0 +1,34 @@
+/* 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::error::ContractError;
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum MapError {
+    #[error("Setting on locked slot")]
+    Locked,
+}
+
+impl From<MapError> for ContractError {
+    fn from(e: MapError) -> Self {
+        match e {
+            MapError::Locked => Self::Custom(1),
+        }
+    }
+}

+ 50 - 0
src/lib.rs

@@ -0,0 +1,50 @@
+/* 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::error::ContractError;
+
+/// Functions available in the contract
+pub enum ContractFunction {
+    Set = 0x00,
+}
+pub const MAP_CONTRACT_ENTRIES_TREE: &str = "entries";
+
+pub const MAP_CONTRACT_ZKAS_SET_NS: &str = "Set_V1";
+
+impl TryFrom<u8> for ContractFunction {
+    type Error = ContractError;
+
+    fn try_from(b: u8) -> Result<Self, Self::Error> {
+        match b {
+            0x00 => Ok(Self::Set),
+            _ => Err(ContractError::InvalidFunction),
+        }
+    }
+}
+
+#[cfg(not(feature = "no-entrypoint"))]
+/// WASM entrypoint functions
+pub mod entrypoint;
+
+#[cfg(feature = "client")]
+/// Client API for interaction with this smart contract
+pub mod client;
+
+pub mod model;
+
+pub mod error;

+ 35 - 0
src/model.rs

@@ -0,0 +1,35 @@
+use darkfi_sdk::pasta::pallas;
+
+use darkfi_serial::{
+    SerialDecodable, 
+    SerialEncodable
+};
+
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+pub struct SetParamsV1 {
+    pub account: pallas::Base,
+    pub lock:    pallas::Base,
+    pub car:     pallas::Base,
+    pub key:     pallas::Base,
+    pub value:   pallas::Base,
+}
+
+impl SetParamsV1 {
+    pub fn to_vec(&self) -> Vec<pallas::Base> {
+        vec![
+            self.account,
+            self.lock,
+            self.car,
+            self.key,
+            self.value,
+        ]
+    }
+}
+
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+pub struct SetUpdateV1 {
+    pub slot:  pallas::Base,
+    pub lock:  pallas::Base,
+    pub value: pallas::Base,
+}
+

+ 197 - 0
tests/harness.rs

@@ -0,0 +1,197 @@
+/* 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::collections::HashMap;
+
+use darkfi::{
+    consensus::{
+        ValidatorState,
+        ValidatorStatePtr,
+        TESTNET_BOOTSTRAP_TIMESTAMP,
+        TESTNET_GENESIS_HASH_BYTES,
+        TESTNET_GENESIS_TIMESTAMP,
+        TESTNET_INITIAL_DISTRIBUTION,
+    },
+    runtime::vm_runtime::SMART_CONTRACT_ZKAS_DB_NAME,
+    tx::Transaction,
+    wallet::{WalletDb, WalletPtr},
+    zk::{empty_witnesses, halo2::Field, ProvingKey, ZkCircuit},
+    zkas::ZkBinary,
+    Result,
+};
+use darkfi_sdk::{
+    crypto::{
+        Keypair,
+        MerkleTree,
+        PublicKey,
+        SecretKey,
+        DARK_TOKEN_ID,
+        MAP_CONTRACT_ID
+    },
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::{deserialize, serialize, Encodable};
+use log::info;
+use rand::rngs::OsRng;
+
+use darkfi_map_contract::{
+    model::SetParamsV1,
+    client::set_v1::SetCallBuilder,
+    ContractFunction,
+};
+
+pub const MAP_CONTRACT_ZKAS_SET_NS_V1: &str = "Set_V1";
+
+pub fn init_logger() {
+    let mut cfg = simplelog::ConfigBuilder::new();
+    cfg.add_filter_ignore("sled".to_string());
+    cfg.add_filter_ignore("blockchain::contractstore".to_string());
+
+    // We check this error so we can execute same file tests in parallel
+    // otherwise second one fails to init logger here.
+    if let Err(_) = simplelog::TermLogger::init(
+        // simplelog::LevelFilter::Info,
+        simplelog::LevelFilter::Debug,
+        //simplelog::LevelFilter::Trace,
+        cfg.build(),
+        simplelog::TerminalMode::Mixed,
+        simplelog::ColorChoice::Auto,
+    ) {
+        info!(target: "map_harness", "Logger already initialized");
+    }
+}
+
+pub struct Wallet {
+    pub keypair: Keypair,
+    pub state: ValidatorStatePtr,
+    pub merkle_tree: MerkleTree,
+    pub wallet: WalletPtr,
+}
+
+impl Wallet {
+    async fn new(
+        keypair: Keypair,
+        faucet_pubkeys: &[PublicKey]
+        ) -> Result<Self> {
+        let wallet = WalletDb::new("sqlite::memory:", "foo").await?;
+        let sled_db = sled::Config::new().temporary(true).open()?;
+
+        let state = ValidatorState::new(
+            &sled_db,
+            *TESTNET_BOOTSTRAP_TIMESTAMP,
+            *TESTNET_GENESIS_TIMESTAMP,
+            *TESTNET_GENESIS_HASH_BYTES,
+            *TESTNET_INITIAL_DISTRIBUTION,
+            wallet.clone(),
+            faucet_pubkeys.to_vec(),
+            false,
+            false,
+        )
+        .await?;
+
+        let merkle_tree = MerkleTree::new(100);
+
+        Ok(Self { keypair, state, merkle_tree, wallet, })
+    }
+}
+
+
+pub struct MapTestHarness {
+    pub faucet: Wallet,
+    pub alice: Wallet,
+    pub proving_keys: HashMap<&'static str, (ProvingKey, ZkBinary)>,
+}
+
+impl MapTestHarness {
+    pub async fn new() -> Result<Self> {
+        let faucet_kp = Keypair::random(&mut OsRng);
+        let faucet_pubkeys = vec![faucet_kp.public];
+        let faucet = Wallet::new(faucet_kp, &faucet_pubkeys).await?;
+
+        let alice_kp = Keypair::random(&mut OsRng);
+        let alice = Wallet::new(alice_kp, &faucet_pubkeys).await?;
+
+        // Get the zkas circuits and build proving keys
+        let alice_sled = 
+            alice.state.read().await.blockchain.sled_db.clone();
+        let db_handle =
+            alice.state.read().await.blockchain.contracts.lookup(
+            &alice_sled,
+            &MAP_CONTRACT_ID,
+            SMART_CONTRACT_ZKAS_DB_NAME,
+        )?;
+
+        // build proving keys
+        let mut proving_keys = HashMap::new();
+        macro_rules! mkpk {
+            ($ns:expr) => {
+                let zkas_bytes =
+                    db_handle.get(&serialize(&$ns))?.unwrap();
+                let (zkbin, _): (Vec<u8>, Vec<u8>) =
+                                 deserialize(&zkas_bytes)?;
+                let zkbin = ZkBinary::decode(&zkbin)?;
+                let witnesses = empty_witnesses(&zkbin);
+                let circuit = ZkCircuit::new(witnesses, zkbin.clone());
+                let pk = ProvingKey::build(13, &circuit);
+                proving_keys.insert($ns, (pk, zkbin));
+            };
+        }
+        mkpk!(MAP_CONTRACT_ZKAS_SET_NS_V1);
+
+        Ok(Self { faucet, alice, proving_keys })
+    }
+
+    pub fn set(
+        &self,
+        secret: SecretKey,
+        lock: pallas::Base,
+        car: pallas::Base,
+        key: pallas::Base,
+        value: pallas::Base,
+    ) -> Result<(Transaction, SetParamsV1)> {
+        let (prove_key, zkbin) = 
+            self.proving_keys.get(
+                &MAP_CONTRACT_ZKAS_SET_NS_V1
+                ).unwrap();   
+        let debris = SetCallBuilder {
+            zkbin: zkbin.clone(),
+            prove_key: prove_key.clone(),
+            secret: secret.clone(),
+            lock: lock.clone(),
+            car: car.clone(),
+            key: key.clone(),
+            value: value.clone()
+        }.build()?;
+
+        let mut data = vec![ContractFunction::Set as u8];
+        debris.params.encode(&mut data)?;
+        let calls = vec![
+            ContractCall { contract_id: *MAP_CONTRACT_ID, data: data }
+        ];
+        let proofs = vec![debris.proofs];
+
+        let mut tx = Transaction { calls, proofs, signatures: vec![] };
+        let sigs = tx.create_sigs(&mut OsRng, &debris.signature_secrets)?;
+        tx.signatures = vec![sigs];
+
+        Ok((tx, debris.params))
+    }
+}
+

+ 103 - 0
tests/integration.rs

@@ -0,0 +1,103 @@
+/* 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::time::Instant;
+use darkfi::Result;
+use darkfi_sdk::{
+    crypto::{
+        poseidon_hash,
+        Keypair,
+        MerkleNode,
+        Nullifier,
+        MAP_CONTRACT_ID
+    },
+    incrementalmerkletree::Tree,
+    pasta::pallas,
+    // db::{db_lookup, db_get} link error?
+};
+use log::{info, debug};
+use rand::rngs::OsRng;
+use darkfi_map_contract::MAP_CONTRACT_ENTRIES_TREE;
+use darkfi_serial::{deserialize, serialize};
+
+mod harness;
+use harness::{init_logger, MapTestHarness};
+
+#[async_std::test]
+async fn map_integration() -> Result<()> {
+    let current_slot = 0;
+
+    init_logger();
+
+    let mut th = MapTestHarness::new().await?;
+    let (alice_tx, alice_params) = th.set(
+        th.alice.keypair.secret,
+        pallas::Base::from(1), // lock
+        pallas::Base::from(1), // car
+        pallas::Base::from(2), // key
+        pallas::Base::from(4), // value
+    )?;
+
+    info!(target: "map", "[Faucet] =============================");
+    info!(target: "map", "[Faucet] Executing Alice set tx");
+    info!(target: "map", "[Faucet] =============================");
+    let timer = Instant::now();
+    let erroneous_txs = th
+        .faucet
+        .state
+        .read()
+        .await
+        .verify_transactions(&[alice_tx.clone()], current_slot, true)
+        .await?;
+    assert!(erroneous_txs.is_empty());
+
+    info!(target: "map", "[Alice] =============================");
+    info!(target: "map", "[Alice] Executing Alice set tx");
+    info!(target: "map", "[Alice] =============================");
+    let timer = Instant::now();
+    let erroneous_txs = th
+        .alice
+        .state
+        .read()
+        .await
+        .verify_transactions(&[alice_tx.clone()], current_slot, true)
+        .await?;
+    debug!("error_tx: {:?}", erroneous_txs);
+    assert!(erroneous_txs.is_empty());
+
+    // let slot = poseidon_hash([alice_params.account, alice_params.key]);
+    // let db   = db_lookup(*MAP_CONTRACT_ID, MAP_CONTRACT_ENTRIES_TREE)?;
+    // db_get(db, &serialize(&slot))?;
+    // match db_get(db, &serialize(&slot))? {
+    //     None => panic!("slot should be set"),
+    //     Some(locked) => {
+    //         let lock: pallas::Base = deserialize(&locked)?;
+    //         assert!(lock == pallas::Base::one());
+    //     }
+    // };
+    // match db_get(db, &serialize(&(slot.add(&pallas::Base::one()))))? {
+    //     None => panic!("slot + 1 should be set"),
+    //     Some(value) => {
+    //         let value: pallas::Base = deserialize(&value)?;
+    //         assert!(value == pallas::Base::from(4));
+    //     }
+    // };
+
+    Ok(())
+}