فهرست منبع

drk/deploy: Add ZK proof for deployment, and some cleanups and roadmap.

Luther Blissett 3 سال پیش
والد
کامیت
4888b25830
5فایلهای تغییر یافته به همراه160 افزوده شده و 109 حذف شده
  1. 1 0
      Cargo.lock
  2. 1 0
      bin/drk/Cargo.toml
  3. 85 81
      bin/drk/src/deploy_contract.rs
  4. 44 28
      bin/drk/src/main.rs
  5. 29 0
      proof/deploy_contract.zk

+ 1 - 0
Cargo.lock

@@ -1605,6 +1605,7 @@ dependencies = [
  "bs58",
  "bs58",
  "clap 3.2.22",
  "clap 3.2.22",
  "darkfi",
  "darkfi",
+ "indicatif",
  "log",
  "log",
  "pasta_curves",
  "pasta_curves",
  "prettytable-rs",
  "prettytable-rs",

+ 1 - 0
bin/drk/Cargo.toml

@@ -13,6 +13,7 @@ async-std = {version = "1.12.0", features = ["attributes"]}
 bs58 = "0.4.0"
 bs58 = "0.4.0"
 clap = {version = "3.2.20", features = ["derive"]}
 clap = {version = "3.2.20", features = ["derive"]}
 darkfi = {path = "../../", features = ["crypto", "util", "rpc", "wasm-runtime", "zkas"]}
 darkfi = {path = "../../", features = ["crypto", "util", "rpc", "wasm-runtime", "zkas"]}
+indicatif = "0.17.1"
 log = "0.4.17"
 log = "0.4.17"
 pasta_curves = "0.4.0"
 pasta_curves = "0.4.0"
 prettytable-rs = "0.9.0"
 prettytable-rs = "0.9.0"

+ 85 - 81
bin/drk/src/deploy_contract.rs

@@ -1,73 +1,48 @@
 use std::{
 use std::{
     env::set_current_dir,
     env::set_current_dir,
     fs::{read, read_dir, read_to_string, File},
     fs::{read, read_dir, read_to_string, File},
-    io::Write,
-    path::Path,
-    process::exit,
+    io::{ErrorKind, Write},
+    path::{Path, PathBuf},
     str::FromStr,
     str::FromStr,
 };
 };
 
 
-use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas};
-use rand::RngCore;
+use rand::{rngs::OsRng, RngCore};
 
 
 use darkfi::{
 use darkfi::{
-    crypto::{
-        keypair::{PublicKey, SecretKey},
-        util::poseidon_hash,
-    },
+    crypto::keypair::SecretKey,
     runtime::vm_runtime::{Runtime, ENTRYPOINT},
     runtime::vm_runtime::{Runtime, ENTRYPOINT},
+    util::cli::{fg_green, fg_red},
     zkas::ZkBinary,
     zkas::ZkBinary,
-    Result,
+    Error, Result,
 };
 };
 
 
-// TODO: Move some of this generic stuff into the library
-
-const DEPLOY_KEY_NAME: &str = "deploy.key";
 const CIRCUIT_DIR_NAME: &str = "proof";
 const CIRCUIT_DIR_NAME: &str = "proof";
 const CONTRACT_FILE_NAME: &str = "contract.wasm";
 const CONTRACT_FILE_NAME: &str = "contract.wasm";
