ソースを参照

drk: Draft code for contract deployment.

Luther Blissett 3 年 前
コミット
9b0f68460d
6 ファイル変更196 行追加4 行削除
  1. 3 0
      Cargo.lock
  2. 4 1
      bin/drk/Cargo.toml
  3. 155 0
      bin/drk/src/deploy_contract.rs
  4. 18 0
      bin/drk/src/main.rs
  5. 13 0
      src/crypto/keypair.rs
  6. 3 3
      src/runtime/vm_runtime.rs

+ 3 - 0
Cargo.lock

@@ -1602,10 +1602,13 @@ name = "drk"
 version = "0.3.0"
 dependencies = [
  "async-std",
+ "bs58",
  "clap 3.2.22",
  "darkfi",
  "log",
+ "pasta_curves",
  "prettytable-rs",
+ "rand",
  "serde_json",
  "simplelog",
  "url",

+ 4 - 1
bin/drk/Cargo.toml

@@ -10,10 +10,13 @@ edition = "2021"
 
 [dependencies]
 async-std = {version = "1.12.0", features = ["attributes"]}
+bs58 = "0.4.0"
 clap = {version = "3.2.20", features = ["derive"]}
-darkfi = {path = "../../", features = ["crypto", "util", "rpc"]}
+darkfi = {path = "../../", features = ["crypto", "util", "rpc", "wasm-runtime", "zkas"]}
 log = "0.4.17"
+pasta_curves = "0.4.0"
 prettytable-rs = "0.9.0"
+rand = "0.8.5"
 serde_json = "1.0.85"
 simplelog = "0.12.0"
 url = "2.3.1"

+ 155 - 0
bin/drk/src/deploy_contract.rs

@@ -0,0 +1,155 @@
+use std::{
+    env::set_current_dir,
+    fs::{read, read_dir, read_to_string, File},
+    io::Write,
+    path::Path,
+    process::exit,
+    str::FromStr,
+};
+
+use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas};
+use rand::RngCore;
+
+use darkfi::{
+    crypto::{
+        keypair::{PublicKey, SecretKey},
+        util::poseidon_hash,
+    },
+    runtime::vm_runtime::{Runtime, ENTRYPOINT},
+    zkas::ZkBinary,
+    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 CONTRACT_FILE_NAME: &str = "contract.wasm";
+
+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");
+    let secret = SecretKey::random(&mut rng);
+    let mut file = File::create(path)?;
+    file.write_all(&bs58::encode(&secret.to_bytes()).into_string().as_bytes())?;
+    eprintln!("Written deploy key to {}", path.display());
+    Ok(())
+}
+
+/// 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);
+    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))
+}
+
+/// Deploys a given compiled smart contract on the network.
+/// TODO: Implement storage/tx fees in ZK, linear to the size of the binary.
+/// 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:
+///
+/// ```text
+/// smart-contract
+/// ├── Cargo.toml
+/// ├── deploy.key
+/// ├── Makefile
+/// ├── proof
+/// │   ├── circuit0.zk
+/// │   ├── circuit0.zk.bin
+/// │   ├── circuit1.zk
+/// │   └── circuit1.zk.bin
+/// ├── contract.wasm
+/// ├── src
+/// │   └── lib.rs
+/// └── tests
+/// ```
+pub fn deploy_contract(path: &Path) -> Result<ContractDeploy> {
+    // chdir into the contract directory
+    if let Err(e) = set_current_dir(path) {
+        eprintln!("Error changing directory to {}: {}", path.display(), e);
+        exit(1);
+    }
+
+    let deploy_key = match read_deploy_key(DEPLOY_KEY_NAME) {
+        Ok(v) => v,
+        Err(e) => {
+            eprintln!("Error: Failed to read {}: {}", DEPLOY_KEY_NAME, e);
+            exit(1);
+        }
+    };
+
+    // Validate compiled circuits. Looks for files ending with `.zk.bin`.
+    eprintln!("Validating compiled circuits in {}/", CIRCUIT_DIR_NAME);
+    let mut circuits = vec![];
+    let dir_iter = read_dir(CIRCUIT_DIR_NAME)?;
+    for i in dir_iter {
+        if let Err(e) = i {
+            eprintln!("Error iterating over directory: {}", e);
+            exit(1);
+        }
+
+        let f = i.unwrap();
+        let fname = f.file_name();
+        let fname = fname.to_str().unwrap();
+
+        if fname.ends_with(".zk.bin") {
+            // Validate that it can be decoded
+            eprintln!("Found {}", f.path().display());
+            let buf = read(f.path())?;
+            if let Err(e) = ZkBinary::decode(&buf) {
+                eprintln!("Error decoding zkas bincode in {}: {}", f.path().display(), e);
+                exit(1);
+            }
+
+            eprintln!("{} is a valid zkas circuit", f.path().display());
+            circuits.push(buf.clone());
+        }
+    }
+
+    // Validate wasm binary.
+    eprintln!("Reading wasm binary in {}", CONTRACT_FILE_NAME);
+    let wasm_bytes = read(CONTRACT_FILE_NAME)?;
+    eprintln!("Initializing mock wasm runtime to check validity");
+    let runtime = match Runtime::new(&wasm_bytes) {
+        Ok(v) => v,
+        Err(e) => {
+            eprintln!("Error: Failed to initialize wasm runtime: {}", e);
+            exit(1);
+        }
+    };
+
+    eprintln!("Looking for entrypoint function");
+    if let Err(e) = runtime.instance.exports.get_function(ENTRYPOINT) {
+        eprintln!("Error: Did not find entrypoint function in the wasm: {}", e);
+        exit(1);
+    }
+
+    let cd = ContractDeploy {
+        deploy_key: deploy_key.0,
+        public: deploy_key.1,
+        binary: wasm_bytes,
+        circuits,
+    };
+
+    Ok(cd)
+}

