/* 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 . */ use crate::{ error::MapError, ContractFunction, MAP_CONTRACT_ENTRIES_TREE, MAP_CONTRACT_ZKAS_SET_NS, }; use darkfi_sdk::{ crypto::{poseidon_hash, ContractId, PublicKey}, db::{db_get, db_init, db_lookup, db_set, zkas_db_set}, error::{ContractError, ContractResult}, msg, pasta::pallas, util::set_return_data, ContractCall, }; use darkfi_serial::{deserialize, serialize, Encodable, WriteExt}; use crate::model::{SetParamsV1, SetUpdateV1}; // A macro defining the 4 entrypoints // init: called during (re)deployment // The rest are called during a message call to the contract // metadata: called first // exec: called second // apply: called last darkfi_sdk::define_contract!( init: init_contract, exec: process_instruction, apply: process_update, metadata: get_metadata ); // init takes: // - the contract ID given by the host // - deployment payload // then: // - initializes all the databases // - and bundle zkas circuits that will gate this contract's functions fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult { // Hardcode the `set` circuit's binary into the wasm binary // during the wasm module's compilation. // TODO: do we need to update for non-native deployment? let set_v1_bincode = include_bytes!("../proof/set_v1.zk.bin"); // When init is called, create a verifying key for this circuit. // The verifying key will later be used to verify proofs generated by the `set` circuit. zkas_db_set(&set_v1_bincode[..])?; // If this is a redeployment, skip the databsae initialization, // initialize otherwise. // We want MAP_CONTRACT_ENTRIES_TREE to store the key-value pairs of the // name registries. if db_lookup(cid, MAP_CONTRACT_ENTRIES_TREE).is_err() { // "Under the hood" are comments for the studious ones about how // something works inside the host. // // Under the hood: db_init is only allowed callable inside init. // https://github.com/darkrenaissance/darkfi/blob/35405831e366eaa74522ab14645a5a05ce5cfa1e/src/runtime/import/db.rs#L55-L58 // // Under the hood: cid must match the contract ID of this contract // https://github.com/darkrenaissance/darkfi/blob/35405831e366eaa74522ab14645a5a05ce5cfa1e/src/runtime/import/db.rs#L105-L108 db_init(cid, MAP_CONTRACT_ENTRIES_TREE)?; } Ok(()) } // The `metadata` entrypoint takes 1) contract ID and 2) (call's idx, calls), // then it is supposed to return the public keys and public inputs, for // the host to verify the signatures and zero knowledge proofs, respectively. fn get_metadata(_cid: ContractId, ix: &[u8]) -> ContractResult { // Parse the index and calls from the payload let (call_idx, calls): (u32, Vec) = deserialize(ix)?; if call_idx >= calls.len() as u32 { msg!("Error: call_idx >= calls.len()"); return Err(ContractError::Internal); } // Selects this contract call struct let self_ = &calls[call_idx as usize]; // Match on the first byte to select the function match ContractFunction::try_from(self_.data[0])? { // When the first byte is matched as `Set` ContractFunction::Set => { // Deserialize contract call, excluding the first byte let params: SetParamsV1 = deserialize(&self_.data[1..])?; // Initialize two vectors to store // a vector of public keys and // a vector of (zkas namespace, public inputs) let signature_pubkeys: Vec = vec![]; let mut zk_public_inputs: Vec<(String, Vec)> = vec![]; zk_public_inputs.push((MAP_CONTRACT_ZKAS_SET_NS.to_string(), params.to_vec())); // Encode the two vectors into one vector let mut metadata = vec![]; zk_public_inputs.encode(&mut metadata)?; signature_pubkeys.encode(&mut metadata)?; // Return data to the host using an import // // Under the hood: metadata is invoked here // https://github.com/darkrenaissance/darkfi/blob/35405831e366eaa74522ab14645a5a05ce5cfa1e/src/consensus/validator.rs#L1045C1-L1045C1 // // Under the hood: The metadata is returned here // https://github.com/darkrenaissance/darkfi/blob/35405831e366eaa74522ab14645a5a05ce5cfa1e/src/runtime/import/util.rs#L43 set_return_data(&metadata)?; Ok(()) } } } /// Taking call_idx and calls, `set_return_data` a state update to /// return to the host **to be applied in `process_update()`. fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult { let (call_idx, calls): (u32, Vec) = 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 => { let params: SetParamsV1 = deserialize(&calls[call_idx as usize].data[1..])?; // Calculating the slot // If the prover wants to set a top-level name, // i.e. in the canonical root name registry, // then slot = poseidon_hash(0, key) let slot = if params.car == pallas::Base::one() { poseidon_hash([pallas::Base::zero(), params.key]) // else slot = poseidon_hash(account, key). // That is, if you don't have the account's secret, // you cannot overwrite the names written by the account. } else { poseidon_hash([params.account, params.key]) }; // Check if this slot is locked. // Allow only setting unlocked slot. let db = db_lookup(cid, MAP_CONTRACT_ENTRIES_TREE)?; match db_get(db, &serialize(&slot))? { None => {} 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); // Prepare the return data for the host. let update = SetUpdateV1 { slot, lock: params.lock, value: params.value, }; let mut update_data = vec![]; update_data.write_u8(ContractFunction::Set as u8)?; let _ = update.encode(&mut update_data)?; // Setting the return data for the host. // Under the hood: https://github.com/darkrenaissance/darkfi/blob/35405831e366eaa74522ab14645a5a05ce5cfa1e/src/runtime/import/util.rs#L43 set_return_data(&update_data)?; Ok(()) } } } /// Taking the cid and the update data set in `process_instruction`, /// write to the relevant databases. /// In particular, set in db MAP_CONTRACT_ENTRIES_TREE: /// * slot = lock /// * slot + 1 = value 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..])?; // 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(()) } } }