Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixup transaction validation for 1.2.0 #1460

Merged
merged 5 commits into from
Feb 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions consensus/api/proto/consensus_common.proto
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ enum ProposeTxResult {
KeyError = 38;
UnsortedInputs = 39;
MissingMemo = 40;
MemosNotAllowed = 41;
}

/// Response from TxPropose RPC call.
Expand Down
174 changes: 71 additions & 103 deletions consensus/api/src/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,122 +9,90 @@
//! `mc_transaction_core::validation::TransactionValidationError`.

use crate::consensus_common::ProposeTxResult;
use mc_transaction_core::{ring_signature, validation::TransactionValidationError};
use mc_transaction_core::{ring_signature, validation::TransactionValidationError as Error};
use std::convert::{From, TryInto};

/// Convert TransactionValidationError --> ProposeTxResult.
impl From<TransactionValidationError> for ProposeTxResult {
fn from(src: TransactionValidationError) -> Self {
impl From<Error> for ProposeTxResult {
fn from(src: Error) -> Self {
match src {
TransactionValidationError::InputsProofsLengthMismatch => {
Self::InputsProofsLengthMismatch
}
TransactionValidationError::NoInputs => Self::NoInputs,
TransactionValidationError::TooManyInputs => Self::TooManyInputs,
TransactionValidationError::InsufficientInputSignatures => {
Self::InsufficientInputSignatures
}
TransactionValidationError::InvalidInputSignature => Self::InvalidInputSignature,
TransactionValidationError::InvalidTransactionSignature(_e) => {
Self::InvalidTransactionSignature
}
TransactionValidationError::InvalidRangeProof => Self::InvalidRangeProof,
TransactionValidationError::InsufficientRingSize => Self::InsufficientRingSize,
TransactionValidationError::TombstoneBlockExceeded => Self::TombstoneBlockExceeded,
TransactionValidationError::TombstoneBlockTooFar => Self::TombstoneBlockTooFar,
TransactionValidationError::NoOutputs => Self::NoOutputs,
TransactionValidationError::TooManyOutputs => Self::TooManyOutputs,
TransactionValidationError::ExcessiveRingSize => Self::ExcessiveRingSize,
TransactionValidationError::DuplicateRingElements => Self::DuplicateRingElements,
TransactionValidationError::UnsortedRingElements => Self::UnsortedRingElements,
TransactionValidationError::UnequalRingSizes => Self::UnequalRingSizes,
TransactionValidationError::UnsortedKeyImages => Self::UnsortedKeyImages,
TransactionValidationError::ContainsSpentKeyImage => Self::ContainsSpentKeyImage,
TransactionValidationError::DuplicateKeyImages => Self::DuplicateKeyImages,
TransactionValidationError::DuplicateOutputPublicKey => Self::DuplicateOutputPublicKey,
TransactionValidationError::ContainsExistingOutputPublicKey => {
Self::ContainsExistingOutputPublicKey
}
TransactionValidationError::MissingTxOutMembershipProof => {
Self::MissingTxOutMembershipProof
}
TransactionValidationError::InvalidTxOutMembershipProof => {
Self::InvalidTxOutMembershipProof
}
TransactionValidationError::InvalidRistrettoPublicKey => {
Self::InvalidRistrettoPublicKey
}
TransactionValidationError::InvalidLedgerContext => Self::InvalidLedgerContext,
TransactionValidationError::Ledger(_) => Self::Ledger,
TransactionValidationError::MembershipProofValidationError => {
Self::MembershipProofValidationError
}
TransactionValidationError::TxFeeError => Self::TxFeeError,
TransactionValidationError::KeyError => Self::KeyError,
TransactionValidationError::UnsortedInputs => Self::UnsortedInputs,
TransactionValidationError::MissingMemo => Self::MissingMemo,
Error::InputsProofsLengthMismatch => Self::InputsProofsLengthMismatch,
Error::NoInputs => Self::NoInputs,
Error::TooManyInputs => Self::TooManyInputs,
Error::InsufficientInputSignatures => Self::InsufficientInputSignatures,
Error::InvalidInputSignature => Self::InvalidInputSignature,
Error::InvalidTransactionSignature(_e) => Self::InvalidTransactionSignature,
Error::InvalidRangeProof => Self::InvalidRangeProof,
Error::InsufficientRingSize => Self::InsufficientRingSize,
Error::TombstoneBlockExceeded => Self::TombstoneBlockExceeded,
Error::TombstoneBlockTooFar => Self::TombstoneBlockTooFar,
Error::NoOutputs => Self::NoOutputs,
Error::TooManyOutputs => Self::TooManyOutputs,
Error::ExcessiveRingSize => Self::ExcessiveRingSize,
Error::DuplicateRingElements => Self::DuplicateRingElements,
Error::UnsortedRingElements => Self::UnsortedRingElements,
Error::UnequalRingSizes => Self::UnequalRingSizes,
Error::UnsortedKeyImages => Self::UnsortedKeyImages,
Error::ContainsSpentKeyImage => Self::ContainsSpentKeyImage,
Error::DuplicateKeyImages => Self::DuplicateKeyImages,
Error::DuplicateOutputPublicKey => Self::DuplicateOutputPublicKey,
Error::ContainsExistingOutputPublicKey => Self::ContainsExistingOutputPublicKey,
Error::MissingTxOutMembershipProof => Self::MissingTxOutMembershipProof,
Error::InvalidTxOutMembershipProof => Self::InvalidTxOutMembershipProof,
Error::InvalidRistrettoPublicKey => Self::InvalidRistrettoPublicKey,
Error::InvalidLedgerContext => Self::InvalidLedgerContext,
Error::Ledger(_) => Self::Ledger,
Error::MembershipProofValidationError => Self::MembershipProofValidationError,
Error::TxFeeError => Self::TxFeeError,
Error::KeyError => Self::KeyError,
Error::UnsortedInputs => Self::UnsortedInputs,
Error::MissingMemo => Self::MissingMemo,
Error::MemosNotAllowed => Self::MemosNotAllowed,
}
}
}

/// Convert ProposeTxResult --> TransactionValidationError.
impl TryInto<TransactionValidationError> for ProposeTxResult {
impl TryInto<Error> for ProposeTxResult {
type Error = &'static str;

fn try_into(self) -> Result<TransactionValidationError, Self::Error> {
fn try_into(self) -> Result<Error, Self::Error> {
match self {
Self::Ok => Err("Ok value cannot be convererted into TransactionValidationError"),
Self::InputsProofsLengthMismatch => {
Ok(TransactionValidationError::InputsProofsLengthMismatch)
}
Self::NoInputs => Ok(TransactionValidationError::NoInputs),
Self::TooManyInputs => Ok(TransactionValidationError::TooManyInputs),
Self::InsufficientInputSignatures => {
Ok(TransactionValidationError::InsufficientInputSignatures)
}
Self::InvalidInputSignature => Ok(TransactionValidationError::InvalidInputSignature),
Self::InvalidTransactionSignature => {
Ok(TransactionValidationError::InvalidTransactionSignature(
ring_signature::Error::InvalidSignature,
))
}
Self::InvalidRangeProof => Ok(TransactionValidationError::InvalidRangeProof),
Self::InsufficientRingSize => Ok(TransactionValidationError::InsufficientRingSize),
Self::TombstoneBlockExceeded => Ok(TransactionValidationError::TombstoneBlockExceeded),
Self::TombstoneBlockTooFar => Ok(TransactionValidationError::TombstoneBlockTooFar),
Self::NoOutputs => Ok(TransactionValidationError::NoOutputs),
Self::TooManyOutputs => Ok(TransactionValidationError::TooManyOutputs),
Self::ExcessiveRingSize => Ok(TransactionValidationError::ExcessiveRingSize),
Self::DuplicateRingElements => Ok(TransactionValidationError::DuplicateRingElements),
Self::UnsortedRingElements => Ok(TransactionValidationError::UnsortedRingElements),
Self::UnequalRingSizes => Ok(TransactionValidationError::UnequalRingSizes),
Self::UnsortedKeyImages => Ok(TransactionValidationError::UnsortedKeyImages),
Self::ContainsSpentKeyImage => Ok(TransactionValidationError::ContainsSpentKeyImage),
Self::DuplicateKeyImages => Ok(TransactionValidationError::DuplicateKeyImages),
Self::DuplicateOutputPublicKey => {
Ok(TransactionValidationError::DuplicateOutputPublicKey)
}
Self::ContainsExistingOutputPublicKey => {
Ok(TransactionValidationError::ContainsExistingOutputPublicKey)
}
Self::MissingTxOutMembershipProof => {
Ok(TransactionValidationError::MissingTxOutMembershipProof)
}
Self::InvalidTxOutMembershipProof => {
Ok(TransactionValidationError::InvalidTxOutMembershipProof)
}
Self::InvalidRistrettoPublicKey => {
Ok(TransactionValidationError::InvalidRistrettoPublicKey)
}
Self::InvalidLedgerContext => Ok(TransactionValidationError::InvalidLedgerContext),
Self::Ledger => Ok(TransactionValidationError::Ledger(String::default())),
Self::MembershipProofValidationError => {
Ok(TransactionValidationError::MembershipProofValidationError)
}
Self::TxFeeError => Ok(TransactionValidationError::TxFeeError),
Self::KeyError => Ok(TransactionValidationError::KeyError),
Self::UnsortedInputs => Ok(TransactionValidationError::UnsortedInputs),
Self::MissingMemo => Ok(TransactionValidationError::MissingMemo),
Self::InputsProofsLengthMismatch => Ok(Error::InputsProofsLengthMismatch),
Self::NoInputs => Ok(Error::NoInputs),
Self::TooManyInputs => Ok(Error::TooManyInputs),
Self::InsufficientInputSignatures => Ok(Error::InsufficientInputSignatures),
Self::InvalidInputSignature => Ok(Error::InvalidInputSignature),
Self::InvalidTransactionSignature => Ok(Error::InvalidTransactionSignature(
ring_signature::Error::InvalidSignature,
)),
Self::InvalidRangeProof => Ok(Error::InvalidRangeProof),
Self::InsufficientRingSize => Ok(Error::InsufficientRingSize),
Self::TombstoneBlockExceeded => Ok(Error::TombstoneBlockExceeded),
Self::TombstoneBlockTooFar => Ok(Error::TombstoneBlockTooFar),
Self::NoOutputs => Ok(Error::NoOutputs),
Self::TooManyOutputs => Ok(Error::TooManyOutputs),
Self::ExcessiveRingSize => Ok(Error::ExcessiveRingSize),
Self::DuplicateRingElements => Ok(Error::DuplicateRingElements),
Self::UnsortedRingElements => Ok(Error::UnsortedRingElements),
Self::UnequalRingSizes => Ok(Error::UnequalRingSizes),
Self::UnsortedKeyImages => Ok(Error::UnsortedKeyImages),
Self::ContainsSpentKeyImage => Ok(Error::ContainsSpentKeyImage),
Self::DuplicateKeyImages => Ok(Error::DuplicateKeyImages),
Self::DuplicateOutputPublicKey => Ok(Error::DuplicateOutputPublicKey),
Self::ContainsExistingOutputPublicKey => Ok(Error::ContainsExistingOutputPublicKey),
Self::MissingTxOutMembershipProof => Ok(Error::MissingTxOutMembershipProof),
Self::InvalidTxOutMembershipProof => Ok(Error::InvalidTxOutMembershipProof),
Self::InvalidRistrettoPublicKey => Ok(Error::InvalidRistrettoPublicKey),
Self::InvalidLedgerContext => Ok(Error::InvalidLedgerContext),
Self::Ledger => Ok(Error::Ledger(String::default())),
Self::MembershipProofValidationError => Ok(Error::MembershipProofValidationError),
Self::TxFeeError => Ok(Error::TxFeeError),
Self::KeyError => Ok(Error::KeyError),
Self::UnsortedInputs => Ok(Error::UnsortedInputs),
Self::MissingMemo => Ok(Error::MissingMemo),
Self::MemosNotAllowed => Ok(Error::MemosNotAllowed),
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions transaction/core/src/validation/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ pub enum TransactionValidationError {

/// A TxOut is missing the required memo field
MissingMemo,

/// A TxOut includes a memo, but this is not allowed yet
MemosNotAllowed,
}

impl From<mc_crypto_keys::KeyError> for TransactionValidationError {
Expand Down
76 changes: 57 additions & 19 deletions transaction/core/src/validation/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@ pub fn validate<R: RngCore + CryptoRng>(

validate_number_of_outputs(&tx.prefix, MAX_OUTPUTS)?;

// Bring this back in Issue #905 (Make memos mandatory)
// https://github.com/mobilecoinfoundation/mobilecoin/issues/905
// validate_memos_exist(tx)?;

validate_ring_sizes(&tx.prefix, RING_SIZE)?;

validate_ring_elements_are_unique(&tx.prefix)?;
Expand All @@ -65,6 +61,13 @@ pub fn validate<R: RngCore + CryptoRng>(
// Note: The transaction must not contain a Key Image that has previously been
// spent. This must be checked outside the enclave.

// In the 1.2 release, it is planned that clients will know to read memos,
// but memos will not be allowed to exist in the chain until the next release.
// If we implement "block-version-based protocol evolution" (MCIP 26), then this
// function would become block-version aware and this could become a branch.
validate_no_memos_exist(tx)?;
// validate_memos_exist(tx)?;

Ok(())
}

Expand Down Expand Up @@ -195,7 +198,20 @@ fn validate_outputs_public_keys_are_unique(tx: &Tx) -> TransactionValidationResu
Ok(())
}

/// All outputs have a memo (old-style TxOuts are rejected)
/// All outputs have no memo (new-style TxOuts (Post MCIP #3) are rejected)
fn validate_no_memos_exist(tx: &Tx) -> TransactionValidationResult<()> {
if tx
.prefix
.outputs
.iter()
.any(|output| output.e_memo.is_some())
{
return Err(TransactionValidationError::MemosNotAllowed);
}
Ok(())
}

/// All outputs have a memo (old-style TxOuts (Pre MCIP #3) are rejected)
///
/// Note: This is only under test for now, and can become live
/// at the time that we address mobilecoinfoundation/mobilecoin/issues/905
Expand Down Expand Up @@ -393,6 +409,8 @@ pub fn validate_tombstone(

#[cfg(test)]
mod tests {
use super::*;

extern crate alloc;

use alloc::vec::Vec;
Expand All @@ -401,16 +419,7 @@ mod tests {
constants::RING_SIZE,
tokens::Mob,
tx::{Tx, TxOutMembershipHash, TxOutMembershipProof},
validation::{
error::TransactionValidationError,
validate::{
validate_inputs_are_sorted, validate_key_images_are_unique,
validate_membership_proofs, validate_memos_exist, validate_number_of_inputs,
validate_number_of_outputs, validate_outputs_public_keys_are_unique,
validate_ring_elements_are_unique, validate_ring_sizes, validate_signature,
validate_tombstone, validate_transaction_fee, MAX_TOMBSTONE_BLOCKS,
},
},
validation::error::TransactionValidationError,
Token,
};

Expand Down Expand Up @@ -492,17 +501,46 @@ mod tests {
}

#[test]
// Should return MissingMemo when memos are missing in the outputs
// Should return MissingMemo when memos are missing in any the outputs
fn test_validate_memos_exist() {
let (mut tx, _) = create_test_tx();
for ref mut output in tx.prefix.outputs.iter_mut() {
output.e_memo = None;
}

assert!(tx.prefix.outputs.first_mut().unwrap().e_memo.is_none());
assert_eq!(
validate_memos_exist(&tx),
Err(TransactionValidationError::MissingMemo)
);

for ref mut output in tx.prefix.outputs.iter_mut() {
output.e_memo = Some(Default::default());
}

assert_eq!(validate_memos_exist(&tx), Ok(()));
}

#[test]
// Should return MemosNotAllowed when memos are present in any of the outputs
fn test_validate_no_memos_exist() {
let (mut tx, _) = create_test_tx();

assert!(tx.prefix.outputs.first_mut().unwrap().e_memo.is_none());
assert_eq!(validate_no_memos_exist(&tx), Ok(()));

tx.prefix.outputs.first_mut().unwrap().e_memo = Some(Default::default());

assert_eq!(
validate_no_memos_exist(&tx),
Err(TransactionValidationError::MemosNotAllowed)
);

for ref mut output in tx.prefix.outputs.iter_mut() {
output.e_memo = Some(Default::default());
}

assert_eq!(
validate_no_memos_exist(&tx),
Err(TransactionValidationError::MemosNotAllowed)
);
}

#[test]
Expand Down
4 changes: 2 additions & 2 deletions transaction/core/test-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub use mc_transaction_core::{
tx::{Tx, TxOut, TxOutMembershipElement, TxOutMembershipHash},
Block, BlockID, BlockIndex, Token, BLOCK_VERSION,
};
use mc_transaction_std::{EmptyMemoBuilder, InputCredentials, TransactionBuilder};
use mc_transaction_std::{InputCredentials, NoMemoBuilder, TransactionBuilder};
use mc_util_from_random::FromRandom;
use rand::{seq::SliceRandom, Rng};
use tempdir::TempDir;
Expand Down Expand Up @@ -88,7 +88,7 @@ pub fn create_transaction_with_amount<L: Ledger, R: RngCore + CryptoRng>(
rng: &mut R,
) -> Tx {
let mut transaction_builder =
TransactionBuilder::new(MockFogResolver::default(), EmptyMemoBuilder::default());
TransactionBuilder::new(MockFogResolver::default(), NoMemoBuilder::default());

// The first transaction in the origin block should contain enough outputs to
// use as mixins.
Expand Down