mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-09 12:49:23 +00:00
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.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -115,6 +115,7 @@ fn offset(ring: &[u64]) -> Vec<u64> {
|
||||
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<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
rpc: &Rpc,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<Vec<u8>>,
|
||||
}
|
||||
|
||||
@@ -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<O: Clone + Zeroize>(Timelock, Vec<O>);
|
||||
impl<O: Clone + Zeroize> Drop for Timelocked<O> {
|
||||
@@ -197,6 +207,7 @@ impl<O: Clone + Zeroize> Timelocked<O> {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Return the outputs if they're not timelocked, or an empty vector if they are.
|
||||
pub fn not_locked(&self) -> Vec<O> {
|
||||
if self.0 == Timelock::None {
|
||||
return self.1.clone();
|
||||
@@ -204,7 +215,7 @@ impl<O: Clone + Zeroize> Timelocked<O> {
|
||||
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<Vec<O>> {
|
||||
// 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<O: Clone + Zeroize> Timelocked<O> {
|
||||
}
|
||||
|
||||
impl Scanner {
|
||||
/// Scan a transaction to discover the received outputs.
|
||||
pub fn scan_transaction(&mut self, tx: &Transaction) -> Timelocked<ReceivedOutput> {
|
||||
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,
|
||||
|
||||
@@ -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<R: RngCore + CryptoRng>(
|
||||
@@ -156,6 +151,7 @@ async fn prepare_inputs<R: RngCore + CryptoRng>(
|
||||
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<SpendableOutput>,
|
||||
@@ -352,6 +353,7 @@ impl SignableTransaction {
|
||||
)
|
||||
}
|
||||
|
||||
/// Sign this transaction.
|
||||
pub async fn sign<R: RngCore + CryptoRng>(
|
||||
&mut self,
|
||||
rng: &mut R,
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user