mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-11 21:49:26 +00:00
Finish documenting monero-serai
This commit is contained in:
@@ -270,7 +270,7 @@ pub trait Rpc: Sync + Clone + Debug {
|
|||||||
async fn get_protocol(&self) -> Result<u8, RpcError> {
|
async fn get_protocol(&self) -> Result<u8, RpcError> {
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct ProtocolResponse {
|
struct ProtocolResponse {
|
||||||
major_version: u8,
|
hardfork_version: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
@@ -283,7 +283,7 @@ pub trait Rpc: Sync + Clone + Debug {
|
|||||||
.json_rpc_call::<LastHeaderResponse>("get_last_block_header", None)
|
.json_rpc_call::<LastHeaderResponse>("get_last_block_header", None)
|
||||||
.await?
|
.await?
|
||||||
.block_header
|
.block_header
|
||||||
.major_version,
|
.hardfork_version,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ mod binaries {
|
|||||||
);
|
);
|
||||||
assert_eq!(tx.hash(), tx_hash, "Transaction hash was different");
|
assert_eq!(tx.hash(), tx_hash, "Transaction hash was different");
|
||||||
|
|
||||||
if matches!(tx.rct_signatures.prunable, RctPrunable::Null) {
|
if matches!(tx.proofs.prunable, RctPrunable::Null) {
|
||||||
assert_eq!(tx.prefix.version, 1);
|
assert_eq!(tx.prefix.version, 1);
|
||||||
assert!(!tx.signatures.is_empty());
|
assert!(!tx.signatures.is_empty());
|
||||||
continue;
|
continue;
|
||||||
@@ -126,7 +126,7 @@ mod binaries {
|
|||||||
// multisig explicitly calling verify as part of its signing process
|
// multisig explicitly calling verify as part of its signing process
|
||||||
// Accordingly, making sure our signature_hash algorithm is correct is great, and further
|
// Accordingly, making sure our signature_hash algorithm is correct is great, and further
|
||||||
// making sure the verification functions are valid is appreciated
|
// making sure the verification functions are valid is appreciated
|
||||||
match tx.rct_signatures.prunable {
|
match tx.proofs.prunable {
|
||||||
RctPrunable::Null |
|
RctPrunable::Null |
|
||||||
RctPrunable::AggregateMlsagBorromean { .. } |
|
RctPrunable::AggregateMlsagBorromean { .. } |
|
||||||
RctPrunable::MlsagBorromean { .. } => {}
|
RctPrunable::MlsagBorromean { .. } => {}
|
||||||
@@ -134,14 +134,14 @@ mod binaries {
|
|||||||
assert!(bulletproofs.batch_verify(
|
assert!(bulletproofs.batch_verify(
|
||||||
&mut rand_core::OsRng,
|
&mut rand_core::OsRng,
|
||||||
&mut batch,
|
&mut batch,
|
||||||
&tx.rct_signatures.base.commitments
|
&tx.proofs.base.commitments
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
RctPrunable::Clsag { bulletproofs, clsags, pseudo_outs } => {
|
RctPrunable::Clsag { bulletproofs, clsags, pseudo_outs } => {
|
||||||
assert!(bulletproofs.batch_verify(
|
assert!(bulletproofs.batch_verify(
|
||||||
&mut rand_core::OsRng,
|
&mut rand_core::OsRng,
|
||||||
&mut batch,
|
&mut batch,
|
||||||
&tx.rct_signatures.base.commitments
|
&tx.proofs.base.commitments
|
||||||
));
|
));
|
||||||
|
|
||||||
for (i, clsag) in clsags.into_iter().enumerate() {
|
for (i, clsag) in clsags.into_iter().enumerate() {
|
||||||
|
|||||||
@@ -18,10 +18,14 @@ const EXISTING_BLOCK_HASH_202612: [u8; 32] =
|
|||||||
/// A Monero block's header.
|
/// A Monero block's header.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct BlockHeader {
|
pub struct BlockHeader {
|
||||||
/// The major version of the protocol, denoting the hard fork.
|
/// The hard fork of the protocol this block follows.
|
||||||
pub major_version: u8,
|
///
|
||||||
/// The minor version of the protocol.
|
/// Per the C++ codebase, this is the `major_version`.
|
||||||
pub minor_version: u8,
|
pub hardfork_version: u8,
|
||||||
|
/// A signal for a proposed hard fork.
|
||||||
|
///
|
||||||
|
/// Per the C++ codebase, this is the `minor_version`.
|
||||||
|
pub hardfork_signal: u8,
|
||||||
/// Seconds since the epoch.
|
/// Seconds since the epoch.
|
||||||
pub timestamp: u64,
|
pub timestamp: u64,
|
||||||
/// The previous block's hash.
|
/// The previous block's hash.
|
||||||
@@ -36,8 +40,8 @@ pub struct BlockHeader {
|
|||||||
impl BlockHeader {
|
impl BlockHeader {
|
||||||
/// Write the BlockHeader.
|
/// Write the BlockHeader.
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
write_varint(&self.major_version, w)?;
|
write_varint(&self.hardfork_version, w)?;
|
||||||
write_varint(&self.minor_version, w)?;
|
write_varint(&self.hardfork_signal, w)?;
|
||||||
write_varint(&self.timestamp, w)?;
|
write_varint(&self.timestamp, w)?;
|
||||||
w.write_all(&self.previous)?;
|
w.write_all(&self.previous)?;
|
||||||
w.write_all(&self.nonce.to_le_bytes())
|
w.write_all(&self.nonce.to_le_bytes())
|
||||||
@@ -53,8 +57,8 @@ impl BlockHeader {
|
|||||||
/// Read a BlockHeader.
|
/// Read a BlockHeader.
|
||||||
pub fn read<R: Read>(r: &mut R) -> io::Result<BlockHeader> {
|
pub fn read<R: Read>(r: &mut R) -> io::Result<BlockHeader> {
|
||||||
Ok(BlockHeader {
|
Ok(BlockHeader {
|
||||||
major_version: read_varint(r)?,
|
hardfork_version: read_varint(r)?,
|
||||||
minor_version: read_varint(r)?,
|
hardfork_signal: read_varint(r)?,
|
||||||
timestamp: read_varint(r)?,
|
timestamp: read_varint(r)?,
|
||||||
previous: read_bytes(r)?,
|
previous: read_bytes(r)?,
|
||||||
nonce: read_bytes(r).map(u32::from_le_bytes)?,
|
nonce: read_bytes(r).map(u32::from_le_bytes)?,
|
||||||
@@ -79,13 +83,15 @@ impl Block {
|
|||||||
/// This information comes from the Block's miner transaction. If the miner transaction isn't
|
/// This information comes from the Block's miner transaction. If the miner transaction isn't
|
||||||
/// structed as expected, this will return None.
|
/// structed as expected, this will return None.
|
||||||
pub fn number(&self) -> Option<u64> {
|
pub fn number(&self) -> Option<u64> {
|
||||||
match self.miner_tx.prefix.inputs.first() {
|
match &self.miner_tx {
|
||||||
|
Transaction::V1 { prefix, .. } | Transaction::V2 { prefix, .. } => match prefix.inputs.first() {
|
||||||
Some(Input::Gen(number)) => Some(*number),
|
Some(Input::Gen(number)) => Some(*number),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Write the BlockHeader.
|
/// Write the Block.
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
self.header.write(w)?;
|
self.header.write(w)?;
|
||||||
self.miner_tx.write(w)?;
|
self.miner_tx.write(w)?;
|
||||||
@@ -96,7 +102,7 @@ impl Block {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serialize the BlockHeader to a Vec<u8>.
|
/// Serialize the Block to a Vec<u8>.
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
let mut serialized = vec![];
|
let mut serialized = vec![];
|
||||||
self.write(&mut serialized).unwrap();
|
self.write(&mut serialized).unwrap();
|
||||||
@@ -116,10 +122,10 @@ impl Block {
|
|||||||
|
|
||||||
/// Get the hash of this block.
|
/// Get the hash of this block.
|
||||||
pub fn hash(&self) -> [u8; 32] {
|
pub fn hash(&self) -> [u8; 32] {
|
||||||
let mut hashable = self.serialize_hashable();
|
let mut hashable = self.serialize_pow_hash();
|
||||||
// Monero pre-appends a VarInt of the block hashing blobs length before getting the block hash
|
// Monero pre-appends a VarInt of the block-to-hash'ss length before getting the block hash,
|
||||||
// but doesn't do this when getting the proof of work hash :)
|
// but doesn't do this when getting the proof of work hash :)
|
||||||
let mut hashing_blob = Vec::with_capacity(8 + hashable.len());
|
let mut hashing_blob = Vec::with_capacity(9 + hashable.len());
|
||||||
write_varint(&u64::try_from(hashable.len()).unwrap(), &mut hashing_blob).unwrap();
|
write_varint(&u64::try_from(hashable.len()).unwrap(), &mut hashing_blob).unwrap();
|
||||||
hashing_blob.append(&mut hashable);
|
hashing_blob.append(&mut hashable);
|
||||||
|
|
||||||
@@ -130,18 +136,11 @@ impl Block {
|
|||||||
hash
|
hash
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read a BlockHeader.
|
/// Read a Block.
|
||||||
pub fn read<R: Read>(r: &mut R) -> io::Result<Block> {
|
pub fn read<R: Read>(r: &mut R) -> io::Result<Block> {
|
||||||
let header = BlockHeader::read(r)?;
|
|
||||||
|
|
||||||
let miner_tx = Transaction::read(r)?;
|
|
||||||
if !matches!(miner_tx.prefix.inputs.as_slice(), &[Input::Gen(_)]) {
|
|
||||||
Err(io::Error::other("Miner transaction has incorrect input type."))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Block {
|
Ok(Block {
|
||||||
header,
|
header: BlockHeader::read(r)?,
|
||||||
miner_tx,
|
miner_tx: Transaction::read(r)?,
|
||||||
txs: (0_usize .. read_varint(r)?).map(|_| read_bytes(r)).collect::<Result<_, _>>()?,
|
txs: (0_usize .. read_varint(r)?).map(|_| read_bytes(r)).collect::<Result<_, _>>()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
// #![deny(missing_docs)] // TODO
|
#![deny(missing_docs)]
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
pub use monero_io as io;
|
pub use monero_io as io;
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
use core::ops::Deref;
|
|
||||||
use std_shims::{
|
use std_shims::{
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
io::{self, Read, Write},
|
io::{self, Read, Write},
|
||||||
};
|
};
|
||||||
|
|
||||||
use zeroize::{Zeroize, Zeroizing};
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwards::EdwardsPoint};
|
use curve25519_dalek::edwards::EdwardsPoint;
|
||||||
|
|
||||||
pub use monero_mlsag as mlsag;
|
pub use monero_mlsag as mlsag;
|
||||||
pub use monero_clsag as clsag;
|
pub use monero_clsag as clsag;
|
||||||
@@ -15,15 +14,24 @@ pub use monero_bulletproofs as bulletproofs;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
io::*,
|
io::*,
|
||||||
generators::hash_to_point,
|
|
||||||
ringct::{mlsag::Mlsag, clsag::Clsag, borromean::BorromeanRange, bulletproofs::Bulletproof},
|
ringct::{mlsag::Mlsag, clsag::Clsag, borromean::BorromeanRange, bulletproofs::Bulletproof},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An encrypted amount.
|
/// An encrypted amount.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum EncryptedAmount {
|
pub enum EncryptedAmount {
|
||||||
Original { mask: [u8; 32], amount: [u8; 32] },
|
/// The original format for encrypted amounts.
|
||||||
Compact { amount: [u8; 8] },
|
Original {
|
||||||
|
/// A mask used with a mask derived from the shared secret to encrypt the amount.
|
||||||
|
mask: [u8; 32],
|
||||||
|
/// The amount, as a scalar, encrypted.
|
||||||
|
amount: [u8; 32],
|
||||||
|
},
|
||||||
|
/// The "compact" format for encrypted amounts.
|
||||||
|
Compact {
|
||||||
|
/// The amount, as a u64, encrypted.
|
||||||
|
amount: [u8; 8],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EncryptedAmount {
|
impl EncryptedAmount {
|
||||||
@@ -51,44 +59,41 @@ impl EncryptedAmount {
|
|||||||
/// The type of the RingCT data.
|
/// The type of the RingCT data.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
pub enum RctType {
|
pub enum RctType {
|
||||||
/// No RCT proofs.
|
|
||||||
Null,
|
|
||||||
/// One MLSAG for multiple inputs and Borromean range proofs.
|
/// One MLSAG for multiple inputs and Borromean range proofs.
|
||||||
///
|
///
|
||||||
/// This lines up with RCTTypeFull.
|
/// This aligns with RCTTypeFull.
|
||||||
MlsagAggregate,
|
AggregateMlsagBorromean,
|
||||||
// One MLSAG for each input and a Borromean range proof.
|
// One MLSAG for each input and a Borromean range proof.
|
||||||
///
|
///
|
||||||
/// This lines up with RCTTypeSimple.
|
/// This aligns with RCTTypeSimple.
|
||||||
MlsagIndividual,
|
MlsagBorromean,
|
||||||
// One MLSAG for each input and a Bulletproof.
|
// One MLSAG for each input and a Bulletproof.
|
||||||
///
|
///
|
||||||
/// This lines up with RCTTypeBulletproof.
|
/// This aligns with RCTTypeBulletproof.
|
||||||
Bulletproofs,
|
MlsagBulletproofs,
|
||||||
/// One MLSAG for each input and a Bulletproof, yet using EncryptedAmount::Compact.
|
/// One MLSAG for each input and a Bulletproof, yet using EncryptedAmount::Compact.
|
||||||
///
|
///
|
||||||
/// This lines up with RCTTypeBulletproof2.
|
/// This aligns with RCTTypeBulletproof2.
|
||||||
BulletproofsCompactAmount,
|
MlsagBulletproofsCompactAmount,
|
||||||
/// One CLSAG for each input and a Bulletproof.
|
/// One CLSAG for each input and a Bulletproof.
|
||||||
///
|
///
|
||||||
/// This lines up with RCTTypeCLSAG.
|
/// This aligns with RCTTypeCLSAG.
|
||||||
Clsag,
|
ClsagBulletproof,
|
||||||
/// One CLSAG for each input and a Bulletproof+.
|
/// One CLSAG for each input and a Bulletproof+.
|
||||||
///
|
///
|
||||||
/// This lines up with RCTTypeBulletproofPlus.
|
/// This aligns with RCTTypeBulletproofPlus.
|
||||||
BulletproofsPlus,
|
ClsagBulletproofPlus,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RctType> for u8 {
|
impl From<RctType> for u8 {
|
||||||
fn from(kind: RctType) -> u8 {
|
fn from(kind: RctType) -> u8 {
|
||||||
match kind {
|
match kind {
|
||||||
RctType::Null => 0,
|
RctType::AggregateMlsagBorromean => 1,
|
||||||
RctType::MlsagAggregate => 1,
|
RctType::MlsagBorromean => 2,
|
||||||
RctType::MlsagIndividual => 2,
|
RctType::MlsagBulletproofs => 3,
|
||||||
RctType::Bulletproofs => 3,
|
RctType::MlsagBulletproofsCompactAmount => 4,
|
||||||
RctType::BulletproofsCompactAmount => 4,
|
RctType::ClsagBulletproof => 5,
|
||||||
RctType::Clsag => 5,
|
RctType::ClsagBulletproofPlus => 6,
|
||||||
RctType::BulletproofsPlus => 6,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,27 +102,51 @@ impl TryFrom<u8> for RctType {
|
|||||||
type Error = ();
|
type Error = ();
|
||||||
fn try_from(byte: u8) -> Result<Self, ()> {
|
fn try_from(byte: u8) -> Result<Self, ()> {
|
||||||
Ok(match byte {
|
Ok(match byte {
|
||||||
0 => RctType::Null,
|
1 => RctType::AggregateMlsagBorromean,
|
||||||
1 => RctType::MlsagAggregate,
|
2 => RctType::MlsagBorromean,
|
||||||
2 => RctType::MlsagIndividual,
|
3 => RctType::MlsagBulletproofs,
|
||||||
3 => RctType::Bulletproofs,
|
4 => RctType::MlsagBulletproofsCompactAmount,
|
||||||
4 => RctType::BulletproofsCompactAmount,
|
5 => RctType::ClsagBulletproof,
|
||||||
5 => RctType::Clsag,
|
6 => RctType::ClsagBulletproofPlus,
|
||||||
6 => RctType::BulletproofsPlus,
|
|
||||||
_ => Err(())?,
|
_ => Err(())?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RctType {
|
impl RctType {
|
||||||
/// Returns true if this RctType uses compact encrypted amounts, false otherwise.
|
/// True if this RctType uses compact encrypted amounts, false otherwise.
|
||||||
pub fn compact_encrypted_amounts(&self) -> bool {
|
fn compact_encrypted_amounts(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
RctType::Null |
|
RctType::AggregateMlsagBorromean | RctType::MlsagBorromean | RctType::MlsagBulletproofs => {
|
||||||
RctType::MlsagAggregate |
|
false
|
||||||
RctType::MlsagIndividual |
|
}
|
||||||
RctType::Bulletproofs => false,
|
RctType::MlsagBulletproofsCompactAmount |
|
||||||
RctType::BulletproofsCompactAmount | RctType::Clsag | RctType::BulletproofsPlus => true,
|
RctType::ClsagBulletproof |
|
||||||
|
RctType::ClsagBulletproofPlus => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if this RctType uses a Bulletproof, false otherwise.
|
||||||
|
pub(crate) fn bulletproof(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
RctType::MlsagBulletproofs |
|
||||||
|
RctType::MlsagBulletproofsCompactAmount |
|
||||||
|
RctType::ClsagBulletproof => true,
|
||||||
|
RctType::AggregateMlsagBorromean |
|
||||||
|
RctType::MlsagBorromean |
|
||||||
|
RctType::ClsagBulletproofPlus => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if this RctType uses a Bulletproof+, false otherwise.
|
||||||
|
pub(crate) fn bulletproof_plus(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
RctType::ClsagBulletproofPlus => true,
|
||||||
|
RctType::AggregateMlsagBorromean |
|
||||||
|
RctType::MlsagBorromean |
|
||||||
|
RctType::MlsagBulletproofs |
|
||||||
|
RctType::MlsagBulletproofsCompactAmount |
|
||||||
|
RctType::ClsagBulletproof => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,7 +166,7 @@ pub struct RctBase {
|
|||||||
///
|
///
|
||||||
/// This field was deprecated and is empty for modern RctTypes.
|
/// This field was deprecated and is empty for modern RctTypes.
|
||||||
pub pseudo_outs: Vec<EdwardsPoint>,
|
pub pseudo_outs: Vec<EdwardsPoint>,
|
||||||
/// The encrypted amounts for the recipient to decrypt.
|
/// The encrypted amounts for the recipients to decrypt.
|
||||||
pub encrypted_amounts: Vec<EncryptedAmount>,
|
pub encrypted_amounts: Vec<EncryptedAmount>,
|
||||||
/// The output commitments.
|
/// The output commitments.
|
||||||
pub commitments: Vec<EdwardsPoint>,
|
pub commitments: Vec<EdwardsPoint>,
|
||||||
@@ -153,11 +182,9 @@ impl RctBase {
|
|||||||
/// Write the RctBase.
|
/// Write the RctBase.
|
||||||
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
|
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
|
||||||
w.write_all(&[u8::from(rct_type)])?;
|
w.write_all(&[u8::from(rct_type)])?;
|
||||||
match rct_type {
|
|
||||||
RctType::Null => Ok(()),
|
|
||||||
_ => {
|
|
||||||
write_varint(&self.fee, w)?;
|
write_varint(&self.fee, w)?;
|
||||||
if rct_type == RctType::MlsagIndividual {
|
if rct_type == RctType::MlsagBorromean {
|
||||||
write_raw_vec(write_point, &self.pseudo_outs, w)?;
|
write_raw_vec(write_point, &self.pseudo_outs, w)?;
|
||||||
}
|
}
|
||||||
for encrypted_amount in &self.encrypted_amounts {
|
for encrypted_amount in &self.encrypted_amounts {
|
||||||
@@ -165,20 +192,18 @@ impl RctBase {
|
|||||||
}
|
}
|
||||||
write_raw_vec(write_point, &self.commitments, w)
|
write_raw_vec(write_point, &self.commitments, w)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a RctBase.
|
/// Read a RctBase.
|
||||||
pub fn read<R: Read>(inputs: usize, outputs: usize, r: &mut R) -> io::Result<(RctBase, RctType)> {
|
pub fn read<R: Read>(inputs: usize, outputs: usize, r: &mut R) -> io::Result<(RctBase, RctType)> {
|
||||||
let rct_type =
|
let rct_type =
|
||||||
RctType::try_from(read_byte(r)?).map_err(|_| io::Error::other("invalid RCT type"))?;
|
RctType::try_from(read_byte(r)?).map_err(|()| io::Error::other("invalid RCT type"))?;
|
||||||
|
|
||||||
match rct_type {
|
match rct_type {
|
||||||
RctType::Null | RctType::MlsagAggregate | RctType::MlsagIndividual => {}
|
RctType::AggregateMlsagBorromean | RctType::MlsagBorromean => {}
|
||||||
RctType::Bulletproofs |
|
RctType::MlsagBulletproofs |
|
||||||
RctType::BulletproofsCompactAmount |
|
RctType::MlsagBulletproofsCompactAmount |
|
||||||
RctType::Clsag |
|
RctType::ClsagBulletproof |
|
||||||
RctType::BulletproofsPlus => {
|
RctType::ClsagBulletproofPlus => {
|
||||||
if outputs == 0 {
|
if outputs == 0 {
|
||||||
// Because the Bulletproofs(+) layout must be canonical, there must be 1 Bulletproof if
|
// Because the Bulletproofs(+) layout must be canonical, there must be 1 Bulletproof if
|
||||||
// Bulletproofs are in use
|
// Bulletproofs are in use
|
||||||
@@ -191,14 +216,11 @@ impl RctBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
if rct_type == RctType::Null {
|
|
||||||
RctBase { fee: 0, pseudo_outs: vec![], encrypted_amounts: vec![], commitments: vec![] }
|
|
||||||
} else {
|
|
||||||
RctBase {
|
RctBase {
|
||||||
fee: read_varint(r)?,
|
fee: read_varint(r)?,
|
||||||
// Only read pseudo_outs if they have yet to be moved to RctPrunable
|
// Only read pseudo_outs if they have yet to be moved to RctPrunable
|
||||||
// TODO: Shouldn't this be any Mlsag*?
|
// TODO: Shouldn't this be any Mlsag*?
|
||||||
pseudo_outs: if rct_type == RctType::MlsagIndividual {
|
pseudo_outs: if rct_type == RctType::MlsagBorromean {
|
||||||
read_raw_vec(read_point, inputs, r)?
|
read_raw_vec(read_point, inputs, r)?
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
@@ -207,7 +229,6 @@ impl RctBase {
|
|||||||
.map(|_| EncryptedAmount::read(rct_type.compact_encrypted_amounts(), r))
|
.map(|_| EncryptedAmount::read(rct_type.compact_encrypted_amounts(), r))
|
||||||
.collect::<Result<_, _>>()?,
|
.collect::<Result<_, _>>()?,
|
||||||
commitments: read_raw_vec(read_point, outputs, r)?,
|
commitments: read_raw_vec(read_point, outputs, r)?,
|
||||||
}
|
|
||||||
},
|
},
|
||||||
rct_type,
|
rct_type,
|
||||||
))
|
))
|
||||||
@@ -217,20 +238,50 @@ impl RctBase {
|
|||||||
/// The prunable part of the RingCT data.
|
/// The prunable part of the RingCT data.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum RctPrunable {
|
pub enum RctPrunable {
|
||||||
/// Null.
|
|
||||||
Null,
|
|
||||||
/// An aggregate MLSAG with Borromean range proofs.
|
/// An aggregate MLSAG with Borromean range proofs.
|
||||||
AggregateMlsagBorromean { borromean: Vec<BorromeanRange>, mlsag: Mlsag },
|
AggregateMlsagBorromean {
|
||||||
|
/// The aggregate MLSAG ring signature.
|
||||||
|
mlsag: Mlsag,
|
||||||
|
/// The Borromean range proofs for each output.
|
||||||
|
borromean: Vec<BorromeanRange>,
|
||||||
|
},
|
||||||
/// MLSAGs with Borromean range proofs.
|
/// MLSAGs with Borromean range proofs.
|
||||||
MlsagBorromean { borromean: Vec<BorromeanRange>, mlsags: Vec<Mlsag> },
|
MlsagBorromean {
|
||||||
|
/// The MLSAG ring signatures for each input.
|
||||||
|
mlsags: Vec<Mlsag>,
|
||||||
|
/// The Borromean range proofs for each output.
|
||||||
|
borromean: Vec<BorromeanRange>,
|
||||||
|
},
|
||||||
/// MLSAGs with Bulletproofs.
|
/// MLSAGs with Bulletproofs.
|
||||||
MlsagBulletproofs {
|
MlsagBulletproofs {
|
||||||
bulletproofs: Bulletproof,
|
/// The MLSAG ring signatures for each input.
|
||||||
mlsags: Vec<Mlsag>,
|
mlsags: Vec<Mlsag>,
|
||||||
|
/// The re-blinded commitments for the outputs being spent.
|
||||||
pseudo_outs: Vec<EdwardsPoint>,
|
pseudo_outs: Vec<EdwardsPoint>,
|
||||||
|
/// The aggregate Bulletproof, proving the outputs are within range.
|
||||||
|
bulletproof: Bulletproof,
|
||||||
|
},
|
||||||
|
/// MLSAGs with Bulletproofs and compact encrypted amounts.
|
||||||
|
///
|
||||||
|
/// This has an identical layout to MlsagBulletproofs and is interpreted the exact same way. It's
|
||||||
|
/// only differentiated to ensure discovery of the correct RctType.
|
||||||
|
MlsagBulletproofsCompactAmount {
|
||||||
|
/// The MLSAG ring signatures for each input.
|
||||||
|
mlsags: Vec<Mlsag>,
|
||||||
|
/// The re-blinded commitments for the outputs being spent.
|
||||||
|
pseudo_outs: Vec<EdwardsPoint>,
|
||||||
|
/// The aggregate Bulletproof, proving the outputs are within range.
|
||||||
|
bulletproof: Bulletproof,
|
||||||
},
|
},
|
||||||
/// CLSAGs with Bulletproofs(+).
|
/// CLSAGs with Bulletproofs(+).
|
||||||
Clsag { bulletproofs: Bulletproof, clsags: Vec<Clsag>, pseudo_outs: Vec<EdwardsPoint> },
|
Clsag {
|
||||||
|
/// The CLSAGs for each input.
|
||||||
|
clsags: Vec<Clsag>,
|
||||||
|
/// The re-blinded commitments for the outputs being spent.
|
||||||
|
pseudo_outs: Vec<EdwardsPoint>,
|
||||||
|
/// The aggregate Bulletproof(+), proving the outputs are within range.
|
||||||
|
bulletproof: Bulletproof,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RctPrunable {
|
impl RctPrunable {
|
||||||
@@ -247,7 +298,6 @@ impl RctPrunable {
|
|||||||
/// Write the RctPrunable.
|
/// Write the RctPrunable.
|
||||||
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
|
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
RctPrunable::Null => Ok(()),
|
|
||||||
RctPrunable::AggregateMlsagBorromean { borromean, mlsag } => {
|
RctPrunable::AggregateMlsagBorromean { borromean, mlsag } => {
|
||||||
write_raw_vec(BorromeanRange::write, borromean, w)?;
|
write_raw_vec(BorromeanRange::write, borromean, w)?;
|
||||||
mlsag.write(w)
|
mlsag.write(w)
|
||||||
@@ -256,20 +306,21 @@ impl RctPrunable {
|
|||||||
write_raw_vec(BorromeanRange::write, borromean, w)?;
|
write_raw_vec(BorromeanRange::write, borromean, w)?;
|
||||||
write_raw_vec(Mlsag::write, mlsags, w)
|
write_raw_vec(Mlsag::write, mlsags, w)
|
||||||
}
|
}
|
||||||
RctPrunable::MlsagBulletproofs { bulletproofs, mlsags, pseudo_outs } => {
|
RctPrunable::MlsagBulletproofs { bulletproof, mlsags, pseudo_outs } |
|
||||||
if rct_type == RctType::Bulletproofs {
|
RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, mlsags, pseudo_outs } => {
|
||||||
|
if rct_type == RctType::MlsagBulletproofs {
|
||||||
w.write_all(&1u32.to_le_bytes())?;
|
w.write_all(&1u32.to_le_bytes())?;
|
||||||
} else {
|
} else {
|
||||||
w.write_all(&[1])?;
|
w.write_all(&[1])?;
|
||||||
}
|
}
|
||||||
bulletproofs.write(w)?;
|
bulletproof.write(w)?;
|
||||||
|
|
||||||
write_raw_vec(Mlsag::write, mlsags, w)?;
|
write_raw_vec(Mlsag::write, mlsags, w)?;
|
||||||
write_raw_vec(write_point, pseudo_outs, w)
|
write_raw_vec(write_point, pseudo_outs, w)
|
||||||
}
|
}
|
||||||
RctPrunable::Clsag { bulletproofs, clsags, pseudo_outs } => {
|
RctPrunable::Clsag { bulletproof, clsags, pseudo_outs } => {
|
||||||
w.write_all(&[1])?;
|
w.write_all(&[1])?;
|
||||||
bulletproofs.write(w)?;
|
bulletproof.write(w)?;
|
||||||
|
|
||||||
write_raw_vec(Clsag::write, clsags, w)?;
|
write_raw_vec(Clsag::write, clsags, w)?;
|
||||||
write_raw_vec(write_point, pseudo_outs, w)
|
write_raw_vec(write_point, pseudo_outs, w)
|
||||||
@@ -293,19 +344,17 @@ impl RctPrunable {
|
|||||||
r: &mut R,
|
r: &mut R,
|
||||||
) -> io::Result<RctPrunable> {
|
) -> io::Result<RctPrunable> {
|
||||||
Ok(match rct_type {
|
Ok(match rct_type {
|
||||||
RctType::Null => RctPrunable::Null,
|
RctType::AggregateMlsagBorromean => RctPrunable::AggregateMlsagBorromean {
|
||||||
RctType::MlsagAggregate => RctPrunable::AggregateMlsagBorromean {
|
|
||||||
borromean: read_raw_vec(BorromeanRange::read, outputs, r)?,
|
borromean: read_raw_vec(BorromeanRange::read, outputs, r)?,
|
||||||
mlsag: Mlsag::read(ring_length, inputs + 1, r)?,
|
mlsag: Mlsag::read(ring_length, inputs + 1, r)?,
|
||||||
},
|
},
|
||||||
RctType::MlsagIndividual => RctPrunable::MlsagBorromean {
|
RctType::MlsagBorromean => RctPrunable::MlsagBorromean {
|
||||||
borromean: read_raw_vec(BorromeanRange::read, outputs, r)?,
|
borromean: read_raw_vec(BorromeanRange::read, outputs, r)?,
|
||||||
mlsags: (0 .. inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::<Result<_, _>>()?,
|
mlsags: (0 .. inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::<Result<_, _>>()?,
|
||||||
},
|
},
|
||||||
RctType::Bulletproofs | RctType::BulletproofsCompactAmount => {
|
RctType::MlsagBulletproofs | RctType::MlsagBulletproofsCompactAmount => {
|
||||||
RctPrunable::MlsagBulletproofs {
|
let bulletproof = {
|
||||||
bulletproofs: {
|
if (if rct_type == RctType::MlsagBulletproofs {
|
||||||
if (if rct_type == RctType::Bulletproofs {
|
|
||||||
u64::from(read_u32(r)?)
|
u64::from(read_u32(r)?)
|
||||||
} else {
|
} else {
|
||||||
read_varint(r)?
|
read_varint(r)?
|
||||||
@@ -314,19 +363,27 @@ impl RctPrunable {
|
|||||||
Err(io::Error::other("n bulletproofs instead of one"))?;
|
Err(io::Error::other("n bulletproofs instead of one"))?;
|
||||||
}
|
}
|
||||||
Bulletproof::read(r)?
|
Bulletproof::read(r)?
|
||||||
},
|
};
|
||||||
mlsags: (0 .. inputs)
|
let mlsags =
|
||||||
.map(|_| Mlsag::read(ring_length, 2, r))
|
(0 .. inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::<Result<_, _>>()?;
|
||||||
.collect::<Result<_, _>>()?,
|
let pseudo_outs = read_raw_vec(read_point, inputs, r)?;
|
||||||
pseudo_outs: read_raw_vec(read_point, inputs, r)?,
|
if rct_type == RctType::MlsagBulletproofs {
|
||||||
|
RctPrunable::MlsagBulletproofs { bulletproof, mlsags, pseudo_outs }
|
||||||
|
} else {
|
||||||
|
debug_assert_eq!(rct_type, RctType::MlsagBulletproofsCompactAmount);
|
||||||
|
RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, mlsags, pseudo_outs }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RctType::Clsag | RctType::BulletproofsPlus => RctPrunable::Clsag {
|
RctType::ClsagBulletproof | RctType::ClsagBulletproofPlus => RctPrunable::Clsag {
|
||||||
bulletproofs: {
|
bulletproof: {
|
||||||
if read_varint::<_, u64>(r)? != 1 {
|
if read_varint::<_, u64>(r)? != 1 {
|
||||||
Err(io::Error::other("n bulletproofs instead of one"))?;
|
Err(io::Error::other("n bulletproofs instead of one"))?;
|
||||||
}
|
}
|
||||||
(if rct_type == RctType::Clsag { Bulletproof::read } else { Bulletproof::read_plus })(r)?
|
(if rct_type == RctType::ClsagBulletproof {
|
||||||
|
Bulletproof::read
|
||||||
|
} else {
|
||||||
|
Bulletproof::read_plus
|
||||||
|
})(r)?
|
||||||
},
|
},
|
||||||
clsags: (0 .. inputs).map(|_| Clsag::read(ring_length, r)).collect::<Result<_, _>>()?,
|
clsags: (0 .. inputs).map(|_| Clsag::read(ring_length, r)).collect::<Result<_, _>>()?,
|
||||||
pseudo_outs: read_raw_vec(read_point, inputs, r)?,
|
pseudo_outs: read_raw_vec(read_point, inputs, r)?,
|
||||||
@@ -335,59 +392,51 @@ impl RctPrunable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Write the RctPrunable as necessary for signing the signature.
|
/// Write the RctPrunable as necessary for signing the signature.
|
||||||
///
|
pub(crate) fn signature_write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
/// This function will return None if the object is `RctPrunable::Null` (and has no
|
match self {
|
||||||
/// representation here).
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn signature_write<W: Write>(&self, w: &mut W) -> Option<io::Result<()>> {
|
|
||||||
Some(match self {
|
|
||||||
RctPrunable::Null => None?,
|
|
||||||
RctPrunable::AggregateMlsagBorromean { borromean, .. } |
|
RctPrunable::AggregateMlsagBorromean { borromean, .. } |
|
||||||
RctPrunable::MlsagBorromean { borromean, .. } => {
|
RctPrunable::MlsagBorromean { borromean, .. } => {
|
||||||
borromean.iter().try_for_each(|rs| rs.write(w))
|
borromean.iter().try_for_each(|rs| rs.write(w))
|
||||||
}
|
}
|
||||||
RctPrunable::MlsagBulletproofs { bulletproofs, .. } |
|
RctPrunable::MlsagBulletproofs { bulletproof, .. } |
|
||||||
RctPrunable::Clsag { bulletproofs, .. } => bulletproofs.signature_write(w),
|
RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, .. } |
|
||||||
})
|
RctPrunable::Clsag { bulletproof, .. } => bulletproof.signature_write(w),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The RingCT proofs.
|
||||||
|
///
|
||||||
|
/// This contains both the RctBase and RctPrunable structs.
|
||||||
|
///
|
||||||
|
/// The C++ codebase refers to this as rct_signatures.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct RctSignatures {
|
pub struct RctProofs {
|
||||||
|
/// The data necessary for handling this transaction.
|
||||||
pub base: RctBase,
|
pub base: RctBase,
|
||||||
|
/// The data necessary for verifying this transaction.
|
||||||
pub prunable: RctPrunable,
|
pub prunable: RctPrunable,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RctSignatures {
|
impl RctProofs {
|
||||||
/// RctType for a given RctSignatures struct.
|
/// RctType for a given RctProofs struct.
|
||||||
///
|
pub fn rct_type(&self) -> RctType {
|
||||||
/// This is only guaranteed to return the type for a well-formed RctSignatures. For a malformed
|
match &self.prunable {
|
||||||
/// RctSignatures, this will return either the presumed RctType (with no guarantee of compliance
|
RctPrunable::AggregateMlsagBorromean { .. } => RctType::AggregateMlsagBorromean,
|
||||||
/// with that type) or None.
|
RctPrunable::MlsagBorromean { .. } => RctType::MlsagBorromean,
|
||||||
#[must_use]
|
RctPrunable::MlsagBulletproofs { .. } => RctType::MlsagBulletproofs,
|
||||||
pub fn rct_type(&self) -> Option<RctType> {
|
RctPrunable::MlsagBulletproofsCompactAmount { .. } => RctType::MlsagBulletproofsCompactAmount,
|
||||||
Some(match &self.prunable {
|
RctPrunable::Clsag { bulletproof, .. } => {
|
||||||
RctPrunable::Null => RctType::Null,
|
if matches!(bulletproof, Bulletproof::Original { .. }) {
|
||||||
RctPrunable::AggregateMlsagBorromean { .. } => RctType::MlsagAggregate,
|
RctType::ClsagBulletproof
|
||||||
RctPrunable::MlsagBorromean { .. } => RctType::MlsagIndividual,
|
|
||||||
RctPrunable::MlsagBulletproofs { .. } => {
|
|
||||||
if matches!(self.base.encrypted_amounts.first()?, EncryptedAmount::Original { .. }) {
|
|
||||||
RctType::Bulletproofs
|
|
||||||
} else {
|
} else {
|
||||||
RctType::BulletproofsCompactAmount
|
RctType::ClsagBulletproofPlus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RctPrunable::Clsag { bulletproofs, .. } => {
|
|
||||||
if matches!(bulletproofs, Bulletproof::Original { .. }) {
|
|
||||||
RctType::Clsag
|
|
||||||
} else {
|
|
||||||
RctType::BulletproofsPlus
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The weight of this RctSignatures as relevant for fees.
|
/// The weight of this RctProofs, as relevant for fees.
|
||||||
pub fn fee_weight(
|
pub fn fee_weight(
|
||||||
bp_plus: bool,
|
bp_plus: bool,
|
||||||
ring_len: usize,
|
ring_len: usize,
|
||||||
@@ -398,30 +447,29 @@ impl RctSignatures {
|
|||||||
RctBase::fee_weight(outputs, fee) + RctPrunable::fee_weight(bp_plus, ring_len, inputs, outputs)
|
RctBase::fee_weight(outputs, fee) + RctPrunable::fee_weight(bp_plus, ring_len, inputs, outputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
/// Write the RctProofs.
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> Option<io::Result<()>> {
|
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
let rct_type = self.rct_type()?;
|
let rct_type = self.rct_type();
|
||||||
if let Err(e) = self.base.write(w, rct_type) {
|
self.base.write(w, rct_type)?;
|
||||||
return Some(Err(e));
|
self.prunable.write(w, rct_type)
|
||||||
};
|
|
||||||
Some(self.prunable.write(w, rct_type))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
/// Serialize the RctProofs to a Vec<u8>.
|
||||||
pub fn serialize(&self) -> Option<Vec<u8>> {
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
let mut serialized = vec![];
|
let mut serialized = vec![];
|
||||||
self.write(&mut serialized)?.unwrap();
|
self.write(&mut serialized).unwrap();
|
||||||
Some(serialized)
|
serialized
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a RctProofs.
|
||||||
pub fn read<R: Read>(
|
pub fn read<R: Read>(
|
||||||
ring_length: usize,
|
ring_length: usize,
|
||||||
inputs: usize,
|
inputs: usize,
|
||||||
outputs: usize,
|
outputs: usize,
|
||||||
r: &mut R,
|
r: &mut R,
|
||||||
) -> io::Result<RctSignatures> {
|
) -> io::Result<RctProofs> {
|
||||||
let base = RctBase::read(inputs, outputs, r)?;
|
let base = RctBase::read(inputs, outputs, r)?;
|
||||||
Ok(RctSignatures {
|
Ok(RctProofs {
|
||||||
base: base.0,
|
base: base.0,
|
||||||
prunable: RctPrunable::read(base.1, ring_length, inputs, outputs, r)?,
|
prunable: RctPrunable::read(base.1, ring_length, inputs, outputs, r)?,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,22 +12,34 @@ use crate::{
|
|||||||
io::*,
|
io::*,
|
||||||
primitives::keccak256,
|
primitives::keccak256,
|
||||||
ring_signatures::RingSignature,
|
ring_signatures::RingSignature,
|
||||||
ringct::{bulletproofs::Bulletproof, RctType, RctBase, RctPrunable, RctSignatures},
|
ringct::{bulletproofs::Bulletproof, RctProofs},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// An input in the Monero protocol.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum Input {
|
pub enum Input {
|
||||||
|
/// An input for a miner transaction, which is generating new coins.
|
||||||
Gen(u64),
|
Gen(u64),
|
||||||
ToKey { amount: Option<u64>, key_offsets: Vec<u64>, key_image: EdwardsPoint },
|
/// An input spending an output on-chain.
|
||||||
|
ToKey {
|
||||||
|
/// The pool this input spends an output of.
|
||||||
|
amount: Option<u64>,
|
||||||
|
/// The decoys used by this input's ring, specified as their offset distance from each other.
|
||||||
|
key_offsets: Vec<u64>,
|
||||||
|
/// The key image (linking tag, nullifer) for the spent output.
|
||||||
|
key_image: EdwardsPoint,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
|
/// The weight of this Input, as relevant for fees.
|
||||||
pub fn fee_weight(offsets_weight: usize) -> usize {
|
pub fn fee_weight(offsets_weight: usize) -> usize {
|
||||||
// Uses 1 byte for the input type
|
// Uses 1 byte for the input type
|
||||||
// Uses 1 byte for the VarInt amount due to amount being 0
|
// Uses 1 byte for the VarInt amount due to amount being 0
|
||||||
1 + 1 + offsets_weight + 32
|
1 + 1 + offsets_weight + 32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write the Input.
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Input::Gen(height) => {
|
Input::Gen(height) => {
|
||||||
@@ -44,12 +56,14 @@ impl Input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Serialize the Input to a Vec<u8>.
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
let mut res = vec![];
|
let mut res = vec![];
|
||||||
self.write(&mut res).unwrap();
|
self.write(&mut res).unwrap();
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read an Input.
|
||||||
pub fn read<R: Read>(r: &mut R) -> io::Result<Input> {
|
pub fn read<R: Read>(r: &mut R) -> io::Result<Input> {
|
||||||
Ok(match read_byte(r)? {
|
Ok(match read_byte(r)? {
|
||||||
255 => Input::Gen(read_varint(r)?),
|
255 => Input::Gen(read_varint(r)?),
|
||||||
@@ -72,21 +86,26 @@ impl Input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Doesn't bother moving to an enum for the unused Script classes
|
/// An output in the Monero protocol.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Output {
|
pub struct Output {
|
||||||
|
/// The pool this output should be sorted into.
|
||||||
pub amount: Option<u64>,
|
pub amount: Option<u64>,
|
||||||
|
/// The key which can spend this output.
|
||||||
pub key: CompressedEdwardsY,
|
pub key: CompressedEdwardsY,
|
||||||
|
/// The view tag for this output, as used to accelerate scanning.
|
||||||
pub view_tag: Option<u8>,
|
pub view_tag: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Output {
|
impl Output {
|
||||||
|
/// The weight of this Output, as relevant for fees.
|
||||||
pub fn fee_weight(view_tags: bool) -> usize {
|
pub fn fee_weight(view_tags: bool) -> usize {
|
||||||
// Uses 1 byte for the output type
|
// Uses 1 byte for the output type
|
||||||
// Uses 1 byte for the VarInt amount due to amount being 0
|
// Uses 1 byte for the VarInt amount due to amount being 0
|
||||||
1 + 1 + 32 + if view_tags { 1 } else { 0 }
|
1 + 1 + 32 + if view_tags { 1 } else { 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write the Output.
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
write_varint(&self.amount.unwrap_or(0), w)?;
|
write_varint(&self.amount.unwrap_or(0), w)?;
|
||||||
w.write_all(&[2 + u8::from(self.view_tag.is_some())])?;
|
w.write_all(&[2 + u8::from(self.view_tag.is_some())])?;
|
||||||
@@ -97,12 +116,14 @@ impl Output {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write the Output to a Vec<u8>.
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
let mut res = Vec::with_capacity(8 + 1 + 32);
|
let mut res = Vec::with_capacity(8 + 1 + 32);
|
||||||
self.write(&mut res).unwrap();
|
self.write(&mut res).unwrap();
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read an Output.
|
||||||
pub fn read<R: Read>(rct: bool, r: &mut R) -> io::Result<Output> {
|
pub fn read<R: Read>(rct: bool, r: &mut R) -> io::Result<Output> {
|
||||||
let amount = read_varint(r)?;
|
let amount = read_varint(r)?;
|
||||||
let amount = if rct {
|
let amount = if rct {
|
||||||
@@ -128,10 +149,17 @@ impl Output {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An additional timelock for a Monero transaction.
|
||||||
|
///
|
||||||
|
/// Monero outputs are locked by a default timelock. If a timelock is explicitly specified, the
|
||||||
|
/// longer of the two will be the timelock used.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
pub enum Timelock {
|
pub enum Timelock {
|
||||||
|
/// No timelock.
|
||||||
None,
|
None,
|
||||||
|
/// Locked until this block.
|
||||||
Block(usize),
|
Block(usize),
|
||||||
|
/// Locked until this many seconds since the epoch.
|
||||||
Time(u64),
|
Time(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,16 +201,27 @@ impl PartialOrd for Timelock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The transaction prefix.
|
||||||
|
///
|
||||||
|
/// This is common to all transaction versions and contains most parts of the transaction needed to
|
||||||
|
/// handle it. It excludes any proofs.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct TransactionPrefix {
|
pub struct TransactionPrefix {
|
||||||
pub version: u64,
|
/// The timelock this transaction uses.
|
||||||
pub timelock: Timelock,
|
pub timelock: Timelock,
|
||||||
|
/// The inputs for this transaction.
|
||||||
pub inputs: Vec<Input>,
|
pub inputs: Vec<Input>,
|
||||||
|
/// The outputs for this transaction.
|
||||||
pub outputs: Vec<Output>,
|
pub outputs: Vec<Output>,
|
||||||
|
/// The additional data included within the transaction.
|
||||||
|
///
|
||||||
|
/// This is an arbitrary data field, yet is used by wallets for containing the data necessary to
|
||||||
|
/// scan the transaction.
|
||||||
pub extra: Vec<u8>,
|
pub extra: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransactionPrefix {
|
impl TransactionPrefix {
|
||||||
|
/// The weight of this TransactionPrefix, as relevant for fees.
|
||||||
pub fn fee_weight(
|
pub fn fee_weight(
|
||||||
decoy_weights: &[usize],
|
decoy_weights: &[usize],
|
||||||
outputs: usize,
|
outputs: usize,
|
||||||
@@ -191,8 +230,7 @@ impl TransactionPrefix {
|
|||||||
) -> usize {
|
) -> usize {
|
||||||
// Assumes Timelock::None since this library won't let you create a TX with a timelock
|
// Assumes Timelock::None since this library won't let you create a TX with a timelock
|
||||||
// 1 input for every decoy weight
|
// 1 input for every decoy weight
|
||||||
1 + 1 +
|
1 + varint_len(decoy_weights.len()) +
|
||||||
varint_len(decoy_weights.len()) +
|
|
||||||
decoy_weights.iter().map(|&offsets_weight| Input::fee_weight(offsets_weight)).sum::<usize>() +
|
decoy_weights.iter().map(|&offsets_weight| Input::fee_weight(offsets_weight)).sum::<usize>() +
|
||||||
varint_len(outputs) +
|
varint_len(outputs) +
|
||||||
(outputs * Output::fee_weight(view_tags)) +
|
(outputs * Output::fee_weight(view_tags)) +
|
||||||
@@ -200,8 +238,10 @@ impl TransactionPrefix {
|
|||||||
extra
|
extra
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
/// Write a TransactionPrefix.
|
||||||
write_varint(&self.version, w)?;
|
///
|
||||||
|
/// This is distinct from Monero in that it won't write any version.
|
||||||
|
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
self.timelock.write(w)?;
|
self.timelock.write(w)?;
|
||||||
write_vec(Input::write, &self.inputs, w)?;
|
write_vec(Input::write, &self.inputs, w)?;
|
||||||
write_vec(Output::write, &self.outputs, w)?;
|
write_vec(Output::write, &self.outputs, w)?;
|
||||||
@@ -209,19 +249,11 @@ impl TransactionPrefix {
|
|||||||
w.write_all(&self.extra)
|
w.write_all(&self.extra)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
/// Read a TransactionPrefix.
|
||||||
let mut res = vec![];
|
///
|
||||||
self.write(&mut res).unwrap();
|
/// This is distinct from Monero in that it won't read the version. The version must be passed
|
||||||
res
|
/// in.
|
||||||
}
|
pub fn read<R: Read>(r: &mut R, version: u64) -> io::Result<TransactionPrefix> {
|
||||||
|
|
||||||
pub fn read<R: Read>(r: &mut R) -> io::Result<TransactionPrefix> {
|
|
||||||
let version = read_varint(r)?;
|
|
||||||
// TODO: Create an enum out of version
|
|
||||||
if (version == 0) || (version > 2) {
|
|
||||||
Err(io::Error::other("unrecognized transaction version"))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let timelock = Timelock::from_raw(read_varint(r)?);
|
let timelock = Timelock::from_raw(read_varint(r)?);
|
||||||
|
|
||||||
let inputs = read_vec(|r| Input::read(r), r)?;
|
let inputs = read_vec(|r| Input::read(r), r)?;
|
||||||
@@ -231,7 +263,6 @@ impl TransactionPrefix {
|
|||||||
let is_miner_tx = matches!(inputs[0], Input::Gen { .. });
|
let is_miner_tx = matches!(inputs[0], Input::Gen { .. });
|
||||||
|
|
||||||
let mut prefix = TransactionPrefix {
|
let mut prefix = TransactionPrefix {
|
||||||
version,
|
|
||||||
timelock,
|
timelock,
|
||||||
inputs,
|
inputs,
|
||||||
outputs: read_vec(|r| Output::read((!is_miner_tx) && (version == 2), r), r)?,
|
outputs: read_vec(|r| Output::read((!is_miner_tx) && (version == 2), r), r)?,
|
||||||
@@ -241,20 +272,36 @@ impl TransactionPrefix {
|
|||||||
Ok(prefix)
|
Ok(prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hash(&self) -> [u8; 32] {
|
fn hash(&self, version: u64) -> [u8; 32] {
|
||||||
keccak256(self.serialize())
|
let mut buf = vec![];
|
||||||
|
write_varint(&version, &mut buf).unwrap();
|
||||||
|
self.write(&mut buf).unwrap();
|
||||||
|
keccak256(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Monero transaction. For version 1, rct_signatures still contains an accurate fee value.
|
/// A Monero transaction.
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Transaction {
|
pub enum Transaction {
|
||||||
pub prefix: TransactionPrefix,
|
/// A version 1 transaction, used by the original Cryptonote codebase.
|
||||||
pub signatures: Vec<RingSignature>,
|
V1 {
|
||||||
pub rct_signatures: RctSignatures,
|
/// The transaction's prefix.
|
||||||
|
prefix: TransactionPrefix,
|
||||||
|
/// The transaction's ring signatures.
|
||||||
|
signatures: Vec<RingSignature>,
|
||||||
|
},
|
||||||
|
/// A version 2 transaction, used by the RingCT protocol.
|
||||||
|
V2 {
|
||||||
|
/// The transaction's prefix.
|
||||||
|
prefix: TransactionPrefix,
|
||||||
|
/// The transaction's proofs.
|
||||||
|
proofs: Option<RctProofs>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transaction {
|
impl Transaction {
|
||||||
|
/// The weight of this Transaction, as relevant for fees.
|
||||||
// TODO: Replace ring_len, decoy_weights for &[&[usize]], where the inner buf is the decoy
|
// TODO: Replace ring_len, decoy_weights for &[&[usize]], where the inner buf is the decoy
|
||||||
// offsets
|
// offsets
|
||||||
pub fn fee_weight(
|
pub fn fee_weight(
|
||||||
@@ -266,88 +313,58 @@ impl Transaction {
|
|||||||
extra: usize,
|
extra: usize,
|
||||||
fee: u64,
|
fee: u64,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
TransactionPrefix::fee_weight(decoy_weights, outputs, view_tags, extra) +
|
1 + TransactionPrefix::fee_weight(decoy_weights, outputs, view_tags, extra) +
|
||||||
RctSignatures::fee_weight(bp_plus, ring_len, decoy_weights.len(), outputs, fee)
|
RctProofs::fee_weight(bp_plus, ring_len, decoy_weights.len(), outputs, fee)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
/// Write the Transaction.
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> Option<io::Result<()>> {
|
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
if let Err(e) = self.prefix.write(w) {
|
match self {
|
||||||
return Some(Err(e));
|
Transaction::V1 { prefix, signatures } => {
|
||||||
};
|
write_varint(&1u8, w)?;
|
||||||
if self.prefix.version == 1 {
|
prefix.write(w)?;
|
||||||
for ring_sig in &self.signatures {
|
for ring_sig in signatures {
|
||||||
if let Err(e) = ring_sig.write(w) {
|
ring_sig.write(w)?;
|
||||||
return Some(Err(e));
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
Some(Ok(()))
|
|
||||||
} else if self.prefix.version == 2 {
|
|
||||||
if let Err(e) = self.rct_signatures.write(w)? {
|
|
||||||
return Some(Err(e));
|
|
||||||
}
|
}
|
||||||
Some(Ok(()))
|
Transaction::V2 { prefix, proofs } => {
|
||||||
} else {
|
write_varint(&2u8, w)?;
|
||||||
Some(Err(io::Error::other("transaction had an unknown version")))
|
prefix.write(w)?;
|
||||||
|
match proofs {
|
||||||
|
None => w.write_all(&[0])?,
|
||||||
|
Some(proofs) => proofs.write(w)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
/// Write the Transaction to a Vec<u8>.
|
||||||
pub fn serialize(&self) -> Option<Vec<u8>> {
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
let mut res = Vec::with_capacity(2048);
|
let mut res = Vec::with_capacity(2048);
|
||||||
self.write(&mut res)?.unwrap();
|
self.write(&mut res).unwrap();
|
||||||
Some(res)
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a Transaction.
|
||||||
pub fn read<R: Read>(r: &mut R) -> io::Result<Transaction> {
|
pub fn read<R: Read>(r: &mut R) -> io::Result<Transaction> {
|
||||||
let prefix = TransactionPrefix::read(r)?;
|
let version = read_varint(r)?;
|
||||||
let mut signatures = vec![];
|
let prefix = TransactionPrefix::read(r, version)?;
|
||||||
let mut rct_signatures = RctSignatures {
|
|
||||||
base: RctBase { fee: 0, encrypted_amounts: vec![], pseudo_outs: vec![], commitments: vec![] },
|
|
||||||
prunable: RctPrunable::Null,
|
|
||||||
};
|
|
||||||
|
|
||||||
if prefix.version == 1 {
|
if version == 1 {
|
||||||
signatures = prefix
|
let signatures = prefix
|
||||||
.inputs
|
.inputs
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|input| match input {
|
.filter_map(|input| match input {
|
||||||
|
// TODO: This allows mixing Gen and ToKey, which is likely undefined behavior?
|
||||||
Input::ToKey { key_offsets, .. } => Some(RingSignature::read(key_offsets.len(), r)),
|
Input::ToKey { key_offsets, .. } => Some(RingSignature::read(key_offsets.len(), r)),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
if !matches!(prefix.inputs[0], Input::Gen(..)) {
|
Ok(Transaction::V1 { prefix, signatures })
|
||||||
let in_amount = prefix
|
} else if version == 2 {
|
||||||
.inputs
|
let proofs = Some(RctProofs::read(
|
||||||
.iter()
|
|
||||||
.map(|input| match input {
|
|
||||||
Input::Gen(..) => Err(io::Error::other("Input::Gen present in non-coinbase v1 TX"))?,
|
|
||||||
// v1 TXs can burn v2 outputs
|
|
||||||
// dcff3fe4f914d6b6bd4a5b800cc4cca8f2fdd1bd73352f0700d463d36812f328 is one such TX
|
|
||||||
// It includes a pre-RCT signature for a RCT output, yet if you interpret the RCT
|
|
||||||
// output as being worth 0, it passes a sum check (guaranteed since no outputs are RCT)
|
|
||||||
Input::ToKey { amount, .. } => Ok(amount.unwrap_or(0)),
|
|
||||||
})
|
|
||||||
.collect::<io::Result<Vec<_>>>()?
|
|
||||||
.into_iter()
|
|
||||||
.sum::<u64>();
|
|
||||||
|
|
||||||
let mut out = 0;
|
|
||||||
for output in &prefix.outputs {
|
|
||||||
if output.amount.is_none() {
|
|
||||||
Err(io::Error::other("v1 transaction had a 0-amount output"))?;
|
|
||||||
}
|
|
||||||
out += output.amount.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if in_amount < out {
|
|
||||||
Err(io::Error::other("transaction spent more than it had as inputs"))?;
|
|
||||||
}
|
|
||||||
rct_signatures.base.fee = in_amount - out;
|
|
||||||
}
|
|
||||||
} else if prefix.version == 2 {
|
|
||||||
rct_signatures = RctSignatures::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(),
|
||||||
@@ -355,98 +372,109 @@ impl Transaction {
|
|||||||
prefix.inputs.len(),
|
prefix.inputs.len(),
|
||||||
prefix.outputs.len(),
|
prefix.outputs.len(),
|
||||||
r,
|
r,
|
||||||
)?;
|
)?);
|
||||||
|
|
||||||
|
Ok(Transaction::V2 { prefix, proofs })
|
||||||
} else {
|
} else {
|
||||||
Err(io::Error::other("Tried to deserialize unknown version"))?;
|
Err(io::Error::other("tried to deserialize unknown version"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Transaction { prefix, signatures, rct_signatures })
|
/// The hash of the transaction.
|
||||||
}
|
pub fn hash(&self) -> [u8; 32] {
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn hash(&self) -> Option<[u8; 32]> {
|
|
||||||
let mut buf = Vec::with_capacity(2048);
|
let mut buf = Vec::with_capacity(2048);
|
||||||
if self.prefix.version == 1 {
|
match self {
|
||||||
self.write(&mut buf)?.unwrap();
|
Transaction::V1 { .. } => {
|
||||||
Some(keccak256(buf))
|
self.write(&mut buf).unwrap();
|
||||||
} else {
|
keccak256(buf)
|
||||||
|
}
|
||||||
|
Transaction::V2 { prefix, proofs } => {
|
||||||
let mut hashes = Vec::with_capacity(96);
|
let mut hashes = Vec::with_capacity(96);
|
||||||
|
|
||||||
hashes.extend(self.prefix.hash());
|
hashes.extend(prefix.hash(2));
|
||||||
|
|
||||||
let rct_type = self.rct_signatures.rct_type()?;
|
if let Some(proofs) = proofs {
|
||||||
self.rct_signatures.base.write(&mut buf, rct_type).unwrap();
|
let rct_type = proofs.rct_type();
|
||||||
|
proofs.base.write(&mut buf, rct_type).unwrap();
|
||||||
hashes.extend(keccak256(&buf));
|
hashes.extend(keccak256(&buf));
|
||||||
buf.clear();
|
buf.clear();
|
||||||
|
|
||||||
hashes.extend(&match self.rct_signatures.prunable {
|
proofs.prunable.write(&mut buf, rct_type).unwrap();
|
||||||
RctPrunable::Null => [0; 32],
|
hashes.extend(keccak256(buf));
|
||||||
_ => {
|
} else {
|
||||||
self.rct_signatures.prunable.write(&mut buf, rct_type).unwrap();
|
// Serialization of RctBase::Null
|
||||||
keccak256(buf)
|
hashes.extend(keccak256([0]));
|
||||||
|
hashes.extend([0; 32]);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
Some(keccak256(hashes))
|
keccak256(hashes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the hash of this transaction as needed for signing it.
|
/// Calculate the hash of this transaction as needed for signing it.
|
||||||
#[must_use]
|
///
|
||||||
|
/// 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]> {
|
||||||
if self.prefix.version == 1 {
|
match self {
|
||||||
return Some(self.prefix.hash());
|
Transaction::V1 { prefix, .. } => Some(prefix.hash(1)),
|
||||||
}
|
Transaction::V2 { prefix, proofs } => {
|
||||||
|
|
||||||
let mut buf = Vec::with_capacity(2048);
|
let mut buf = Vec::with_capacity(2048);
|
||||||
let mut sig_hash = Vec::with_capacity(96);
|
let mut sig_hash = Vec::with_capacity(96);
|
||||||
|
|
||||||
sig_hash.extend(self.prefix.hash());
|
sig_hash.extend(prefix.hash(2));
|
||||||
|
|
||||||
self.rct_signatures.base.write(&mut buf, self.rct_signatures.rct_type()?).unwrap();
|
let proofs = proofs.as_ref()?;
|
||||||
|
proofs.base.write(&mut buf, proofs.rct_type()).unwrap();
|
||||||
sig_hash.extend(keccak256(&buf));
|
sig_hash.extend(keccak256(&buf));
|
||||||
buf.clear();
|
buf.clear();
|
||||||
|
|
||||||
self.rct_signatures.prunable.signature_write(&mut buf)?.unwrap();
|
proofs.prunable.signature_write(&mut buf).unwrap();
|
||||||
sig_hash.extend(keccak256(buf));
|
sig_hash.extend(keccak256(buf));
|
||||||
|
|
||||||
Some(keccak256(sig_hash))
|
Some(keccak256(sig_hash))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_rct_bulletproof(&self) -> bool {
|
fn is_rct_bulletproof(&self) -> bool {
|
||||||
let Some(rct_type) = self.rct_signatures.rct_type() else { return false };
|
match self {
|
||||||
match rct_type {
|
Transaction::V1 { .. } => false,
|
||||||
RctType::Bulletproofs | RctType::BulletproofsCompactAmount | RctType::Clsag => true,
|
Transaction::V2 { proofs, .. } => {
|
||||||
RctType::Null |
|
let Some(proofs) = proofs else { return false };
|
||||||
RctType::MlsagAggregate |
|
proofs.rct_type().bulletproof()
|
||||||
RctType::MlsagIndividual |
|
}
|
||||||
RctType::BulletproofsPlus => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_rct_bulletproof_plus(&self) -> bool {
|
fn is_rct_bulletproof_plus(&self) -> bool {
|
||||||
let Some(rct_type) = self.rct_signatures.rct_type() else { return false };
|
match self {
|
||||||
match rct_type {
|
Transaction::V1 { .. } => false,
|
||||||
RctType::BulletproofsPlus => true,
|
Transaction::V2 { proofs, .. } => {
|
||||||
RctType::Null |
|
let Some(proofs) = proofs else { return false };
|
||||||
RctType::MlsagAggregate |
|
proofs.rct_type().bulletproof_plus()
|
||||||
RctType::MlsagIndividual |
|
}
|
||||||
RctType::Bulletproofs |
|
|
||||||
RctType::BulletproofsCompactAmount |
|
|
||||||
RctType::Clsag => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the transaction's weight.
|
/// Calculate the transaction's weight.
|
||||||
pub fn weight(&self) -> Option<usize> {
|
pub fn weight(&self) -> usize {
|
||||||
let blob_size = self.serialize()?.len();
|
let blob_size = self.serialize().len();
|
||||||
|
|
||||||
let bp = self.is_rct_bulletproof();
|
let bp = self.is_rct_bulletproof();
|
||||||
let bp_plus = self.is_rct_bulletproof_plus();
|
let bp_plus = self.is_rct_bulletproof_plus();
|
||||||
Some(if !(bp || bp_plus) {
|
if !(bp || bp_plus) {
|
||||||
blob_size
|
blob_size
|
||||||
} else {
|
} else {
|
||||||
blob_size + Bulletproof::calculate_bp_clawback(bp_plus, self.prefix.outputs.len()).0
|
blob_size +
|
||||||
})
|
Bulletproof::calculate_bp_clawback(
|
||||||
|
bp_plus,
|
||||||
|
match self {
|
||||||
|
Transaction::V1 { .. } => panic!("v1 transaction was BP(+)"),
|
||||||
|
Transaction::V2 { prefix, .. } => prefix.outputs.len(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -419,7 +419,7 @@ impl Scanner {
|
|||||||
commitment.amount = amount;
|
commitment.amount = amount;
|
||||||
// Regular transaction
|
// Regular transaction
|
||||||
} else {
|
} else {
|
||||||
commitment = match tx.rct_signatures.base.encrypted_amounts.get(o) {
|
commitment = match tx.proofs.base.encrypted_amounts.get(o) {
|
||||||
Some(amount) => amount.decrypt(shared_key),
|
Some(amount) => amount.decrypt(shared_key),
|
||||||
// This should never happen, yet it may be possible with miner transactions?
|
// This should never happen, yet it may be possible with miner transactions?
|
||||||
// Using get just decreases the possibility of a panic and lets us move on in that case
|
// Using get just decreases the possibility of a panic and lets us move on in that case
|
||||||
@@ -428,7 +428,7 @@ impl Scanner {
|
|||||||
|
|
||||||
// If this is a malicious commitment, move to the next output
|
// If this is a malicious commitment, move to the next output
|
||||||
// Any other R value will calculate to a different spend key and are therefore ignorable
|
// Any other R value will calculate to a different spend key and are therefore ignorable
|
||||||
if Some(&commitment.calculate()) != tx.rct_signatures.base.commitments.get(o) {
|
if Some(&commitment.calculate()) != tx.proofs.base.commitments.get(o) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ use monero_serai::{
|
|||||||
hash_to_point,
|
hash_to_point,
|
||||||
clsag::{ClsagError, ClsagContext, Clsag},
|
clsag::{ClsagError, ClsagContext, Clsag},
|
||||||
bulletproofs::{MAX_COMMITMENTS, Bulletproof},
|
bulletproofs::{MAX_COMMITMENTS, Bulletproof},
|
||||||
RctBase, RctPrunable, RctSignatures,
|
RctBase, RctPrunable, RctProofs,
|
||||||
},
|
},
|
||||||
transaction::{Input, Output, Timelock, TransactionPrefix, Transaction},
|
transaction::{Input, Output, Timelock, TransactionPrefix, Transaction},
|
||||||
};
|
};
|
||||||
@@ -809,7 +809,7 @@ impl SignableTransaction {
|
|||||||
extra,
|
extra,
|
||||||
},
|
},
|
||||||
signatures: vec![],
|
signatures: vec![],
|
||||||
rct_signatures: RctSignatures {
|
proofs: RctProofs {
|
||||||
base: RctBase {
|
base: RctBase {
|
||||||
fee,
|
fee,
|
||||||
encrypted_amounts,
|
encrypted_amounts,
|
||||||
@@ -855,7 +855,7 @@ impl SignableTransaction {
|
|||||||
|
|
||||||
let clsag_pairs = Clsag::sign(rng, signable, mask_sum, tx.signature_hash())
|
let clsag_pairs = Clsag::sign(rng, signable, mask_sum, tx.signature_hash())
|
||||||
.map_err(|_| TransactionError::WrongPrivateKey)?;
|
.map_err(|_| TransactionError::WrongPrivateKey)?;
|
||||||
match tx.rct_signatures.prunable {
|
match tx.proofs.prunable {
|
||||||
RctPrunable::Null => panic!("Signing for RctPrunable::Null"),
|
RctPrunable::Null => panic!("Signing for RctPrunable::Null"),
|
||||||
RctPrunable::Clsag { ref mut clsags, ref mut pseudo_outs, .. } => {
|
RctPrunable::Clsag { ref mut clsags, ref mut pseudo_outs, .. } => {
|
||||||
clsags.append(&mut clsag_pairs.iter().map(|clsag| clsag.0.clone()).collect::<Vec<_>>());
|
clsags.append(&mut clsag_pairs.iter().map(|clsag| clsag.0.clone()).collect::<Vec<_>>());
|
||||||
@@ -867,7 +867,7 @@ impl SignableTransaction {
|
|||||||
if self.has_change {
|
if self.has_change {
|
||||||
debug_assert_eq!(
|
debug_assert_eq!(
|
||||||
self.fee_rate.calculate_fee_from_weight(tx.weight()),
|
self.fee_rate.calculate_fee_from_weight(tx.weight()),
|
||||||
tx.rct_signatures.base.fee,
|
tx.proofs.base.fee,
|
||||||
"transaction used unexpected fee",
|
"transaction used unexpected fee",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -917,7 +917,7 @@ impl Eventuality {
|
|||||||
uniqueness(&tx.prefix.inputs),
|
uniqueness(&tx.prefix.inputs),
|
||||||
);
|
);
|
||||||
|
|
||||||
let rct_type = tx.rct_signatures.rct_type();
|
let rct_type = tx.proofs.rct_type();
|
||||||
if rct_type != self.protocol.optimal_rct_type() {
|
if rct_type != self.protocol.optimal_rct_type() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -935,9 +935,9 @@ impl Eventuality {
|
|||||||
key: expected.dest.compress(),
|
key: expected.dest.compress(),
|
||||||
view_tag: Some(expected.view_tag).filter(|_| self.protocol.view_tags()),
|
view_tag: Some(expected.view_tag).filter(|_| self.protocol.view_tags()),
|
||||||
} != actual) ||
|
} != actual) ||
|
||||||
(Some(&expected.commitment.calculate()) != tx.rct_signatures.base.commitments.get(o)) ||
|
(Some(&expected.commitment.calculate()) != tx.proofs.base.commitments.get(o)) ||
|
||||||
(Some(&EncryptedAmount::Compact { amount: expected.amount }) !=
|
(Some(&EncryptedAmount::Compact { amount: expected.amount }) !=
|
||||||
tx.rct_signatures.base.encrypted_amounts.get(o))
|
tx.proofs.base.encrypted_amounts.get(o))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ impl SignatureMachine<Transaction> for TransactionSignatureMachine {
|
|||||||
shares: HashMap<Participant, Self::SignatureShare>,
|
shares: HashMap<Participant, Self::SignatureShare>,
|
||||||
) -> Result<Transaction, FrostError> {
|
) -> Result<Transaction, FrostError> {
|
||||||
let mut tx = self.tx;
|
let mut tx = self.tx;
|
||||||
match tx.rct_signatures.prunable {
|
match tx.proofs.prunable {
|
||||||
RctPrunable::Null => panic!("Signing for RctPrunable::Null"),
|
RctPrunable::Null => panic!("Signing for RctPrunable::Null"),
|
||||||
RctPrunable::Clsag { ref mut clsags, ref mut pseudo_outs, .. } => {
|
RctPrunable::Clsag { ref mut clsags, ref mut pseudo_outs, .. } => {
|
||||||
for (c, clsag) in self.clsags.drain(..).enumerate() {
|
for (c, clsag) in self.clsags.drain(..).enumerate() {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ test!(
|
|||||||
assert!(eventuality.matches(&tx));
|
assert!(eventuality.matches(&tx));
|
||||||
|
|
||||||
// Mutate the TX
|
// Mutate the TX
|
||||||
tx.rct_signatures.base.commitments[0] += ED25519_BASEPOINT_POINT;
|
tx.proofs.base.commitments[0] += ED25519_BASEPOINT_POINT;
|
||||||
// Verify it no longer matches
|
// Verify it no longer matches
|
||||||
assert!(!eventuality.matches(&tx));
|
assert!(!eventuality.matches(&tx));
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ pub async fn get_miner_tx_output(rpc: &SimpleRequestRpc, view: &ViewPair) -> Spe
|
|||||||
|
|
||||||
/// Make sure the weight and fee match the expected calculation.
|
/// Make sure the weight and fee match the expected calculation.
|
||||||
pub fn check_weight_and_fee(tx: &Transaction, fee_rate: FeeRate) {
|
pub fn check_weight_and_fee(tx: &Transaction, fee_rate: FeeRate) {
|
||||||
let fee = tx.rct_signatures.base.fee;
|
let fee = tx.proofs.base.fee;
|
||||||
|
|
||||||
let weight = tx.weight();
|
let weight = tx.weight();
|
||||||
let expected_weight = fee_rate.calculate_weight_from_fee(fee);
|
let expected_weight = fee_rate.calculate_weight_from_fee(fee);
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ test!(
|
|||||||
assert_eq!(outputs[1].commitment().amount, 50000);
|
assert_eq!(outputs[1].commitment().amount, 50000);
|
||||||
|
|
||||||
// The remainder should get shunted to fee, which is fingerprintable
|
// The remainder should get shunted to fee, which is fingerprintable
|
||||||
assert_eq!(tx.rct_signatures.base.fee, 1000000000000 - 10000 - 50000);
|
assert_eq!(tx.proofs.base.fee, 1000000000000 - 10000 - 50000);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user