diff --git a/coins/monero/src/block.rs b/coins/monero/src/block.rs index c1440b9a3..1f67b56e1 100644 --- a/coins/monero/src/block.rs +++ b/coins/monero/src/block.rs @@ -15,8 +15,8 @@ const CORRECT_BLOCK_HASH_202612: [u8; 32] = const EXISTING_BLOCK_HASH_202612: [u8; 32] = hex_literal::hex!("bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698"); -#[derive(Clone, PartialEq, Eq, Debug)] /// The header of a [`Block`]. +#[derive(Clone, PartialEq, Eq, Debug)] pub struct BlockHeader { /// This represents the hardfork number of the block. pub major_version: u8, @@ -60,9 +60,7 @@ impl BlockHeader { w.write_all(&self.nonce.to_le_bytes()) } - /// Serialize [`Self`]. - /// - /// This allocates and returns a new buffer containing the serialized bytes. + /// Serialize [`Self`] into a new byte buffer. /// /// # Example /// ```rust @@ -125,8 +123,8 @@ impl BlockHeader { } } -#[derive(Clone, PartialEq, Eq, Debug)] /// Block on the Monero blockchain. +#[derive(Clone, PartialEq, Eq, Debug)] pub struct Block { /// The header of this block. pub header: BlockHeader, @@ -197,9 +195,7 @@ impl Block { hash } - /// Serialize [`Self`]. - /// - /// This allocates and returns a new buffer containing the serialized bytes. + /// Serialize [`Self`] into a new byte buffer. pub fn serialize(&self) -> Vec { let mut serialized = vec![]; self.write(&mut serialized).unwrap(); diff --git a/coins/monero/src/lib.rs b/coins/monero/src/lib.rs index 0ddd25ac2..0ecdb7544 100644 --- a/coins/monero/src/lib.rs +++ b/coins/monero/src/lib.rs @@ -93,19 +93,34 @@ pub(crate) fn BASEPOINT_PRECOMP() -> &'static VartimeEdwardsPrecomputation { #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] #[allow(non_camel_case_types)] pub enum Protocol { + /// Version 14. v14, + /// Version 16. v16, + /// A custom version with customized properties. Custom { + /// See [`Self::ring_len`]. ring_len: usize, + /// See [`Self::bp_plus`]. bp_plus: bool, + /// See [`Self::optimal_rct_type`]. optimal_rct_type: RctType, + /// See [`Self::view_tags`]. view_tags: bool, + /// See [`Self::v16_fee`]. v16_fee: bool, }, } impl Protocol { /// Amount of ring members under this protocol version. + /// + /// # Example + /// ```rust + /// # use monero_serai::*; + /// assert_eq!(Protocol::v14.ring_len(), 11); + /// assert_eq!(Protocol::v16.ring_len(), 16); + /// ``` pub fn ring_len(&self) -> usize { match self { Protocol::v14 => 11, @@ -117,6 +132,13 @@ impl Protocol { /// Whether or not the specified version uses Bulletproofs or Bulletproofs+. /// /// This method will likely be reworked when versions not using Bulletproofs at all are added. + /// + /// # Example + /// ```rust + /// # use monero_serai::*; + /// assert_eq!(Protocol::v14.bp_plus(), false); + /// assert_eq!(Protocol::v16.bp_plus(), true); + /// ``` pub fn bp_plus(&self) -> bool { match self { Protocol::v14 => false, @@ -125,6 +147,14 @@ impl Protocol { } } + /// The optimal RingCT type for this version. + /// + /// # Example + /// ```rust + /// # use monero_serai::{*, ringct::*}; + /// assert_eq!(Protocol::v14.optimal_rct_type(), RctType::Clsag); + /// assert_eq!(Protocol::v16.optimal_rct_type(), RctType::BulletproofsPlus); + /// ``` // TODO: Make this an Option when we support pre-RCT protocols pub fn optimal_rct_type(&self) -> RctType { match self { @@ -135,6 +165,13 @@ impl Protocol { } /// Whether or not the specified version uses view tags. + /// + /// # Example + /// ```rust + /// # use monero_serai::{*, ringct::*}; + /// assert_eq!(Protocol::v14.view_tags(), false); + /// assert_eq!(Protocol::v16.view_tags(), true); + /// ``` pub fn view_tags(&self) -> bool { match self { Protocol::v14 => false, @@ -145,6 +182,13 @@ impl Protocol { /// Whether or not the specified version uses the fee algorithm from Monero /// hard fork version 16 (released in v18 binaries). + /// + /// # Example + /// ```rust + /// # use monero_serai::{*, ringct::*}; + /// assert_eq!(Protocol::v14.v16_fee(), false); + /// assert_eq!(Protocol::v16.v16_fee(), true); + /// ``` pub fn v16_fee(&self) -> bool { match self { Protocol::v14 => false, @@ -206,11 +250,15 @@ impl Protocol { } } -/// Transparent structure representing a Pedersen commitment's contents. +/// Transparent structure representing a [https://web.getmonero.org/resources/moneropedia/pedersen-commitment.html]()'s contents. #[allow(non_snake_case)] #[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)] pub struct Commitment { + /// The value used to mask the `amount`. pub mask: Scalar, + /// The value being masked. + /// + /// In Monero's case, this is the amount of XMR in atomic units. pub amount: u64, } @@ -226,6 +274,7 @@ impl Commitment { Commitment { mask: Scalar::ONE, amount: 0 } } + /// Create a new [`Self`]. pub fn new(mask: Scalar, amount: u64) -> Commitment { Commitment { mask, amount } } diff --git a/coins/monero/src/ring_signatures.rs b/coins/monero/src/ring_signatures.rs index 72d30b0e9..3e8d0bfa9 100644 --- a/coins/monero/src/ring_signatures.rs +++ b/coins/monero/src/ring_signatures.rs @@ -11,6 +11,7 @@ use monero_generators::hash_to_point; use crate::{serialize::*, hash_to_scalar}; +/// A signature within a [`RingSignature`]. #[derive(Clone, PartialEq, Eq, Debug, Zeroize)] pub struct Signature { c: Scalar, @@ -18,23 +19,37 @@ pub struct Signature { } impl Signature { + /// Serialize [`Self`] into the writer `w`. + /// + /// # Errors + /// This function returns any errors from the writer itself. pub fn write(&self, w: &mut W) -> io::Result<()> { write_scalar(&self.c, w)?; write_scalar(&self.r, w)?; Ok(()) } + /// Create [`Self`] from the reader `r`. + /// + /// # Errors + /// This function returns an error if either the reader failed, + /// or if the data could not be deserialized into a [`Self`]. pub fn read(r: &mut R) -> io::Result { Ok(Signature { c: read_scalar(r)?, r: read_scalar(r)? }) } } +/// A [ring signature](https://en.wikipedia.org/wiki/Ring_signature). #[derive(Clone, PartialEq, Eq, Debug, Zeroize)] pub struct RingSignature { sigs: Vec, } impl RingSignature { + /// Serialize [`Self`] into the writer `w`. + /// + /// # Errors + /// This function returns any errors from the writer itself. pub fn write(&self, w: &mut W) -> io::Result<()> { for sig in &self.sigs { sig.write(w)?; @@ -42,6 +57,11 @@ impl RingSignature { Ok(()) } + /// Create [`Self`] from the reader `r`. + /// + /// # Errors + /// This function returns an error if either the reader failed, + /// or if the data could not be deserialized into a [`Self`]. pub fn read(members: usize, r: &mut R) -> io::Result { Ok(RingSignature { sigs: read_raw_vec(Signature::read, members, r)? }) } diff --git a/coins/monero/src/ringct/mod.rs b/coins/monero/src/ringct/mod.rs index 3780c5da3..7e8379a17 100644 --- a/coins/monero/src/ringct/mod.rs +++ b/coins/monero/src/ringct/mod.rs @@ -81,7 +81,18 @@ pub enum RctType { } impl RctType { - /// Convert the RctType to its byte representation. + /// Convert [`self`] to its byte representation. + /// + /// ```rust + /// # use monero_serai::ringct::*; + /// assert_eq!(RctType::Null.to_bytes(), 0); + /// assert_eq!(RctType::MlsagAggregate.to_bytes(), 1); + /// assert_eq!(RctType::MlsagIndividual.to_bytes(), 2); + /// assert_eq!(RctType::Bulletproofs.to_bytes(), 3); + /// assert_eq!(RctType::BulletproofsCompactAmount.to_bytes(), 4); + /// assert_eq!(RctType::Clsag.to_bytes(), 5); + /// assert_eq!(RctType::BulletproofsPlus.to_bytes(), 6); + /// ``` pub fn to_byte(self) -> u8 { match self { RctType::Null => 0, @@ -94,7 +105,25 @@ impl RctType { } } - /// Convert the RctType from its byte representation. + /// Create [`Self`] from a byte representation. + /// + /// ```rust + /// # use monero_serai::ringct::*; + /// assert_eq!(RctType::from_bytes(0).unwrap(), RctType::Null); + /// assert_eq!(RctType::from_bytes(1).unwrap(), RctType::MlsagAggregate); + /// assert_eq!(RctType::from_bytes(2).unwrap(), RctType::MlsagIndividual); + /// assert_eq!(RctType::from_bytes(3).unwrap(), RctType::Bulletproofs); + /// assert_eq!(RctType::from_bytes(4).unwrap(), RctType::BulletproofsCompactAmount); + /// assert_eq!(RctType::from_bytes(5).unwrap(), RctType::Clsag); + /// assert_eq!(RctType::from_bytes(6).unwrap(), RctType::BulletproofsPlus); + /// ``` + /// + /// # Errors + /// This function returns [`None`] if the byte representation is invalid. + /// ```rust + /// # use monero_serai::ringct::*; + /// assert_eq!(RctType::from_bytes(7), None); + /// ``` pub fn from_byte(byte: u8) -> Option { Some(match byte { 0 => RctType::Null, @@ -109,12 +138,23 @@ impl RctType { } /// Returns true if this RctType uses compact encrypted amounts, false otherwise. + /// + /// ```rust + /// # use monero_serai::ringct::*; + /// assert_eq!(RctType::Null.compact_encrypted_amounts(), false); + /// assert_eq!(RctType::MlsagAggregate.compact_encrypted_amounts(), false); + /// assert_eq!(RctType::MlsagIndividual.compact_encrypted_amounts(), false); + /// assert_eq!(RctType::Bulletproofs.compact_encrypted_amounts(), false); + /// assert_eq!(RctType::BulletproofsCompactAmount.compact_encrypted_amounts(), true); + /// assert_eq!(RctType::Clsag.compact_encrypted_amounts(), true); + /// assert_eq!(RctType::BulletproofsPlus.compact_encrypted_amounts(), true); + /// ``` pub fn compact_encrypted_amounts(&self) -> bool { match self { - RctType::Null | - RctType::MlsagAggregate | - RctType::MlsagIndividual | - RctType::Bulletproofs => false, + RctType::Null + | RctType::MlsagAggregate + | RctType::MlsagIndividual + | RctType::Bulletproofs => false, RctType::BulletproofsCompactAmount | RctType::Clsag | RctType::BulletproofsPlus => true, } } @@ -170,10 +210,10 @@ impl RctBase { match rct_type { RctType::Null | RctType::MlsagAggregate | RctType::MlsagIndividual => {} - RctType::Bulletproofs | - RctType::BulletproofsCompactAmount | - RctType::Clsag | - RctType::BulletproofsPlus => { + RctType::Bulletproofs + | RctType::BulletproofsCompactAmount + | RctType::Clsag + | RctType::BulletproofsPlus => { if outputs == 0 { // Because the Bulletproofs(+) layout must be canonical, there must be 1 Bulletproof if // Bulletproofs are in use @@ -198,7 +238,7 @@ impl RctBase { } else { vec![] }, - encrypted_amounts: (0 .. outputs) + encrypted_amounts: (0..outputs) .map(|_| EncryptedAmount::read(rct_type.compact_encrypted_amounts(), r)) .collect::>()?, commitments: read_raw_vec(read_point, outputs, r)?, @@ -209,6 +249,7 @@ impl RctBase { } } +/// The prunable portion of the RingCT data. #[derive(Clone, PartialEq, Eq, Debug)] pub enum RctPrunable { Null, @@ -235,10 +276,14 @@ pub enum RctPrunable { impl RctPrunable { pub(crate) fn fee_weight(protocol: Protocol, inputs: usize, outputs: usize) -> usize { // 1 byte for number of BPs (technically a VarInt, yet there's always just zero or one) - 1 + Bulletproof::fee_weight(protocol.bp_plus(), outputs) + - (inputs * (Clsag::fee_weight(protocol.ring_len()) + 32)) + 1 + Bulletproof::fee_weight(protocol.bp_plus(), outputs) + + (inputs * (Clsag::fee_weight(protocol.ring_len()) + 32)) } + /// Serialize [`Self`] into the writer `w`. + /// + /// # Errors + /// This function returns any errors from the writer itself. pub fn write(&self, w: &mut W, rct_type: RctType) -> io::Result<()> { match self { RctPrunable::Null => Ok(()), @@ -271,12 +316,18 @@ impl RctPrunable { } } + /// Serialize [`Self`] into a new byte buffer. pub fn serialize(&self, rct_type: RctType) -> Vec { let mut serialized = vec![]; self.write(&mut serialized, rct_type).unwrap(); serialized } + /// Create [`Self`] from the reader `r`. + /// + /// # Errors + /// This function returns an error if either the reader failed, + /// or if the data could not be deserialized into a [`Self`]. pub fn read( rct_type: RctType, ring_length: usize, @@ -303,7 +354,7 @@ impl RctPrunable { }, RctType::MlsagIndividual => RctPrunable::MlsagBorromean { borromean: read_raw_vec(BorromeanRange::read, outputs, r)?, - mlsags: (0 .. inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::>()?, + mlsags: (0..inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::>()?, }, RctType::Bulletproofs | RctType::BulletproofsCompactAmount => { RctPrunable::MlsagBulletproofs { @@ -318,9 +369,7 @@ impl RctPrunable { } Bulletproof::read(r)? }, - mlsags: (0 .. inputs) - .map(|_| Mlsag::read(ring_length, 2, r)) - .collect::>()?, + mlsags: (0..inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::>()?, pseudo_outs: read_raw_vec(read_point, inputs, r)?, } } @@ -331,7 +380,7 @@ impl RctPrunable { } (if rct_type == RctType::Clsag { Bulletproof::read } else { Bulletproof::read_plus })(r)? }, - clsags: (0 .. inputs).map(|_| Clsag::read(ring_length, r)).collect::>()?, + clsags: (0..inputs).map(|_| Clsag::read(ring_length, r)).collect::>()?, pseudo_outs: read_raw_vec(read_point, inputs, r)?, }, }) @@ -340,19 +389,22 @@ impl RctPrunable { pub(crate) fn signature_write(&self, w: &mut W) -> io::Result<()> { match self { RctPrunable::Null => panic!("Serializing RctPrunable::Null for a signature"), - RctPrunable::AggregateMlsagBorromean { borromean, .. } | - RctPrunable::MlsagBorromean { borromean, .. } => { + RctPrunable::AggregateMlsagBorromean { borromean, .. } + | RctPrunable::MlsagBorromean { borromean, .. } => { borromean.iter().try_for_each(|rs| rs.write(w)) } - RctPrunable::MlsagBulletproofs { bulletproofs, .. } | - RctPrunable::Clsag { bulletproofs, .. } => bulletproofs.signature_write(w), + RctPrunable::MlsagBulletproofs { bulletproofs, .. } + | RctPrunable::Clsag { bulletproofs, .. } => bulletproofs.signature_write(w), } } } +/// RingCT signature data. #[derive(Clone, PartialEq, Eq, Debug)] pub struct RctSignatures { + /// The base of the RingCT data. pub base: RctBase, + /// The prunable portion of the RingCT data. pub prunable: RctPrunable, } @@ -393,18 +445,28 @@ impl RctSignatures { RctBase::fee_weight(outputs, fee) + RctPrunable::fee_weight(protocol, inputs, outputs) } + /// Serialize [`Self`] into the writer `w`. + /// + /// # Errors + /// This function returns any errors from the writer itself. pub fn 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) } + /// Serialize [`Self`] into a new byte buffer. pub fn serialize(&self) -> Vec { let mut serialized = vec![]; self.write(&mut serialized).unwrap(); serialized } + /// Create [`Self`] from the reader `r` and other data. + /// + /// # Errors + /// This function returns an error if either the reader failed, + /// or if the data could not be deserialized into a [`Self`]. pub fn read( ring_length: usize, inputs: usize,