mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-23 03:59:22 +00:00
Add OutputType, either external, branch, or change
Used to delineate, by address received to, the intention of the output.
This commit is contained in:
parent
a646ec5aaa
commit
b303649f9d
2 changed files with 59 additions and 22 deletions
|
@ -13,15 +13,24 @@ use frost::{
|
||||||
pub mod monero;
|
pub mod monero;
|
||||||
pub use self::monero::Monero;
|
pub use self::monero::Monero;
|
||||||
|
|
||||||
#[derive(Clone, Error, Debug)]
|
#[derive(Clone, Copy, Error, Debug)]
|
||||||
pub enum CoinError {
|
pub enum CoinError {
|
||||||
#[error("failed to connect to coin daemon")]
|
#[error("failed to connect to coin daemon")]
|
||||||
ConnectionError,
|
ConnectionError,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum OutputType {
|
||||||
|
External,
|
||||||
|
Branch,
|
||||||
|
Change,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Output: Sized + Clone {
|
pub trait Output: Sized + Clone {
|
||||||
type Id: AsRef<[u8]>;
|
type Id: AsRef<[u8]>;
|
||||||
|
|
||||||
|
fn kind(&self) -> OutputType;
|
||||||
|
|
||||||
fn id(&self) -> Self::Id;
|
fn id(&self) -> Self::Id;
|
||||||
fn amount(&self) -> u64;
|
fn amount(&self) -> u64;
|
||||||
|
|
||||||
|
@ -48,8 +57,11 @@ pub trait Coin {
|
||||||
const MAX_INPUTS: usize;
|
const MAX_INPUTS: usize;
|
||||||
const MAX_OUTPUTS: usize; // TODO: Decide if this includes change or not
|
const MAX_OUTPUTS: usize; // TODO: Decide if this includes change or not
|
||||||
|
|
||||||
|
/// Address for the given group key to receive external coins to.
|
||||||
// Doesn't have to take self, enables some level of caching which is pleasant
|
// Doesn't have to take self, enables some level of caching which is pleasant
|
||||||
fn address(&self, key: <Self::Curve as Ciphersuite>::G) -> Self::Address;
|
fn address(&self, key: <Self::Curve as Ciphersuite>::G) -> Self::Address;
|
||||||
|
/// Address for the given group key to use for scheduled branches.
|
||||||
|
fn branch_address(&self, key: <Self::Curve as Ciphersuite>::G) -> Self::Address;
|
||||||
|
|
||||||
async fn get_latest_block_number(&self) -> Result<usize, CoinError>;
|
async fn get_latest_block_number(&self) -> Result<usize, CoinError>;
|
||||||
async fn get_block(&self, number: usize) -> Result<Self::Block, CoinError>;
|
async fn get_block(&self, number: usize) -> Result<Self::Block, CoinError>;
|
||||||
|
@ -59,9 +71,7 @@ pub trait Coin {
|
||||||
key: <Self::Curve as Ciphersuite>::G,
|
key: <Self::Curve as Ciphersuite>::G,
|
||||||
) -> Result<Vec<Self::Output>, CoinError>;
|
) -> Result<Vec<Self::Output>, CoinError>;
|
||||||
|
|
||||||
// TODO: Remove
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn is_confirmed(&self, tx: &[u8]) -> Result<bool, CoinError>;
|
|
||||||
|
|
||||||
async fn prepare_send(
|
async fn prepare_send(
|
||||||
&self,
|
&self,
|
||||||
keys: ThresholdKeys<Self::Curve>,
|
keys: ThresholdKeys<Self::Curve>,
|
||||||
|
@ -69,6 +79,7 @@ pub trait Coin {
|
||||||
block_number: usize,
|
block_number: usize,
|
||||||
inputs: Vec<Self::Output>,
|
inputs: Vec<Self::Output>,
|
||||||
payments: &[(Self::Address, u64)],
|
payments: &[(Self::Address, u64)],
|
||||||
|
change: Option<<Self::Curve as Ciphersuite>::G>,
|
||||||
fee: Self::Fee,
|
fee: Self::Fee,
|
||||||
) -> Result<Self::SignableTransaction, CoinError>;
|
) -> Result<Self::SignableTransaction, CoinError>;
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ use monero_serai::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
additional_key,
|
additional_key,
|
||||||
coin::{CoinError, Output as OutputTrait, Coin},
|
coin::{CoinError, OutputType, Output as OutputTrait, Coin},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -32,12 +32,25 @@ impl From<SpendableOutput> for Output {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EXTERNAL_SUBADDRESS: (u32, u32) = (0, 0);
|
||||||
|
const BRANCH_SUBADDRESS: (u32, u32) = (1, 0);
|
||||||
|
const CHANGE_SUBADDRESS: (u32, u32) = (2, 0);
|
||||||
|
|
||||||
impl OutputTrait for Output {
|
impl OutputTrait for Output {
|
||||||
// While we could use (tx, o), using the key ensures we won't be susceptible to the burning bug.
|
// While we could use (tx, o), using the key ensures we won't be susceptible to the burning bug.
|
||||||
// While the Monero library offers a variant which allows senders to ensure their TXs have unique
|
// While we already are immune, thanks to using featured address, this doesn't hurt and is
|
||||||
// output keys, Serai can still be targeted using the classic burning bug
|
// technically more efficient.
|
||||||
type Id = [u8; 32];
|
type Id = [u8; 32];
|
||||||
|
|
||||||
|
fn kind(&self) -> OutputType {
|
||||||
|
match self.0.output.metadata.subaddress {
|
||||||
|
EXTERNAL_SUBADDRESS => OutputType::External,
|
||||||
|
BRANCH_SUBADDRESS => OutputType::Branch,
|
||||||
|
CHANGE_SUBADDRESS => OutputType::Change,
|
||||||
|
_ => panic!("unrecognized address was scanned for"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn id(&self) -> Self::Id {
|
fn id(&self) -> Self::Id {
|
||||||
self.0.output.data.key.compress().to_bytes()
|
self.0.output.data.key.compress().to_bytes()
|
||||||
}
|
}
|
||||||
|
@ -79,8 +92,18 @@ impl Monero {
|
||||||
ViewPair::new(spend.0, self.view.clone())
|
ViewPair::new(spend.0, self.view.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn address_internal(&self, spend: dfg::EdwardsPoint, subaddress: (u32, u32)) -> MoneroAddress {
|
||||||
|
self
|
||||||
|
.view_pair(spend)
|
||||||
|
.address(Network::Mainnet, AddressSpec::Featured(Some(subaddress), None, true))
|
||||||
|
}
|
||||||
|
|
||||||
fn scanner(&self, spend: dfg::EdwardsPoint) -> Scanner {
|
fn scanner(&self, spend: dfg::EdwardsPoint) -> Scanner {
|
||||||
Scanner::from_view(self.view_pair(spend), None)
|
let mut scanner = Scanner::from_view(self.view_pair(spend), None);
|
||||||
|
scanner.register_subaddress(EXTERNAL_SUBADDRESS); // Pointless as (0, 0) is already registered
|
||||||
|
scanner.register_subaddress(BRANCH_SUBADDRESS);
|
||||||
|
scanner.register_subaddress(CHANGE_SUBADDRESS);
|
||||||
|
scanner
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -126,7 +149,11 @@ impl Coin for Monero {
|
||||||
const MAX_OUTPUTS: usize = 16;
|
const MAX_OUTPUTS: usize = 16;
|
||||||
|
|
||||||
fn address(&self, key: dfg::EdwardsPoint) -> Self::Address {
|
fn address(&self, key: dfg::EdwardsPoint) -> Self::Address {
|
||||||
self.view_pair(key).address(Network::Mainnet, AddressSpec::Featured(None, None, true))
|
self.address_internal(key, EXTERNAL_SUBADDRESS)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn branch_address(&self, key: dfg::EdwardsPoint) -> Self::Address {
|
||||||
|
self.address_internal(key, BRANCH_SUBADDRESS)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_latest_block_number(&self) -> Result<usize, CoinError> {
|
async fn get_latest_block_number(&self) -> Result<usize, CoinError> {
|
||||||
|
@ -151,21 +178,20 @@ impl Coin for Monero {
|
||||||
.map_err(|_| CoinError::ConnectionError)?
|
.map_err(|_| CoinError::ConnectionError)?
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|outputs| outputs.not_locked())
|
.flat_map(|outputs| outputs.not_locked())
|
||||||
.map(Output::from)
|
// This should be pointless as we shouldn't be able to scan for any other subaddress
|
||||||
|
// This just ensures nothing invalid makes it in
|
||||||
|
.filter_map(|output| {
|
||||||
|
if ![EXTERNAL_SUBADDRESS, BRANCH_SUBADDRESS, CHANGE_SUBADDRESS]
|
||||||
|
.contains(&output.output.metadata.subaddress)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(Output::from(output))
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn is_confirmed(&self, tx: &[u8]) -> Result<bool, CoinError> {
|
|
||||||
let tx_block_number = self
|
|
||||||
.rpc
|
|
||||||
.get_transaction_block_number(tx)
|
|
||||||
.await
|
|
||||||
.map_err(|_| CoinError::ConnectionError)?
|
|
||||||
.unwrap_or(usize::MAX);
|
|
||||||
Ok((self.get_latest_block_number().await?.saturating_sub(tx_block_number) + 1) >= 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn prepare_send(
|
async fn prepare_send(
|
||||||
&self,
|
&self,
|
||||||
keys: ThresholdKeys<Ed25519>,
|
keys: ThresholdKeys<Ed25519>,
|
||||||
|
@ -173,9 +199,9 @@ impl Coin for Monero {
|
||||||
block_number: usize,
|
block_number: usize,
|
||||||
mut inputs: Vec<Output>,
|
mut inputs: Vec<Output>,
|
||||||
payments: &[(MoneroAddress, u64)],
|
payments: &[(MoneroAddress, u64)],
|
||||||
|
change: Option<dfg::EdwardsPoint>,
|
||||||
fee: Fee,
|
fee: Fee,
|
||||||
) -> Result<SignableTransaction, CoinError> {
|
) -> Result<SignableTransaction, CoinError> {
|
||||||
let spend = keys.group_key();
|
|
||||||
Ok(SignableTransaction {
|
Ok(SignableTransaction {
|
||||||
keys,
|
keys,
|
||||||
transcript,
|
transcript,
|
||||||
|
@ -184,7 +210,7 @@ impl Coin for Monero {
|
||||||
self.rpc.get_protocol().await.unwrap(), // TODO: Make this deterministic
|
self.rpc.get_protocol().await.unwrap(), // TODO: Make this deterministic
|
||||||
inputs.drain(..).map(|input| input.0).collect(),
|
inputs.drain(..).map(|input| input.0).collect(),
|
||||||
payments.to_vec(),
|
payments.to_vec(),
|
||||||
Some(self.address(spend)),
|
change.map(|change| self.address_internal(change, CHANGE_SUBADDRESS)),
|
||||||
vec![],
|
vec![],
|
||||||
fee,
|
fee,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue