mirror of
https://github.com/hinto-janai/cuprate.git
synced 2024-11-16 15:58:14 +00:00
add ringCT rules
This commit is contained in:
parent
84343a8297
commit
f037532610
5 changed files with 240 additions and 1 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -1147,15 +1147,20 @@ dependencies = [
|
||||||
"cryptonight-cuprate",
|
"cryptonight-cuprate",
|
||||||
"cuprate-common",
|
"cuprate-common",
|
||||||
"curve25519-dalek",
|
"curve25519-dalek",
|
||||||
|
"dalek-ff-group",
|
||||||
"hex",
|
"hex",
|
||||||
|
"hex-literal",
|
||||||
"monero-serai",
|
"monero-serai",
|
||||||
|
"multiexp",
|
||||||
"primitive-types",
|
"primitive-types",
|
||||||
"proptest",
|
"proptest",
|
||||||
"proptest-derive",
|
"proptest-derive",
|
||||||
|
"rand",
|
||||||
"rayon",
|
"rayon",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -35,7 +35,7 @@ opt-level = 3
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
async-trait = { version = "0.1.74" }
|
async-trait = { version = "0.1.74" }
|
||||||
borsh = { version = "1.2.1" }
|
borsh = { version = "1.2.1" }
|
||||||
bytes = { version = "1.5.0" }
|
bytes = { version = "1.5.0" }
|
||||||
clap = { version = "4.4.7" }
|
clap = { version = "4.4.7" }
|
||||||
chrono = { version = "0.4.31" }
|
chrono = { version = "0.4.31" }
|
||||||
|
|
|
@ -13,9 +13,15 @@ cryptonight-cuprate = {path = "../../cryptonight"}
|
||||||
cuprate-common = {path = "../../common"}
|
cuprate-common = {path = "../../common"}
|
||||||
|
|
||||||
monero-serai = { workspace = true }
|
monero-serai = { workspace = true }
|
||||||
|
multiexp = { workspace = true }
|
||||||
|
dalek-ff-group = { workspace = true }
|
||||||
|
zeroize = "1.7"
|
||||||
curve25519-dalek = { workspace = true }
|
curve25519-dalek = { workspace = true }
|
||||||
|
|
||||||
|
rand = { workspace = true }
|
||||||
|
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
hex-literal = "0.4"
|
||||||
primitive-types = { version = "0.12.2", default-features = false }
|
primitive-types = { version = "0.12.2", default-features = false }
|
||||||
|
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
|
|
@ -5,6 +5,7 @@ use monero_serai::transaction::{Input, Output, Timelock};
|
||||||
use crate::{check_point_canonically_encoded, is_decomposed_amount, HardFork};
|
use crate::{check_point_canonically_encoded, is_decomposed_amount, HardFork};
|
||||||
|
|
||||||
mod contextual_data;
|
mod contextual_data;
|
||||||
|
mod ring_ct;
|
||||||
pub use contextual_data::*;
|
pub use contextual_data::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||||
|
|
227
consensus/rules/src/transactions/ring_ct.rs
Normal file
227
consensus/rules/src/transactions/ring_ct.rs
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use curve25519_dalek::{EdwardsPoint, Scalar};
|
||||||
|
use hex_literal::hex;
|
||||||
|
use monero_serai::{
|
||||||
|
ringct::{
|
||||||
|
clsag::ClsagError,
|
||||||
|
mlsag::{AggregateRingMatrixBuilder, MlsagError, RingMatrix},
|
||||||
|
RctPrunable, RctSignatures, RctType,
|
||||||
|
},
|
||||||
|
transaction::{Input, Output},
|
||||||
|
H,
|
||||||
|
};
|
||||||
|
use multiexp::BatchVerifier;
|
||||||
|
use rand::thread_rng;
|
||||||
|
#[cfg(feature = "rayon")]
|
||||||
|
use rayon::prelude::*;
|
||||||
|
|
||||||
|
use crate::{transactions::Rings, try_par_iter, HardFork};
|
||||||
|
|
||||||
|
/// This constant contains the IDs of 2 transactions that should be allowed after the fork the ringCT
|
||||||
|
/// type they used should be banned.
|
||||||
|
const GRANDFATHERED_TRANSACTIONS: [[u8; 32]; 2] = [
|
||||||
|
hex!("c5151944f0583097ba0c88cd0f43e7fabb3881278aa2f73b3b0a007c5d34e910"),
|
||||||
|
hex!("6f2f117cde6fbcf8d4a6ef8974fcac744726574ac38cf25d3322c996b21edd4c"),
|
||||||
|
];
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||||
|
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.")]
|
||||||
|
BorromeanRangeInvalid,
|
||||||
|
#[error("The bulletproofs range proof is invalid.")]
|
||||||
|
BulletproofsRangeInvalid,
|
||||||
|
#[error("One or more input ring is invalid.")]
|
||||||
|
RingInvalid,
|
||||||
|
#[error("MLSAG Error: {0}.")]
|
||||||
|
MLSAGError(#[from] MlsagError),
|
||||||
|
#[error("CLSAG Error: {0}.")]
|
||||||
|
CLSAGError(#[from] ClsagError),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks the RingCT type is allowed for the current hard fork.
|
||||||
|
///
|
||||||
|
/// https://monero-book.cuprate.org/consensus_rules/ring_ct.html#type
|
||||||
|
fn check_rct_type(ty: &RctType, hf: HardFork, tx_hash: &[u8; 32]) -> Result<(), RingCTError> {
|
||||||
|
use HardFork as F;
|
||||||
|
use RctType as T;
|
||||||
|
|
||||||
|
match ty {
|
||||||
|
T::MlsagAggregate | T::MlsagIndividual if hf >= F::V4 && hf < F::V9 => Ok(()),
|
||||||
|
T::Bulletproofs if hf >= F::V8 && hf < F::V11 => Ok(()),
|
||||||
|
T::BulletproofsCompactAmount if hf >= F::V10 && hf < F::V14 => Ok(()),
|
||||||
|
T::BulletproofsCompactAmount if GRANDFATHERED_TRANSACTIONS.contains(tx_hash) => Ok(()),
|
||||||
|
T::Clsag if hf >= F::V13 && hf < F::V16 => Ok(()),
|
||||||
|
T::BulletproofsPlus if hf >= F::V15 => Ok(()),
|
||||||
|
_ => Err(RingCTError::TypeNotAllowed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
fn simple_type_balances(rct_sig: &RctSignatures) -> Result<(), RingCTError> {
|
||||||
|
let pseudo_outs = if rct_sig.rct_type() == RctType::MlsagIndividual {
|
||||||
|
&rct_sig.base.pseudo_outs
|
||||||
|
} else {
|
||||||
|
match &rct_sig.prunable {
|
||||||
|
RctPrunable::Clsag { pseudo_outs, .. }
|
||||||
|
| RctPrunable::MlsagBulletproofs { pseudo_outs, .. } => pseudo_outs,
|
||||||
|
_ => panic!("RingCT type is not simple!"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let sum_inputs = pseudo_outs.iter().sum::<EdwardsPoint>();
|
||||||
|
let sum_outputs = rct_sig.base.commitments.iter().sum::<EdwardsPoint>()
|
||||||
|
+ Scalar::from(rct_sig.base.fee) * H();
|
||||||
|
|
||||||
|
if sum_inputs == sum_outputs {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(RingCTError::SimpleAmountDoNotBalance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks the outputs range proof(s)
|
||||||
|
///
|
||||||
|
/// https://monero-book.cuprate.org/consensus_rules/ring_ct/borromean.html
|
||||||
|
/// https://monero-book.cuprate.org/consensus_rules/ring_ct/bulletproofs.html
|
||||||
|
/// https://monero-book.cuprate.org/consensus_rules/ring_ct/bulletproofs+.html
|
||||||
|
fn check_output_range_proofs(
|
||||||
|
rct_sig: &RctSignatures,
|
||||||
|
verifier: &mut BatchVerifier<(), dalek_ff_group::EdwardsPoint>,
|
||||||
|
) -> Result<(), RingCTError> {
|
||||||
|
let commitments = &rct_sig.base.commitments;
|
||||||
|
|
||||||
|
match &rct_sig.prunable {
|
||||||
|
RctPrunable::Null => Err(RingCTError::TypeNotAllowed)?,
|
||||||
|
RctPrunable::MlsagBorromean { borromean, .. }
|
||||||
|
| RctPrunable::AggregateMlsagBorromean { borromean, .. } => try_par_iter(borromean)
|
||||||
|
.zip(commitments)
|
||||||
|
.try_for_each(|(borro, commitment)| {
|
||||||
|
if borro.verify(commitment) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(RingCTError::BorromeanRangeInvalid)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
RctPrunable::MlsagBulletproofs { bulletproofs, .. }
|
||||||
|
| RctPrunable::Clsag { bulletproofs, .. } => {
|
||||||
|
if bulletproofs.batch_verify(&mut thread_rng(), verifier, (), commitments) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(RingCTError::BulletproofsRangeInvalid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
fn check_input_signatures(
|
||||||
|
msg: &[u8; 32],
|
||||||
|
inputs: &[Input],
|
||||||
|
rct_sig: &RctSignatures,
|
||||||
|
rings: &Rings,
|
||||||
|
) -> Result<(), RingCTError> {
|
||||||
|
let Rings::RingCT(rings) = rings else {
|
||||||
|
panic!("Tried to verify RCT transaction without RCT ring");
|
||||||
|
};
|
||||||
|
|
||||||
|
if rings.is_empty() {
|
||||||
|
Err(RingCTError::RingInvalid)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
match &rct_sig.prunable {
|
||||||
|
RctPrunable::Null => Err(RingCTError::TypeNotAllowed)?,
|
||||||
|
RctPrunable::AggregateMlsagBorromean { mlsag, .. } => {
|
||||||
|
let key_images = inputs
|
||||||
|
.iter()
|
||||||
|
.map(|inp| {
|
||||||
|
let Input::ToKey { key_image, .. } = inp else {
|
||||||
|
panic!("How did we build a ring with no decoys?");
|
||||||
|
};
|
||||||
|
*key_image
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut matrix =
|
||||||
|
AggregateRingMatrixBuilder::new(&rct_sig.base.commitments, rct_sig.base.fee);
|
||||||
|
rings.iter().try_for_each(|ring| matrix.push_ring(ring))?;
|
||||||
|
Ok(mlsag.verify(msg, &matrix.build()?, &key_images)?)
|
||||||
|
}
|
||||||
|
RctPrunable::MlsagBorromean { mlsags, .. }
|
||||||
|
| RctPrunable::MlsagBulletproofs { mlsags, .. } => try_par_iter(mlsags)
|
||||||
|
.zip(&rct_sig.base.pseudo_outs)
|
||||||
|
.zip(inputs)
|
||||||
|
.zip(rings)
|
||||||
|
.try_for_each(|(((mlsag, pseudo_out), input), ring)| {
|
||||||
|
let Input::ToKey { key_image, .. } = input else {
|
||||||
|
panic!("How did we build a ring with no decoys?");
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(mlsag.verify(
|
||||||
|
msg,
|
||||||
|
&RingMatrix::individual(ring, *pseudo_out)?,
|
||||||
|
&[*key_image],
|
||||||
|
)?)
|
||||||
|
}),
|
||||||
|
RctPrunable::Clsag { clsags, .. } => try_par_iter(clsags)
|
||||||
|
.zip(&rct_sig.base.pseudo_outs)
|
||||||
|
.zip(inputs)
|
||||||
|
.zip(rings)
|
||||||
|
.try_for_each(|(((clsags, pseudo_out), input), ring)| {
|
||||||
|
let Input::ToKey { key_image, .. } = input else {
|
||||||
|
panic!("How did we build a ring with no decoys?");
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(clsags.verify(ring, key_image, pseudo_out, msg)?)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn grandfathered_bulletproofs2() {
|
||||||
|
assert!(
|
||||||
|
check_rct_type(&RctType::BulletproofsCompactAmount, HardFork::V14, &[0; 32]).is_err()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(check_rct_type(
|
||||||
|
&RctType::BulletproofsCompactAmount,
|
||||||
|
HardFork::V14,
|
||||||
|
&GRANDFATHERED_TRANSACTIONS[0]
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
assert!(check_rct_type(
|
||||||
|
&RctType::BulletproofsCompactAmount,
|
||||||
|
HardFork::V14,
|
||||||
|
&GRANDFATHERED_TRANSACTIONS[1]
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue