mirror of
https://github.com/serai-dex/serai.git
synced 2025-03-22 23:28:46 +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:
parent
f48a48ec3f
commit
fd48bbd15e
28 changed files with 153 additions and 35 deletions
|
@ -6,6 +6,9 @@ license = "MIT"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hex-literal = "0.3"
|
hex-literal = "0.3"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
|
|
|
@ -6,6 +6,9 @@ license = "MIT"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use dalek_ff_group::field::FieldElement;
|
||||||
|
|
||||||
use crate::hash;
|
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 {
|
pub fn hash_to_point(bytes: [u8; 32]) -> EdwardsPoint {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
let A = FieldElement::from(486662u64);
|
let A = FieldElement::from(486662u64);
|
||||||
|
|
|
@ -25,6 +25,7 @@ fn hash(data: &[u8]) -> [u8; 32] {
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
/// Monero alternate generator `H`, used for amounts in Pedersen commitments.
|
||||||
pub static ref H: DalekPoint =
|
pub static ref H: DalekPoint =
|
||||||
CompressedEdwardsY(hash(&ED25519_BASEPOINT_POINT.compress().to_bytes()))
|
CompressedEdwardsY(hash(&ED25519_BASEPOINT_POINT.compress().to_bytes()))
|
||||||
.decompress()
|
.decompress()
|
||||||
|
@ -36,20 +37,22 @@ const MAX_M: usize = 16;
|
||||||
const N: usize = 64;
|
const N: usize = 64;
|
||||||
const MAX_MN: usize = MAX_M * N;
|
const MAX_MN: usize = MAX_M * N;
|
||||||
|
|
||||||
|
/// Container struct for Bulletproofs(+) generators.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub struct Generators {
|
pub struct Generators {
|
||||||
pub G: [EdwardsPoint; MAX_MN],
|
pub G: [EdwardsPoint; MAX_MN],
|
||||||
pub H: [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 =
|
let mut res =
|
||||||
Generators { G: [EdwardsPoint::identity(); MAX_MN], H: [EdwardsPoint::identity(); MAX_MN] };
|
Generators { G: [EdwardsPoint::identity(); MAX_MN], H: [EdwardsPoint::identity(); MAX_MN] };
|
||||||
for i in 0 .. MAX_MN {
|
for i in 0 .. MAX_MN {
|
||||||
let i = 2 * i;
|
let i = 2 * i;
|
||||||
|
|
||||||
let mut even = H.compress().to_bytes().to_vec();
|
let mut even = H.compress().to_bytes().to_vec();
|
||||||
even.extend(prefix);
|
even.extend(dst);
|
||||||
let mut odd = even.clone();
|
let mut odd = even.clone();
|
||||||
|
|
||||||
write_varint(&i.try_into().unwrap(), &mut even).unwrap();
|
write_varint(&i.try_into().unwrap(), &mut even).unwrap();
|
||||||
|
|
|
@ -14,13 +14,9 @@ use dalek_ff_group as dfg;
|
||||||
use dleq::DLEqProof;
|
use dleq::DLEqProof;
|
||||||
|
|
||||||
#[derive(Clone, Error, Debug)]
|
#[derive(Clone, Error, Debug)]
|
||||||
pub enum MultisigError {
|
pub(crate) enum MultisigError {
|
||||||
#[error("internal error ({0})")]
|
|
||||||
InternalError(String),
|
|
||||||
#[error("invalid discrete log equality proof")]
|
#[error("invalid discrete log equality proof")]
|
||||||
InvalidDLEqProof(u16),
|
InvalidDLEqProof(u16),
|
||||||
#[error("invalid key image {0}")]
|
|
||||||
InvalidKeyImage(u16),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transcript() -> RecommendedTranscript {
|
fn transcript() -> RecommendedTranscript {
|
||||||
|
|
|
@ -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 lazy_static::lazy_static;
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
|
@ -14,7 +28,7 @@ use curve25519_dalek::{
|
||||||
pub use monero_generators::H;
|
pub use monero_generators::H;
|
||||||
|
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
pub mod frost;
|
pub(crate) mod frost;
|
||||||
|
|
||||||
mod serialize;
|
mod serialize;
|
||||||
|
|
||||||
|
@ -29,6 +43,8 @@ pub mod wallet;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
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)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub enum Protocol {
|
pub enum Protocol {
|
||||||
|
@ -38,6 +54,7 @@ pub enum Protocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Protocol {
|
impl Protocol {
|
||||||
|
/// Amount of ring members under this protocol version.
|
||||||
pub fn ring_len(&self) -> usize {
|
pub fn ring_len(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Protocol::Unsupported => panic!("Unsupported protocol version"),
|
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 {
|
pub fn bp_plus(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Protocol::Unsupported => panic!("Unsupported protocol version"),
|
Protocol::Unsupported => panic!("Unsupported protocol version"),
|
||||||
|
@ -59,6 +78,7 @@ lazy_static! {
|
||||||
static ref H_TABLE: EdwardsBasepointTable = EdwardsBasepointTable::create(&H);
|
static ref H_TABLE: EdwardsBasepointTable = EdwardsBasepointTable::create(&H);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transparent structure representing a Pedersen commitment's contents.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct Commitment {
|
pub struct Commitment {
|
||||||
|
@ -67,6 +87,7 @@ pub struct Commitment {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
pub fn zero() -> Commitment {
|
||||||
Commitment { mask: Scalar::one(), amount: 0 }
|
Commitment { mask: Scalar::one(), amount: 0 }
|
||||||
}
|
}
|
||||||
|
@ -75,12 +96,13 @@ impl Commitment {
|
||||||
Commitment { mask, amount }
|
Commitment { mask, amount }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculate a Pedersen commitment, as a point, from the transparent structure.
|
||||||
pub fn calculate(&self) -> EdwardsPoint {
|
pub fn calculate(&self) -> EdwardsPoint {
|
||||||
(&self.mask * &ED25519_BASEPOINT_TABLE) + (&Scalar::from(self.amount) * &*H_TABLE)
|
(&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 {
|
pub fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Scalar {
|
||||||
let mut r = [0; 64];
|
let mut r = [0; 64];
|
||||||
rng.fill_bytes(&mut r);
|
rng.fill_bytes(&mut r);
|
||||||
|
@ -95,6 +117,7 @@ pub fn hash(data: &[u8]) -> [u8; 32] {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Hash the provided data to a scalar via keccak256(data) % l.
|
||||||
pub fn hash_to_scalar(data: &[u8]) -> Scalar {
|
pub fn hash_to_scalar(data: &[u8]) -> Scalar {
|
||||||
let scalar = Scalar::from_bytes_mod_order(hash(data));
|
let scalar = Scalar::from_bytes_mod_order(hash(data));
|
||||||
// Monero will explicitly error in this case
|
// Monero will explicitly error in this case
|
||||||
|
|
|
@ -23,6 +23,7 @@ pub(crate) use self::plus::PlusStruct;
|
||||||
|
|
||||||
pub(crate) const MAX_OUTPUTS: usize = self::core::MAX_M;
|
pub(crate) const MAX_OUTPUTS: usize = self::core::MAX_M;
|
||||||
|
|
||||||
|
/// Bulletproofs enum, supporting the original and plus formulations.
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum Bulletproofs {
|
pub enum Bulletproofs {
|
||||||
|
@ -50,6 +51,7 @@ impl Bulletproofs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prove the list of commitments are within [0 .. 2^64).
|
||||||
pub fn prove<R: RngCore + CryptoRng>(
|
pub fn prove<R: RngCore + CryptoRng>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
outputs: &[Commitment],
|
outputs: &[Commitment],
|
||||||
|
@ -65,6 +67,7 @@ impl Bulletproofs {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verify the given Bulletproofs.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn verify<R: RngCore + CryptoRng>(&self, rng: &mut R, commitments: &[EdwardsPoint]) -> bool {
|
pub fn verify<R: RngCore + CryptoRng>(&self, rng: &mut R, commitments: &[EdwardsPoint]) -> bool {
|
||||||
match self {
|
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]
|
#[must_use]
|
||||||
pub fn batch_verify<ID: Copy + Zeroize, R: RngCore + CryptoRng>(
|
pub fn batch_verify<ID: Copy + Zeroize, R: RngCore + CryptoRng>(
|
||||||
&self,
|
&self,
|
||||||
|
@ -128,6 +134,7 @@ impl Bulletproofs {
|
||||||
self.serialize_core(w, |points, w| write_vec(write_point, points, w))
|
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> {
|
pub fn deserialize<R: std::io::Read>(r: &mut R) -> std::io::Result<Bulletproofs> {
|
||||||
Ok(Bulletproofs::Original(OriginalStruct {
|
Ok(Bulletproofs::Original(OriginalStruct {
|
||||||
A: read_point(r)?,
|
A: read_point(r)?,
|
||||||
|
@ -144,6 +151,7 @@ impl Bulletproofs {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deserialize Bulletproofs+.
|
||||||
pub fn deserialize_plus<R: std::io::Read>(r: &mut R) -> std::io::Result<Bulletproofs> {
|
pub fn deserialize_plus<R: std::io::Read>(r: &mut R) -> std::io::Result<Bulletproofs> {
|
||||||
Ok(Bulletproofs::Plus(PlusStruct {
|
Ok(Bulletproofs::Plus(PlusStruct {
|
||||||
A: read_point(r)?,
|
A: read_point(r)?,
|
||||||
|
|
|
@ -28,6 +28,7 @@ lazy_static! {
|
||||||
static ref INV_EIGHT: Scalar = Scalar::from(8u8).invert();
|
static ref INV_EIGHT: Scalar = Scalar::from(8u8).invert();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Errors returned when CLSAG signing fails.
|
||||||
#[derive(Clone, Error, Debug)]
|
#[derive(Clone, Error, Debug)]
|
||||||
pub enum ClsagError {
|
pub enum ClsagError {
|
||||||
#[error("internal error ({0})")]
|
#[error("internal error ({0})")]
|
||||||
|
@ -48,6 +49,7 @@ pub enum ClsagError {
|
||||||
InvalidC1,
|
InvalidC1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Input being signed for.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct ClsagInput {
|
pub struct ClsagInput {
|
||||||
// The actual commitment for the true spend
|
// The actual commitment for the true spend
|
||||||
|
@ -189,6 +191,7 @@ fn core(
|
||||||
((D, c * mu_P, c * mu_C), c1.unwrap_or(c))
|
((D, c * mu_P, c * mu_C), c1.unwrap_or(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// CLSAG signature, as used in Monero.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Clsag {
|
pub struct Clsag {
|
||||||
pub D: EdwardsPoint,
|
pub D: EdwardsPoint,
|
||||||
|
@ -225,7 +228,9 @@ impl Clsag {
|
||||||
(Clsag { D, s, c1 }, pseudo_out, p, c * z)
|
(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>(
|
pub fn sign<R: RngCore + CryptoRng>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
mut inputs: Vec<(Scalar, EdwardsPoint, ClsagInput)>,
|
mut inputs: Vec<(Scalar, EdwardsPoint, ClsagInput)>,
|
||||||
|
@ -262,6 +267,7 @@ impl Clsag {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verify the CLSAG signature against the given Transaction data.
|
||||||
pub fn verify(
|
pub fn verify(
|
||||||
&self,
|
&self,
|
||||||
ring: &[[EdwardsPoint; 2]],
|
ring: &[[EdwardsPoint; 2]],
|
||||||
|
|
|
@ -23,7 +23,7 @@ use frost::{curve::Ed25519, FrostError, FrostView, algorithm::Algorithm};
|
||||||
use dalek_ff_group as dfg;
|
use dalek_ff_group as dfg;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
frost::{MultisigError, write_dleq, read_dleq},
|
frost::{write_dleq, read_dleq},
|
||||||
ringct::{
|
ringct::{
|
||||||
hash_to_point,
|
hash_to_point,
|
||||||
clsag::{ClsagInput, Clsag},
|
clsag::{ClsagInput, Clsag},
|
||||||
|
@ -54,6 +54,7 @@ impl ClsagInput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// CLSAG Input and the mask to use for it.
|
||||||
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct ClsagDetails {
|
pub struct ClsagDetails {
|
||||||
input: ClsagInput,
|
input: ClsagInput,
|
||||||
|
@ -76,6 +77,7 @@ struct Interim {
|
||||||
pseudo_out: EdwardsPoint,
|
pseudo_out: EdwardsPoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// FROST algorithm for producing a CLSAG signature.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ClsagMultisig {
|
pub struct ClsagMultisig {
|
||||||
|
@ -97,8 +99,8 @@ impl ClsagMultisig {
|
||||||
transcript: RecommendedTranscript,
|
transcript: RecommendedTranscript,
|
||||||
output_key: EdwardsPoint,
|
output_key: EdwardsPoint,
|
||||||
details: Arc<RwLock<Option<ClsagDetails>>>,
|
details: Arc<RwLock<Option<ClsagDetails>>>,
|
||||||
) -> Result<ClsagMultisig, MultisigError> {
|
) -> ClsagMultisig {
|
||||||
Ok(ClsagMultisig {
|
ClsagMultisig {
|
||||||
transcript,
|
transcript,
|
||||||
|
|
||||||
H: hash_to_point(output_key),
|
H: hash_to_point(output_key),
|
||||||
|
@ -108,7 +110,7 @@ impl ClsagMultisig {
|
||||||
|
|
||||||
msg: None,
|
msg: None,
|
||||||
interim: None,
|
interim: None,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const fn serialized_len() -> usize {
|
pub(crate) const fn serialized_len() -> usize {
|
||||||
|
|
|
@ -2,6 +2,7 @@ use curve25519_dalek::edwards::EdwardsPoint;
|
||||||
|
|
||||||
pub use monero_generators::{hash_to_point as raw_hash_to_point};
|
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 {
|
pub fn hash_to_point(key: EdwardsPoint) -> EdwardsPoint {
|
||||||
raw_hash_to_point(key.compress().to_bytes())
|
raw_hash_to_point(key.compress().to_bytes())
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ use crate::{
|
||||||
ringct::{clsag::Clsag, bulletproofs::Bulletproofs},
|
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 {
|
pub fn generate_key_image(mut secret: Scalar) -> EdwardsPoint {
|
||||||
let res = secret * hash_to_point(&secret * &ED25519_BASEPOINT_TABLE);
|
let res = secret * hash_to_point(&secret * &ED25519_BASEPOINT_TABLE);
|
||||||
secret.zeroize();
|
secret.zeroize();
|
||||||
|
@ -74,6 +75,7 @@ pub enum RctPrunable {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RctPrunable {
|
impl RctPrunable {
|
||||||
|
/// RCT Type byte for a given RctPrunable struct.
|
||||||
pub fn rct_type(&self) -> u8 {
|
pub fn rct_type(&self) -> u8 {
|
||||||
match self {
|
match self {
|
||||||
RctPrunable::Null => 0,
|
RctPrunable::Null => 0,
|
||||||
|
|
|
@ -59,6 +59,10 @@ impl Rpc {
|
||||||
Rpc(daemon)
|
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>(
|
pub async fn rpc_call<Params: Serialize + Debug, Response: DeserializeOwned + Debug>(
|
||||||
&self,
|
&self,
|
||||||
method: &str,
|
method: &str,
|
||||||
|
@ -73,6 +77,7 @@ impl Rpc {
|
||||||
self.call_tail(method, builder).await
|
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>(
|
pub async fn bin_call<Response: DeserializeOwned + Debug>(
|
||||||
&self,
|
&self,
|
||||||
method: &str,
|
method: &str,
|
||||||
|
@ -99,6 +104,7 @@ impl Rpc {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the active blockchain protocol version.
|
||||||
pub async fn get_protocol(&self) -> Result<Protocol, RpcError> {
|
pub async fn get_protocol(&self) -> Result<Protocol, RpcError> {
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct ProtocolResponse {
|
struct ProtocolResponse {
|
||||||
|
@ -230,6 +236,7 @@ impl Rpc {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the output indexes of the specified transaction.
|
||||||
pub async fn get_o_indexes(&self, hash: [u8; 32]) -> Result<Vec<u64>, RpcError> {
|
pub async fn get_o_indexes(&self, hash: [u8; 32]) -> Result<Vec<u64>, RpcError> {
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
struct Request {
|
struct Request {
|
||||||
|
@ -256,7 +263,8 @@ impl Rpc {
|
||||||
Ok(indexes.o_indexes)
|
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(
|
pub async fn get_output_distribution(
|
||||||
&self,
|
&self,
|
||||||
from: usize,
|
from: usize,
|
||||||
|
@ -293,6 +301,8 @@ impl Rpc {
|
||||||
Ok(distributions.result.distributions.swap_remove(0).distribution)
|
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(
|
pub async fn get_unlocked_outputs(
|
||||||
&self,
|
&self,
|
||||||
indexes: &[u64],
|
indexes: &[u64],
|
||||||
|
@ -354,6 +364,9 @@ impl Rpc {
|
||||||
.collect()
|
.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> {
|
pub async fn get_fee(&self) -> Result<Fee, RpcError> {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
|
|
|
@ -120,8 +120,7 @@ fn clsag_multisig() -> Result<(), MultisigError> {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
mask_sum,
|
mask_sum,
|
||||||
)))),
|
)))),
|
||||||
)
|
),
|
||||||
.unwrap(),
|
|
||||||
&keys,
|
&keys,
|
||||||
),
|
),
|
||||||
&[1; 32],
|
&[1; 32],
|
||||||
|
|
|
@ -189,6 +189,7 @@ impl TransactionPrefix {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Monero transaction. For version 1, rct_signatures still contains an accurate fee value.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Transaction {
|
pub struct Transaction {
|
||||||
pub prefix: TransactionPrefix,
|
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] {
|
pub fn signature_hash(&self) -> [u8; 32] {
|
||||||
let mut serialized = Vec::with_capacity(2048);
|
let mut serialized = Vec::with_capacity(2048);
|
||||||
let mut sig_hash = Vec::with_capacity(96);
|
let mut sig_hash = Vec::with_capacity(96);
|
||||||
|
|
|
@ -15,6 +15,8 @@ pub enum Network {
|
||||||
Stagenet,
|
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)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
pub enum AddressType {
|
pub enum AddressType {
|
||||||
Standard,
|
Standard,
|
||||||
|
|
|
@ -115,6 +115,7 @@ fn offset(ring: &[u64]) -> Vec<u64> {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decoy data, containing the actual member as well (at index `i`).
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct Decoys {
|
pub struct Decoys {
|
||||||
pub i: u8,
|
pub i: u8,
|
||||||
|
@ -127,6 +128,7 @@ impl Decoys {
|
||||||
self.offsets.len()
|
self.offsets.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Select decoys using the same distribution as Monero.
|
||||||
pub async fn select<R: RngCore + CryptoRng>(
|
pub async fn select<R: RngCore + CryptoRng>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
rpc: &Rpc,
|
rpc: &Rpc,
|
||||||
|
|
|
@ -92,6 +92,7 @@ pub(crate) fn commitment_mask(shared_key: Scalar) -> Scalar {
|
||||||
hash_to_scalar(&mask)
|
hash_to_scalar(&mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The private view key and public spend key, enabling scanning transactions.
|
||||||
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct ViewPair {
|
pub struct ViewPair {
|
||||||
spend: EdwardsPoint,
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct Scanner {
|
pub struct Scanner {
|
||||||
pair: ViewPair,
|
pair: ViewPair,
|
||||||
|
@ -155,8 +160,13 @@ impl Drop for Scanner {
|
||||||
impl ZeroizeOnDrop for Scanner {}
|
impl ZeroizeOnDrop for Scanner {}
|
||||||
|
|
||||||
impl Scanner {
|
impl Scanner {
|
||||||
// For burning bug immune addresses (Featured Address w/ the Guaranteed feature), pass None
|
/// Create a Scanner from a ViewPair.
|
||||||
// For traditional Monero address, provide a HashSet of all historically scanned output keys
|
/// 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(
|
pub fn from_view(
|
||||||
pair: ViewPair,
|
pair: ViewPair,
|
||||||
network: Network,
|
network: Network,
|
||||||
|
@ -167,6 +177,7 @@ impl Scanner {
|
||||||
Scanner { pair, network, subaddresses, burning_bug }
|
Scanner { pair, network, subaddresses, burning_bug }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the main address for this view pair.
|
||||||
pub fn address(&self) -> Address {
|
pub fn address(&self) -> Address {
|
||||||
Address::new(
|
Address::new(
|
||||||
AddressMeta {
|
AddressMeta {
|
||||||
|
@ -182,6 +193,7 @@ impl Scanner {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the specified subaddress for this view pair.
|
||||||
pub fn subaddress(&mut self, index: (u32, u32)) -> Address {
|
pub fn subaddress(&mut self, index: (u32, u32)) -> Address {
|
||||||
if index == (0, 0) {
|
if index == (0, 0) {
|
||||||
return self.address();
|
return self.address();
|
||||||
|
|
|
@ -13,6 +13,7 @@ use crate::{
|
||||||
wallet::{PaymentId, Extra, Scanner, uniqueness, shared_key, amount_decryption, commitment_mask},
|
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)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct AbsoluteId {
|
pub struct AbsoluteId {
|
||||||
pub tx: [u8; 32],
|
pub tx: [u8; 32],
|
||||||
|
@ -32,10 +33,11 @@ impl AbsoluteId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The data contained with an output.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct OutputData {
|
pub struct OutputData {
|
||||||
pub key: EdwardsPoint,
|
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 key_offset: Scalar,
|
||||||
pub commitment: Commitment,
|
pub commitment: Commitment,
|
||||||
}
|
}
|
||||||
|
@ -59,17 +61,18 @@ impl OutputData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The metadata for an output.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
// Does not have to be an Option since the 0 subaddress is the main address
|
// 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),
|
pub subaddress: (u32, u32),
|
||||||
// Can be an Option, as extra doesn't necessarily have a payment ID, yet all Monero TXs should
|
/// The payment ID included with this output.
|
||||||
// have this
|
/// This will be gibberish if the payment ID wasn't intended for the recipient or wasn't included.
|
||||||
// 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
|
||||||
// 0xff was chosen as it'd be distinct from [0; 8], enabling atomically incrementing IDs (though
|
// have this making it simplest for it to be as-is.
|
||||||
// they should be randomly generated)
|
|
||||||
pub payment_id: [u8; 8],
|
pub payment_id: [u8; 8],
|
||||||
// Arbitrary data
|
/// Arbitrary data encoded in TX extra.
|
||||||
pub arbitrary_data: Option<Vec<u8>>,
|
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)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct ReceivedOutput {
|
pub struct ReceivedOutput {
|
||||||
pub absolute: AbsoluteId,
|
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)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct SpendableOutput {
|
pub struct SpendableOutput {
|
||||||
pub output: ReceivedOutput,
|
pub output: ReceivedOutput,
|
||||||
|
@ -147,6 +154,8 @@ pub struct SpendableOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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> {
|
pub async fn refresh_global_index(&mut self, rpc: &Rpc) -> Result<(), RpcError> {
|
||||||
self.global_index =
|
self.global_index =
|
||||||
rpc.get_o_indexes(self.output.absolute.tx).await?[usize::from(self.output.absolute.o)];
|
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)]
|
#[derive(Zeroize)]
|
||||||
pub struct Timelocked<O: Clone + Zeroize>(Timelock, Vec<O>);
|
pub struct Timelocked<O: Clone + Zeroize>(Timelock, Vec<O>);
|
||||||
impl<O: Clone + Zeroize> Drop for Timelocked<O> {
|
impl<O: Clone + Zeroize> Drop for Timelocked<O> {
|
||||||
|
@ -197,6 +207,7 @@ impl<O: Clone + Zeroize> Timelocked<O> {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the outputs if they're not timelocked, or an empty vector if they are.
|
||||||
pub fn not_locked(&self) -> Vec<O> {
|
pub fn not_locked(&self) -> Vec<O> {
|
||||||
if self.0 == Timelock::None {
|
if self.0 == Timelock::None {
|
||||||
return self.1.clone();
|
return self.1.clone();
|
||||||
|
@ -204,7 +215,7 @@ impl<O: Clone + Zeroize> Timelocked<O> {
|
||||||
vec![]
|
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>> {
|
pub fn unlocked(&self, timelock: Timelock) -> Option<Vec<O>> {
|
||||||
// If the Timelocks are comparable, return the outputs if they're now unlocked
|
// 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())
|
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 {
|
impl Scanner {
|
||||||
|
/// Scan a transaction to discover the received outputs.
|
||||||
pub fn scan_transaction(&mut self, tx: &Transaction) -> Timelocked<ReceivedOutput> {
|
pub fn scan_transaction(&mut self, tx: &Transaction) -> Timelocked<ReceivedOutput> {
|
||||||
let extra = Extra::deserialize(&mut Cursor::new(&tx.prefix.extra));
|
let extra = Extra::deserialize(&mut Cursor::new(&tx.prefix.extra));
|
||||||
let keys;
|
let keys;
|
||||||
|
@ -325,6 +337,11 @@ impl Scanner {
|
||||||
Timelocked(tx.prefix.timelock, res)
|
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(
|
pub async fn scan(
|
||||||
&mut self,
|
&mut self,
|
||||||
rpc: &Rpc,
|
rpc: &Rpc,
|
||||||
|
|
|
@ -25,8 +25,6 @@ use crate::{
|
||||||
uniqueness, shared_key, commitment_mask, amount_encryption,
|
uniqueness, shared_key, commitment_mask, amount_encryption,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
#[cfg(feature = "multisig")]
|
|
||||||
use crate::frost::MultisigError;
|
|
||||||
|
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
mod multisig;
|
mod multisig;
|
||||||
|
@ -103,9 +101,6 @@ pub enum TransactionError {
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
#[error("frost error {0}")]
|
#[error("frost error {0}")]
|
||||||
FrostError(FrostError),
|
FrostError(FrostError),
|
||||||
#[cfg(feature = "multisig")]
|
|
||||||
#[error("multisig error {0}")]
|
|
||||||
MultisigError(MultisigError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn prepare_inputs<R: RngCore + CryptoRng>(
|
async fn prepare_inputs<R: RngCore + CryptoRng>(
|
||||||
|
@ -156,6 +151,7 @@ async fn prepare_inputs<R: RngCore + CryptoRng>(
|
||||||
Ok(signable)
|
Ok(signable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fee struct, defined as a per-unit cost and a mask for rounding purposes.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
pub struct Fee {
|
pub struct Fee {
|
||||||
pub per_weight: u64,
|
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)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct SignableTransaction {
|
pub struct SignableTransaction {
|
||||||
protocol: Protocol,
|
protocol: Protocol,
|
||||||
|
@ -178,6 +175,10 @@ pub struct SignableTransaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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(
|
pub fn new(
|
||||||
protocol: Protocol,
|
protocol: Protocol,
|
||||||
inputs: Vec<SpendableOutput>,
|
inputs: Vec<SpendableOutput>,
|
||||||
|
@ -352,6 +353,7 @@ impl SignableTransaction {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sign this transaction.
|
||||||
pub async fn sign<R: RngCore + CryptoRng>(
|
pub async fn sign<R: RngCore + CryptoRng>(
|
||||||
&mut self,
|
&mut self,
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
|
|
|
@ -34,6 +34,7 @@ use crate::{
|
||||||
wallet::{TransactionError, SignableTransaction, Decoys, key_image_sort, uniqueness},
|
wallet::{TransactionError, SignableTransaction, Decoys, key_image_sort, uniqueness},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// FROST signing machine to produce a signed transaction.
|
||||||
pub struct TransactionMachine {
|
pub struct TransactionMachine {
|
||||||
signable: SignableTransaction,
|
signable: SignableTransaction,
|
||||||
i: u16,
|
i: u16,
|
||||||
|
@ -66,6 +67,8 @@ pub struct TransactionSignatureMachine {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignableTransaction {
|
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(
|
pub async fn multisig(
|
||||||
self,
|
self,
|
||||||
rpc: &Rpc,
|
rpc: &Rpc,
|
||||||
|
@ -123,8 +126,7 @@ impl SignableTransaction {
|
||||||
|
|
||||||
clsags.push(
|
clsags.push(
|
||||||
AlgorithmMachine::new(
|
AlgorithmMachine::new(
|
||||||
ClsagMultisig::new(transcript.clone(), input.key(), inputs[i].clone())
|
ClsagMultisig::new(transcript.clone(), input.key(), inputs[i].clone()),
|
||||||
.map_err(TransactionError::MultisigError)?,
|
|
||||||
offset,
|
offset,
|
||||||
&included,
|
&included,
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,6 +5,7 @@ description = "An ink! extension for exposing Serai to ink"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||||
|
|
|
@ -5,6 +5,7 @@ description = "An ink! tracker for Serai's current multisig"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||||
|
|
|
@ -8,6 +8,9 @@ authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = ["curve25519", "ed25519", "ristretto", "dalek", "group"]
|
keywords = ["curve25519", "ed25519", "ristretto", "dalek", "group"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rand_core = "0.6"
|
rand_core = "0.6"
|
||||||
digest = "0.10"
|
digest = "0.10"
|
||||||
|
|
|
@ -7,6 +7,9 @@ repository = "https://github.com/serai-dex/serai"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
rand_core = "0.6"
|
rand_core = "0.6"
|
||||||
|
|
|
@ -8,6 +8,9 @@ authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = ["ed448", "ff", "group"]
|
keywords = ["ed448", "ff", "group"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,9 @@ authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = ["frost", "multisig", "threshold"]
|
keywords = ["frost", "multisig", "threshold"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,9 @@ authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = ["multiexp", "ff", "group"]
|
keywords = ["multiexp", "ff", "group"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
zeroize = { version = "1.3", features = ["zeroize_derive"] }
|
zeroize = { version = "1.3", features = ["zeroize_derive"] }
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,9 @@ authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = ["transcript"]
|
keywords = ["transcript"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
digest = "0.10"
|
digest = "0.10"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue