From fd48bbd15e3f65aef012053e53885a7d3cd2cd5f Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 28 Sep 2022 07:44:49 -0500 Subject: [PATCH] Initial documentation for the Monero libraries (#122) * Document all features * Largely document the Monero libraries Relevant to https://github.com/serai-dex/serai/issues/103 and likely sufficient to get this removed from https://github.com/serai-dex/serai/issues/102. --- coins/monero/Cargo.toml | 3 ++ coins/monero/generators/Cargo.toml | 3 ++ coins/monero/generators/src/hash_to_point.rs | 2 +- coins/monero/generators/src/lib.rs | 7 +++-- coins/monero/src/frost.rs | 6 +--- coins/monero/src/lib.rs | 27 ++++++++++++++-- coins/monero/src/ringct/bulletproofs/mod.rs | 8 +++++ coins/monero/src/ringct/clsag/mod.rs | 8 ++++- coins/monero/src/ringct/clsag/multisig.rs | 10 +++--- coins/monero/src/ringct/hash_to_point.rs | 1 + coins/monero/src/ringct/mod.rs | 2 ++ coins/monero/src/rpc.rs | 15 ++++++++- coins/monero/src/tests/clsag.rs | 3 +- coins/monero/src/transaction.rs | 2 ++ coins/monero/src/wallet/address.rs | 2 ++ coins/monero/src/wallet/decoys.rs | 2 ++ coins/monero/src/wallet/mod.rs | 16 ++++++++-- coins/monero/src/wallet/scan.rs | 33 +++++++++++++++----- coins/monero/src/wallet/send/mod.rs | 12 ++++--- coins/monero/src/wallet/send/multisig.rs | 6 ++-- contracts/extension/Cargo.toml | 1 + contracts/multisig/Cargo.toml | 1 + crypto/dalek-ff-group/Cargo.toml | 3 ++ crypto/dleq/Cargo.toml | 3 ++ crypto/ed448/Cargo.toml | 3 ++ crypto/frost/Cargo.toml | 3 ++ crypto/multiexp/Cargo.toml | 3 ++ crypto/transcript/Cargo.toml | 3 ++ 28 files changed, 153 insertions(+), 35 deletions(-) diff --git a/coins/monero/Cargo.toml b/coins/monero/Cargo.toml index 69ef8f90..08611b27 100644 --- a/coins/monero/Cargo.toml +++ b/coins/monero/Cargo.toml @@ -6,6 +6,9 @@ license = "MIT" authors = ["Luke Parker "] edition = "2021" +[package.metadata.docs.rs] +all-features = true + [dependencies] hex-literal = "0.3" lazy_static = "1" diff --git a/coins/monero/generators/Cargo.toml b/coins/monero/generators/Cargo.toml index b46c574e..3cb6f10e 100644 --- a/coins/monero/generators/Cargo.toml +++ b/coins/monero/generators/Cargo.toml @@ -6,6 +6,9 @@ license = "MIT" authors = ["Luke Parker "] edition = "2021" +[package.metadata.docs.rs] +all-features = true + [dependencies] lazy_static = "1" diff --git a/coins/monero/generators/src/hash_to_point.rs b/coins/monero/generators/src/hash_to_point.rs index 9c13f849..bf25f9ea 100644 --- a/coins/monero/generators/src/hash_to_point.rs +++ b/coins/monero/generators/src/hash_to_point.rs @@ -7,7 +7,7 @@ use dalek_ff_group::field::FieldElement; use crate::hash; -#[allow(dead_code)] +/// Monero's hash to point function, as named `ge_fromfe_frombytes_vartime`. pub fn hash_to_point(bytes: [u8; 32]) -> EdwardsPoint { #[allow(non_snake_case)] let A = FieldElement::from(486662u64); diff --git a/coins/monero/generators/src/lib.rs b/coins/monero/generators/src/lib.rs index 6fdf98f1..6b5b4868 100644 --- a/coins/monero/generators/src/lib.rs +++ b/coins/monero/generators/src/lib.rs @@ -25,6 +25,7 @@ fn hash(data: &[u8]) -> [u8; 32] { } lazy_static! { + /// Monero alternate generator `H`, used for amounts in Pedersen commitments. pub static ref H: DalekPoint = CompressedEdwardsY(hash(&ED25519_BASEPOINT_POINT.compress().to_bytes())) .decompress() @@ -36,20 +37,22 @@ const MAX_M: usize = 16; const N: usize = 64; const MAX_MN: usize = MAX_M * N; +/// Container struct for Bulletproofs(+) generators. #[allow(non_snake_case)] pub struct Generators { pub G: [EdwardsPoint; MAX_MN], pub H: [EdwardsPoint; MAX_MN], } -pub fn bulletproofs_generators(prefix: &'static [u8]) -> Generators { +/// Generate generators as needed for Bulletproofs(+), as Monero does. +pub fn bulletproofs_generators(dst: &'static [u8]) -> Generators { let mut res = Generators { G: [EdwardsPoint::identity(); MAX_MN], H: [EdwardsPoint::identity(); MAX_MN] }; for i in 0 .. MAX_MN { let i = 2 * i; let mut even = H.compress().to_bytes().to_vec(); - even.extend(prefix); + even.extend(dst); let mut odd = even.clone(); write_varint(&i.try_into().unwrap(), &mut even).unwrap(); diff --git a/coins/monero/src/frost.rs b/coins/monero/src/frost.rs index 0453e28c..bd27ffdc 100644 --- a/coins/monero/src/frost.rs +++ b/coins/monero/src/frost.rs @@ -14,13 +14,9 @@ use dalek_ff_group as dfg; use dleq::DLEqProof; #[derive(Clone, Error, Debug)] -pub enum MultisigError { - #[error("internal error ({0})")] - InternalError(String), +pub(crate) enum MultisigError { #[error("invalid discrete log equality proof")] InvalidDLEqProof(u16), - #[error("invalid key image {0}")] - InvalidKeyImage(u16), } fn transcript() -> RecommendedTranscript { diff --git a/coins/monero/src/lib.rs b/coins/monero/src/lib.rs index 364566be..83c65f3d 100644 --- a/coins/monero/src/lib.rs +++ b/coins/monero/src/lib.rs @@ -1,3 +1,17 @@ +///! monero-serai: A modern Monero transaction library intended for usage in wallets. It prides +///! itself on accuracy, correctness, and removing common pit falls developers may face. +///! +///! monero-serai contains safety features, such as first-class acknowledgement of the burning bug, +///! yet also a high level API around creating transactions. monero-serai also offers a FROST-based +///! multisig, which is orders of magnitude more performant than Monero's. +///! +///! monero-serai was written for Serai, a decentralized exchange aiming to support Monero. +///! Despite this, monero-serai is intended to be a widely usable library, accurate to Monero. +///! monero-serai guarantees the functionality needed for Serai, yet will not deprive functionality +///! from other users, and may potentially leave Serai's umbrella at some point. +///! +///! Various legacy transaction formats are not currently implemented, yet monero-serai is still +///! increasing its support for various transaction types. use lazy_static::lazy_static; use rand_core::{RngCore, CryptoRng}; @@ -14,7 +28,7 @@ use curve25519_dalek::{ pub use monero_generators::H; #[cfg(feature = "multisig")] -pub mod frost; +pub(crate) mod frost; mod serialize; @@ -29,6 +43,8 @@ pub mod wallet; #[cfg(test)] mod tests; +/// Monero protocol version. v15 is omitted as v15 was simply v14 and v16 being active at the same +/// time, with regards to the transactions supported. Accordingly, v16 should be used during v15. #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] #[allow(non_camel_case_types)] pub enum Protocol { @@ -38,6 +54,7 @@ pub enum Protocol { } impl Protocol { + /// Amount of ring members under this protocol version. pub fn ring_len(&self) -> usize { match self { Protocol::Unsupported => panic!("Unsupported protocol version"), @@ -46,6 +63,8 @@ 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. pub fn bp_plus(&self) -> bool { match self { Protocol::Unsupported => panic!("Unsupported protocol version"), @@ -59,6 +78,7 @@ lazy_static! { static ref H_TABLE: EdwardsBasepointTable = EdwardsBasepointTable::create(&H); } +/// Transparent structure representing a Pedersen commitment's contents. #[allow(non_snake_case)] #[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] pub struct Commitment { @@ -67,6 +87,7 @@ pub struct Commitment { } impl Commitment { + /// The zero commitment, defined as a mask of 1 (as to not be the identity) and a 0 amount. pub fn zero() -> Commitment { Commitment { mask: Scalar::one(), amount: 0 } } @@ -75,12 +96,13 @@ impl Commitment { Commitment { mask, amount } } + /// Calculate a Pedersen commitment, as a point, from the transparent structure. pub fn calculate(&self) -> EdwardsPoint { (&self.mask * &ED25519_BASEPOINT_TABLE) + (&Scalar::from(self.amount) * &*H_TABLE) } } -// Allows using a modern rand as dalek's is notoriously dated +/// Support generating a random scalar using a modern rand, as dalek's is notoriously dated. pub fn random_scalar(rng: &mut R) -> Scalar { let mut r = [0; 64]; rng.fill_bytes(&mut r); @@ -95,6 +117,7 @@ pub fn hash(data: &[u8]) -> [u8; 32] { res } +/// Hash the provided data to a scalar via keccak256(data) % l. pub fn hash_to_scalar(data: &[u8]) -> Scalar { let scalar = Scalar::from_bytes_mod_order(hash(data)); // Monero will explicitly error in this case diff --git a/coins/monero/src/ringct/bulletproofs/mod.rs b/coins/monero/src/ringct/bulletproofs/mod.rs index 117cb472..227890d3 100644 --- a/coins/monero/src/ringct/bulletproofs/mod.rs +++ b/coins/monero/src/ringct/bulletproofs/mod.rs @@ -23,6 +23,7 @@ pub(crate) use self::plus::PlusStruct; pub(crate) const MAX_OUTPUTS: usize = self::core::MAX_M; +/// Bulletproofs enum, supporting the original and plus formulations. #[allow(clippy::large_enum_variant)] #[derive(Clone, PartialEq, Eq, Debug)] pub enum Bulletproofs { @@ -50,6 +51,7 @@ impl Bulletproofs { } } + /// Prove the list of commitments are within [0 .. 2^64). pub fn prove( rng: &mut R, outputs: &[Commitment], @@ -65,6 +67,7 @@ impl Bulletproofs { }) } + /// Verify the given Bulletproofs. #[must_use] pub fn verify(&self, rng: &mut R, commitments: &[EdwardsPoint]) -> bool { match self { @@ -73,6 +76,9 @@ impl Bulletproofs { } } + /// Accumulate the verification for the given Bulletproofs into the specified BatchVerifier. + /// Returns false if the Bulletproofs aren't sane, without mutating the BatchVerifier. + /// Returns true if the Bulletproofs are sane, regardless of their validity. #[must_use] pub fn batch_verify( &self, @@ -128,6 +134,7 @@ impl Bulletproofs { self.serialize_core(w, |points, w| write_vec(write_point, points, w)) } + /// Deserialize non-plus Bulletproofs. pub fn deserialize(r: &mut R) -> std::io::Result { Ok(Bulletproofs::Original(OriginalStruct { A: read_point(r)?, @@ -144,6 +151,7 @@ impl Bulletproofs { })) } + /// Deserialize Bulletproofs+. pub fn deserialize_plus(r: &mut R) -> std::io::Result { Ok(Bulletproofs::Plus(PlusStruct { A: read_point(r)?, diff --git a/coins/monero/src/ringct/clsag/mod.rs b/coins/monero/src/ringct/clsag/mod.rs index 930af467..71fa2072 100644 --- a/coins/monero/src/ringct/clsag/mod.rs +++ b/coins/monero/src/ringct/clsag/mod.rs @@ -28,6 +28,7 @@ lazy_static! { static ref INV_EIGHT: Scalar = Scalar::from(8u8).invert(); } +/// Errors returned when CLSAG signing fails. #[derive(Clone, Error, Debug)] pub enum ClsagError { #[error("internal error ({0})")] @@ -48,6 +49,7 @@ pub enum ClsagError { InvalidC1, } +/// Input being signed for. #[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] pub struct ClsagInput { // The actual commitment for the true spend @@ -189,6 +191,7 @@ fn core( ((D, c * mu_P, c * mu_C), c1.unwrap_or(c)) } +/// CLSAG signature, as used in Monero. #[derive(Clone, PartialEq, Eq, Debug)] pub struct Clsag { pub D: EdwardsPoint, @@ -225,7 +228,9 @@ impl Clsag { (Clsag { D, s, c1 }, pseudo_out, p, c * z) } - // Single signer CLSAG + /// Generate CLSAG signatures for the given inputs. + /// inputs is of the form (private key, key image, input). + /// sum_outputs is for the sum of the outputs' commitment masks. pub fn sign( rng: &mut R, mut inputs: Vec<(Scalar, EdwardsPoint, ClsagInput)>, @@ -262,6 +267,7 @@ impl Clsag { res } + /// Verify the CLSAG signature against the given Transaction data. pub fn verify( &self, ring: &[[EdwardsPoint; 2]], diff --git a/coins/monero/src/ringct/clsag/multisig.rs b/coins/monero/src/ringct/clsag/multisig.rs index a2a014fc..f9816b88 100644 --- a/coins/monero/src/ringct/clsag/multisig.rs +++ b/coins/monero/src/ringct/clsag/multisig.rs @@ -23,7 +23,7 @@ use frost::{curve::Ed25519, FrostError, FrostView, algorithm::Algorithm}; use dalek_ff_group as dfg; use crate::{ - frost::{MultisigError, write_dleq, read_dleq}, + frost::{write_dleq, read_dleq}, ringct::{ hash_to_point, clsag::{ClsagInput, Clsag}, @@ -54,6 +54,7 @@ impl ClsagInput { } } +/// CLSAG Input and the mask to use for it. #[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)] pub struct ClsagDetails { input: ClsagInput, @@ -76,6 +77,7 @@ struct Interim { pseudo_out: EdwardsPoint, } +/// FROST algorithm for producing a CLSAG signature. #[allow(non_snake_case)] #[derive(Clone, Debug)] pub struct ClsagMultisig { @@ -97,8 +99,8 @@ impl ClsagMultisig { transcript: RecommendedTranscript, output_key: EdwardsPoint, details: Arc>>, - ) -> Result { - Ok(ClsagMultisig { + ) -> ClsagMultisig { + ClsagMultisig { transcript, H: hash_to_point(output_key), @@ -108,7 +110,7 @@ impl ClsagMultisig { msg: None, interim: None, - }) + } } pub(crate) const fn serialized_len() -> usize { diff --git a/coins/monero/src/ringct/hash_to_point.rs b/coins/monero/src/ringct/hash_to_point.rs index 9fb8f95a..65bea9db 100644 --- a/coins/monero/src/ringct/hash_to_point.rs +++ b/coins/monero/src/ringct/hash_to_point.rs @@ -2,6 +2,7 @@ use curve25519_dalek::edwards::EdwardsPoint; pub use monero_generators::{hash_to_point as raw_hash_to_point}; +/// Monero's hash to point function, as named `ge_fromfe_frombytes_vartime`. pub fn hash_to_point(key: EdwardsPoint) -> EdwardsPoint { raw_hash_to_point(key.compress().to_bytes()) } diff --git a/coins/monero/src/ringct/mod.rs b/coins/monero/src/ringct/mod.rs index d37763d0..aed14697 100644 --- a/coins/monero/src/ringct/mod.rs +++ b/coins/monero/src/ringct/mod.rs @@ -14,6 +14,7 @@ use crate::{ ringct::{clsag::Clsag, bulletproofs::Bulletproofs}, }; +/// Generate a key image for a given key. Defined as `x * hash_to_point(xG)`. pub fn generate_key_image(mut secret: Scalar) -> EdwardsPoint { let res = secret * hash_to_point(&secret * &ED25519_BASEPOINT_TABLE); secret.zeroize(); @@ -74,6 +75,7 @@ pub enum RctPrunable { } impl RctPrunable { + /// RCT Type byte for a given RctPrunable struct. pub fn rct_type(&self) -> u8 { match self { RctPrunable::Null => 0, diff --git a/coins/monero/src/rpc.rs b/coins/monero/src/rpc.rs index bfd23ac5..21c8488d 100644 --- a/coins/monero/src/rpc.rs +++ b/coins/monero/src/rpc.rs @@ -59,6 +59,10 @@ impl Rpc { Rpc(daemon) } + /// Perform a RPC call to the specific method with the provided parameters (JSON-encoded). + /// This is NOT a JSON-RPC call, which requires setting a method of "json_rpc" and properly + /// formatting the request. + // TODO: Offer jsonrpc_call pub async fn rpc_call( &self, method: &str, @@ -73,6 +77,7 @@ impl Rpc { self.call_tail(method, builder).await } + /// Perform a binary call to the specified method with the provided parameters. pub async fn bin_call( &self, method: &str, @@ -99,6 +104,7 @@ impl Rpc { }) } + /// Get the active blockchain protocol version. pub async fn get_protocol(&self) -> Result { #[derive(Deserialize, Debug)] struct ProtocolResponse { @@ -230,6 +236,7 @@ impl Rpc { Ok(res) } + /// Get the output indexes of the specified transaction. pub async fn get_o_indexes(&self, hash: [u8; 32]) -> Result, RpcError> { #[derive(Serialize, Debug)] struct Request { @@ -256,7 +263,8 @@ impl Rpc { Ok(indexes.o_indexes) } - // from and to are inclusive + /// Get the output distribution, from the specified height to the specified height (both + /// inclusive). pub async fn get_output_distribution( &self, from: usize, @@ -293,6 +301,8 @@ impl Rpc { Ok(distributions.result.distributions.swap_remove(0).distribution) } + /// Get the specified outputs from the RingCT (zero-amount) pool, but only return them if they're + /// unlocked. pub async fn get_unlocked_outputs( &self, indexes: &[u64], @@ -354,6 +364,9 @@ impl Rpc { .collect() } + /// Get the currently estimated fee from the node. This may be manipulated to unsafe levels and + /// MUST be sanity checked. + // TODO: Take a sanity check argument pub async fn get_fee(&self) -> Result { #[allow(dead_code)] #[derive(Deserialize, Debug)] diff --git a/coins/monero/src/tests/clsag.rs b/coins/monero/src/tests/clsag.rs index d51f7c91..0068073b 100644 --- a/coins/monero/src/tests/clsag.rs +++ b/coins/monero/src/tests/clsag.rs @@ -120,8 +120,7 @@ fn clsag_multisig() -> Result<(), MultisigError> { .unwrap(), mask_sum, )))), - ) - .unwrap(), + ), &keys, ), &[1; 32], diff --git a/coins/monero/src/transaction.rs b/coins/monero/src/transaction.rs index b3a5c598..bf2c359b 100644 --- a/coins/monero/src/transaction.rs +++ b/coins/monero/src/transaction.rs @@ -189,6 +189,7 @@ impl TransactionPrefix { } } +/// Monero transaction. For version 1, rct_signatures still contains an accurate fee value. #[derive(Clone, PartialEq, Eq, Debug)] pub struct Transaction { pub prefix: TransactionPrefix, @@ -296,6 +297,7 @@ impl Transaction { } } + /// Calculate the hash of this transaction as needed for signing it. pub fn signature_hash(&self) -> [u8; 32] { let mut serialized = Vec::with_capacity(2048); let mut sig_hash = Vec::with_capacity(96); diff --git a/coins/monero/src/wallet/address.rs b/coins/monero/src/wallet/address.rs index aa11eefb..c2cb9d55 100644 --- a/coins/monero/src/wallet/address.rs +++ b/coins/monero/src/wallet/address.rs @@ -15,6 +15,8 @@ pub enum Network { Stagenet, } +/// The address type, supporting the officially documented addresses, along with +/// [Featured Addresses](https://gist.github.com/kayabaNerve/01c50bbc35441e0bbdcee63a9d823789). #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] pub enum AddressType { Standard, diff --git a/coins/monero/src/wallet/decoys.rs b/coins/monero/src/wallet/decoys.rs index c3d34a1d..b62b2b51 100644 --- a/coins/monero/src/wallet/decoys.rs +++ b/coins/monero/src/wallet/decoys.rs @@ -115,6 +115,7 @@ fn offset(ring: &[u64]) -> Vec { res } +/// Decoy data, containing the actual member as well (at index `i`). #[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] pub struct Decoys { pub i: u8, @@ -127,6 +128,7 @@ impl Decoys { self.offsets.len() } + /// Select decoys using the same distribution as Monero. pub async fn select( rng: &mut R, rpc: &Rpc, diff --git a/coins/monero/src/wallet/mod.rs b/coins/monero/src/wallet/mod.rs index 4f5b4534..f5cb22ac 100644 --- a/coins/monero/src/wallet/mod.rs +++ b/coins/monero/src/wallet/mod.rs @@ -92,6 +92,7 @@ pub(crate) fn commitment_mask(shared_key: Scalar) -> Scalar { hash_to_scalar(&mask) } +/// The private view key and public spend key, enabling scanning transactions. #[derive(Clone, Zeroize, ZeroizeOnDrop)] pub struct ViewPair { spend: EdwardsPoint, @@ -120,6 +121,10 @@ impl ViewPair { } } +/// Transaction scanner. +/// This scanner is capable of generating subaddresses, additionally scanning for them once they've +/// been explicitly generated. If the burning bug is attempted, any secondary outputs will be +/// ignored. #[derive(Clone)] pub struct Scanner { pair: ViewPair, @@ -155,8 +160,13 @@ impl Drop for Scanner { impl ZeroizeOnDrop for Scanner {} impl Scanner { - // For burning bug immune addresses (Featured Address w/ the Guaranteed feature), pass None - // For traditional Monero address, provide a HashSet of all historically scanned output keys + /// Create a Scanner from a ViewPair. + /// The network is used for generating subaddresses. + /// burning_bug is a HashSet of used keys, intended to prevent key reuse which would burn funds. + /// When an output is successfully scanned, the output key MUST be saved to disk. + /// When a new scanner is created, ALL saved output keys must be passed in to be secure. + /// If None is passed, a modified shared key derivation is used which is immune to the burning + /// bug (specifically the Guaranteed feature from Featured Addresses). pub fn from_view( pair: ViewPair, network: Network, @@ -167,6 +177,7 @@ impl Scanner { Scanner { pair, network, subaddresses, burning_bug } } + /// Return the main address for this view pair. pub fn address(&self) -> Address { Address::new( AddressMeta { @@ -182,6 +193,7 @@ impl Scanner { ) } + /// Return the specified subaddress for this view pair. pub fn subaddress(&mut self, index: (u32, u32)) -> Address { if index == (0, 0) { return self.address(); diff --git a/coins/monero/src/wallet/scan.rs b/coins/monero/src/wallet/scan.rs index b3d635e4..cbcaf6da 100644 --- a/coins/monero/src/wallet/scan.rs +++ b/coins/monero/src/wallet/scan.rs @@ -13,6 +13,7 @@ use crate::{ wallet::{PaymentId, Extra, Scanner, uniqueness, shared_key, amount_decryption, commitment_mask}, }; +/// An absolute output ID, defined as its transaction hash and output index. #[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] pub struct AbsoluteId { pub tx: [u8; 32], @@ -32,10 +33,11 @@ impl AbsoluteId { } } +/// The data contained with an output. #[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] pub struct OutputData { pub key: EdwardsPoint, - // Absolute difference between the spend key and the key in this output + /// Absolute difference between the spend key and the key in this output pub key_offset: Scalar, pub commitment: Commitment, } @@ -59,17 +61,18 @@ impl OutputData { } } +/// The metadata for an output. #[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] pub struct Metadata { // Does not have to be an Option since the 0 subaddress is the main address + /// The subaddress this output was sent to. pub subaddress: (u32, u32), - // Can be an Option, as extra doesn't necessarily have a payment ID, yet all Monero TXs should - // have this - // This will be gibberish if the payment ID wasn't intended for the recipient or wasn't included - // 0xff was chosen as it'd be distinct from [0; 8], enabling atomically incrementing IDs (though - // they should be randomly generated) + /// The payment ID included with this output. + /// This will be gibberish if the payment ID wasn't intended for the recipient or wasn't included. + // Could be an Option, as extra doesn't necessarily have a payment ID, yet all Monero TXs should + // have this making it simplest for it to be as-is. pub payment_id: [u8; 8], - // Arbitrary data + /// Arbitrary data encoded in TX extra. pub arbitrary_data: Option>, } @@ -104,6 +107,7 @@ impl Metadata { } } +/// A received output, defined as its absolute ID, data, and metadara. #[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] pub struct ReceivedOutput { pub absolute: AbsoluteId, @@ -140,6 +144,9 @@ impl ReceivedOutput { } } +/// A spendable output, defined as a received output and its index on the Monero blockchain. +/// This index is dependent on the Monero blockchain and will only be known once the output is +/// included within a block. This may change if there's a reorganization. #[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] pub struct SpendableOutput { pub output: ReceivedOutput, @@ -147,6 +154,8 @@ pub struct SpendableOutput { } impl SpendableOutput { + /// Update the spendable output's global index. This is intended to be called if a + /// re-organization occurred. pub async fn refresh_global_index(&mut self, rpc: &Rpc) -> Result<(), RpcError> { self.global_index = rpc.get_o_indexes(self.output.absolute.tx).await?[usize::from(self.output.absolute.o)]; @@ -182,6 +191,7 @@ impl SpendableOutput { } } +/// A collection of timelocked outputs, either received or spendable. #[derive(Zeroize)] pub struct Timelocked(Timelock, Vec); impl Drop for Timelocked { @@ -197,6 +207,7 @@ impl Timelocked { self.0 } + /// Return the outputs if they're not timelocked, or an empty vector if they are. pub fn not_locked(&self) -> Vec { if self.0 == Timelock::None { return self.1.clone(); @@ -204,7 +215,7 @@ impl Timelocked { vec![] } - /// Returns None if the Timelocks aren't comparable. Returns Some(vec![]) if none are unlocked + /// Returns None if the Timelocks aren't comparable. Returns Some(vec![]) if none are unlocked. pub fn unlocked(&self, timelock: Timelock) -> Option> { // If the Timelocks are comparable, return the outputs if they're now unlocked self.0.partial_cmp(&timelock).filter(|_| self.0 <= timelock).map(|_| self.1.clone()) @@ -216,6 +227,7 @@ impl Timelocked { } impl Scanner { + /// Scan a transaction to discover the received outputs. pub fn scan_transaction(&mut self, tx: &Transaction) -> Timelocked { let extra = Extra::deserialize(&mut Cursor::new(&tx.prefix.extra)); let keys; @@ -325,6 +337,11 @@ impl Scanner { Timelocked(tx.prefix.timelock, res) } + /// Scan a block to obtain its spendable outputs. Its the presence in a block giving these + /// transactions their global index, and this must be batched as asking for the index of specific + /// transactions is a dead giveaway for which transactions you successfully scanned. This + /// function obtains the output indexes for the miner transaction, incrementing from there + /// instead. pub async fn scan( &mut self, rpc: &Rpc, diff --git a/coins/monero/src/wallet/send/mod.rs b/coins/monero/src/wallet/send/mod.rs index 2e20998d..1c8b308d 100644 --- a/coins/monero/src/wallet/send/mod.rs +++ b/coins/monero/src/wallet/send/mod.rs @@ -25,8 +25,6 @@ use crate::{ uniqueness, shared_key, commitment_mask, amount_encryption, }, }; -#[cfg(feature = "multisig")] -use crate::frost::MultisigError; #[cfg(feature = "multisig")] mod multisig; @@ -103,9 +101,6 @@ pub enum TransactionError { #[cfg(feature = "multisig")] #[error("frost error {0}")] FrostError(FrostError), - #[cfg(feature = "multisig")] - #[error("multisig error {0}")] - MultisigError(MultisigError), } async fn prepare_inputs( @@ -156,6 +151,7 @@ async fn prepare_inputs( Ok(signable) } +/// Fee struct, defined as a per-unit cost and a mask for rounding purposes. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Fee { pub per_weight: u64, @@ -168,6 +164,7 @@ impl Fee { } } +/// A signable transaction, either in a single-signer or multisig context. #[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] pub struct SignableTransaction { protocol: Protocol, @@ -178,6 +175,10 @@ pub struct SignableTransaction { } impl SignableTransaction { + /// Create a signable transaction. If the change address is specified, leftover funds will be + /// sent to it. If the change address isn't specified, up to 16 outputs may be specified, using + /// any leftover funds as a bonus to the fee. The optional data field will be embedded in TX + /// extra. pub fn new( protocol: Protocol, inputs: Vec, @@ -352,6 +353,7 @@ impl SignableTransaction { ) } + /// Sign this transaction. pub async fn sign( &mut self, rng: &mut R, diff --git a/coins/monero/src/wallet/send/multisig.rs b/coins/monero/src/wallet/send/multisig.rs index 0644a09c..e3205bed 100644 --- a/coins/monero/src/wallet/send/multisig.rs +++ b/coins/monero/src/wallet/send/multisig.rs @@ -34,6 +34,7 @@ use crate::{ wallet::{TransactionError, SignableTransaction, Decoys, key_image_sort, uniqueness}, }; +/// FROST signing machine to produce a signed transaction. pub struct TransactionMachine { signable: SignableTransaction, i: u16, @@ -66,6 +67,8 @@ pub struct TransactionSignatureMachine { } impl SignableTransaction { + /// Create a FROST signing machine out of this signable transaction. + /// The height is the Monero blockchain height to synchronize around. pub async fn multisig( self, rpc: &Rpc, @@ -123,8 +126,7 @@ impl SignableTransaction { clsags.push( AlgorithmMachine::new( - ClsagMultisig::new(transcript.clone(), input.key(), inputs[i].clone()) - .map_err(TransactionError::MultisigError)?, + ClsagMultisig::new(transcript.clone(), input.key(), inputs[i].clone()), offset, &included, ) diff --git a/contracts/extension/Cargo.toml b/contracts/extension/Cargo.toml index b36450a2..4d717cdf 100644 --- a/contracts/extension/Cargo.toml +++ b/contracts/extension/Cargo.toml @@ -5,6 +5,7 @@ description = "An ink! extension for exposing Serai to ink" license = "AGPL-3.0-only" authors = ["Luke Parker "] edition = "2021" +publish = false [dependencies] scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } diff --git a/contracts/multisig/Cargo.toml b/contracts/multisig/Cargo.toml index 52046d65..2b2e3565 100644 --- a/contracts/multisig/Cargo.toml +++ b/contracts/multisig/Cargo.toml @@ -5,6 +5,7 @@ description = "An ink! tracker for Serai's current multisig" license = "AGPL-3.0-only" authors = ["Luke Parker "] edition = "2021" +publish = false [dependencies] scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } diff --git a/crypto/dalek-ff-group/Cargo.toml b/crypto/dalek-ff-group/Cargo.toml index 1f22f24d..7bf4495f 100644 --- a/crypto/dalek-ff-group/Cargo.toml +++ b/crypto/dalek-ff-group/Cargo.toml @@ -8,6 +8,9 @@ authors = ["Luke Parker "] keywords = ["curve25519", "ed25519", "ristretto", "dalek", "group"] edition = "2021" +[package.metadata.docs.rs] +all-features = true + [dependencies] rand_core = "0.6" digest = "0.10" diff --git a/crypto/dleq/Cargo.toml b/crypto/dleq/Cargo.toml index dee76477..7bbfa58f 100644 --- a/crypto/dleq/Cargo.toml +++ b/crypto/dleq/Cargo.toml @@ -7,6 +7,9 @@ repository = "https://github.com/serai-dex/serai" authors = ["Luke Parker "] edition = "2021" +[package.metadata.docs.rs] +all-features = true + [dependencies] thiserror = "1" rand_core = "0.6" diff --git a/crypto/ed448/Cargo.toml b/crypto/ed448/Cargo.toml index f262b3fd..9468a081 100644 --- a/crypto/ed448/Cargo.toml +++ b/crypto/ed448/Cargo.toml @@ -8,6 +8,9 @@ authors = ["Luke Parker "] keywords = ["ed448", "ff", "group"] edition = "2021" +[package.metadata.docs.rs] +all-features = true + [dependencies] lazy_static = "1" diff --git a/crypto/frost/Cargo.toml b/crypto/frost/Cargo.toml index a240faf7..ed7a12ea 100644 --- a/crypto/frost/Cargo.toml +++ b/crypto/frost/Cargo.toml @@ -8,6 +8,9 @@ authors = ["Luke Parker "] keywords = ["frost", "multisig", "threshold"] edition = "2021" +[package.metadata.docs.rs] +all-features = true + [dependencies] thiserror = "1" diff --git a/crypto/multiexp/Cargo.toml b/crypto/multiexp/Cargo.toml index d12c954b..d1986cec 100644 --- a/crypto/multiexp/Cargo.toml +++ b/crypto/multiexp/Cargo.toml @@ -8,6 +8,9 @@ authors = ["Luke Parker "] keywords = ["multiexp", "ff", "group"] edition = "2021" +[package.metadata.docs.rs] +all-features = true + [dependencies] zeroize = { version = "1.3", features = ["zeroize_derive"] } diff --git a/crypto/transcript/Cargo.toml b/crypto/transcript/Cargo.toml index 777a0c5f..544f7630 100644 --- a/crypto/transcript/Cargo.toml +++ b/crypto/transcript/Cargo.toml @@ -8,6 +8,9 @@ authors = ["Luke Parker "] keywords = ["transcript"] edition = "2021" +[package.metadata.docs.rs] +all-features = true + [dependencies] digest = "0.10"