+const DEPLOY_KEY_NAME: &str = "deploy.key";
 
 
-pub struct ContractDeploy {
-    /// Secret key used for deploy authorization
-    pub deploy_key: SecretKey,
-    /// Public address of the contract, derived from the deploy key
-    pub public: pallas::Base,
-    /// Compiled smart contract wasm binary to be executed in the wasm vm runtime
-    pub binary: Vec<u8>,
-    /// Compiled zkas circuits used by the smart contract provers and verifiers
-    pub circuits: Vec<Vec<u8>>,
-}
-
-/// Creates a new deploy key for deploying a private smart contract.
-/// This key allows to update the wasm code on the blockchain by creating
-/// a signature. When deployed, the contract can be accessed by requesting
-/// the public counterpart of this secret key.
-fn create_deploy_key(mut rng: impl RngCore, path: &Path) -> Result<()> {
-    eprintln!("Creating a deploy key");
+/// Creates a new deploy key used for deploying private smart contracts.
+/// This key allows to update the wasm code and the zk circuits on chain
+/// by creating a signature. When deployed, the contract can be accessed
+/// by requesting the public counterpart of this secret key.
+pub fn create_deploy_key(mut rng: impl RngCore, path: &Path) -> Result<SecretKey> {
     let secret = SecretKey::random(&mut rng);
     let secret = SecretKey::random(&mut rng);
     let mut file = File::create(path)?;
     let mut file = File::create(path)?;
     file.write_all(&bs58::encode(&secret.to_bytes()).into_string().as_bytes())?;
     file.write_all(&bs58::encode(&secret.to_bytes()).into_string().as_bytes())?;
-    eprintln!("Written deploy key to {}", path.display());
-    Ok(())
+    Ok(secret)
 }
 }
 
 
-/// Reads a deploy key from a file on the filesystem, and returns it,
-/// along with its public counterpart.
-/// TODO: Make a type for the public counterpart.
-fn read_deploy_key(s: &str) -> Result<(SecretKey, pallas::Base)> {
-    eprintln!("Reading deploy key from file: {}", s);
+/// Reads a deploy key from a file on the filesystem and returns it.
+fn read_deploy_key(s: &Path) -> core::result::Result<SecretKey, std::io::Error> {
+    eprintln!("Trying to read deploy key from file: {:?}", s);
     let contents = read_to_string(s)?;
     let contents = read_to_string(s)?;
-    let secret = SecretKey::from_str(&contents)?;
-    let coords = PublicKey::from_secret(secret).0.to_affine().coordinates().unwrap();
-    let public = poseidon_hash::<2>([*coords.x(), *coords.y()]);
-    Ok((secret, public))
+    let secret = SecretKey::from_str(&contents).unwrap();
+    Ok(secret)
 }
 }
 
 
-/// Deploys a given compiled smart contract on the network.
-/// TODO: Implement storage/tx fees in ZK, linear to the size of the binary.
+/// Creates necessary data to deploy a given smart contract on the network.
 /// For consistency, we point this function to a directory where our smart
 /// For consistency, we point this function to a directory where our smart
-/// contract and the compiled circuits are contained. This gives us a uniform
-/// approach to scm and gives a generic layout of a smart contract repository:
-///
+/// contract and the compiled circuits are contained. This is going to give
+/// us a uniform approach to scm and gives a generic layout of the source:
 /// ```text
 /// ```text
 /// smart-contract
 /// smart-contract
 /// ├── Cargo.toml
 /// ├── Cargo.toml
