|
|
@@ -16,249 +16,62 @@
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
*/
|
|
|
|
|
|
-use std::io;
|
|
|
-
|
|
|
-use darkfi_sdk::crypto::{
|
|
|
- pedersen::{pedersen_commitment_base, pedersen_commitment_u64},
|
|
|
- schnorr,
|
|
|
- schnorr::SchnorrPublic,
|
|
|
- PublicKey, TokenId,
|
|
|
-};
|
|
|
-use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable, VarInt};
|
|
|
-use log::error;
|
|
|
-use pasta_curves::group::Group;
|
|
|
-
|
|
|
-use crate::{
|
|
|
+use darkfi_sdk::{
|
|
|
crypto::{
|
|
|
- burn_proof::verify_burn_proof,
|
|
|
- mint_proof::verify_mint_proof,
|
|
|
- note::EncryptedNote,
|
|
|
- proof::VerifyingKey,
|
|
|
- types::{DrkValueBlind, DrkValueCommit},
|
|
|
- BurnRevealedValues, MintRevealedValues, Proof,
|
|
|
+ schnorr::{SchnorrPublic, Signature},
|
|
|
+ PublicKey,
|
|
|
},
|
|
|
- Result, VerifyFailed, VerifyResult,
|
|
|
+ pasta::pallas,
|
|
|
+ tx::ContractCall,
|
|
|
};
|
|
|
+use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
|
|
|
+use log::{debug, error};
|
|
|
|
|
|
-pub mod builder;
|
|
|
-pub mod partial;
|
|
|
+use crate::{crypto::Proof, Error, Result};
|
|
|
|
|
|
-/// A DarkFi transaction
|
|
|
-#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
|
|
+/// A Transaction contains an arbitrary number of `ContractCall` objects,
|
|
|
+/// along with corresponding ZK proofs and Schnorr signatures.
|
|
|
+#[derive(Debug, Clone, Eq, PartialEq, 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: TokenId,
|
|
|
- /// 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,
|
|
|
+ /// Calls executed in this transaction
|
|
|
+ pub calls: Vec<ContractCall>,
|
|
|
+ /// Attached ZK proofs
|
|
|
+ pub proofs: Vec<Vec<Proof>>,
|
|
|
+ /// Attached Schnorr signatures
|
|
|
+ pub signatures: Vec<Vec<Signature>>,
|
|
|
}
|
|
|
|
|
|
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.is_empty() {
|
|
|
- error!("tx::verify(): Missing outputs");
|
|
|
- return Err(VerifyFailed::LackingOutputs)
|
|
|
- }
|
|
|
-
|
|
|
- // Accumulator for the value commitments
|
|
|
- let mut valcom_total = DrkValueCommit::identity();
|
|
|
+ /// Verify ZK proofs for the entire transaction.
|
|
|
+ pub fn verify_zkps(&self, zkp_table: Vec<Vec<(String, Vec<pallas::Base>)>>) -> Result<()> {
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
|
|
|
- // Add values from the clear inputs
|
|
|
- for input in &self.clear_inputs {
|
|
|
- valcom_total += pedersen_commitment_u64(input.value, input.value_blind);
|
|
|
- }
|
|
|
+ /// Verify Schnorr signatures for the entire transaction.
|
|
|
+ pub fn verify_sigs(&self, pub_table: Vec<Vec<PublicKey>>) -> Result<()> {
|
|
|
+ let tx_data = self.encode_without_sigs()?;
|
|
|
+ let data_hash = blake3::hash(&tx_data);
|
|
|
|
|
|
- // 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))
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ assert!(pub_table.len() == self.signatures.len());
|
|
|
|
|
|
- // 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))
|
|
|
+ for (i, (sigs, pubkeys)) in self.signatures.iter().zip(pub_table.iter()).enumerate() {
|
|
|
+ for (pubkey, signature) in pubkeys.iter().zip(sigs) {
|
|
|
+ if !pubkey.verify(&data_hash.as_bytes()[..], &signature) {
|
|
|
+ error!("tx::verify_sigs[{}] failed to verify", i);
|
|
|
+ return Err(Error::InvalidSignature)
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- // 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))
|
|
|
- }
|
|
|
+ debug!("tx::verify_sigs[{}] passed", 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.inner(), 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 }
|
|
|
+ /// Encode the object into a byte vector for signing
|
|
|
+ pub fn encode_without_sigs(&self) -> Result<Vec<u8>> {
|
|
|
+ let mut buf = vec![];
|
|
|
+ self.calls.encode(&mut buf)?;
|
|
|
+ self.proofs.encode(&mut buf)?;
|
|
|
+ Ok(buf)
|
|
|
}
|
|
|
-
|
|
|
- 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);
|