Skip to content

Commit

Permalink
Largely document the Monero libraries
Browse files Browse the repository at this point in the history
Relevant to #103 and likely 
sufficient to get this removed from 
#102.
  • Loading branch information
kayabaNerve committed Sep 28, 2022
1 parent 7986d84 commit 983c107
Show file tree
Hide file tree
Showing 18 changed files with 127 additions and 35 deletions.
2 changes: 1 addition & 1 deletion coins/monero/generators/src/hash_to_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 5 additions & 2 deletions coins/monero/generators/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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();
Expand Down
6 changes: 1 addition & 5 deletions coins/monero/src/frost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
27 changes: 25 additions & 2 deletions coins/monero/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -14,7 +28,7 @@ use curve25519_dalek::{
pub use monero_generators::H;

#[cfg(feature = "multisig")]
pub mod frost;
pub(crate) mod frost;

mod serialize;

Expand All @@ -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 {
Expand All @@ -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"),
Expand All @@ -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"),
Expand All @@ -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 {
Expand All @@ -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 }
}
Expand All @@ -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<R: RngCore + CryptoRng>(rng: &mut R) -> Scalar {
let mut r = [0; 64];
rng.fill_bytes(&mut r);
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions coins/monero/src/ringct/bulletproofs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -50,6 +51,7 @@ impl Bulletproofs {
}
}

/// Prove the list of commitments are within [0 .. 2^64).
pub fn prove<R: RngCore + CryptoRng>(
rng: &mut R,
outputs: &[Commitment],
Expand All @@ -65,6 +67,7 @@ impl Bulletproofs {
})
}

/// Verify the given Bulletproofs.
#[must_use]
pub fn verify<R: RngCore + CryptoRng>(&self, rng: &mut R, commitments: &[EdwardsPoint]) -> bool {
match self {
Expand All @@ -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<ID: Copy + Zeroize, R: RngCore + CryptoRng>(
&self,
Expand Down Expand Up @@ -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: std::io::Read>(r: &mut R) -> std::io::Result<Bulletproofs> {
Ok(Bulletproofs::Original(OriginalStruct {
A: read_point(r)?,
Expand All @@ -144,6 +151,7 @@ impl Bulletproofs {
}))
}

/// Deserialize Bulletproofs+.
pub fn deserialize_plus<R: std::io::Read>(r: &mut R) -> std::io::Result<Bulletproofs> {
Ok(Bulletproofs::Plus(PlusStruct {
A: read_point(r)?,
Expand Down
8 changes: 7 additions & 1 deletion coins/monero/src/ringct/clsag/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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})")]
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<R: RngCore + CryptoRng>(
rng: &mut R,
mut inputs: Vec<(Scalar, EdwardsPoint, ClsagInput)>,
Expand Down Expand Up @@ -262,6 +267,7 @@ impl Clsag {
res
}

/// Verify the CLSAG signature against the given Transaction data.
pub fn verify(
&self,
ring: &[[EdwardsPoint; 2]],
Expand Down
10 changes: 6 additions & 4 deletions coins/monero/src/ringct/clsag/multisig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand All @@ -97,8 +99,8 @@ impl ClsagMultisig {
transcript: RecommendedTranscript,
output_key: EdwardsPoint,
details: Arc<RwLock<Option<ClsagDetails>>>,
) -> Result<ClsagMultisig, MultisigError> {
Ok(ClsagMultisig {
) -> ClsagMultisig {
ClsagMultisig {
transcript,

H: hash_to_point(output_key),
Expand All @@ -108,7 +110,7 @@ impl ClsagMultisig {

msg: None,
interim: None,
})
}
}

pub(crate) const fn serialized_len() -> usize {
Expand Down
1 change: 1 addition & 0 deletions coins/monero/src/ringct/hash_to_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
2 changes: 2 additions & 0 deletions coins/monero/src/ringct/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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,
Expand Down
15 changes: 14 additions & 1 deletion coins/monero/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Params: Serialize + Debug, Response: DeserializeOwned + Debug>(
&self,
method: &str,
Expand All @@ -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<Response: DeserializeOwned + Debug>(
&self,
method: &str,
Expand All @@ -99,6 +104,7 @@ impl Rpc {
})
}

/// Get the active blockchain protocol version.
pub async fn get_protocol(&self) -> Result<Protocol, RpcError> {
#[derive(Deserialize, Debug)]
struct ProtocolResponse {
Expand Down Expand Up @@ -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<Vec<u64>, RpcError> {
#[derive(Serialize, Debug)]
struct Request {
Expand All @@ -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,
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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<Fee, RpcError> {
#[allow(dead_code)]
#[derive(Deserialize, Debug)]
Expand Down
3 changes: 1 addition & 2 deletions coins/monero/src/tests/clsag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,7 @@ fn clsag_multisig() -> Result<(), MultisigError> {
.unwrap(),
mask_sum,
)))),
)
.unwrap(),
),
&keys,
),
&[1; 32],
Expand Down
Loading

0 comments on commit 983c107

Please sign in to comment.