@@ -83,29 +58,46 @@ fn read_deploy_key(s: &str) -> Result<(SecretKey, pallas::Base)> {
 /// │   └── lib.rs
 /// │   └── lib.rs
 /// └── tests
 /// └── tests
 /// ```
 /// ```
-pub fn deploy_contract(path: &Path) -> Result<ContractDeploy> {
-    // chdir into the contract directory
+//pub fn create_deploy_data(path: &Path) -> Result<ContractDeploy> {
+pub fn create_deploy_data(path: &Path) -> Result<()> {
+    // Try to chdir into the contract directory
     if let Err(e) = set_current_dir(path) {
     if let Err(e) = set_current_dir(path) {
-        eprintln!("Error changing directory to {}: {}", path.display(), e);
-        exit(1);
+        eprintln!("Failed to chdir into {:?}", path);
+        return Err(e.into())
     }
     }
 
 
-    let deploy_key = match read_deploy_key(DEPLOY_KEY_NAME) {
-        Ok(v) => v,
+    let deploy_key: SecretKey;
+
+    let deploy_key = match read_deploy_key(&PathBuf::from(DEPLOY_KEY_NAME)) {
+        Ok(v) => deploy_key = v,
         Err(e) => {
         Err(e) => {
-            eprintln!("Error: Failed to read {}: {}", DEPLOY_KEY_NAME, e);
-            exit(1);
+            if e.kind() == ErrorKind::NotFound {
+                // We didn't find a deploy key, generate a new one.
+                eprintln!("Did not find an existing key, creating a new one.");
+                match create_deploy_key(&mut OsRng, &PathBuf::from(DEPLOY_KEY_NAME)) {
+                    Ok(v) => {
+                        eprintln!("Created new deploy key in \"{}\".", DEPLOY_KEY_NAME);
+                        deploy_key = v;
+                    }
+                    Err(e) => {
+                        eprintln!("Failed to create new deploy key");
+                        return Err(e)
+                    }
+                }
+            }
+            eprintln!("Failed to read deploy key");
+            return Err(e.into())
         }
         }
     };
     };
 
 
-    // Validate compiled circuits. Looks for files ending with `.zk.bin`.
-    eprintln!("Validating compiled circuits in {}/", CIRCUIT_DIR_NAME);
+    // Search for ZK circuits in the directory. If none are found, we'll bail.
+    // The logic searches for `.zk.bin` files created by zkas.
+    eprintln!("Searching for compiled ZK circuits in \"{}\" ...", CIRCUIT_DIR_NAME);
     let mut circuits = vec![];
     let mut circuits = vec![];
-    let dir_iter = read_dir(CIRCUIT_DIR_NAME)?;
-    for i in dir_iter {
+    for i in read_dir(CIRCUIT_DIR_NAME)? {
         if let Err(e) = i {
         if let Err(e) = i {
-            eprintln!("Error iterating over directory: {}", e);
-            exit(1);
+            eprintln!("Error iterating over \"{}\" directory", CIRCUIT_DIR_NAME);
+            return Err(e.into())
         }
         }
 
 
         let f = i.unwrap();
         let f = i.unwrap();
@@ -113,43 +105,55 @@ pub fn deploy_contract(path: &Path) -> Result<ContractDeploy> {
         let fname = fname.to_str().unwrap();
         let fname = fname.to_str().unwrap();
 
 
         if fname.ends_with(".zk.bin") {
         if fname.ends_with(".zk.bin") {
-            // Validate that it can be decoded
-            eprintln!("Found {}", f.path().display());
+            // Validate that the files can be properly decoded
+            eprintln!("{} {}", fg_green("Found:"), f.path().display());
             let buf = read(f.path())?;
             let buf = read(f.path())?;
             if let Err(e) = ZkBinary::decode(&buf) {
             if let Err(e) = ZkBinary::decode(&buf) {
-                eprintln!("Error decoding zkas bincode in {}: {}", f.path().display(), e);
-                exit(1);
+                eprintln!("{} Failed to decode zkas bincode in {:?}", fg_red("Error:"), f.path());
+                return Err(e)
             }
             }
 
 
-            eprintln!("{} is a valid zkas circuit", f.path().display());
             circuits.push(buf.clone());
             circuits.push(buf.clone());
         }
         }
     }
     }
 
 
-    // Validate wasm binary.
-    eprintln!("Reading wasm binary in {}", CONTRACT_FILE_NAME);
+    if circuits.is_empty() {
+        return Err(Error::Custom("Found no valid ZK circuits".to_string()))
+    }
+
+    // Validate wasm binary. We inspect the bincode and try to load it into
+    // the wasm runtime. If loaded, we then look for the `ENTRYPOINT` function
+    // which we hardcode into our sdk and runtime and is the canonical way to
+    // run wasm binaries on chain.
+    eprintln!("Inspecting wasm binary in \"{}\"", CONTRACT_FILE_NAME);
     let wasm_bytes = read(CONTRACT_FILE_NAME)?;
     let wasm_bytes = read(CONTRACT_FILE_NAME)?;
-    eprintln!("Initializing mock wasm runtime to check validity");
+    eprintln!("Initializing moch wasm runtime to check validity");
     let runtime = match Runtime::new(&wasm_bytes) {
     let runtime = match Runtime::new(&wasm_bytes) {
-        Ok(v) => v,
+        Ok(v) => {
+            eprintln!("Found {} wasm binary", fg_green("valid"));
+            v
+        }
         Err(e) => {
         Err(e) => {
-            eprintln!("Error: Failed to initialize wasm runtime: {}", e);
-            exit(1);
+            eprintln!("Failed to initialize wasm runtime");
+            return Err(e)
         }
         }
     };
     };
 
 
-    eprintln!("Looking for entrypoint function");
+    eprintln!("Looking for entrypoint function inside the wasm");
     if let Err(e) = runtime.instance.exports.get_function(ENTRYPOINT) {
     if let Err(e) = runtime.instance.exports.get_function(ENTRYPOINT) {
-        eprintln!("Error: Did not find entrypoint function in the wasm: {}", e);
-        exit(1);
+        eprintln!("{} Could not find entrypoint function", fg_red("Error:"));
+        return Err(e.into())
     }
     }
 
 
-    let cd = ContractDeploy {
-        deploy_key: deploy_key.0,
-        public: deploy_key.1,
-        binary: wasm_bytes,
-        circuits,
-    };
+    // TODO: Create a ZK proof enforcing the deploy key relations with their public
+    // counterparts (public key and contract address)
+    let mut total_bytes = 0;
+    total_bytes += wasm_bytes.len();
+    for circuit in circuits {
+        total_bytes += circuit.len();
+    }
 
 
-    Ok(cd)
+    // TODO: Return the data back to the main function, and work further in creating
+    // a transaction and broadcasting it.
+    Ok(())
 }
 }

+ 44 - 28
bin/drk/src/main.rs

@@ -8,7 +8,6 @@ use std::{
 
 
 use clap::{Parser, Subcommand};
 use clap::{Parser, Subcommand};
 use prettytable::{format, row, Table};
 use prettytable::{format, row, Table};
-
 use serde_json::json;
 use serde_json::json;
 use simplelog::{ColorChoice, TermLogger, TerminalMode};
 use simplelog::{ColorChoice, TermLogger, TerminalMode};
 use url::Url;
 use url::Url;
@@ -18,7 +17,7 @@ use darkfi::{
     crypto::{address::Address, token_id},
     crypto::{address::Address, token_id},
     rpc::{client::RpcClient, jsonrpc::JsonRequest},
     rpc::{client::RpcClient, jsonrpc::JsonRequest},
     util::{
     util::{
-        cli::{get_log_config, get_log_level, progress_bar},
+        cli::{fg_red, get_log_config, get_log_level, progress_bar},
         net_name::NetworkName,
         net_name::NetworkName,
         parse::encode_base10,
         parse::encode_base10,
     },
     },
@@ -26,7 +25,7 @@ use darkfi::{
 };
 };
 
 
 mod deploy_contract;
 mod deploy_contract;
-use deploy_contract::deploy_contract;
+use deploy_contract::create_deploy_data;
 
 
 #[derive(Parser)]
 #[derive(Parser)]
 #[clap(name = "drk", about = cli_desc!(), version)]
 #[clap(name = "drk", about = cli_desc!(), version)]
@@ -41,11 +40,11 @@ struct Args {
     endpoint: Url,
     endpoint: Url,
 
 
     #[clap(subcommand)]
     #[clap(subcommand)]
-    command: DrkSubcommand,
+    command: Subcmd,
 }
 }
 
 
 #[derive(Subcommand)]
 #[derive(Subcommand)]
-enum DrkSubcommand {
+enum Subcmd {
     /// Send a ping request to the RPC
     /// Send a ping request to the RPC
     Ping,
     Ping,
 
 
@@ -108,10 +107,10 @@ enum DrkSubcommand {
     /// Broadcast a given transaction from stdin
     /// Broadcast a given transaction from stdin
     Broadcast,
     Broadcast,
 
 
-    /// Smart contract operations
-    Contract {
-        /// Deploy
-        deploy: bool,
+    /// Deploy a smart contract in the current directory or a given path.
+    DeployContract {
+        #[clap(long, default_value = ".")]
+        path: PathBuf,
     },
     },
 }
 }
 
 
@@ -261,17 +260,24 @@ async fn main() -> Result<()> {
     let log_config = get_log_config();
     let log_config = get_log_config();
     TermLogger::init(log_level, log_config, TerminalMode::Mixed, ColorChoice::Auto)?;
     TermLogger::init(log_level, log_config, TerminalMode::Mixed, ColorChoice::Auto)?;
 
 
-    let rpc_client = RpcClient::new(args.endpoint).await?;
-    let drk = Drk { rpc_client };
-
     match args.command {
     match args.command {
-        DrkSubcommand::Ping => drk.ping().await,
+        Subcmd::Ping => {
+            let rpc_client = RpcClient::new(args.endpoint).await?;
+            let drk = Drk { rpc_client };
+            return drk.ping().await
+        }
 
 
-        DrkSubcommand::Airdrop { address, faucet_endpoint, amount, token_id } => {
-            drk.airdrop(address, faucet_endpoint, amount, token_id).await
+        Subcmd::Airdrop { address, faucet_endpoint, amount, token_id } => {
+            let rpc_client = RpcClient::new(args.endpoint).await?;
+            let drk = Drk { rpc_client };
+
+            return drk.airdrop(address, faucet_endpoint, amount, token_id).await
         }
         }
 
 
-        DrkSubcommand::Wallet { keygen, balance, address, all_addresses } => {
+        Subcmd::Wallet { keygen, balance, address, all_addresses } => {
+            let rpc_client = RpcClient::new(args.endpoint).await?;
+            let drk = Drk { rpc_client };
+
             if keygen {
             if keygen {
                 return drk.wallet_keygen().await
                 return drk.wallet_keygen().await
             }
             }
@@ -292,24 +298,34 @@ async fn main() -> Result<()> {
             exit(2);
             exit(2);
         }
         }
 
 
-        DrkSubcommand::Transfer { recipient, amount, network, token_id } => {
-            drk.tx_transfer(network, token_id, recipient, amount).await
+        Subcmd::Transfer { recipient, amount, network, token_id } => {
+            let rpc_client = RpcClient::new(args.endpoint).await?;
+            let drk = Drk { rpc_client };
+
+            return drk.tx_transfer(network, token_id, recipient, amount).await
         }
         }
 
 
-        DrkSubcommand::Broadcast => {
+        Subcmd::Broadcast => {
+            let rpc_client = RpcClient::new(args.endpoint).await?;
+            let drk = Drk { rpc_client };
+
             let mut buf = String::new();
             let mut buf = String::new();
             stdin().read_to_string(&mut buf)?;
             stdin().read_to_string(&mut buf)?;
-            drk.tx_broadcast(buf).await
+
+            return drk.tx_broadcast(buf).await
         }
         }
 
 
-        DrkSubcommand::Contract { deploy } => {
-            // TODO
-            if deploy {
-                let data = deploy_contract(&PathBuf::from("."))?;
-            }
+        Subcmd::DeployContract { path } => {
+            eprintln!("Trying to deploy the smart contract in {:?}", path);
+            let deploy_data = match create_deploy_data(&path) {
+                Ok(v) => v,
+                Err(e) => {
+                    eprintln!("{}: Failed to deploy smart contract: {}", fg_red("Error:"), e);
+                    exit(1);
+                }
+            };
+
             Ok(())
             Ok(())
         }
         }
-    }?;
-
-    drk.close_connection().await
+    }
 }
 }

+ 29 - 0
proof/deploy_contract.zk

@@ -0,0 +1,29 @@
+constant "DeployContract" {
+	EcFixedPointBase NULLIFIER_K,
+}
+
+contract "DeployContract" {
+	# Amount of bytes to store on-chain
+	Base bytes,
+	# Deploy key used for signing and contract reference
+	Base deploy_key,
+}
+
+circuit "DeployContract" {
+	# Derive a public key used for the signature and constrain
+	# its coordinates:
+	signature_public = ec_mul_base(deploy_key, NULLIFIER_K);
+	signature_x = ec_get_x(signature_public);
+	signature_y = ec_get_y(signature_public);
+	constrain_instance(signature_x);
+	constrain_instance(signature_y);
+
+	# Derive the contract address from the public key's coordinates
+	address = poseidon_hash(signature_x, signature_y)
+	constrain_instance(address);
+
+	# Constrain the byte size of the deployed binaries
+	constrain_instance(bytes);
+
+	# TODO: Fee cost for storing this data on-chain
+}