From 5d9067b84d54ecf2c2fdcb57edfadf3b1c732116 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 8 Jul 2023 21:09:22 -0400 Subject: [PATCH] Update reserialize_chain for v1 and migration TXs Also always marks 0-amount inputs as RCT due to impossibility of non-RCT 0-amount outputs. --- coins/monero/src/bin/reserialize_chain.rs | 43 +++++++++++++++++++---- coins/monero/src/transaction.rs | 15 +++++--- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/coins/monero/src/bin/reserialize_chain.rs b/coins/monero/src/bin/reserialize_chain.rs index 31e79ab7..c293fd07 100644 --- a/coins/monero/src/bin/reserialize_chain.rs +++ b/coins/monero/src/bin/reserialize_chain.rs @@ -1,11 +1,15 @@ use std::sync::Arc; -use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint}; +use curve25519_dalek::{ + scalar::Scalar, + edwards::{CompressedEdwardsY, EdwardsPoint}, +}; use serde::Deserialize; use serde_json::json; use monero_serai::{ + Commitment, ringct::RctPrunable, transaction::{Input, Transaction}, block::Block, @@ -82,6 +86,12 @@ async fn check_block(rpc: Arc>, block_i: usize) { ); assert_eq!(tx.hash(), tx_hash, "Transaction hash was different"); + if matches!(tx.rct_signatures.prunable, RctPrunable::Null) { + assert_eq!(tx.prefix.version, 1); + assert!(!tx.signatures.is_empty()); + continue; + } + let sig_hash = tx.signature_hash(); // Verify all proofs we support proving for // This is due to having debug_asserts calling verify within their proving, and CLSAG @@ -97,9 +107,9 @@ async fn check_block(rpc: Arc>, block_i: usize) { assert!(bulletproofs.verify(&mut rand_core::OsRng, &tx.rct_signatures.base.commitments)); for (i, clsag) in clsags.into_iter().enumerate() { - let (image, key_offsets) = match &tx.prefix.inputs[i] { + let (amount, key_offsets, image) = match &tx.prefix.inputs[i] { Input::Gen(_) => panic!("Input::Gen"), - Input::ToKey { key_image, key_offsets, .. } => (key_image, key_offsets), + Input::ToKey { amount, key_offsets, key_image } => (amount, key_offsets, key_image), }; let mut running_sum = 0; @@ -109,7 +119,11 @@ async fn check_block(rpc: Arc>, block_i: usize) { actual_indexes.push(running_sum); } - async fn get_outs(rpc: &Rpc, indexes: &[u64]) -> Vec<[EdwardsPoint; 2]> { + async fn get_outs( + rpc: &Rpc, + amount: u64, + indexes: &[u64], + ) -> Vec<[EdwardsPoint; 2]> { #[derive(Deserialize, Debug)] struct Out { key: String, @@ -127,7 +141,7 @@ async fn check_block(rpc: Arc>, block_i: usize) { Some(json!({ "get_txid": true, "outputs": indexes.iter().map(|o| json!({ - "amount": 0, + "amount": amount, "index": o })).collect::>() })), @@ -146,11 +160,26 @@ async fn check_block(rpc: Arc>, block_i: usize) { .expect("invalid point for ring member") }; - outs.outs.iter().map(|out| [rpc_point(&out.key), rpc_point(&out.mask)]).collect() + outs + .outs + .iter() + .map(|out| { + let mask = rpc_point(&out.mask); + if amount != 0 { + assert_eq!(mask, Commitment::new(Scalar::from(1u8), amount).calculate()); + } + [rpc_point(&out.key), mask] + }) + .collect() } clsag - .verify(&get_outs(&rpc, &actual_indexes).await, image, &pseudo_outs[i], &sig_hash) + .verify( + &get_outs(&rpc, amount.unwrap_or(0), &actual_indexes).await, + image, + &pseudo_outs[i], + &sig_hash, + ) .unwrap(); } } diff --git a/coins/monero/src/transaction.rs b/coins/monero/src/transaction.rs index f6189ac9..84e40fe5 100644 --- a/coins/monero/src/transaction.rs +++ b/coins/monero/src/transaction.rs @@ -53,12 +53,17 @@ impl Input { res } - pub fn read(interpret_as_rct: bool, r: &mut R) -> io::Result { + pub fn read(r: &mut R) -> io::Result { Ok(match read_byte(r)? { 255 => Input::Gen(read_varint(r)?), 2 => { let amount = read_varint(r)?; - let amount = if (amount == 0) && interpret_as_rct { None } else { Some(amount) }; + // https://github.com/monero-project/monero/ + // blob/00fd416a99686f0956361d1cd0337fe56e58d4a7/ + // src/cryptonote_basic/cryptonote_format_utils.cpp#L860-L863 + // A non-RCT 0-amount input can't exist because only RCT TXs can have a 0-amount output + // That's why collapsing to None if the amount is 0 is safe, even without knowing if RCT + let amount = if amount == 0 { None } else { Some(amount) }; Input::ToKey { amount, key_offsets: read_vec(read_varint, r)?, @@ -101,9 +106,9 @@ impl Output { res } - pub fn read(interpret_as_rct: bool, r: &mut R) -> io::Result { + pub fn read(rct: bool, r: &mut R) -> io::Result { let amount = read_varint(r)?; - let amount = if interpret_as_rct { + let amount = if rct { if amount != 0 { Err(io::Error::new(io::ErrorKind::Other, "RCT TX output wasn't 0"))?; } @@ -215,7 +220,7 @@ impl TransactionPrefix { let timelock = Timelock::from_raw(read_varint(r)?); - let inputs = read_vec(|r| Input::read(version == 2, r), r)?; + let inputs = read_vec(|r| Input::read(r), r)?; if inputs.is_empty() { Err(io::Error::new(io::ErrorKind::Other, "transaction had no inputs"))?; }