doc ringct/mod.rs

This commit is contained in:
hinto.janai
2024-05-16 19:06:59 -04:00
parent 58fe79da10
commit d4b22e5136
4 changed files with 158 additions and 31 deletions

View File

@@ -15,8 +15,8 @@ const CORRECT_BLOCK_HASH_202612: [u8; 32] =
const EXISTING_BLOCK_HASH_202612: [u8; 32] = const EXISTING_BLOCK_HASH_202612: [u8; 32] =
hex_literal::hex!("bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698"); hex_literal::hex!("bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698");
#[derive(Clone, PartialEq, Eq, Debug)]
/// The header of a [`Block`]. /// The header of a [`Block`].
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct BlockHeader { pub struct BlockHeader {
/// This represents the hardfork number of the block. /// This represents the hardfork number of the block.
pub major_version: u8, pub major_version: u8,
@@ -60,9 +60,7 @@ impl BlockHeader {
w.write_all(&self.nonce.to_le_bytes()) w.write_all(&self.nonce.to_le_bytes())
} }
/// Serialize [`Self`]. /// Serialize [`Self`] into a new byte buffer.
///
/// This allocates and returns a new buffer containing the serialized bytes.
/// ///
/// # Example /// # Example
/// ```rust /// ```rust
@@ -125,8 +123,8 @@ impl BlockHeader {
} }
} }
#[derive(Clone, PartialEq, Eq, Debug)]
/// Block on the Monero blockchain. /// Block on the Monero blockchain.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Block { pub struct Block {
/// The header of this block. /// The header of this block.
pub header: BlockHeader, pub header: BlockHeader,
@@ -197,9 +195,7 @@ impl Block {
hash hash
} }
/// Serialize [`Self`]. /// Serialize [`Self`] into a new byte buffer.
///
/// This allocates and returns a new buffer containing the serialized bytes.
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();

View File

