mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-03 17:40:34 +00:00
Add support for pruned transactions to monero-serai
Some checks failed
coins/ Tests / test-coins (push) Has been cancelled
Coordinator Tests / build (push) Has been cancelled
Full Stack Tests / build (push) Has been cancelled
Lint / clippy (macos-13) (push) Has been cancelled
Lint / clippy (macos-14) (push) Has been cancelled
Lint / clippy (ubuntu-latest) (push) Has been cancelled
Lint / clippy (windows-latest) (push) Has been cancelled
Lint / deny (push) Has been cancelled
Lint / fmt (push) Has been cancelled
Lint / machete (push) Has been cancelled
Monero Tests / unit-tests (push) Has been cancelled
Monero Tests / integration-tests (v0.17.3.2) (push) Has been cancelled
Monero Tests / integration-tests (v0.18.2.0) (push) Has been cancelled
no-std build / build (push) Has been cancelled
Processor Tests / build (push) Has been cancelled
Tests / test-infra (push) Has been cancelled
Tests / test-substrate (push) Has been cancelled
Tests / test-serai-client (push) Has been cancelled
Some checks failed
coins/ Tests / test-coins (push) Has been cancelled
Coordinator Tests / build (push) Has been cancelled
Full Stack Tests / build (push) Has been cancelled
Lint / clippy (macos-13) (push) Has been cancelled
Lint / clippy (macos-14) (push) Has been cancelled
Lint / clippy (ubuntu-latest) (push) Has been cancelled
Lint / clippy (windows-latest) (push) Has been cancelled
Lint / deny (push) Has been cancelled
Lint / fmt (push) Has been cancelled
Lint / machete (push) Has been cancelled
Monero Tests / unit-tests (push) Has been cancelled
Monero Tests / integration-tests (v0.17.3.2) (push) Has been cancelled
Monero Tests / integration-tests (v0.18.2.0) (push) Has been cancelled
no-std build / build (push) Has been cancelled
Processor Tests / build (push) Has been cancelled
Tests / test-infra (push) Has been cancelled
Tests / test-substrate (push) Has been cancelled
Tests / test-serai-client (push) Has been cancelled
This commit is contained in:
parent
70fe52437c
commit
7b8bcae396
2 changed files with 208 additions and 54 deletions
|
@ -87,8 +87,8 @@ pub enum RctType {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RctType> for u8 {
|
impl From<RctType> for u8 {
|
||||||
fn from(kind: RctType) -> u8 {
|
fn from(rct_type: RctType) -> u8 {
|
||||||
match kind {
|
match rct_type {
|
||||||
RctType::AggregateMlsagBorromean => 1,
|
RctType::AggregateMlsagBorromean => 1,
|
||||||
RctType::MlsagBorromean => 2,
|
RctType::MlsagBorromean => 2,
|
||||||
RctType::MlsagBulletproofs => 3,
|
RctType::MlsagBulletproofs => 3,
|
||||||
|
@ -459,3 +459,12 @@ impl RctProofs {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A pruned set of RingCT proofs.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct PrunedRctProofs {
|
||||||
|
/// The type of RctProofs this used to be.
|
||||||
|
pub rct_type: RctType,
|
||||||
|
/// The data necessary for handling this transaction.
|
||||||
|
pub base: RctBase,
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use crate::{
|
||||||
io::*,
|
io::*,
|
||||||
primitives::keccak256,
|
primitives::keccak256,
|
||||||
ring_signatures::RingSignature,
|
ring_signatures::RingSignature,
|
||||||
ringct::{bulletproofs::Bulletproof, RctProofs},
|
ringct::{bulletproofs::Bulletproof, PrunedRctProofs},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An input in the Monero protocol.
|
/// An input in the Monero protocol.
|
||||||
|
@ -265,27 +265,143 @@ impl TransactionPrefix {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Monero transaction.
|
mod sealed {
|
||||||
#[allow(clippy::large_enum_variant)]
|
use core::fmt::Debug;
|
||||||
|
use crate::ringct::*;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(crate) trait RingSignatures: Clone + PartialEq + Eq + Default + Debug {
|
||||||
|
fn signatures_to_write(&self) -> &[RingSignature];
|
||||||
|
fn read_signatures(inputs: &[Input], r: &mut impl Read) -> io::Result<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RingSignatures for Vec<RingSignature> {
|
||||||
|
fn signatures_to_write(&self) -> &[RingSignature] {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn read_signatures(inputs: &[Input], r: &mut impl Read) -> io::Result<Self> {
|
||||||
|
let mut signatures = Vec::with_capacity(inputs.len());
|
||||||
|
for input in inputs {
|
||||||
|
match input {
|
||||||
|
Input::ToKey { key_offsets, .. } => {
|
||||||
|
signatures.push(RingSignature::read(key_offsets.len(), r)?)
|
||||||
|
}
|
||||||
|
_ => Err(io::Error::other("reading signatures for a transaction with non-ToKey inputs"))?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(signatures)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RingSignatures for () {
|
||||||
|
fn signatures_to_write(&self) -> &[RingSignature] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
fn read_signatures(_: &[Input], _: &mut impl Read) -> io::Result<Self> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait RctProofsTrait: Clone + PartialEq + Eq + Debug {
|
||||||
|
fn write(&self, w: &mut impl Write) -> io::Result<()>;
|
||||||
|
fn read(
|
||||||
|
ring_length: usize,
|
||||||
|
inputs: usize,
|
||||||
|
outputs: usize,
|
||||||
|
r: &mut impl Read,
|
||||||
|
) -> io::Result<Option<Self>>;
|
||||||
|
fn rct_type(&self) -> RctType;
|
||||||
|
fn base(&self) -> &RctBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RctProofsTrait for RctProofs {
|
||||||
|
fn write(&self, w: &mut impl Write) -> io::Result<()> {
|
||||||
|
self.write(w)
|
||||||
|
}
|
||||||
|
fn read(
|
||||||
|
ring_length: usize,
|
||||||
|
inputs: usize,
|
||||||
|
outputs: usize,
|
||||||
|
r: &mut impl Read,
|
||||||
|
) -> io::Result<Option<Self>> {
|
||||||
|
RctProofs::read(ring_length, inputs, outputs, r)
|
||||||
|
}
|
||||||
|
fn rct_type(&self) -> RctType {
|
||||||
|
self.rct_type()
|
||||||
|
}
|
||||||
|
fn base(&self) -> &RctBase {
|
||||||
|
&self.base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RctProofsTrait for PrunedRctProofs {
|
||||||
|
fn write(&self, w: &mut impl Write) -> io::Result<()> {
|
||||||
|
self.base.write(w, self.rct_type)
|
||||||
|
}
|
||||||
|
fn read(
|
||||||
|
_ring_length: usize,
|
||||||
|
inputs: usize,
|
||||||
|
outputs: usize,
|
||||||
|
r: &mut impl Read,
|
||||||
|
) -> io::Result<Option<Self>> {
|
||||||
|
Ok(RctBase::read(inputs, outputs, r)?.map(|(rct_type, base)| Self { rct_type, base }))
|
||||||
|
}
|
||||||
|
fn rct_type(&self) -> RctType {
|
||||||
|
self.rct_type
|
||||||
|
}
|
||||||
|
fn base(&self) -> &RctBase {
|
||||||
|
&self.base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait PotentiallyPruned {
|
||||||
|
type RingSignatures: RingSignatures;
|
||||||
|
type RctProofs: RctProofsTrait;
|
||||||
|
}
|
||||||
|
/// A transaction which isn't pruned.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum Transaction {
|
pub struct NotPruned;
|
||||||
|
impl PotentiallyPruned for NotPruned {
|
||||||
|
type RingSignatures = Vec<RingSignature>;
|
||||||
|
type RctProofs = RctProofs;
|
||||||
|
}
|
||||||
|
/// A transaction which is pruned.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Pruned;
|
||||||
|
impl PotentiallyPruned for Pruned {
|
||||||
|
type RingSignatures = ();
|
||||||
|
type RctProofs = PrunedRctProofs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub use sealed::*;
|
||||||
|
|
||||||
|
/// A Monero transaction.
|
||||||
|
#[allow(private_bounds, private_interfaces, clippy::large_enum_variant)]
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum Transaction<P: PotentiallyPruned = NotPruned> {
|
||||||
/// A version 1 transaction, used by the original Cryptonote codebase.
|
/// A version 1 transaction, used by the original Cryptonote codebase.
|
||||||
V1 {
|
V1 {
|
||||||
/// The transaction's prefix.
|
/// The transaction's prefix.
|
||||||
prefix: TransactionPrefix,
|
prefix: TransactionPrefix,
|
||||||
/// The transaction's ring signatures.
|
/// The transaction's ring signatures.
|
||||||
signatures: Vec<RingSignature>,
|
signatures: P::RingSignatures,
|
||||||
},
|
},
|
||||||
/// A version 2 transaction, used by the RingCT protocol.
|
/// A version 2 transaction, used by the RingCT protocol.
|
||||||
V2 {
|
V2 {
|
||||||
/// The transaction's prefix.
|
/// The transaction's prefix.
|
||||||
prefix: TransactionPrefix,
|
prefix: TransactionPrefix,
|
||||||
/// The transaction's proofs.
|
/// The transaction's proofs.
|
||||||
proofs: Option<RctProofs>,
|
proofs: Option<P::RctProofs>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transaction {
|
enum PrunableHash<'a> {
|
||||||
|
V1(&'a [RingSignature]),
|
||||||
|
V2([u8; 32]),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(private_bounds)]
|
||||||
|
impl<P: PotentiallyPruned> Transaction<P> {
|
||||||
/// Get the version of this transaction.
|
/// Get the version of this transaction.
|
||||||
pub fn version(&self) -> u8 {
|
pub fn version(&self) -> u8 {
|
||||||
match self {
|
match self {
|
||||||
|
@ -317,7 +433,7 @@ impl Transaction {
|
||||||
match self {
|
match self {
|
||||||
Transaction::V1 { prefix, signatures } => {
|
Transaction::V1 { prefix, signatures } => {
|
||||||
prefix.write(w)?;
|
prefix.write(w)?;
|
||||||
for ring_sig in signatures {
|
for ring_sig in signatures.signatures_to_write() {
|
||||||
ring_sig.write(w)?;
|
ring_sig.write(w)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -340,31 +456,20 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read a Transaction.
|
/// Read a Transaction.
|
||||||
pub fn read<R: Read>(r: &mut R) -> io::Result<Transaction> {
|
pub fn read<R: Read>(r: &mut R) -> io::Result<Self> {
|
||||||
let version = read_varint(r)?;
|
let version = read_varint(r)?;
|
||||||
let prefix = TransactionPrefix::read(r, version)?;
|
let prefix = TransactionPrefix::read(r, version)?;
|
||||||
|
|
||||||
if version == 1 {
|
if version == 1 {
|
||||||
let signatures = if (prefix.inputs.len() == 1) && matches!(prefix.inputs[0], Input::Gen(_)) {
|
let signatures = if (prefix.inputs.len() == 1) && matches!(prefix.inputs[0], Input::Gen(_)) {
|
||||||
vec![]
|
Default::default()
|
||||||
} else {
|
} else {
|
||||||
let mut signatures = Vec::with_capacity(prefix.inputs.len());
|
P::RingSignatures::read_signatures(&prefix.inputs, r)?
|
||||||
for input in &prefix.inputs {
|
|
||||||
match input {
|
|
||||||
Input::ToKey { key_offsets, .. } => {
|
|
||||||
signatures.push(RingSignature::read(key_offsets.len(), r)?)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
Err(io::Error::other("reading signatures for a transaction with non-ToKey inputs"))?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
signatures
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Transaction::V1 { prefix, signatures })
|
Ok(Transaction::V1 { prefix, signatures })
|
||||||
} else if version == 2 {
|
} else if version == 2 {
|
||||||
let proofs = RctProofs::read(
|
let proofs = P::RctProofs::read(
|
||||||
prefix.inputs.first().map_or(0, |input| match input {
|
prefix.inputs.first().map_or(0, |input| match input {
|
||||||
Input::Gen(_) => 0,
|
Input::Gen(_) => 0,
|
||||||
Input::ToKey { key_offsets, .. } => key_offsets.len(),
|
Input::ToKey { key_offsets, .. } => key_offsets.len(),
|
||||||
|
@ -380,12 +485,25 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The hash of the transaction.
|
// The hash of the transaction.
|
||||||
pub fn hash(&self) -> [u8; 32] {
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
let mut buf = Vec::with_capacity(2048);
|
fn hash_with_prunable_hash(&self, prunable: PrunableHash<'_>) -> [u8; 32] {
|
||||||
match self {
|
match self {
|
||||||
Transaction::V1 { .. } => {
|
Transaction::V1 { prefix, .. } => {
|
||||||
self.write(&mut buf).unwrap();
|
let mut buf = Vec::with_capacity(512);
|
||||||
|
|
||||||
|
// We don't use `self.write` as that may write the signatures (if this isn't pruned)
|
||||||
|
write_varint(&self.version(), &mut buf).unwrap();
|
||||||
|
prefix.write(&mut buf).unwrap();
|
||||||
|
|
||||||
|
// We explicitly write the signatures ourselves here
|
||||||
|
let PrunableHash::V1(signatures) = prunable else {
|
||||||
|
panic!("hashing v1 TX with non-v1 prunable data")
|
||||||
|
};
|
||||||
|
for signature in signatures {
|
||||||
|
signature.write(&mut buf).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
keccak256(buf)
|
keccak256(buf)
|
||||||
}
|
}
|
||||||
Transaction::V2 { prefix, proofs } => {
|
Transaction::V2 { prefix, proofs } => {
|
||||||
|
@ -394,47 +512,61 @@ impl Transaction {
|
||||||
hashes.extend(prefix.hash(2));
|
hashes.extend(prefix.hash(2));
|
||||||
|
|
||||||
if let Some(proofs) = proofs {
|
if let Some(proofs) = proofs {
|
||||||
let rct_type = proofs.rct_type();
|
let mut buf = Vec::with_capacity(512);
|
||||||
proofs.base.write(&mut buf, rct_type).unwrap();
|
proofs.base().write(&mut buf, proofs.rct_type()).unwrap();
|
||||||
hashes.extend(keccak256(&buf));
|
hashes.extend(keccak256(&buf));
|
||||||
buf.clear();
|
|
||||||
|
|
||||||
proofs.prunable.write(&mut buf, rct_type).unwrap();
|
|
||||||
hashes.extend(keccak256(buf));
|
|
||||||
} else {
|
} else {
|
||||||
// Serialization of RctBase::Null
|
// Serialization of RctBase::Null
|
||||||
hashes.extend(keccak256([0]));
|
hashes.extend(keccak256([0]));
|
||||||
hashes.extend([0; 32]);
|
|
||||||
}
|
}
|
||||||
|
let PrunableHash::V2(prunable_hash) = prunable else {
|
||||||
|
panic!("hashing v2 TX with non-v2 prunable data")
|
||||||
|
};
|
||||||
|
hashes.extend(prunable_hash);
|
||||||
|
|
||||||
keccak256(hashes)
|
keccak256(hashes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Transaction<NotPruned> {
|
||||||
|
/// The hash of the transaction.
|
||||||
|
pub fn hash(&self) -> [u8; 32] {
|
||||||
|
match self {
|
||||||
|
Transaction::V1 { signatures, .. } => {
|
||||||
|
self.hash_with_prunable_hash(PrunableHash::V1(signatures))
|
||||||
|
}
|
||||||
|
Transaction::V2 { proofs, .. } => {
|
||||||
|
self.hash_with_prunable_hash(PrunableHash::V2(if let Some(proofs) = proofs {
|
||||||
|
let mut buf = Vec::with_capacity(1024);
|
||||||
|
proofs.prunable.write(&mut buf, proofs.rct_type()).unwrap();
|
||||||
|
keccak256(buf)
|
||||||
|
} else {
|
||||||
|
[0; 32]
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Calculate the hash of this transaction as needed for signing it.
|
/// Calculate the hash of this transaction as needed for signing it.
|
||||||
///
|
///
|
||||||
/// This returns None if the transaction is without signatures.
|
/// This returns None if the transaction is without signatures.
|
||||||
pub fn signature_hash(&self) -> Option<[u8; 32]> {
|
pub fn signature_hash(&self) -> Option<[u8; 32]> {
|
||||||
match self {
|
Some(match self {
|
||||||
Transaction::V1 { prefix, .. } => Some(prefix.hash(1)),
|
Transaction::V1 { prefix, signatures } => {
|
||||||
Transaction::V2 { prefix, proofs } => {
|
if (prefix.inputs.len() == 1) && matches!(prefix.inputs[0], Input::Gen(_)) {
|
||||||
let mut buf = Vec::with_capacity(2048);
|
None?;
|
||||||
let mut sig_hash = Vec::with_capacity(96);
|
}
|
||||||
|
self.hash_with_prunable_hash(PrunableHash::V1(signatures))
|
||||||
sig_hash.extend(prefix.hash(2));
|
}
|
||||||
|
Transaction::V2 { proofs, .. } => self.hash_with_prunable_hash({
|
||||||
let proofs = proofs.as_ref()?;
|
let Some(proofs) = proofs else { None? };
|
||||||
proofs.base.write(&mut buf, proofs.rct_type()).unwrap();
|
let mut buf = Vec::with_capacity(1024);
|
||||||
sig_hash.extend(keccak256(&buf));
|
|
||||||
buf.clear();
|
|
||||||
|
|
||||||
proofs.prunable.signature_write(&mut buf).unwrap();
|
proofs.prunable.signature_write(&mut buf).unwrap();
|
||||||
sig_hash.extend(keccak256(buf));
|
PrunableHash::V2(keccak256(buf))
|
||||||
|
}),
|
||||||
Some(keccak256(sig_hash))
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_rct_bulletproof(&self) -> bool {
|
fn is_rct_bulletproof(&self) -> bool {
|
||||||
|
@ -478,3 +610,16 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Transaction<NotPruned>> for Transaction<Pruned> {
|
||||||
|
fn from(tx: Transaction<NotPruned>) -> Transaction<Pruned> {
|
||||||
|
match tx {
|
||||||
|
Transaction::V1 { prefix, .. } => Transaction::V1 { prefix, signatures: () },
|
||||||
|
Transaction::V2 { prefix, proofs } => Transaction::V2 {
|
||||||
|
prefix,
|
||||||
|
proofs: proofs
|
||||||
|
.map(|proofs| PrunedRctProofs { rct_type: proofs.rct_type(), base: proofs.base }),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue