use std_shims::{
vec,
vec::Vec,
io::{self, Read, Write},
};
use zeroize::Zeroize;
use curve25519_dalek::edwards::EdwardsPoint;
pub use monero_mlsag as mlsag;
pub use monero_clsag as clsag;
pub use monero_borromean as borromean;
pub use monero_bulletproofs as bulletproofs;
use crate::{
io::*,
ringct::{mlsag::Mlsag, clsag::Clsag, borromean::BorromeanRange, bulletproofs::Bulletproof},
};
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum EncryptedAmount {
Original {
mask: [u8; 32],
amount: [u8; 32],
},
Compact {
amount: [u8; 8],
},
}
impl EncryptedAmount {
pub fn read<R: Read>(compact: bool, r: &mut R) -> io::Result<EncryptedAmount> {
Ok(if !compact {
EncryptedAmount::Original { mask: read_bytes(r)?, amount: read_bytes(r)? }
} else {
EncryptedAmount::Compact { amount: read_bytes(r)? }
})
}
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
match self {
EncryptedAmount::Original { mask, amount } => {
w.write_all(mask)?;
w.write_all(amount)
}
EncryptedAmount::Compact { amount } => w.write_all(amount),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub enum RctType {
AggregateMlsagBorromean,
MlsagBorromean,
MlsagBulletproofs,
MlsagBulletproofsCompactAmount,
ClsagBulletproof,
ClsagBulletproofPlus,
}
impl From<RctType> for u8 {
fn from(rct_type: RctType) -> u8 {
match rct_type {
RctType::AggregateMlsagBorromean => 1,
RctType::MlsagBorromean => 2,
RctType::MlsagBulletproofs => 3,
RctType::MlsagBulletproofsCompactAmount => 4,
RctType::ClsagBulletproof => 5,
RctType::ClsagBulletproofPlus => 6,
}
}
}
impl TryFrom<u8> for RctType {
type Error = ();
fn try_from(byte: u8) -> Result<Self, ()> {
Ok(match byte {
1 => RctType::AggregateMlsagBorromean,
2 => RctType::MlsagBorromean,
3 => RctType::MlsagBulletproofs,
4 => RctType::MlsagBulletproofsCompactAmount,
5 => RctType::ClsagBulletproof,
6 => RctType::ClsagBulletproofPlus,
_ => Err(())?,
})
}
}
impl RctType {
pub fn compact_encrypted_amounts(&self) -> bool {
match self {
RctType::AggregateMlsagBorromean | RctType::MlsagBorromean | RctType::MlsagBulletproofs => {
false
}
RctType::MlsagBulletproofsCompactAmount |
RctType::ClsagBulletproof |
RctType::ClsagBulletproofPlus => true,
}
}
pub(crate) fn bulletproof(&self) -> bool {
match self {
RctType::MlsagBulletproofs |
RctType::MlsagBulletproofsCompactAmount |
RctType::ClsagBulletproof => true,
RctType::AggregateMlsagBorromean |
RctType::MlsagBorromean |
RctType::ClsagBulletproofPlus => false,
}
}
pub(crate) fn bulletproof_plus(&self) -> bool {
match self {
RctType::ClsagBulletproofPlus => true,
RctType::AggregateMlsagBorromean |
RctType::MlsagBorromean |
RctType::MlsagBulletproofs |
RctType::MlsagBulletproofsCompactAmount |
RctType::ClsagBulletproof => false,
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct RctBase {
pub fee: u64,
pub pseudo_outs: Vec<EdwardsPoint>,
pub encrypted_amounts: Vec<EncryptedAmount>,
pub commitments: Vec<EdwardsPoint>,
}
impl RctBase {
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
w.write_all(&[u8::from(rct_type)])?;
write_varint(&self.fee, w)?;
if rct_type == RctType::MlsagBorromean {
write_raw_vec(write_point, &self.pseudo_outs, w)?;
}
for encrypted_amount in &self.encrypted_amounts {
encrypted_amount.write(w)?;
}
write_raw_vec(write_point, &self.commitments, w)
}
pub fn read<R: Read>(
inputs: usize,
outputs: usize,
r: &mut R,
) -> io::Result<Option<(RctType, RctBase)>> {
let rct_type = read_byte(r)?;
if rct_type == 0 {
return Ok(None);
}
let rct_type =
RctType::try_from(rct_type).map_err(|()| io::Error::other("invalid RCT type"))?;
match rct_type {
RctType::AggregateMlsagBorromean | RctType::MlsagBorromean => {}
RctType::MlsagBulletproofs |
RctType::MlsagBulletproofsCompactAmount |
RctType::ClsagBulletproof |
RctType::ClsagBulletproofPlus => {
if outputs == 0 {
Err(io::Error::other("RCT with Bulletproofs(+) had 0 outputs"))?;
}
}
}
Ok(Some((
rct_type,
RctBase {
fee: read_varint(r)?,
pseudo_outs: if rct_type == RctType::MlsagBorromean {
read_raw_vec(read_point, inputs, r)?
} else {
vec![]
},
encrypted_amounts: (0 .. outputs)
.map(|_| EncryptedAmount::read(rct_type.compact_encrypted_amounts(), r))
.collect::<Result<_, _>>()?,
commitments: read_raw_vec(read_point, outputs, r)?,
},
)))
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum RctPrunable {
AggregateMlsagBorromean {
mlsag: Mlsag,
borromean: Vec<BorromeanRange>,
},
MlsagBorromean {
mlsags: Vec<Mlsag>,
borromean: Vec<BorromeanRange>,
},
MlsagBulletproofs {
mlsags: Vec<Mlsag>,
pseudo_outs: Vec<EdwardsPoint>,
bulletproof: Bulletproof,
},
MlsagBulletproofsCompactAmount {
mlsags: Vec<Mlsag>,
pseudo_outs: Vec<EdwardsPoint>,
bulletproof: Bulletproof,
},
Clsag {
clsags: Vec<Clsag>,
pseudo_outs: Vec<EdwardsPoint>,
bulletproof: Bulletproof,
},
}
impl RctPrunable {
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
match self {
RctPrunable::AggregateMlsagBorromean { borromean, mlsag } => {
write_raw_vec(BorromeanRange::write, borromean, w)?;
mlsag.write(w)
}
RctPrunable::MlsagBorromean { borromean, mlsags } => {
write_raw_vec(BorromeanRange::write, borromean, w)?;
write_raw_vec(Mlsag::write, mlsags, w)
}
RctPrunable::MlsagBulletproofs { bulletproof, mlsags, pseudo_outs } |
RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, mlsags, pseudo_outs } => {
if rct_type == RctType::MlsagBulletproofs {
w.write_all(&1u32.to_le_bytes())?;
} else {
w.write_all(&[1])?;
}
bulletproof.write(w)?;
write_raw_vec(Mlsag::write, mlsags, w)?;
write_raw_vec(write_point, pseudo_outs, w)
}
RctPrunable::Clsag { bulletproof, clsags, pseudo_outs } => {
w.write_all(&[1])?;
bulletproof.write(w)?;
write_raw_vec(Clsag::write, clsags, w)?;
write_raw_vec(write_point, pseudo_outs, w)
}
}
}
pub fn serialize(&self, rct_type: RctType) -> Vec<u8> {
let mut serialized = vec![];
self.write(&mut serialized, rct_type).unwrap();
serialized
}
pub fn read<R: Read>(
rct_type: RctType,
ring_length: usize,
inputs: usize,
outputs: usize,
r: &mut R,
) -> io::Result<RctPrunable> {
Ok(match rct_type {
RctType::AggregateMlsagBorromean => RctPrunable::AggregateMlsagBorromean {
borromean: read_raw_vec(BorromeanRange::read, outputs, r)?,
mlsag: Mlsag::read(ring_length, inputs + 1, r)?,
},
RctType::MlsagBorromean => RctPrunable::MlsagBorromean {
borromean: read_raw_vec(BorromeanRange::read, outputs, r)?,
mlsags: (0 .. inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::<Result<_, _>>()?,
},
RctType::MlsagBulletproofs | RctType::MlsagBulletproofsCompactAmount => {
let bulletproof = {
if (if rct_type == RctType::MlsagBulletproofs {
u64::from(read_u32(r)?)
} else {
read_varint(r)?
}) != 1
{
Err(io::Error::other("n bulletproofs instead of one"))?;
}
Bulletproof::read(r)?
};
let mlsags =
(0 .. inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::<Result<_, _>>()?;
let 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::ClsagBulletproof | RctType::ClsagBulletproofPlus => RctPrunable::Clsag {
bulletproof: {
if read_varint::<_, u64>(r)? != 1 {
Err(io::Error::other("n bulletproofs instead of one"))?;
}
(if rct_type == RctType::ClsagBulletproof {
Bulletproof::read
} else {
Bulletproof::read_plus
})(r)?
},
clsags: (0 .. inputs).map(|_| Clsag::read(ring_length, r)).collect::<Result<_, _>>()?,
pseudo_outs: read_raw_vec(read_point, inputs, r)?,
},
})
}
pub(crate) fn signature_write<W: Write>(&self, w: &mut W) -> io::Result<()> {
match self {
RctPrunable::AggregateMlsagBorromean { borromean, .. } |
RctPrunable::MlsagBorromean { borromean, .. } => {
borromean.iter().try_for_each(|rs| rs.write(w))
}
RctPrunable::MlsagBulletproofs { bulletproof, .. } |
RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, .. } |
RctPrunable::Clsag { bulletproof, .. } => bulletproof.signature_write(w),
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct RctProofs {
pub base: RctBase,
pub prunable: RctPrunable,
}
impl RctProofs {
pub fn rct_type(&self) -> RctType {
match &self.prunable {
RctPrunable::AggregateMlsagBorromean { .. } => RctType::AggregateMlsagBorromean,
RctPrunable::MlsagBorromean { .. } => RctType::MlsagBorromean,
RctPrunable::MlsagBulletproofs { .. } => RctType::MlsagBulletproofs,
RctPrunable::MlsagBulletproofsCompactAmount { .. } => RctType::MlsagBulletproofsCompactAmount,
RctPrunable::Clsag { bulletproof, .. } => {
if matches!(bulletproof, Bulletproof::Original { .. }) {
RctType::ClsagBulletproof
} else {
RctType::ClsagBulletproofPlus
}
}
}
}
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
let rct_type = self.rct_type();
self.base.write(w, rct_type)?;
self.prunable.write(w, rct_type)
}
pub fn serialize(&self) -> Vec<u8> {
let mut serialized = vec![];
self.write(&mut serialized).unwrap();
serialized
}
pub fn read<R: Read>(
ring_length: usize,
inputs: usize,
outputs: usize,
r: &mut R,
) -> io::Result<Option<RctProofs>> {
let Some((rct_type, base)) = RctBase::read(inputs, outputs, r)? else { return Ok(None) };
Ok(Some(RctProofs {
base,
prunable: RctPrunable::read(rct_type, ring_length, inputs, outputs, r)?,
}))
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct PrunedRctProofs {
pub rct_type: RctType,
pub base: RctBase,
}