link transaction rules to monero-book

This commit is contained in:
Boog900 2024-01-08 01:26:44 +00:00
parent 13957a5e7f
commit 730bc8fb42
No known key found for this signature in database
GPG key ID: 5401367FB7302004
7 changed files with 209 additions and 91 deletions

View file

@ -15,6 +15,10 @@ const BLOCK_FUTURE_TIME_LIMIT: u64 = 60 * 60 * 2;
const BLOCK_202612_POW_HASH: [u8; 32] =
hex_literal::hex!("84f64766475d51837ac9efbef1926486e58563c95a19fef4aec3254f03000000");
pub const PENALTY_FREE_ZONE_1: usize = 20000;
pub const PENALTY_FREE_ZONE_2: usize = 60000;
pub const PENALTY_FREE_ZONE_5: usize = 300000;
const RX_SEEDHASH_EPOCH_BLOCKS: u64 = 2048;
const RX_SEEDHASH_EPOCH_LAG: u64 = 64;
@ -109,6 +113,19 @@ pub fn check_block_pow(hash: &[u8; 32], difficulty: u128) -> Result<(), BlockErr
}
}
/// Returns the penalty free zone
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks/weight_limit.html#penalty-free-zone
pub fn penalty_free_zone(hf: &HardFork) -> usize {
if hf == &HardFork::V1 {
PENALTY_FREE_ZONE_1
} else if hf >= &HardFork::V2 && hf < &HardFork::V5 {
PENALTY_FREE_ZONE_2
} else {
PENALTY_FREE_ZONE_5
}
}
/// Sanity check on the block blob size.
///
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks.html#block-weight-and-size
@ -177,7 +194,7 @@ fn check_timestamp(block: &Block, median_timestamp: u64) -> Result<(), BlockErro
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks.html#no-duplicate-transactions
fn check_txs_unique(txs: &[[u8; 32]]) -> Result<(), BlockError> {
txs.windows(2).try_for_each(|window| {
if window[0] != window[1] {
if window[0] == window[1] {
Err(BlockError::DuplicateTransaction)?;
}
Ok(())

View file

@ -1,9 +1,12 @@
use monero_serai::ringct::RctType;
use std::{cmp::Ordering, collections::HashSet, sync::Arc};
use monero_serai::transaction::{Input, Output, Timelock, Transaction};
use multiexp::BatchVerifier;
use crate::{check_point_canonically_encoded, is_decomposed_amount, HardFork};
use crate::{
blocks::penalty_free_zone, check_point_canonically_encoded, is_decomposed_amount, HardFork,
};
mod contextual_data;
mod ring_ct;
@ -12,10 +15,15 @@ mod ring_signatures;
pub use contextual_data::*;
pub use ring_ct::RingCTError;
const MAX_BULLETPROOFS_OUTPUTS: usize = 16;
const MAX_TX_BLOB_SIZE: usize = 1_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum TransactionError {
#[error("The transactions version is incorrect.")]
TransactionVersionInvalid,
#[error("The transactions is too big.")]
TooBig,
//-------------------------------------------------------- OUTPUTS
#[error("Output is not a valid point.")]
OutputNotValidPoint,
@ -23,12 +31,16 @@ pub enum TransactionError {
OutputTypeInvalid,
#[error("The transaction is v1 with a 0 amount output.")]
ZeroOutputForV1,
#[error("The transaction is v2 with a non 0 amount output.")]
NonZeroOutputForV2,
#[error("The transaction has an output which is not decomposed.")]
AmountNotDecomposed,
#[error("The transactions outputs overflow.")]
OutputsOverflow,
#[error("The transactions outputs too much.")]
OutputsTooHigh,
#[error("The transactions has too many outputs.")]
InvalidNumberOfOutputs,
//-------------------------------------------------------- INPUTS
#[error("One or more inputs don't have the expected number of decoys.")]
InputDoesNotHaveExpectedNumbDecoys,
@ -57,16 +69,25 @@ pub enum TransactionError {
RingSignatureIncorrect,
//-------------------------------------------------------- RingCT
#[error("RingCT Error: {0}.")]
RingCTError(#[from] ring_ct::RingCTError),
RingCTError(#[from] RingCTError),
}
/// An enum representing all valid Monero transaction versions.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum TxVersion {
/// Legacy ring signatures.
RingSignatures,
/// RingCT
RingCT,
}
impl TxVersion {
/// Converts a `raw` version value to a [`TxVersion`].
///
/// This will return `None` on invalid values.
///
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions.html#version
/// && https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#version
pub fn from_raw(version: u64) -> Option<TxVersion> {
Some(match version {
1 => TxVersion::RingSignatures,
@ -80,7 +101,7 @@ impl TxVersion {
/// Checks the output keys are canonically encoded points.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#output-keys-canonical
/// https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#output-keys-canonical
fn check_output_keys(outputs: &[Output]) -> Result<(), TransactionError> {
for out in outputs {
if !check_point_canonically_encoded(&out.key) {
@ -91,12 +112,12 @@ fn check_output_keys(outputs: &[Output]) -> Result<(), TransactionError> {
Ok(())
}
/// Checks the output types are allowed.
/// Checks the output types are allowed for the given hard-fork.
///
/// This is also used during miner-tx verification.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#output-type
/// https://cuprate.github.io/monero-book/consensus_rules/blocks/miner_tx.html#output-type
/// https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#output-type
/// https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#output-type
pub(crate) fn check_output_types(
outputs: &[Output],
hf: &HardFork,
@ -120,9 +141,9 @@ pub(crate) fn check_output_types(
Ok(())
}
/// Checks the outputs amount for version 1 txs.
/// Checks the individual outputs amount for version 1 txs.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#output-amount
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#output-amount
fn check_output_amount_v1(amount: u64, hf: &HardFork) -> Result<(), TransactionError> {
if amount == 0 {
return Err(TransactionError::ZeroOutputForV1);
@ -135,20 +156,35 @@ fn check_output_amount_v1(amount: u64, hf: &HardFork) -> Result<(), TransactionE
Ok(())
}
/// Checks the individual outputs amount for version 2 txs.
///
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#output-amount
fn check_output_amount_v2(amount: u64) -> Result<(), TransactionError> {
if amount == 0 {
Ok(())
} else {
Err(TransactionError::NonZeroOutputForV2)
}
}
/// Sums the outputs, checking for overflow and other consensus rules.
///
/// Should only be used on v1 transactions.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#inputs-and-outputs-must-not-overflow
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#output-amount
fn sum_outputs_v1(outputs: &[Output], hf: &HardFork) -> Result<u64, TransactionError> {
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#output-amount
/// && https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#outputs-must-not-overflow
fn sum_outputs(
outputs: &[Output],
hf: &HardFork,
tx_version: &TxVersion,
) -> Result<u64, TransactionError> {
let mut sum: u64 = 0;
for out in outputs {
let raw_amount = out.amount.unwrap_or(0);
check_output_amount_v1(raw_amount, hf)?;
match tx_version {
TxVersion::RingSignatures => check_output_amount_v1(raw_amount, hf)?,
TxVersion::RingCT => check_output_amount_v2(raw_amount)?,
}
sum = sum
.checked_add(raw_amount)
.ok_or(TransactionError::OutputsOverflow)?;
@ -157,26 +193,60 @@ fn sum_outputs_v1(outputs: &[Output], hf: &HardFork) -> Result<u64, TransactionE
Ok(sum)
}
/// Checks the number of outputs is allowed.
///
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#2-outputs
/// && https://monero-book.cuprate.org/consensus_rules/transactions/ring_ct/bulletproofs.html#max-outputs
/// && https://monero-book.cuprate.org/consensus_rules/transactions/ring_ct/bulletproofs+.html#max-outputs
fn check_number_of_outputs(
outputs: usize,
hf: &HardFork,
tx_version: &TxVersion,
rct_type: &RctType,
) -> Result<(), TransactionError> {
if tx_version == &TxVersion::RingSignatures {
return Ok(());
}
if hf >= &HardFork::V12 && outputs < 2 {
return Err(TransactionError::InvalidNumberOfOutputs);
}
match rct_type {
RctType::Bulletproofs | RctType::BulletproofsCompactAmount | RctType::BulletproofsPlus => {
if outputs <= MAX_BULLETPROOFS_OUTPUTS {
Ok(())
} else {
Err(TransactionError::InvalidNumberOfOutputs)
}
}
_ => Ok(()),
}
}
/// Checks the outputs against all output consensus rules, returning the sum of the output amounts.
///
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html
/// && https://monero-book.cuprate.org/consensus_rules/transactions/ring_ct/bulletproofs.html#max-outputs
/// && https://monero-book.cuprate.org/consensus_rules/transactions/ring_ct/bulletproofs+.html#max-outputs
fn check_outputs_semantics(
outputs: &[Output],
hf: &HardFork,
tx_version: &TxVersion,
rct_type: &RctType,
) -> Result<u64, TransactionError> {
check_output_types(outputs, hf)?;
check_output_keys(outputs)?;
check_number_of_outputs(outputs.len(), hf, tx_version, rct_type)?;
match tx_version {
TxVersion::RingSignatures => sum_outputs_v1(outputs, hf),
TxVersion::RingCT => Ok(0), // RCT outputs are checked to be zero in RCT checks.
}
sum_outputs(outputs, hf, tx_version)
}
//----------------------------------------------------------------------------------------------------------- TIME LOCKS
/// Checks if an outputs unlock time has passed.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#unlock-time
/// https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html
fn output_unlocked(
time_lock: &Timelock,
current_chain_height: u64,
@ -194,18 +264,17 @@ fn output_unlocked(
}
}
/// Returns if a locked output, which uses a block height, can be spend.
/// Returns if a locked output, which uses a block height, can be spent.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#block-height
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html#block-height
fn check_block_time_lock(unlock_height: u64, current_chain_height: u64) -> bool {
// current_chain_height = 1 + top height
unlock_height <= current_chain_height
}
/// ///
/// Returns if a locked output, which uses a block height, can be spend.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#timestamp
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html#timestamp
fn check_timestamp_time_lock(
unlock_timestamp: u64,
current_time_lock_timestamp: u64,
@ -216,9 +285,10 @@ fn check_timestamp_time_lock(
/// Checks all the time locks are unlocked.
///
/// `current_time_lock_timestamp` must be: https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#getting-the-current-time
/// `current_time_lock_timestamp` must be: https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html#getting-the-current-time
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#unlock-time
/// https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html
/// https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#the-output-must-not-be-locked
fn check_all_time_locks(
time_locks: &[Timelock],
current_chain_height: u64,
@ -243,8 +313,8 @@ fn check_all_time_locks(
/// Checks the decoys are allowed.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#minimum-decoys
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#equal-number-of-decoys
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#minimum-decoys
/// && https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#equal-number-of-decoys
fn check_decoy_info(decoy_info: &DecoyInfo, hf: &HardFork) -> Result<(), TransactionError> {
if hf == &HardFork::V15 {
// Hard-fork 15 allows both v14 and v16 rules
@ -276,13 +346,13 @@ fn check_decoy_info(decoy_info: &DecoyInfo, hf: &HardFork) -> Result<(), Transac
Ok(())
}
/// Checks the inputs key images for torsion and for duplicates in the transaction.
/// Checks the inputs key images for torsion and for duplicates in the spent_kis list.
///
/// The `spent_kis` parameter is not meant to be a complete list of key images, just a list of related transactions
/// key images, for example transactions in a block. The chain will be checked for duplicates later.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#unique-key-image
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#torsion-free-key-image
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#unique-key-image
/// && https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#torsion-free-key-image
fn check_key_images(
input: &Input,
spent_kis: &mut HashSet<[u8; 32]>,
@ -305,7 +375,7 @@ fn check_key_images(
/// Checks that the input is of type [`Input::ToKey`] aka txin_to_key.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#input-type
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#input-type
fn check_input_type(input: &Input) -> Result<(), TransactionError> {
match input {
Input::ToKey { .. } => Ok(()),
@ -315,7 +385,7 @@ fn check_input_type(input: &Input) -> Result<(), TransactionError> {
/// Checks that the input has decoys.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#inputs-must-have-decoys
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#no-empty-decoys
fn check_input_has_decoys(input: &Input) -> Result<(), TransactionError> {
match input {
Input::ToKey { key_offsets, .. } => {
@ -331,7 +401,7 @@ fn check_input_has_decoys(input: &Input) -> Result<(), TransactionError> {
/// Checks that the ring members for the input are unique after hard-fork 6.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#unique-ring-members
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#unique-ring-members
fn check_ring_members_unique(input: &Input, hf: &HardFork) -> Result<(), TransactionError> {
if hf >= &HardFork::V6 {
match input {
@ -351,7 +421,7 @@ fn check_ring_members_unique(input: &Input, hf: &HardFork) -> Result<(), Transac
/// Checks that from hf 7 the inputs are sorted by key image.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#sorted-inputs
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#sorted-inputs
fn check_inputs_sorted(inputs: &[Input], hf: &HardFork) -> Result<(), TransactionError> {
let get_ki = |inp: &Input| match inp {
Input::ToKey { key_image, .. } => Ok(key_image.compress().to_bytes()),
@ -373,7 +443,7 @@ fn check_inputs_sorted(inputs: &[Input], hf: &HardFork) -> Result<(), Transactio
/// Checks the youngest output is at least 10 blocks old.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#10-block-lock
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#10-block-lock
fn check_10_block_lock(
youngest_used_out_height: u64,
current_chain_height: u64,
@ -392,7 +462,7 @@ fn check_10_block_lock(
/// Sums the inputs checking for overflow.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#inputs-and-outputs-must-not-overflow
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#inputs-must-not-overflow
fn sum_inputs_check_overflow(inputs: &[Input]) -> Result<u64, TransactionError> {
let mut sum: u64 = 0;
for inp in inputs {
@ -409,7 +479,13 @@ fn sum_inputs_check_overflow(inputs: &[Input]) -> Result<u64, TransactionError>
Ok(sum)
}
/// Checks the inputs semantically validity, returning the sum of the inputs.
///
/// Semantic rules are rules that don't require blockchain context, the hard-fork does not require blockchain context as:
/// - The tx-pool will use the current hard-fork
/// - When syncing the hard-fork is in the block header.
fn check_inputs_semantics(inputs: &[Input], hf: &HardFork) -> Result<u64, TransactionError> {
// https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#no-empty-inputs
if inputs.is_empty() {
return Err(TransactionError::NoInputs);
}
@ -426,6 +502,14 @@ fn check_inputs_semantics(inputs: &[Input], hf: &HardFork) -> Result<u64, Transa
sum_inputs_check_overflow(inputs)
}
/// Checks the inputs contextual validity.
///
/// Contextual rules are rules that require blockchain context to check.
///
/// This function does not check signatures.
///
/// The `spent_kis` parameter is not meant to be a complete list of key images, just a list of related transactions
/// key images, for example transactions in a block. The chain should be checked for duplicates later.
fn check_inputs_contextual(
inputs: &[Input],
tx_ring_members_info: &TxRingMembersInfo,
@ -454,9 +538,11 @@ fn check_inputs_contextual(
Ok(())
}
//----------------------------------------------------------------------------------------------------------- OVERALL
/// Checks the version is in the allowed range.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#version
/// https://monero-book.cuprate.org/consensus_rules/transactions.html#version
fn check_tx_version(
decoy_info: &Option<DecoyInfo>,
version: &TxVersion,
@ -468,7 +554,6 @@ fn check_tx_version(
return Err(TransactionError::TransactionVersionInvalid);
}
// TODO: Doc is wrong here
let min = min_tx_version(hf);
if version < &min && decoy_info.not_mixable == 0 {
return Err(TransactionError::TransactionVersionInvalid);
@ -483,6 +568,7 @@ fn check_tx_version(
Ok(())
}
/// Returns the default maximum tx version for the given hard-fork.
fn max_tx_version(hf: &HardFork) -> TxVersion {
if hf <= &HardFork::V3 {
TxVersion::RingSignatures
@ -491,6 +577,7 @@ fn max_tx_version(hf: &HardFork) -> TxVersion {
}
}
/// Returns the default minimum tx version for the given hard-fork.
fn min_tx_version(hf: &HardFork) -> TxVersion {
if hf >= &HardFork::V6 {
TxVersion::RingCT
@ -499,16 +586,42 @@ fn min_tx_version(hf: &HardFork) -> TxVersion {
}
}
fn transaction_weight_limit(hf: &HardFork) -> usize {
penalty_free_zone(hf) / 2 - 600
}
/// Checks the transaction is semantically valid.
///
/// Semantic rules are rules that don't require blockchain context, the hard-fork does not require blockchain context as:
/// - The tx-pool will use the current hard-fork
/// - When syncing the hard-fork is in the block header.
///
/// To fully verify a transaction this must be accompanied with [`check_transaction_contextual`]
///
pub fn check_transaction_semantic(
tx: &Transaction,
tx_blob_size: usize,
tx_weight: usize,
tx_hash: &[u8; 32],
hf: &HardFork,
verifier: &mut BatchVerifier<(), dalek_ff_group::EdwardsPoint>,
) -> Result<u64, TransactionError> {
// https://monero-book.cuprate.org/consensus_rules/transactions.html#transaction-size
if tx_blob_size > MAX_TX_BLOB_SIZE
|| (hf >= &HardFork::V8 && tx_weight > transaction_weight_limit(hf))
{
Err(TransactionError::TooBig)?;
}
let tx_version = TxVersion::from_raw(tx.prefix.version)
.ok_or(TransactionError::TransactionVersionInvalid)?;
let outputs_sum = check_outputs_semantics(&tx.prefix.outputs, hf, &tx_version)?;
let outputs_sum = check_outputs_semantics(
&tx.prefix.outputs,
hf,
&tx_version,
&tx.rct_signatures.rct_type(),
)?;
let inputs_sum = check_inputs_semantics(&tx.prefix.inputs, hf)?;
let fee = match tx_version {
@ -528,6 +641,12 @@ pub fn check_transaction_semantic(
Ok(fee)
}
/// Checks the transaction is contextually valid.
///
/// To fully verify a transaction this must be accompanied with [`check_transaction_semantic`]
///
/// `current_time_lock_timestamp` must be: https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html#getting-the-current-time
pub fn check_transaction_contextual(
tx: &Transaction,
tx_ring_members_info: &TxRingMembersInfo,
@ -556,7 +675,7 @@ pub fn check_transaction_contextual(
)?;
match tx_version {
TxVersion::RingSignatures => ring_signatures::verify_inputs_signatures(
TxVersion::RingSignatures => ring_signatures::check_input_signatures(
&tx.prefix.inputs,
&tx.signatures,
&tx_ring_members_info.rings,

View file

@ -135,7 +135,7 @@ impl Rings {
#[derive(Debug)]
pub struct TxRingMembersInfo {
pub rings: Rings,
/// Information on the structure of the decoys, will be [`None`] for txs before [`HardFork::V1`]
/// Information on the structure of the decoys, must be [`None`] for txs before [`HardFork::V1`]
pub decoy_info: Option<DecoyInfo>,
pub youngest_used_out_height: u64,
pub time_locked_outs: Vec<Timelock>,
@ -277,7 +277,7 @@ impl DecoyInfo {
/// Returns the default minimum amount of decoys for a hard-fork.
/// **There are exceptions to this always being the minimum decoys**
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/decoys.html#minimum-amount-of-decoys
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#default-minimum-decoys
pub(crate) fn minimum_decoys(hf: &HardFork) -> usize {
use HardFork as HF;
match hf {

View file

@ -6,7 +6,7 @@ use monero_serai::{
mlsag::{AggregateRingMatrixBuilder, MlsagError, RingMatrix},
RctPrunable, RctSignatures, RctType,
},
transaction::{Input, Output, Transaction},
transaction::{Input, Transaction},
H,
};
use multiexp::BatchVerifier;
@ -27,8 +27,6 @@ const GRANDFATHERED_TRANSACTIONS: [[u8; 32]; 2] = [
pub enum RingCTError {
#[error("The RingCT type used is not allowed.")]
TypeNotAllowed,
#[error("One or more of the outputs do not have a zero amount.")]
OutputNotZero,
#[error("RingCT simple: sum pseudo-outs does not equal outputs.")]
SimpleAmountDoNotBalance,
#[error("The borromean range proof is invalid.")]
@ -61,19 +59,6 @@ fn check_rct_type(ty: &RctType, hf: HardFork, tx_hash: &[u8; 32]) -> Result<(),
}
}
/// Checks all the outputs have a zero amount.
///
/// https://monero-book.cuprate.org/consensus_rules/ring_ct.html#output-amount
fn check_output_amount(outputs: &[Output]) -> Result<(), RingCTError> {
outputs.iter().try_for_each(|out| {
if out.amount.is_none() {
Ok(())
} else {
Err(RingCTError::OutputNotZero)
}
})
}
/// Checks that the pseudo-outs sum to the same point as the output commitments.
///
/// https://monero-book.cuprate.org/consensus_rules/ring_ct.html#pseudo-outs-outpks-balance
@ -133,28 +118,29 @@ fn check_output_range_proofs(
}
}
pub fn ring_ct_semantic_checks(
pub(crate) fn ring_ct_semantic_checks(
tx: &Transaction,
tx_hash: &[u8; 32],
verifier: &mut BatchVerifier<(), dalek_ff_group::EdwardsPoint>,
hf: &HardFork,
) -> Result<(), RingCTError> {
check_output_amount(&tx.prefix.outputs)?;
check_rct_type(&tx.rct_signatures.rct_type(), *hf, tx_hash)?;
let rct_type = tx.rct_signatures.rct_type();
check_rct_type(&rct_type, *hf, tx_hash)?;
check_output_range_proofs(&tx.rct_signatures, verifier)?;
if tx.rct_signatures.rct_type() != RctType::MlsagAggregate {
if rct_type != RctType::MlsagAggregate {
simple_type_balances(&tx.rct_signatures)?;
}
Ok(())
}
/// Check the input signatures, MLSAG, CLSAG.
/// Check the input signatures: MLSAG, CLSAG.
///
/// https://monero-book.cuprate.org/consensus_rules/ring_ct/mlsag.html
/// https://monero-book.cuprate.org/consensus_rules/ring_ct/clsag.html
pub fn check_input_signatures(
pub(crate) fn check_input_signatures(
msg: &[u8; 32],
inputs: &[Input],
rct_sig: &RctSignatures,

View file

@ -1,9 +1,9 @@
//! Version 1 ring signature verification.
//!
//! Some checks have to be done at deserialization or with data we don't have so we can't do them here, those checks are:
//! https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#signatures-must-be-canonical
//! https://monero-book.cuprate.org/consensus_rules/transactions/ring_signatures.html#signatures-must-be-canonical
//! this happens at deserialization in monero-serai.
//! https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#amount-of-signatures-in-a-ring
//! https://monero-book.cuprate.org/consensus_rules/transactions/ring_signatures.html#amount-of-signatures-in-a-ring
//! and this happens during ring signature verification in monero-serai.
//!
use monero_serai::{ring_signatures::RingSignature, transaction::Input};
@ -16,9 +16,8 @@ use crate::try_par_iter;
/// Verifies the ring signature.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#the-ring-signature-must-be-valid
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#amount-of-ring-signatures
pub fn verify_inputs_signatures(
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/ring_signatures.html
pub fn check_input_signatures(
inputs: &[Input],
signatures: &[RingSignature],
rings: &Rings,
@ -26,6 +25,7 @@ pub fn verify_inputs_signatures(
) -> Result<(), TransactionError> {
match rings {
Rings::Legacy(rings) => {
// https://monero-book.cuprate.org/consensus_rules/transactions/ring_signatures.html#amount-of-ring-signatures
// rings.len() != inputs.len() can't happen but check any way.
if signatures.len() != inputs.len() || rings.len() != inputs.len() {
return Err(TransactionError::RingSignatureIncorrect);

View file

@ -16,6 +16,8 @@ use rayon::prelude::*;
use tower::ServiceExt;
use tracing::instrument;
use monero_consensus::blocks::{penalty_free_zone, PENALTY_FREE_ZONE_5};
use crate::{
helper::{median, rayon_spawn_async},
Database, DatabaseRequest, DatabaseResponse, ExtendedConsensusError, HardFork,
@ -24,26 +26,9 @@ use crate::{
#[cfg(test)]
pub(super) mod tests;
const PENALTY_FREE_ZONE_1: usize = 20000;
const PENALTY_FREE_ZONE_2: usize = 60000;
const PENALTY_FREE_ZONE_5: usize = 300000;
const SHORT_TERM_WINDOW: u64 = 100;
const LONG_TERM_WINDOW: u64 = 100000;
/// Returns the penalty free zone
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks/weight_limit.html#penalty-free-zone
pub fn penalty_free_zone(hf: &HardFork) -> usize {
if hf == &HardFork::V1 {
PENALTY_FREE_ZONE_1
} else if hf >= &HardFork::V2 && hf < &HardFork::V5 {
PENALTY_FREE_ZONE_2
} else {
PENALTY_FREE_ZONE_5
}
}
/// Configuration for the block weight cache.
///
#[derive(Debug, Clone)]

View file

@ -8,6 +8,7 @@ use std::{
};
use futures::FutureExt;
use monero_serai::ringct::RctType;
use monero_serai::transaction::Transaction;
use rayon::prelude::*;
use tower::{Service, ServiceExt};
@ -50,16 +51,26 @@ impl TransactionVerificationData {
verifier: Arc<MultiThreadedBatchVerifier>,
) -> Result<TransactionVerificationData, ConsensusError> {
let tx_hash = tx.hash();
let tx_blob = tx.serialize();
// the tx weight is only different from the blobs length for bp(+) txs.
let tx_weight = match tx.rct_signatures.rct_type() {
RctType::Bulletproofs
| RctType::BulletproofsCompactAmount
| RctType::Clsag
| RctType::BulletproofsPlus => tx.weight(),
_ => tx_blob.len(),
};
let fee = verifier.queue_statement(|verifier| {
check_transaction_semantic(&tx, &tx_hash, hf, verifier)
check_transaction_semantic(&tx, tx_blob.len(), tx_weight, &tx_hash, hf, verifier)
.map_err(ConsensusError::Transaction)
})?;
Ok(TransactionVerificationData {
tx_hash,
tx_blob: tx.serialize(),
tx_weight: tx.weight(),
tx_blob,
tx_weight,
fee,
rings_member_info: std::sync::Mutex::new(None),
version: TxVersion::from_raw(tx.prefix.version)