@@ -93,19 +93,34 @@ pub(crate) fn BASEPOINT_PRECOMP() -> &'static VartimeEdwardsPrecomputation {
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub enum Protocol { pub enum Protocol {
/// Version 14.
v14, v14,
/// Version 16.
v16, v16,
/// A custom version with customized properties.
Custom { Custom {
/// See [`Self::ring_len`].
ring_len: usize, ring_len: usize,
/// See [`Self::bp_plus`].
bp_plus: bool, bp_plus: bool,
/// See [`Self::optimal_rct_type`].
optimal_rct_type: RctType, optimal_rct_type: RctType,
/// See [`Self::view_tags`].
view_tags: bool, view_tags: bool,
/// See [`Self::v16_fee`].
v16_fee: bool, v16_fee: bool,
}, },
} }
impl Protocol { impl Protocol {
/// Amount of ring members under this protocol version. /// 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 { pub fn ring_len(&self) -> usize {
match self { match self {
Protocol::v14 => 11, Protocol::v14 => 11,
@@ -117,6 +132,13 @@ impl Protocol {
/// Whether or not the specified version uses Bulletproofs or Bulletproofs+. /// 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. /// 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 { pub fn bp_plus(&self) -> bool {
match self { match self {
Protocol::v14 => false, 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 // TODO: Make this an Option when we support pre-RCT protocols
pub fn optimal_rct_type(&self) -> RctType { pub fn optimal_rct_type(&self) -> RctType {
match self { match self {
@@ -135,6 +165,13 @@ impl Protocol {
} }
/// Whether or not the specified version uses view tags. /// 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 { pub fn view_tags(&self) -> bool {
match self { match self {
Protocol::v14 => false, Protocol::v14 => false,
@@ -145,6 +182,13 @@ impl Protocol {
/// Whether or not the specified version uses the fee algorithm from Monero /// Whether or not the specified version uses the fee algorithm from Monero
/// hard fork version 16 (released in v18 binaries). /// 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 { pub fn v16_fee(&self) -> bool {
match self { match self {
Protocol::v14 => false, 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)] #[allow(non_snake_case)]
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)] #[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub struct Commitment { pub struct Commitment {
/// The value used to mask the `amount`.
pub mask: Scalar, pub mask: Scalar,
/// The value being masked.
///
/// In Monero's case, this is the amount of XMR in atomic units.
pub amount: u64, pub amount: u64,
} }
@@ -226,6 +274,7 @@ impl Commitment {
Commitment { mask: Scalar::ONE, amount: 0 } Commitment { mask: Scalar::ONE, amount: 0 }
} }
/// Create a new [`Self`].
pub fn new(mask: Scalar, amount: u64) -> Commitment { pub fn new(mask: Scalar, amount: u64) -> Commitment {
Commitment { mask, amount } Commitment { mask, amount }
} }

View File

@@ -11,6 +11,7 @@ use monero_generators::hash_to_point;
use crate::{serialize::*, hash_to_scalar}; use crate::{serialize::*, hash_to_scalar};
/// A signature within a [`RingSignature`].
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)] #[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub struct Signature { pub struct Signature {
c: Scalar, c: Scalar,
@@ -18,23 +19,37 @@ pub struct Signature {
} }
impl Signature { impl Signature {
/// Serialize [`Self`] into the writer `w`.
///
/// # Errors
/// This function returns any errors from the writer itself.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> { pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
write_scalar(&self.c, w)?; write_scalar(&self.c, w)?;
write_scalar(&self.r, w)?; write_scalar(&self.r, w)?;
Ok(()) 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: Read>(r: &mut R) -> io::Result<Signature> { pub fn read<R: Read>(r: &mut R) -> io::Result<Signature> {
Ok(Signature { c: read_scalar(r)?, r: read_scalar(r)? }) 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)] #[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub struct RingSignature { pub struct RingSignature {
sigs: Vec<Signature>, sigs: Vec<Signature>,
} }
impl RingSignature { impl RingSignature {
/// Serialize [`Self`] into the writer `w`.
///
/// # Errors
/// This function returns any errors from the writer itself.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> { pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
for sig in &self.sigs { for sig in &self.sigs {
sig.write(w)?; sig.write(w)?;
@@ -42,6 +57,11 @@ impl RingSignature {
Ok(()) 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: Read>(members: usize, r: &mut R) -> io::Result<RingSignature> { pub fn read<R: Read>(members: usize, r: &mut R) -> io::Result<RingSignature> {
Ok(RingSignature { sigs: read_raw_vec(Signature::read, members, r)? }) Ok(RingSignature { sigs: read_raw_vec(Signature::read, members, r)? })
} }

View File

@@ -81,7 +81,18 @@ pub enum RctType {
} }
impl 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 { pub fn to_byte(self) -> u8 {
match self { match self {
RctType::Null => 0, 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<Self> { pub fn from_byte(byte: u8) -> Option<Self> {
Some(match byte { Some(match byte {
0 => RctType::Null, 0 => RctType::Null,
@@ -109,12 +138,23 @@ impl RctType {
} }
/// Returns true if this RctType uses compact encrypted amounts, false otherwise. /// 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 { pub fn compact_encrypted_amounts(&self) -> bool {
match self { match self {
RctType::Null | RctType::Null
RctType::MlsagAggregate | | RctType::MlsagAggregate
RctType::MlsagIndividual | | RctType::MlsagIndividual
RctType::Bulletproofs => false, | RctType::Bulletproofs => false,
RctType::BulletproofsCompactAmount | RctType::Clsag | RctType::BulletproofsPlus => true, RctType::BulletproofsCompactAmount | RctType::Clsag | RctType::BulletproofsPlus => true,
} }
} }
@@ -170,10 +210,10 @@ impl RctBase {
match rct_type { match rct_type {
RctType::Null | RctType::MlsagAggregate | RctType::MlsagIndividual => {} RctType::Null | RctType::MlsagAggregate | RctType::MlsagIndividual => {}
RctType::Bulletproofs | RctType::Bulletproofs
RctType::BulletproofsCompactAmount | | RctType::BulletproofsCompactAmount
RctType::Clsag | | RctType::Clsag
RctType::BulletproofsPlus => { | RctType::BulletproofsPlus => {
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
@@ -198,7 +238,7 @@ impl RctBase {
} else { } else {
vec![] vec![]
}, },
encrypted_amounts: (0 .. outputs) encrypted_amounts: (0..outputs)
.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)?,
@@ -209,6 +249,7 @@ impl RctBase {
} }
} }
/// The prunable portion of the RingCT data.
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
pub enum RctPrunable { pub enum RctPrunable {
Null, Null,
@@ -235,10 +276,14 @@ pub enum RctPrunable {
impl RctPrunable { impl RctPrunable {
pub(crate) fn fee_weight(protocol: Protocol, inputs: usize, outputs: usize) -> usize { 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 byte for number of BPs (technically a VarInt, yet there's always just zero or one)
1 + Bulletproof::fee_weight(protocol.bp_plus(), outputs) + 1 + Bulletproof::fee_weight(protocol.bp_plus(), outputs)
(inputs * (Clsag::fee_weight(protocol.ring_len()) + 32)) + (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<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::Null => Ok(()),
@@ -271,12 +316,18 @@ impl RctPrunable {
} }
} }
/// Serialize [`Self`] into a new byte buffer.
pub fn serialize(&self, rct_type: RctType) -> Vec<u8> { pub fn serialize(&self, rct_type: RctType) -> Vec<u8> {
let mut serialized = vec![]; let mut serialized = vec![];
self.write(&mut serialized, rct_type).unwrap(); self.write(&mut serialized, rct_type).unwrap();
serialized 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<R: Read>( pub fn read<R: Read>(
rct_type: RctType, rct_type: RctType,
ring_length: usize, ring_length: usize,
@@ -303,7 +354,7 @@ impl RctPrunable {
}, },
RctType::MlsagIndividual => RctPrunable::MlsagBorromean { RctType::MlsagIndividual => 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::Bulletproofs | RctType::BulletproofsCompactAmount => {
RctPrunable::MlsagBulletproofs { RctPrunable::MlsagBulletproofs {
@@ -318,9 +369,7 @@ impl RctPrunable {
} }
Bulletproof::read(r)? Bulletproof::read(r)?
}, },
mlsags: (0 .. inputs) mlsags: (0..inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::<Result<_, _>>()?,
.map(|_| Mlsag::read(ring_length, 2, r))
.collect::<Result<_, _>>()?,
pseudo_outs: read_raw_vec(read_point, inputs, r)?, 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)? (if rct_type == RctType::Clsag { 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)?,
}, },
}) })
@@ -340,19 +389,22 @@ impl RctPrunable {
pub(crate) fn signature_write<W: Write>(&self, w: &mut W) -> io::Result<()> { pub(crate) fn signature_write<W: Write>(&self, w: &mut W) -> io::Result<()> {
match self { match self {
RctPrunable::Null => panic!("Serializing RctPrunable::Null for a signature"), RctPrunable::Null => panic!("Serializing RctPrunable::Null for a signature"),
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 { bulletproofs, .. }
RctPrunable::Clsag { bulletproofs, .. } => bulletproofs.signature_write(w), | RctPrunable::Clsag { bulletproofs, .. } => bulletproofs.signature_write(w),
} }
} }
} }
/// RingCT signature data.
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
pub struct RctSignatures { pub struct RctSignatures {
/// The base of the RingCT data.
pub base: RctBase, pub base: RctBase,
/// The prunable portion of the RingCT data.
pub prunable: RctPrunable, pub prunable: RctPrunable,
} }
@@ -393,18 +445,28 @@ impl RctSignatures {
RctBase::fee_weight(outputs, fee) + RctPrunable::fee_weight(protocol, inputs, outputs) 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<W: Write>(&self, w: &mut W) -> 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();
self.base.write(w, rct_type)?; self.base.write(w, rct_type)?;
self.prunable.write(w, rct_type) self.prunable.write(w, rct_type)
} }
/// Serialize [`Self`] into a new byte buffer.
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();
serialized 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<R: Read>( pub fn read<R: Read>(
ring_length: usize, ring_length: usize,
inputs: usize, inputs: usize,