Explorar el Código

implement signature checking for proposal inputs

narodnik hace 4 años
padre
commit
a3e73babe8

+ 94 - 1
bin/daod/src/dao_contract/propose/validate.rs

@@ -2,7 +2,14 @@ use crate::{
     dao_contract::{DaoBulla, State},
     demo::{CallDataBase, StateRegistry, Transaction},
 };
-use darkfi::crypto::{merkle_node::MerkleNode, types::DrkCircuitField};
+use darkfi::{
+    crypto::{
+        keypair::PublicKey, merkle_node::MerkleNode, schnorr, schnorr::SchnorrPublic,
+        types::DrkCircuitField, Proof,
+    },
+    util::serial::{Encodable, SerialDecodable, SerialEncodable, VarInt},
+    Error as DarkFiError,
+};
 use log::{debug, error};
 use pasta_curves::{
     arithmetic::CurveAffine,
@@ -11,20 +18,53 @@ use pasta_curves::{
 };
 use std::any::{Any, TypeId};
 
+use super::wallet::{Partial, PartialInput};
+
 const TARGET: &str = "dao_contract::propose::validate::state_transition()";
 
 #[derive(Debug, Clone, thiserror::Error)]
 pub enum Error {
     #[error("Invalid DAO merkle root")]
     InvalidDaoMerkleRoot,
+
+    #[error("Signature verification failed")]
+    SignatureVerifyFailed,
+
+    #[error("DarkFi error: {0}")]
+    DarkFiError(String),
 }
 type Result<T> = std::result::Result<T, Error>;
 
+impl From<DarkFiError> for Error {
+    fn from(err: DarkFiError) -> Self {
+        Self::DarkFiError(err.to_string())
+    }
+}
+
 pub struct CallData {
     pub dao_merkle_root: MerkleNode,
     pub token_commit: pallas::Base,
     // TODO: compute from sum of input commits
     pub total_funds_commit: pallas::Point,
+
+    pub inputs: Vec<Input>,
+}
+
+impl CallData {
+    fn encode_without_signature<S: std::io::Write>(
+        &self,
+        mut s: S,
+        proofs: &Vec<Proof>,
+    ) -> Result<usize> {
+        let mut len = 0;
+        len += self.dao_merkle_root.encode(&mut s)?;
+        len += self.token_commit.encode(&mut s)?;
+        //len += self.value_blind.encode(&mut s)?;
+        //len += self.token_blind.encode(&mut s)?;
+        len += self.inputs.encode_without_signature(&mut s)?;
+        len += proofs.encode(s)?;
+        Ok(len)
+    }
 }
 
 impl CallDataBase for CallData {
@@ -44,6 +84,48 @@ impl CallDataBase for CallData {
     }
 }
 
+pub struct Input {
+    pub signature_public: PublicKey,
+    pub signature: schnorr::Signature,
+}
+
+impl Input {
+    pub fn from_partial(partial: PartialInput, signature: schnorr::Signature) -> Self {
+        Self { signature_public: partial.signature_public, signature }
+    }
+
+    fn encode_without_signature<S: std::io::Write>(&self, mut s: S) -> Result<usize> {
+        let mut len = 0;
+        //len += self.value.encode(&mut s)?;
+        //len += self.token_id.encode(&mut s)?;
+        //len += self.value_blind.encode(&mut s)?;
+        //len += self.token_blind.encode(&mut s)?;
+        len += self.signature_public.encode(s)?;
+        Ok(len)
+    }
+}
+
+trait EncodableWithoutSignature {
+    fn encode_without_signature<S: std::io::Write>(&self, s: S) -> Result<usize>;
+}
+
+macro_rules! impl_vec_without_signature {
+    ($type: ty) => {
+        impl EncodableWithoutSignature for Vec<$type> {
+            #[inline]
+            fn encode_without_signature<S: std::io::Write>(&self, mut s: S) -> Result<usize> {
+                let mut len = 0;
+                len += VarInt(self.len() as u64).encode(&mut s)?;
+                for c in self.iter() {
+                    len += c.encode_without_signature(&mut s)?;
+                }
+                Ok(len)
+            }
+        }
+    };
+}
+impl_vec_without_signature!(Input);
+
 pub fn state_transition(
     states: &StateRegistry,
     func_call_index: usize,
@@ -65,6 +147,17 @@ pub fn state_transition(
         return Err(Error::InvalidDaoMerkleRoot)
     }
 
+    // Verify the available signatures
+    let mut unsigned_tx_data = vec![];
+    call_data.encode_without_signature(&mut unsigned_tx_data, &func_call.proofs)?;
+
+    for (i, input) in call_data.inputs.iter().enumerate() {
+        let public = &input.signature_public;
+        if !public.verify(&unsigned_tx_data[..], &input.signature) {
+            return Err(Error::SignatureVerifyFailed)
+        }
+    }
+
     Ok(Update {})
 }
 

+ 102 - 9
bin/daod/src/dao_contract/propose/wallet.rs

@@ -1,7 +1,7 @@
 use halo2_proofs::circuit::Value;
 use pasta_curves::{
     arithmetic::CurveAffine,
-    group::{ff::Field, Curve},
+    group::{ff::Field, Curve, Group},
     pallas,
 };
 use rand::rngs::OsRng;
@@ -26,13 +26,13 @@ use darkfi::{
 };
 
 use crate::{
-    dao_contract::propose::validate::CallData,
+    dao_contract::propose::validate::{CallData, Input},
     demo::{CallDataBase, FuncCall, StateRegistry, ZkContractInfo, ZkContractTable},
     money_contract,
     util::poseidon_hash,
 };
 
-pub struct Input {
+pub struct BuilderInput {
     pub secret: SecretKey,
     pub note: money_contract::transfer::wallet::Note,
     pub leaf_position: incrementalmerkletree::Position,
@@ -57,7 +57,7 @@ pub struct DaoParams {
 }
 
 pub struct Builder {
-    pub inputs: Vec<Input>,
+    pub inputs: Vec<BuilderInput>,
     pub proposal: Proposal,
     pub dao: DaoParams,
     pub dao_leaf_position: incrementalmerkletree::Position,
@@ -67,8 +67,56 @@ pub struct Builder {
 
 impl Builder {
     pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
-        let total_funds = 110;
-        let total_funds_blind = pallas::Scalar::random(&mut OsRng);
+        let mut inputs = vec![];
+        let mut total_funds = 0;
+        let mut input_funds_blinds = vec![];
+        let mut signature_secrets = vec![];
+        for input in self.inputs {
+            let funds_blind = pallas::Scalar::random(&mut OsRng);
+            input_funds_blinds.push(funds_blind);
+
+            let signature_secret = SecretKey::random(&mut OsRng);
+
+            //let zk_info = zk_bins.lookup(&"money-transfer-burn".to_string()).unwrap();
+            //let zk_info = if let ZkContractInfo::Native(info) = zk_info {
+            //    info
+            //} else {
+            //    panic!("Not native info")
+            //};
+            //let burn_pk = &zk_info.proving_key;
+
+            //// Note from the previous output
+            //let note = input.note;
+
+            //let (burn_proof, revealed) = create_burn_proof(
+            //    burn_pk,
+            //    note.value,
+            //    note.token_id,
+            //    value_blind,
+            //    token_blind,
+            //    note.serial,
+            //    note.spend_hook,
+            //    note.user_data,
+            //    input.user_data_blind,
+            //    note.coin_blind,
+            //    input.secret,
+            //    input.leaf_position,
+            //    input.merkle_path,
+            //    signature_secret,
+            //)?;
+            //proofs.push(burn_proof);
+
+            // First we make the tx then sign after
+            signature_secrets.push(signature_secret);
+
+            let input = PartialInput { signature_public: PublicKey::from_secret(signature_secret) };
+            inputs.push(input);
+        }
+
+        let mut total_funds_blind = pallas::Scalar::from(0);
+        for blind in &input_funds_blinds {
+            total_funds_blind += blind;
+        }
         let total_funds_commit = pedersen_commitment_u64(total_funds, total_funds_blind);
         let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap();
         let total_funds_x = *total_funds_coords.x();
@@ -145,14 +193,59 @@ impl Builder {
         let main_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
             .expect("DAO::propose() proving error!");
 
-        let call_data =
-            CallData { dao_merkle_root: self.dao_merkle_root, token_commit, total_funds_commit };
+        // Create the input signatures
+        let proofs = vec![main_proof];
+
+        let partial = Partial {
+            dao_merkle_root: self.dao_merkle_root,
+            token_commit,
+            total_funds_commit,
+            inputs,
+            proofs,
+        };
+
+        let mut unsigned_tx_data = vec![];
+        partial.encode(&mut unsigned_tx_data).expect("failed to encode data");
+
+        let mut inputs = vec![];
+        for (input, signature_secret) in
+            partial.inputs.into_iter().zip(signature_secrets.into_iter())
+        {
+            let signature = signature_secret.sign(&unsigned_tx_data[..]);
+            let input = Input::from_partial(input, signature);
+            inputs.push(input);
+        }
+
+        let call_data = CallData {
+            dao_merkle_root: self.dao_merkle_root,
+            token_commit,
+            total_funds_commit,
+            inputs: vec![],
+        };
 
         FuncCall {
             contract_id: "DAO".to_string(),
             func_id: "DAO::propose()".to_string(),
             call_data: Box::new(call_data),
-            proofs: vec![main_proof],
+            proofs: partial.proofs,
         }
     }
 }
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct Partial {
+    pub dao_merkle_root: MerkleNode,
+    pub token_commit: pallas::Base,
+    // TODO: compute from sum of input commits
+    pub total_funds_commit: pallas::Point,
+
+    pub inputs: Vec<PartialInput>,
+
+    pub proofs: Vec<Proof>,
+}
+
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct PartialInput {
+    /// Public key for the signature
+    pub signature_public: PublicKey,
+}

+ 1 - 1
bin/daod/src/demo.rs

@@ -577,7 +577,7 @@ pub async fn demo() -> Result<()> {
 
     // TODO: is it possible for an invalid transfer() to be constructed on exec()?
     //       need to look into this
-    let input = dao_contract::propose::wallet::Input {
+    let input = dao_contract::propose::wallet::BuilderInput {
         secret: gov_keypair_1.secret,
         note: gov_recv[0].note.clone(),
         leaf_position: money_leaf_position,