Skip to content

Commit

Permalink
Add stateless validation for transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
SethDusek committed Dec 28, 2023
1 parent 8eaa31a commit 7d3cb1f
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 64 deletions.
1 change: 1 addition & 0 deletions ergo-lib/src/chain/transaction.rs
@@ -1,6 +1,7 @@
//! Ergo transaction

mod data_input;
pub mod ergo_transaction;
pub mod input;
pub mod reduced;
pub mod unsigned;
Expand Down
98 changes: 98 additions & 0 deletions ergo-lib/src/chain/transaction/ergo_transaction.rs
@@ -0,0 +1,98 @@
//! Exposes common properties for signed and unsigned transactions
use ergotree_interpreter::{eval::context::TxIoVec, sigma_protocol::prover::ContextExtension};
use ergotree_ir::chain::ergo_box::{BoxId, ErgoBox};
use itertools::Itertools;
use thiserror::Error;

use super::{unsigned::UnsignedTransaction, DataInput, Transaction};

/// Errors when validating transaction
#[derive(Error, Debug)]
pub enum TxValidationError {
/// Transaction has more than [`i16::MAX`] inputs
/// Sum of ERG in outputs has overflowed
#[error("Sum of ERG in outputs overflowed")]
OutputSumOverflow,
/// The transaction is attempting to spend the same [`BoxId`] twice
#[error("Unique inputs: {0}, actual inputs: {1}")]
DoubleSpend(usize, usize),
}

/// Exposes common properties for signed and unsigned transactions
pub trait ErgoTransaction {
/// input boxes ids
fn inputs_ids(&self) -> TxIoVec<BoxId>;
/// data input boxes
fn data_inputs(&self) -> Option<TxIoVec<DataInput>>;
/// output boxes
fn outputs(&self) -> TxIoVec<ErgoBox>;
/// ContextExtension for the given input index
fn context_extension(&self, input_index: usize) -> Option<ContextExtension>;

/// Stateless transaction validation (no blockchain context) for a transaction
/// Returns [`Ok(())`] if validation has succeeded or returns [`TxValidationError`]
fn validate_stateless(&self) -> Result<(), TxValidationError> {
// Note that we don't need to check if inputs/data inputs/outputs are >= 1 <= 32767 here since BoundedVec takes care of that
let inputs = self.inputs_ids();
let outputs = self.outputs();

// TODO: simplify this once try_reduce is stable
// TODO: Check if outputs are not dust (this should be done outside of validate_stateless since this depends on blockchain parameters)
outputs
.iter()
.try_fold(0i64, |a, b| a.checked_add(b.value.as_i64()))
.ok_or(TxValidationError::OutputSumOverflow)?;

// Check if there are no double-spends in input (one BoxId being spent more than once)
let unique_count = inputs.iter().unique().count();
if unique_count != inputs.len() {
return Err(TxValidationError::DoubleSpend(unique_count, inputs.len()));
}
Ok(())
}
}

impl ErgoTransaction for UnsignedTransaction {
fn inputs_ids(&self) -> TxIoVec<BoxId> {
self.inputs.clone().mapped(|input| input.box_id)
}

fn data_inputs(&self) -> Option<TxIoVec<DataInput>> {
self.data_inputs.clone()
}

fn outputs(&self) -> TxIoVec<ErgoBox> {
#[allow(clippy::unwrap_used)] // box serialization cannot fail?
self.output_candidates
.clone()
.enumerated()
.try_mapped(|(idx, b)| ErgoBox::from_box_candidate(&b, self.id(), idx as u16))
.unwrap()
}

fn context_extension(&self, input_index: usize) -> Option<ContextExtension> {
self.inputs
.get(input_index)
.map(|input| input.extension.clone())
}
}

impl ErgoTransaction for Transaction {
fn inputs_ids(&self) -> TxIoVec<BoxId> {
self.inputs.clone().mapped(|input| input.box_id)
}

fn data_inputs(&self) -> Option<TxIoVec<DataInput>> {
self.data_inputs.clone()
}

fn outputs(&self) -> TxIoVec<ErgoBox> {
self.outputs.clone()
}

fn context_extension(&self, input_index: usize) -> Option<ContextExtension> {
self.inputs
.get(input_index)
.map(|input| input.spending_proof.extension.clone())
}
}
68 changes: 6 additions & 62 deletions ergo-lib/src/wallet/signing.rs
@@ -1,26 +1,25 @@
//! Transaction signing

use crate::chain::transaction::ergo_transaction::ErgoTransaction;
use crate::chain::transaction::reduced::ReducedTransaction;
use crate::chain::transaction::{DataInput, Input, TransactionError};
use crate::chain::transaction::{Input, TransactionError};
use crate::chain::{
ergo_state_context::ErgoStateContext,
transaction::{unsigned::UnsignedTransaction, Transaction},
};
use ergotree_interpreter::sigma_protocol::prover::hint::HintsBag;
use ergotree_interpreter::sigma_protocol::sig_serializer::SigParsingError;
use ergotree_ir::chain::ergo_box::ErgoBox;
use ergotree_ir::serialization::SigmaSerializationError;
use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
use std::rc::Rc;
use std::sync::Arc;

