|
|
@@ -1,16 +1,40 @@
|
|
|
-use std::any::TypeId;
|
|
|
+use std::{
|
|
|
+ any::{Any, TypeId},
|
|
|
+ io,
|
|
|
+};
|
|
|
|
|
|
use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
|
|
|
use log::{debug, error};
|
|
|
|
|
|
+use pasta_curves::group::Group;
|
|
|
+
|
|
|
use darkfi::{
|
|
|
- crypto::{coin::Coin, merkle_node::MerkleNode, note::EncryptedNote, nullifier::Nullifier},
|
|
|
+ crypto::{
|
|
|
+ burn_proof::verify_burn_proof,
|
|
|
+ coin::Coin,
|
|
|
+ keypair::PublicKey,
|
|
|
+ merkle_node::MerkleNode,
|
|
|
+ mint_proof::verify_mint_proof,
|
|
|
+ note::EncryptedNote,
|
|
|
+ nullifier::Nullifier,
|
|
|
+ proof::VerifyingKey,
|
|
|
+ schnorr,
|
|
|
+ schnorr::SchnorrPublic,
|
|
|
+ types::{DrkCircuitField, DrkTokenId, DrkValueBlind, DrkValueCommit},
|
|
|
+ util::{pedersen_commitment_base, pedersen_commitment_u64},
|
|
|
+ BurnRevealedValues, MintRevealedValues, Proof,
|
|
|
+ },
|
|
|
node::state::ProgramState,
|
|
|
+ util::serial::{Encodable, SerialDecodable, SerialEncodable, VarInt},
|
|
|
+ Error as DarkFiError,
|
|
|
};
|
|
|
|
|
|
use crate::{
|
|
|
- demo::{StateRegistry, Transaction},
|
|
|
- money_contract::{state::State, transfer::CallData},
|
|
|
+ demo::{CallDataBase, StateRegistry, Transaction},
|
|
|
+ money_contract::{
|
|
|
+ state::State,
|
|
|
+ transfer::partial::{PartialClearInput, PartialInput},
|
|
|
+ },
|
|
|
};
|
|
|
|
|
|
/// A struct representing a state update.
|
|
|
@@ -126,10 +150,253 @@ pub fn state_transition(
|
|
|
Ok(Update { nullifiers, coins, enc_notes })
|
|
|
}
|
|
|
|
|
|
+/// A DarkFi transaction
|
|
|
+#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
|
|
+pub struct CallData {
|
|
|
+ /// Clear inputs
|
|
|
+ pub clear_inputs: Vec<ClearInput>,
|
|
|
+ /// Anonymous inputs
|
|
|
+ pub inputs: Vec<Input>,
|
|
|
+ /// Anonymous outputs
|
|
|
+ pub outputs: Vec<Output>,
|
|
|
+}
|
|
|
+
|
|
|
+impl CallDataBase for CallData {
|
|
|
+ fn zk_public_values(&self) -> Vec<Vec<DrkCircuitField>> {
|
|
|
+ let mut public_values = Vec::new();
|
|
|
+ for input in &self.inputs {
|
|
|
+ public_values.push(input.revealed.make_outputs());
|
|
|
+ }
|
|
|
+ for output in &self.outputs {
|
|
|
+ public_values.push(output.revealed.make_outputs());
|
|
|
+ }
|
|
|
+ public_values
|
|
|
+ }
|
|
|
+
|
|
|
+ fn zk_proof_addrs(&self) -> Vec<String> {
|
|
|
+ let mut result = Vec::new();
|
|
|
+ for _ in &self.inputs {
|
|
|
+ result.push("money-transfer-burn".to_string());
|
|
|
+ }
|
|
|
+ for _ in &self.outputs {
|
|
|
+ result.push("money-transfer-mint".to_string());
|
|
|
+ }
|
|
|
+ result
|
|
|
+ }
|
|
|
+
|
|
|
+ fn as_any(&self) -> &dyn Any {
|
|
|
+ self
|
|
|
+ }
|
|
|
+}
|
|
|
+impl CallData {
|
|
|
+ /// Verify the transaction
|
|
|
+ pub fn verify(&self, mint_vk: &VerifyingKey, burn_vk: &VerifyingKey) -> VerifyResult<()> {
|
|
|
+ // must have minimum 1 clear or anon input, and 1 output
|
|
|
+ if self.clear_inputs.len() + self.inputs.len() == 0 {
|
|
|
+ error!("tx::verify(): Missing inputs");
|
|
|
+ return Err(VerifyFailed::LackingInputs)
|
|
|
+ }
|
|
|
+ if self.outputs.len() == 0 {
|
|
|
+ error!("tx::verify(): Missing outputs");
|
|
|
+ return Err(VerifyFailed::LackingOutputs)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Accumulator for the value commitments
|
|
|
+ let mut valcom_total = DrkValueCommit::identity();
|
|
|
+
|
|
|
+ // Add values from the clear inputs
|
|
|
+ for input in &self.clear_inputs {
|
|
|
+ valcom_total += pedersen_commitment_u64(input.value, input.value_blind);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add values from the inputs
|
|
|
+ for (i, input) in self.inputs.iter().enumerate() {
|
|
|
+ match verify_burn_proof(burn_vk, &input.burn_proof, &input.revealed) {
|
|
|
+ Ok(()) => valcom_total += &input.revealed.value_commit,
|
|
|
+ Err(e) => {
|
|
|
+ error!("tx::verify(): Failed to verify burn proof {}: {}", i, e);
|
|
|
+ return Err(VerifyFailed::BurnProof(i))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Subtract values from the outputs
|
|
|
+ for (i, output) in self.outputs.iter().enumerate() {
|
|
|
+ match verify_mint_proof(mint_vk, &output.mint_proof, &output.revealed) {
|
|
|
+ Ok(()) => valcom_total -= &output.revealed.value_commit,
|
|
|
+ Err(e) => {
|
|
|
+ error!("tx::verify(): Failed to verify mint proof {}: {}", i, e);
|
|
|
+ return Err(VerifyFailed::MintProof(i))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // If the accumulator is not back in its initial state,
|
|
|
+ // there's a value mismatch.
|
|
|
+ if valcom_total != DrkValueCommit::identity() {
|
|
|
+ error!("tx::verify(): Missing funds");
|
|
|
+ return Err(VerifyFailed::MissingFunds)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify that the token commitments match
|
|
|
+ if !self.verify_token_commitments() {
|
|
|
+ error!("tx::verify(): Token ID mismatch");
|
|
|
+ return Err(VerifyFailed::TokenMismatch)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify the available signatures
|
|
|
+ let mut unsigned_tx_data = vec![];
|
|
|
+ self.encode_without_signature(&mut unsigned_tx_data)?;
|
|
|
+
|
|
|
+ for (i, input) in self.clear_inputs.iter().enumerate() {
|
|
|
+ let public = &input.signature_public;
|
|
|
+ if !public.verify(&unsigned_tx_data[..], &input.signature) {
|
|
|
+ error!("tx::verify(): Failed to verify Clear Input signature {}", i);
|
|
|
+ return Err(VerifyFailed::ClearInputSignature(i))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i, input) in self.inputs.iter().enumerate() {
|
|
|
+ let public = &input.revealed.signature_public;
|
|
|
+ if !public.verify(&unsigned_tx_data[..], &input.signature) {
|
|
|
+ error!("tx::verify(): Failed to verify Input signature {}", i);
|
|
|
+ return Err(VerifyFailed::InputSignature(i))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn encode_without_signature<S: io::Write>(&self, mut s: S) -> Result<usize> {
|
|
|
+ let mut len = 0;
|
|
|
+ len += self.clear_inputs.encode_without_signature(&mut s)?;
|
|
|
+ len += self.inputs.encode_without_signature(&mut s)?;
|
|
|
+ len += self.outputs.encode(s)?;
|
|
|
+ Ok(len)
|
|
|
+ }
|
|
|
+
|
|
|
+ fn verify_token_commitments(&self) -> bool {
|
|
|
+ assert_ne!(self.outputs.len(), 0);
|
|
|
+ let token_commit_value = self.outputs[0].revealed.token_commit;
|
|
|
+
|
|
|
+ let mut failed =
|
|
|
+ self.inputs.iter().any(|input| input.revealed.token_commit != token_commit_value);
|
|
|
+
|
|
|
+ failed = failed ||
|
|
|
+ self.outputs.iter().any(|output| output.revealed.token_commit != token_commit_value);
|
|
|
+
|
|
|
+ failed = failed ||
|
|
|
+ self.clear_inputs.iter().any(|input| {
|
|
|
+ pedersen_commitment_base(input.token_id, input.token_blind) != token_commit_value
|
|
|
+ });
|
|
|
+ !failed
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// A transaction's clear input
|
|
|
+#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
|
|
+pub struct ClearInput {
|
|
|
+ /// Input's value (amount)
|
|
|
+ pub value: u64,
|
|
|
+ /// Input's token ID
|
|
|
+ pub token_id: DrkTokenId,
|
|
|
+ /// Blinding factor for `value`
|
|
|
+ pub value_blind: DrkValueBlind,
|
|
|
+ /// Blinding factor for `token_id`
|
|
|
+ pub token_blind: DrkValueBlind,
|
|
|
+ /// Public key for the signature
|
|
|
+ pub signature_public: PublicKey,
|
|
|
+ /// signature
|
|
|
+ pub signature: schnorr::Signature,
|
|
|
+}
|
|
|
+
|
|
|
+/// A transaction's anonymous input
|
|
|
+#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
|
|
+pub struct Input {
|
|
|
+ /// Zero-knowledge proof for the input
|
|
|
+ pub burn_proof: Proof,
|
|
|
+ /// Public inputs for the zero-knowledge proof
|
|
|
+ pub revealed: BurnRevealedValues,
|
|
|
+ /// Input's signature
|
|
|
+ pub signature: schnorr::Signature,
|
|
|
+}
|
|
|
+
|
|
|
+/// A transaction's anonymous output
|
|
|
+#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
|
|
+pub struct Output {
|
|
|
+ /// Zero-knowledge proof for the output
|
|
|
+ pub mint_proof: Proof,
|
|
|
+ /// Public inputs for the zero-knowledge proof
|
|
|
+ pub revealed: MintRevealedValues,
|
|
|
+ /// The encrypted note
|
|
|
+ pub enc_note: EncryptedNote,
|
|
|
+}
|
|
|
+
|
|
|
+impl ClearInput {
|
|
|
+ pub fn from_partial(partial: PartialClearInput, signature: schnorr::Signature) -> Self {
|
|
|
+ Self {
|
|
|
+ value: partial.value,
|
|
|
+ token_id: partial.token_id,
|
|
|
+ value_blind: partial.value_blind,
|
|
|
+ token_blind: partial.token_blind,
|
|
|
+ signature_public: partial.signature_public,
|
|
|
+ signature,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn encode_without_signature<S: 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)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Input {
|
|
|
+ pub fn from_partial(partial: PartialInput, signature: schnorr::Signature) -> Self {
|
|
|
+ Self { burn_proof: partial.burn_proof, revealed: partial.revealed, signature }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn encode_without_signature<S: io::Write>(&self, mut s: S) -> Result<usize> {
|
|
|
+ let mut len = 0;
|
|
|
+ len += self.burn_proof.encode(&mut s)?;
|
|
|
+ len += self.revealed.encode(&mut s)?;
|
|
|
+ Ok(len)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+trait EncodableWithoutSignature {
|
|
|
+ fn encode_without_signature<S: 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: 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!(ClearInput);
|
|
|
+impl_vec_without_signature!(Input);
|
|
|
+
|
|
|
#[derive(Debug, Clone, thiserror::Error)]
|
|
|
pub enum Error {
|
|
|
#[error(transparent)]
|
|
|
VerifyFailed(#[from] VerifyFailed),
|
|
|
+
|
|
|
+ #[error("DarkFi error: {0}")]
|
|
|
+ DarkFiError(String),
|
|
|
}
|
|
|
|
|
|
/// Transaction verification errors
|
|
|
@@ -182,3 +449,11 @@ impl From<Error> for VerifyFailed {
|
|
|
Self::InternalError(err.to_string())
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+impl From<DarkFiError> for Error {
|
|
|
+ fn from(err: DarkFiError) -> Self {
|
|
|
+ Self::DarkFiError(err.to_string())
|
|
|
+ }
|
|
|
+}
|
|
|
+/// Result type used in transaction verifications
|
|
|
+pub type VerifyResult<T> = std::result::Result<T, VerifyFailed>;
|