|
@@ -0,0 +1,243 @@
|
|
|
|
|
+use std::io;
|
|
|
|
|
+
|
|
|
|
|
+use log::error;
|
|
|
|
|
+use pasta_curves::group::Group;
|
|
|
|
|
+
|
|
|
|
|
+use darkfi::{
|
|
|
|
|
+ crypto::{
|
|
|
|
|
+ burn_proof::verify_burn_proof,
|
|
|
|
|
+ keypair::PublicKey,
|
|
|
|
|
+ mint_proof::verify_mint_proof,
|
|
|
|
|
+ note::EncryptedNote,
|
|
|
|
|
+ proof::VerifyingKey,
|
|
|
|
|
+ schnorr,
|
|
|
|
|
+ schnorr::SchnorrPublic,
|
|
|
|
|
+ types::{DrkTokenId, DrkValueBlind, DrkValueCommit},
|
|
|
|
|
+ util::{pedersen_commitment_base, pedersen_commitment_u64},
|
|
|
|
|
+ BurnRevealedValues, MintRevealedValues, Proof,
|
|
|
|
|
+ },
|
|
|
|
|
+ util::serial::{Encodable, SerialDecodable, SerialEncodable, VarInt},
|
|
|
|
|
+ Result, VerifyFailed, VerifyResult,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+pub mod builder;
|
|
|
|
|
+pub mod partial;
|
|
|
|
|
+
|
|
|
|
|
+/// A DarkFi transaction
|
|
|
|
|
+#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
|
|
|
|
+pub struct Transaction {
|
|
|
|
|
+ /// Clear inputs
|
|
|
|
|
+ pub clear_inputs: Vec<TransactionClearInput>,
|
|
|
|
|
+ /// Anonymous inputs
|
|
|
|
|
+ pub inputs: Vec<TransactionInput>,
|
|
|
|
|
+ /// Anonymous outputs
|
|
|
|
|
+ pub outputs: Vec<TransactionOutput>,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/// A transaction's clear input
|
|
|
|
|
+#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
|
|
|
|
+pub struct TransactionClearInput {
|
|
|
|
|
+ /// 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,
|
|
|
|
|
+ /// Transaction signature
|
|
|
|
|
+ pub signature: schnorr::Signature,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/// A transaction's anonymous input
|
|
|
|
|
+#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
|
|
|
|
+pub struct TransactionInput {
|
|
|
|
|
+ /// 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 TransactionOutput {
|
|
|
|
|
+ /// 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 Transaction {
|
|
|
|
|
+ /// Verify the transaction
|
|
|
|
|
+ pub fn verify(&self, mint_vk: &VerifyingKey, burn_vk: &VerifyingKey) -> VerifyResult<()> {
|
|
|
|
|
+ // Transaction 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
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl TransactionClearInput {
|
|
|
|
|
+ fn from_partial(
|
|
|
|
|
+ partial: partial::PartialTransactionClearInput,
|
|
|
|
|
+ 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 TransactionInput {
|
|
|
|
|
+ pub fn from_partial(
|
|
|
|
|
+ partial: partial::PartialTransactionInput,
|
|
|
|
|
+ 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!(TransactionClearInput);
|
|
|
|
|
+impl_vec_without_signature!(TransactionInput);
|