+ 18 - 0
bin/drk/src/main.rs

@@ -1,5 +1,6 @@
 use std::{
     io::{stdin, Read},
+    path::PathBuf,
     process::exit,
     str::FromStr,
     time::Instant,
@@ -24,6 +25,9 @@ use darkfi::{
     Result,
 };
 
+mod deploy_contract;
+use deploy_contract::deploy_contract;
+
 #[derive(Parser)]
 #[clap(name = "drk", about = cli_desc!(), version)]
 #[clap(arg_required_else_help(true))]
@@ -103,6 +107,12 @@ enum DrkSubcommand {
 
     /// Broadcast a given transaction from stdin
     Broadcast,
+
+    /// Smart contract operations
+    Contract {
+        /// Deploy
+        deploy: bool,
+    },
 }
 
 struct Drk {
@@ -291,6 +301,14 @@ async fn main() -> Result<()> {
             stdin().read_to_string(&mut buf)?;
             drk.tx_broadcast(buf).await
         }
+
+        DrkSubcommand::Contract { deploy } => {
+            // TODO
+            if deploy {
+                let data = deploy_contract(&PathBuf::from("."))?;
+            }
+            Ok(())
+        }
     }?;
 
     drk.close_connection().await

+ 13 - 0
src/crypto/keypair.rs

@@ -58,6 +58,19 @@ impl SecretKey {
     }
 }
 
+impl FromStr for SecretKey {
+    type Err = crate::Error;
+
+    /// Tries to create a `SecretKey` instance from a base58 encoded string.
+    fn from_str(encoded: &str) -> core::result::Result<Self, crate::Error> {
+        let decoded = bs58::decode(encoded).into_vec()?;
+        if decoded.len() != 32 {
+            return Err(Error::SecretKeyFromStr)
+        }
+        Self::from_bytes(decoded.try_into().unwrap())
+    }
+}
+
 #[derive(Copy, Clone, PartialEq, Eq, Debug, SerialDecodable, SerialEncodable)]
 pub struct PublicKey(pub pallas::Point);
 

+ 3 - 3
src/runtime/vm_runtime.rs

@@ -20,7 +20,7 @@ const WASM_MEM_ALLOC: &str = "__drkruntime_mem_alloc";
 /// Name of the wasm linear memory in our guest module
 const MEMORY: &str = "memory";
 /// Hardcoded entrypoint function of a contract
-const ENTRYPOINT: &str = "entrypoint";
+pub const ENTRYPOINT: &str = "entrypoint";
 /// Gas limit for a contract
 const GAS_LIMIT: u64 = 200000;
 
@@ -42,8 +42,8 @@ impl WasmerEnv for Env {
 }
 
 pub struct Runtime {
-    pub(crate) instance: Instance,
-    pub(crate) env: Env,
+    pub instance: Instance,
+    pub env: Env,
 }
 
 impl Runtime {