use crate::ergotree_ir::chain::ergo_box::BoxId;
use crate::wallet::multi_sig::TransactionHintsBag;
use ergotree_interpreter::eval::context::{Context, TxIoVec};
use ergotree_interpreter::eval::context::Context;
use ergotree_interpreter::eval::env::Env;
use ergotree_interpreter::sigma_protocol::prover::Prover;
use ergotree_interpreter::sigma_protocol::prover::ProverError;
use ergotree_interpreter::sigma_protocol::prover::ProverResult;
use ergotree_interpreter::sigma_protocol::prover::{ContextExtension, Prover};
use thiserror::Error;

pub use super::tx_context::TransactionContext;
Expand All @@ -43,63 +42,6 @@ pub enum TxSigningError {
SigParsingError(#[from] SigParsingError),
}

/// Exposes common properties for signed and unsigned transactions
pub trait ErgoTransaction {
/// input boxes ids
fn inputs_ids(&self) -> TxIoVec<BoxId>;
/// data input boxes
fn data_inputs(&self) -> Option<TxIoVec<DataInput>>;
/// output boxes
fn outputs(&self) -> TxIoVec<ErgoBox>;
/// ContextExtension for the given input index
fn context_extension(&self, input_index: usize) -> Option<ContextExtension>;
}

impl ErgoTransaction for UnsignedTransaction {
fn inputs_ids(&self) -> TxIoVec<BoxId> {
self.inputs.clone().mapped(|input| input.box_id)
}

fn data_inputs(&self) -> Option<TxIoVec<DataInput>> {
self.data_inputs.clone()
}

fn outputs(&self) -> TxIoVec<ErgoBox> {
#[allow(clippy::unwrap_used)] // box serialization cannot fail?
self.output_candidates
.clone()
.enumerated()
.try_mapped(|(idx, b)| ErgoBox::from_box_candidate(&b, self.id(), idx as u16))
.unwrap()
}

fn context_extension(&self, input_index: usize) -> Option<ContextExtension> {
self.inputs
.get(input_index)
.map(|input| input.extension.clone())
}
}

impl ErgoTransaction for Transaction {
fn inputs_ids(&self) -> TxIoVec<BoxId> {
self.inputs.clone().mapped(|input| input.box_id)
}

fn data_inputs(&self) -> Option<TxIoVec<DataInput>> {
self.data_inputs.clone()
}

fn outputs(&self) -> TxIoVec<ErgoBox> {
self.outputs.clone()
}

fn context_extension(&self, input_index: usize) -> Option<ContextExtension> {
self.inputs
.get(input_index)
.map(|input| input.spending_proof.extension.clone())
}
}

/// `self_index` - index of the SELF box in the tx_ctx.spending_tx.inputs
pub fn make_context<T: ErgoTransaction>(
state_ctx: &ErgoStateContext,
Expand Down Expand Up @@ -276,6 +218,7 @@ pub fn sign_tx_input(
#[allow(clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
use ergotree_interpreter::eval::context::TxIoVec;
use ergotree_interpreter::sigma_protocol::private_input::DlogProverInput;
use ergotree_interpreter::sigma_protocol::private_input::PrivateInput;
use ergotree_interpreter::sigma_protocol::prover::ContextExtension;
Expand All @@ -287,6 +230,7 @@ mod tests {
use ergotree_ir::chain::address::AddressEncoder;
use ergotree_ir::chain::address::NetworkPrefix;
use ergotree_ir::chain::ergo_box::box_value::BoxValue;
use ergotree_ir::chain::ergo_box::ErgoBox;
use ergotree_ir::chain::ergo_box::NonMandatoryRegisters;
use ergotree_ir::chain::tx_id::TxId;
use ergotree_ir::serialization::SigmaSerializable;
Expand Down
3 changes: 1 addition & 2 deletions ergo-lib/src/wallet/tx_context.rs
Expand Up @@ -3,12 +3,11 @@
use ergotree_ir::chain::ergo_box::ErgoBox;
use thiserror::Error;

use crate::chain::transaction::ergo_transaction::ErgoTransaction;
use crate::chain::transaction::TransactionError;
use crate::ergotree_ir::chain::ergo_box::BoxId;
use ergotree_interpreter::eval::context::TxIoVec;

use super::signing::ErgoTransaction;

/// Transaction and an additional info required for signing
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct TransactionContext<T: ErgoTransaction> {
Expand Down

0 comments on commit 7d3cb1f

Please sign in to comment.