mirror of
https://github.com/serai-dex/serai.git
synced 2025-03-12 09:26:51 +00:00
Implement Guaranteed Addresses
Closes https://github.com/serai-dex/serai/issues/27. monero-rs is now solely used for Extra encoding.
This commit is contained in:
parent
7b70baaa96
commit
7c86e4593a
12 changed files with 311 additions and 117 deletions
|
@ -7,6 +7,7 @@ authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
hex-literal = "0.3"
|
||||
lazy_static = "1"
|
||||
thiserror = "1"
|
||||
|
||||
|
@ -26,6 +27,7 @@ dalek-ff-group = { path = "../../crypto/dalek-ff-group", optional = true }
|
|||
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", features = ["recommended"], optional = true }
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", features = ["ed25519"], optional = true }
|
||||
|
||||
base58-monero = "1"
|
||||
monero = "0.16"
|
||||
|
||||
hex = "0.4"
|
||||
|
|
45
coins/monero/src/tests/address.rs
Normal file
45
coins/monero/src/tests/address.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use hex_literal::hex;
|
||||
|
||||
use crate::wallet::address::{Network, AddressType, Address};
|
||||
|
||||
const SPEND: [u8; 32] = hex!("f8631661f6ab4e6fda310c797330d86e23a682f20d5bc8cc27b18051191f16d7");
|
||||
const VIEW: [u8; 32] = hex!("4a1535063ad1fee2dabbf909d4fd9a873e29541b401f0944754e17c9a41820ce");
|
||||
|
||||
const STANDARD: &'static str = "4B33mFPMq6mKi7Eiyd5XuyKRVMGVZz1Rqb9ZTyGApXW5d1aT7UBDZ89ewmnWFkzJ5wPd2SFbn313vCT8a4E2Qf4KQH4pNey";
|
||||
|
||||
const PAYMENT_ID: [u8; 8] = hex!("b8963a57855cf73f");
|
||||
const INTEGRATED: &'static str = "4Ljin4CrSNHKi7Eiyd5XuyKRVMGVZz1Rqb9ZTyGApXW5d1aT7UBDZ89ewmnWFkzJ5wPd2SFbn313vCT8a4E2Qf4KbaTH6MnpXSn88oBX35";
|
||||
|
||||
const SUB_SPEND: [u8; 32] = hex!("fe358188b528335ad1cfdc24a22a23988d742c882b6f19a602892eaab3c1b62b");
|
||||
const SUB_VIEW: [u8; 32] = hex!("9bc2b464de90d058468522098d5610c5019c45fd1711a9517db1eea7794f5470");
|
||||
const SUBADDRESS: &'static str = "8C5zHM5ud8nGC4hC2ULiBLSWx9infi8JUUmWEat4fcTf8J4H38iWYVdFmPCA9UmfLTZxD43RsyKnGEdZkoGij6csDeUnbEB";
|
||||
|
||||
#[test]
|
||||
fn standard_address() {
|
||||
let addr = Address::from_str(STANDARD, Network::Mainnet).unwrap();
|
||||
assert_eq!(addr.meta.network, Network::Mainnet);
|
||||
assert_eq!(addr.meta.kind, AddressType::Standard);
|
||||
assert_eq!(addr.meta.guaranteed, false);
|
||||
assert_eq!(addr.spend.compress().to_bytes(), SPEND);
|
||||
assert_eq!(addr.view.compress().to_bytes(), VIEW);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integrated_address() {
|
||||
let addr = Address::from_str(INTEGRATED, Network::Mainnet).unwrap();
|
||||
assert_eq!(addr.meta.network, Network::Mainnet);
|
||||
assert_eq!(addr.meta.kind, AddressType::Integrated(PAYMENT_ID));
|
||||
assert_eq!(addr.meta.guaranteed, false);
|
||||
assert_eq!(addr.spend.compress().to_bytes(), SPEND);
|
||||
assert_eq!(addr.view.compress().to_bytes(), VIEW);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subaddress() {
|
||||
let addr = Address::from_str(SUBADDRESS, Network::Mainnet).unwrap();
|
||||
assert_eq!(addr.meta.network, Network::Mainnet);
|
||||
assert_eq!(addr.meta.kind, AddressType::Subaddress);
|
||||
assert_eq!(addr.meta.guaranteed, false);
|
||||
assert_eq!(addr.spend.compress().to_bytes(), SUB_SPEND);
|
||||
assert_eq!(addr.view.compress().to_bytes(), SUB_VIEW);
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
mod clsag;
|
||||
mod address;
|
||||
|
|
152
coins/monero/src/wallet/address.rs
Normal file
152
coins/monero/src/wallet/address.rs
Normal file
|
@ -0,0 +1,152 @@
|
|||
use std::string::ToString;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, edwards::{EdwardsPoint, CompressedEdwardsY}};
|
||||
|
||||
use base58_monero::base58::{encode_check, decode_check};
|
||||
|
||||
use crate::wallet::ViewPair;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Network {
|
||||
Mainnet,
|
||||
Testnet,
|
||||
Stagenet
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum AddressType {
|
||||
Standard,
|
||||
Integrated([u8; 8]),
|
||||
Subaddress
|
||||
}
|
||||
|
||||
impl AddressType {
|
||||
fn network_bytes(network: Network) -> (u8, u8, u8) {
|
||||
match network {
|
||||
Network::Mainnet => (18, 19, 42),
|
||||
Network::Testnet => (53, 54, 63),
|
||||
Network::Stagenet => (24, 25, 36)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct AddressMeta {
|
||||
pub network: Network,
|
||||
pub kind: AddressType,
|
||||
pub guaranteed: bool
|
||||
}
|
||||
|
||||
#[derive(Clone, Error, Debug)]
|
||||
pub enum AddressError {
|
||||
#[error("invalid address byte")]
|
||||
InvalidByte,
|
||||
#[error("invalid address encoding")]
|
||||
InvalidEncoding,
|
||||
#[error("invalid length")]
|
||||
InvalidLength,
|
||||
#[error("different network than expected")]
|
||||
DifferentNetwork,
|
||||
#[error("invalid key")]
|
||||
InvalidKey
|
||||
}
|
||||
|
||||
impl AddressMeta {
|
||||
fn to_byte(&self) -> u8 {
|
||||
let bytes = AddressType::network_bytes(self.network);
|
||||
let byte = match self.kind {
|
||||
AddressType::Standard => bytes.0,
|
||||
AddressType::Integrated(_) => bytes.1,
|
||||
AddressType::Subaddress => bytes.2
|
||||
};
|
||||
byte | (if self.guaranteed { 1 << 7 } else { 0 })
|
||||
}
|
||||
|
||||
// Returns an incomplete type in the case of Integrated addresses
|
||||
fn from_byte(byte: u8) -> Result<AddressMeta, AddressError> {
|
||||
let actual = byte & 0b01111111;
|
||||
let guaranteed = (byte >> 7) == 1;
|
||||
|
||||
let mut meta = None;
|
||||
for network in [Network::Mainnet, Network::Testnet, Network::Stagenet] {
|
||||
let (standard, integrated, subaddress) = AddressType::network_bytes(network);
|
||||
if let Some(kind) = match actual {
|
||||
_ if actual == standard => Some(AddressType::Standard),
|
||||
_ if actual == integrated => Some(AddressType::Integrated([0; 8])),
|
||||
_ if actual == subaddress => Some(AddressType::Subaddress),
|
||||
_ => None
|
||||
} {
|
||||
meta = Some(AddressMeta { network, kind, guaranteed });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
meta.ok_or(AddressError::InvalidByte)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct Address {
|
||||
pub meta: AddressMeta,
|
||||
pub spend: EdwardsPoint,
|
||||
pub view: EdwardsPoint
|
||||
}
|
||||
|
||||
impl ViewPair {
|
||||
pub fn address(&self, network: Network, kind: AddressType, guaranteed: bool) -> Address {
|
||||
Address {
|
||||
meta: AddressMeta {
|
||||
network,
|
||||
kind,
|
||||
guaranteed
|
||||
},
|
||||
spend: self.spend,
|
||||
view: &self.view * &ED25519_BASEPOINT_TABLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Address {
|
||||
fn to_string(&self) -> String {
|
||||
let mut data = vec![self.meta.to_byte()];
|
||||
data.extend(self.spend.compress().to_bytes());
|
||||
data.extend(self.view.compress().to_bytes());
|
||||
if let AddressType::Integrated(id) = self.meta.kind {
|
||||
data.extend(id);
|
||||
}
|
||||
encode_check(&data).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Address {
|
||||
pub fn from_str(s: &str, network: Network) -> Result<Self, AddressError> {
|
||||
let raw = decode_check(s).map_err(|_| AddressError::InvalidEncoding)?;
|
||||
if raw.len() == 1 {
|
||||
Err(AddressError::InvalidLength)?;
|
||||
}
|
||||
|
||||
let mut meta = AddressMeta::from_byte(raw[0])?;
|
||||
if meta.network != network {
|
||||
Err(AddressError::DifferentNetwork)?;
|
||||
}
|
||||
|
||||
let len = match meta.kind {
|
||||
AddressType::Standard | AddressType::Subaddress => 65,
|
||||
AddressType::Integrated(_) => 73
|
||||
};
|
||||
if raw.len() != len {
|
||||
Err(AddressError::InvalidLength)?;
|
||||
}
|
||||
|
||||
let spend = CompressedEdwardsY(raw[1 .. 33].try_into().unwrap()).decompress().ok_or(AddressError::InvalidKey)?;
|
||||
let view = CompressedEdwardsY(raw[33 .. 65].try_into().unwrap()).decompress().ok_or(AddressError::InvalidKey)?;
|
||||
|
||||
if let AddressType::Integrated(ref mut payment_id) = meta.kind {
|
||||
payment_id.copy_from_slice(&raw[65 .. 73]);
|
||||
}
|
||||
|
||||
Ok(Address { meta, spend, view })
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@ use crate::{
|
|||
transaction::Input
|
||||
};
|
||||
|
||||
pub mod address;
|
||||
|
||||
mod scan;
|
||||
pub use scan::SpendableOutput;
|
||||
|
||||
|
@ -23,7 +25,7 @@ fn key_image_sort(x: &EdwardsPoint, y: &EdwardsPoint) -> std::cmp::Ordering {
|
|||
|
||||
// https://github.com/monero-project/research-lab/issues/103
|
||||
pub(crate) fn uniqueness(inputs: &[Input]) -> [u8; 32] {
|
||||
let mut u = b"domain_separator".to_vec();
|
||||
let mut u = b"uniqueness".to_vec();
|
||||
for input in inputs {
|
||||
match input {
|
||||
// If Gen, this should be the only input, making this loop somewhat pointless
|
||||
|
@ -63,3 +65,9 @@ pub(crate) fn commitment_mask(shared_key: Scalar) -> Scalar {
|
|||
mask.extend(shared_key.to_bytes());
|
||||
hash_to_scalar(&mask)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ViewPair {
|
||||
pub spend: EdwardsPoint,
|
||||
pub view: Scalar
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ use crate::{
|
|||
Commitment,
|
||||
serialize::{write_varint, read_32, read_scalar, read_point},
|
||||
transaction::{Timelock, Transaction},
|
||||
wallet::{uniqueness, shared_key, amount_decryption, commitment_mask}
|
||||
wallet::{ViewPair, uniqueness, shared_key, amount_decryption, commitment_mask}
|
||||
};
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
|
@ -55,8 +55,8 @@ impl SpendableOutput {
|
|||
impl Transaction {
|
||||
pub fn scan(
|
||||
&self,
|
||||
view: Scalar,
|
||||
spend: EdwardsPoint
|
||||
view: ViewPair,
|
||||
guaranteed: bool
|
||||
) -> (Vec<SpendableOutput>, Timelock) {
|
||||
let mut extra = vec![];
|
||||
write_varint(&u64::try_from(self.prefix.extra.len()).unwrap(), &mut extra).unwrap();
|
||||
|
@ -82,52 +82,53 @@ impl Transaction {
|
|||
for (o, output) in self.prefix.outputs.iter().enumerate() {
|
||||
// TODO: This may be replaceable by pubkeys[o]
|
||||
for pubkey in &pubkeys {
|
||||
let key_offset = shared_key(
|
||||
Some(uniqueness(&self.prefix.inputs)).filter(|_| guaranteed),
|
||||
view.view,
|
||||
pubkey,
|
||||
o
|
||||
);
|
||||
// P - shared == spend
|
||||
if (output.key - (&key_offset * &ED25519_BASEPOINT_TABLE)) != view.spend {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Since we've found an output to us, get its amount
|
||||
let mut commitment = Commitment::zero();
|
||||
|
||||
// P - shared == spend
|
||||
let matches = |shared_key| (output.key - (&shared_key * &ED25519_BASEPOINT_TABLE)) == spend;
|
||||
let test = |shared_key| Some(shared_key).filter(|shared_key| matches(*shared_key));
|
||||
// Miner transaction
|
||||
if output.amount != 0 {
|
||||
commitment.amount = output.amount;
|
||||
// Regular transaction
|
||||
} else {
|
||||
let amount = match self.rct_signatures.base.ecdh_info.get(o) {
|
||||
Some(amount) => amount_decryption(*amount, key_offset),
|
||||
// This should never happen, yet it may be possible with miner transactions?
|
||||
// Using get just decreases the possibility of a panic and lets us move on in that case
|
||||
None => break
|
||||
};
|
||||
|
||||
// Get the traditional shared key and unique shared key, testing if either matches for this output
|
||||
let traditional = test(shared_key(None, view, pubkey, o));
|
||||
let unique = test(shared_key(Some(uniqueness(&self.prefix.inputs)), view, pubkey, o));
|
||||
|
||||
// If either matches, grab it and decode the amount
|
||||
if let Some(key_offset) = traditional.or(unique) {
|
||||
// Miner transaction
|
||||
if output.amount != 0 {
|
||||
commitment.amount = output.amount;
|
||||
// Regular transaction
|
||||
} else {
|
||||
let amount = match self.rct_signatures.base.ecdh_info.get(o) {
|
||||
Some(amount) => amount_decryption(*amount, key_offset),
|
||||
// This should never happen, yet it may be possible with miner transactions?
|
||||
// Using get just decreases the possibility of a panic and lets us move on in that case
|
||||
None => continue
|
||||
};
|
||||
|
||||
// Rebuild the commitment to verify it
|
||||
commitment = Commitment::new(commitment_mask(key_offset), amount);
|
||||
// If this is a malicious commitment, move to the next output
|
||||
// Any other R value will calculate to a different spend key and are therefore ignorable
|
||||
if Some(&commitment.calculate()) != self.rct_signatures.base.commitments.get(o) {
|
||||
break;
|
||||
}
|
||||
// Rebuild the commitment to verify it
|
||||
commitment = Commitment::new(commitment_mask(key_offset), amount);
|
||||
// If this is a malicious commitment, move to the next output
|
||||
// Any other R value will calculate to a different spend key and are therefore ignorable
|
||||
if Some(&commitment.calculate()) != self.rct_signatures.base.commitments.get(o) {
|
||||
break;
|
||||
}
|
||||
|
||||
if commitment.amount != 0 {
|
||||
res.push(SpendableOutput {
|
||||
tx: self.hash(),
|
||||
o: o.try_into().unwrap(),
|
||||
key: output.key,
|
||||
key_offset,
|
||||
commitment
|
||||
});
|
||||
}
|
||||
// Break to prevent public keys from being included multiple times, triggering multiple
|
||||
// inclusions of the same output
|
||||
break;
|
||||
}
|
||||
|
||||
if commitment.amount != 0 {
|
||||
res.push(SpendableOutput {
|
||||
tx: self.hash(),
|
||||
o: o.try_into().unwrap(),
|
||||
key: output.key,
|
||||
key_offset,
|
||||
commitment
|
||||
});
|
||||
}
|
||||
// Break to prevent public keys from being included multiple times, triggering multiple
|
||||
// inclusions of the same output
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,11 +9,7 @@ use curve25519_dalek::{
|
|||
edwards::EdwardsPoint
|
||||
};
|
||||
|
||||
use monero::{
|
||||
consensus::Encodable,
|
||||
util::{key::PublicKey, address::{AddressType, Address}},
|
||||
blockdata::transaction::SubField
|
||||
};
|
||||
use monero::{consensus::Encodable, PublicKey, blockdata::transaction::SubField};
|
||||
|
||||
#[cfg(feature = "multisig")]
|
||||
use frost::FrostError;
|
||||
|
@ -29,7 +25,10 @@ use crate::{
|
|||
},
|
||||
transaction::{Input, Output, Timelock, TransactionPrefix, Transaction},
|
||||
rpc::{Rpc, RpcError},
|
||||
wallet::{SpendableOutput, Decoys, key_image_sort, uniqueness, shared_key, commitment_mask, amount_encryption}
|
||||
wallet::{
|
||||
address::{AddressType, Address}, SpendableOutput, Decoys,
|
||||
key_image_sort, uniqueness, shared_key, commitment_mask, amount_encryption
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "multisig")]
|
||||
use crate::frost::MultisigError;
|
||||
|
@ -52,23 +51,23 @@ impl SendOutput {
|
|||
fn new<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
unique: [u8; 32],
|
||||
output: (Address, u64, bool),
|
||||
output: (Address, u64),
|
||||
o: usize
|
||||
) -> SendOutput {
|
||||
let r = random_scalar(rng);
|
||||
let shared_key = shared_key(
|
||||
Some(unique).filter(|_| output.2),
|
||||
Some(unique).filter(|_| output.0.meta.guaranteed),
|
||||
r,
|
||||
&output.0.public_view.point.decompress().expect("SendOutput::new requires valid addresses"),
|
||||
&output.0.view,
|
||||
o
|
||||
);
|
||||
|
||||
let spend = output.0.public_spend.point.decompress().expect("SendOutput::new requires valid addresses");
|
||||
let spend = output.0.spend;
|
||||
SendOutput {
|
||||
R: match output.0.addr_type {
|
||||
R: match output.0.meta.kind {
|
||||
AddressType::Standard => &r * &ED25519_BASEPOINT_TABLE,
|
||||
AddressType::SubAddress => &r * spend,
|
||||
AddressType::Integrated(_) => panic!("SendOutput::new doesn't support Integrated addresses")
|
||||
AddressType::Integrated(_) => unimplemented!("SendOutput::new doesn't support Integrated addresses"),
|
||||
AddressType::Subaddress => &r * spend
|
||||
},
|
||||
dest: ((&shared_key * &ED25519_BASEPOINT_TABLE) + spend),
|
||||
commitment: Commitment::new(commitment_mask(shared_key), output.1),
|
||||
|
@ -169,7 +168,7 @@ impl Fee {
|
|||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct SignableTransaction {
|
||||
inputs: Vec<SpendableOutput>,
|
||||
payments: Vec<(Address, u64, bool)>,
|
||||
payments: Vec<(Address, u64)>,
|
||||
outputs: Vec<SendOutput>,
|
||||
fee: u64
|
||||
}
|
||||
|
@ -177,23 +176,16 @@ pub struct SignableTransaction {
|
|||
impl SignableTransaction {
|
||||
pub fn new(
|
||||
inputs: Vec<SpendableOutput>,
|
||||
payments: Vec<(Address, u64)>,
|
||||
mut payments: Vec<(Address, u64)>,
|
||||
change_address: Option<Address>,
|
||||
fee_rate: Fee
|
||||
) -> Result<SignableTransaction, TransactionError> {
|
||||
// Make sure all addresses are valid
|
||||
let test = |addr: Address| {
|
||||
if !(
|
||||
addr.public_view.point.decompress().is_some() &&
|
||||
addr.public_spend.point.decompress().is_some()
|
||||
) {
|
||||
Err(TransactionError::InvalidAddress)?;
|
||||
}
|
||||
|
||||
match addr.addr_type {
|
||||
match addr.meta.kind {
|
||||
AddressType::Standard => Ok(()),
|
||||
AddressType::Integrated(..) => Err(TransactionError::InvalidAddress),
|
||||
AddressType::SubAddress => Ok(())
|
||||
AddressType::Subaddress => Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -250,11 +242,8 @@ impl SignableTransaction {
|
|||
Err(TransactionError::TooManyOutputs)?;
|
||||
}
|
||||
|
||||
let mut payments = payments.iter().map(|(address, amount)| (*address, *amount, false)).collect::<Vec<_>>();
|
||||
if change {
|
||||
// Always use a unique key image for the change output
|
||||
// TODO: Make this a config option
|
||||
payments.push((change_address.unwrap(), in_amount - out_amount, true));
|
||||
payments.push((change_address.unwrap(), in_amount - out_amount));
|
||||
}
|
||||
|
||||
Ok(
|
||||
|
|
|
@ -94,9 +94,8 @@ impl SignableTransaction {
|
|||
transcript.append_message(b"input_shared_key", &input.key_offset.to_bytes());
|
||||
}
|
||||
for payment in &self.payments {
|
||||
transcript.append_message(b"payment_address", &payment.0.as_bytes());
|
||||
transcript.append_message(b"payment_address", &payment.0.to_string().as_bytes());
|
||||
transcript.append_message(b"payment_amount", &payment.1.to_le_bytes());
|
||||
transcript.append_message(b"payment_unique", &(if payment.2 { [1] } else { [0] }));
|
||||
}
|
||||
|
||||
// Sort included before cloning it around
|
||||
|
|
|
@ -18,12 +18,7 @@ use transcript::RecommendedTranscript;
|
|||
#[cfg(feature = "multisig")]
|
||||
use frost::{curve::Ed25519, tests::{THRESHOLD, key_gen, sign}};
|
||||
|
||||
use monero::{
|
||||
network::Network,
|
||||
util::{key::PublicKey, address::Address}
|
||||
};
|
||||
|
||||
use monero_serai::{random_scalar, wallet::SignableTransaction};
|
||||
use monero_serai::{random_scalar, wallet::{ViewPair, address::{Network, AddressType}, SignableTransaction}};
|
||||
|
||||
mod rpc;
|
||||
use crate::rpc::{rpc, mine_block};
|
||||
|
@ -73,11 +68,8 @@ async fn send_core(test: usize, multisig: bool) {
|
|||
}
|
||||
}
|
||||
|
||||
let addr = Address::standard(
|
||||
Network::Mainnet,
|
||||
PublicKey { point: spend_pub.compress() },
|
||||
PublicKey { point: (&view * &ED25519_BASEPOINT_TABLE).compress() }
|
||||
);
|
||||
let view_pair = ViewPair { view, spend: spend_pub };
|
||||
let addr = view_pair.address(Network::Mainnet, AddressType::Standard, false);
|
||||
|
||||
let fee = rpc.get_fee().await.unwrap();
|
||||
|
||||
|
@ -99,7 +91,7 @@ async fn send_core(test: usize, multisig: bool) {
|
|||
|
||||
// Grab the largest output available
|
||||
let output = {
|
||||
let mut outputs = tx.as_ref().unwrap().scan(view, spend_pub).0;
|
||||
let mut outputs = tx.as_ref().unwrap().scan(view_pair, false).0;
|
||||
outputs.sort_by(|x, y| x.commitment.amount.cmp(&y.commitment.amount).reverse());
|
||||
outputs.swap_remove(0)
|
||||
};
|
||||
|
@ -124,7 +116,7 @@ async fn send_core(test: usize, multisig: bool) {
|
|||
|
||||
for i in (start + 1) .. (start + 9) {
|
||||
let tx = rpc.get_block_transactions(i).await.unwrap().swap_remove(0);
|
||||
let output = tx.scan(view, spend_pub).0.swap_remove(0);
|
||||
let output = tx.scan(view_pair, false).0.swap_remove(0);
|
||||
amount += output.commitment.amount;
|
||||
outputs.push(output);
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ pub trait Coin {
|
|||
) -> Result<(Vec<u8>, Vec<<Self::Output as Output>::Id>), CoinError>;
|
||||
|
||||
#[cfg(test)]
|
||||
async fn mine_block(&self, address: Self::Address);
|
||||
async fn mine_block(&self);
|
||||
|
||||
#[cfg(test)]
|
||||
async fn test_send(&self, key: Self::Address);
|
||||
|
|
|
@ -2,17 +2,19 @@ use std::sync::Arc;
|
|||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
|
||||
use dalek_ff_group as dfg;
|
||||
use transcript::RecommendedTranscript;
|
||||
use frost::{curve::Ed25519, MultisigKeys};
|
||||
|
||||
use monero::{PublicKey, network::Network, util::address::Address};
|
||||
use monero_serai::{
|
||||
transaction::{Timelock, Transaction},
|
||||
rpc::Rpc,
|
||||
wallet::{Fee, SpendableOutput, SignableTransaction as MSignableTransaction, TransactionMachine}
|
||||
wallet::{
|
||||
ViewPair, address::{Network, AddressType, Address},
|
||||
Fee, SpendableOutput, SignableTransaction as MSignableTransaction, TransactionMachine
|
||||
}
|
||||
};
|
||||
|
||||
use crate::{coin::{CoinError, Output as OutputTrait, Coin}, view_key};
|
||||
|
@ -59,18 +61,28 @@ pub struct SignableTransaction(
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct Monero {
|
||||
pub(crate) rpc: Rpc,
|
||||
view: Scalar,
|
||||
view_pub: PublicKey
|
||||
view: Scalar
|
||||
}
|
||||
|
||||
impl Monero {
|
||||
pub fn new(url: String) -> Monero {
|
||||
let view = view_key::<Monero>(0).0;
|
||||
Monero {
|
||||
rpc: Rpc::new(url),
|
||||
view,
|
||||
view_pub: PublicKey { point: (&view * &ED25519_BASEPOINT_TABLE).compress() }
|
||||
}
|
||||
Monero { rpc: Rpc::new(url), view }
|
||||
}
|
||||
|
||||
fn view_pair(&self, spend: dfg::EdwardsPoint) -> ViewPair {
|
||||
ViewPair { spend: spend.0, view: self.view }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn empty_view_pair(&self) -> ViewPair {
|
||||
use group::Group;
|
||||
self.view_pair(dfg::EdwardsPoint::generator())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn empty_address(&self) -> Address {
|
||||
self.empty_view_pair().address(Network::Mainnet, AddressType::Standard, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,7 +112,7 @@ impl Coin for Monero {
|
|||
const MAX_OUTPUTS: usize = 16;
|
||||
|
||||
fn address(&self, key: dfg::EdwardsPoint) -> Self::Address {
|
||||
Address::standard(Network::Mainnet, PublicKey { point: key.compress().0 }, self.view_pub)
|
||||
self.view_pair(key).address(Network::Mainnet, AddressType::Standard, true)
|
||||
}
|
||||
|
||||
async fn get_height(&self) -> Result<usize, CoinError> {
|
||||
|
@ -115,7 +127,7 @@ impl Coin for Monero {
|
|||
block
|
||||
.iter()
|
||||
.flat_map(|tx| {
|
||||
let (outputs, timelock) = tx.scan(self.view, key.0);
|
||||
let (outputs, timelock) = tx.scan(self.view_pair(key), true);
|
||||
if timelock == Timelock::None {
|
||||
outputs
|
||||
} else {
|
||||
|
@ -178,13 +190,13 @@ impl Coin for Monero {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
async fn mine_block(&self, address: Self::Address) {
|
||||
async fn mine_block(&self) {
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct EmptyResponse {}
|
||||
let _: EmptyResponse = self.rpc.rpc_call("json_rpc", Some(serde_json::json!({
|
||||
"method": "generateblocks",
|
||||
"params": {
|
||||
"wallet_address": address.to_string(),
|
||||
"wallet_address": self.empty_address().to_string(),
|
||||
"amount_of_blocks": 10
|
||||
},
|
||||
}))).await.unwrap();
|
||||
|
@ -192,31 +204,28 @@ impl Coin for Monero {
|
|||
|
||||
#[cfg(test)]
|
||||
async fn test_send(&self, address: Self::Address) {
|
||||
use group::Group;
|
||||
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
let height = self.get_height().await.unwrap();
|
||||
|
||||
let temp = self.address(dfg::EdwardsPoint::generator());
|
||||
self.mine_block(temp).await;
|
||||
self.mine_block().await;
|
||||
for _ in 0 .. 7 {
|
||||
self.mine_block(temp).await;
|
||||
self.mine_block().await;
|
||||
}
|
||||
|
||||
let outputs = self.rpc
|
||||
.get_block_transactions_possible(height).await.unwrap()
|
||||
.swap_remove(0).scan(self.view, dfg::EdwardsPoint::generator().0).0;
|
||||
.swap_remove(0).scan(self.empty_view_pair(), false).0;
|
||||
|
||||
let amount = outputs[0].commitment.amount;
|
||||
let fee = 1000000000; // TODO
|
||||
let tx = MSignableTransaction::new(
|
||||
outputs,
|
||||
vec![(address, amount - fee)],
|
||||
Some(temp),
|
||||
Some(self.empty_address()),
|
||||
self.rpc.get_fee().await.unwrap()
|
||||
).unwrap().sign(&mut OsRng, &self.rpc, &Scalar::one()).await.unwrap();
|
||||
self.rpc.publish_transaction(&tx).await.unwrap();
|
||||
self.mine_block(temp).await;
|
||||
self.mine_block().await;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,6 @@ use async_trait::async_trait;
|
|||
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use group::Group;
|
||||
|
||||
use frost::curve::Curve;
|
||||
|
||||
use crate::{NetworkError, Network, coin::{Coin, Monero}, wallet::{WalletKeys, MemCoinDb, Wallet}};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -55,7 +51,7 @@ impl Network for LocalNetwork {
|
|||
|
||||
async fn test_send<C: Coin + Clone>(coin: C, fee: C::Fee) {
|
||||
// Mine a block so there's a confirmed height
|
||||
coin.mine_block(coin.address(<C::Curve as Curve>::G::generator())).await;
|
||||
coin.mine_block().await;
|
||||
let height = coin.get_height().await.unwrap();
|
||||
|
||||
let mut keys = frost::tests::key_gen::<_, C::Curve>(&mut OsRng);
|
||||
|
@ -74,7 +70,7 @@ async fn test_send<C: Coin + Clone>(coin: C, fee: C::Fee) {
|
|||
|
||||
// Get the chain to a height where blocks have sufficient confirmations
|
||||
while (height + C::CONFIRMATIONS) > coin.get_height().await.unwrap() {
|
||||
coin.mine_block(coin.address(<C::Curve as Curve>::G::generator())).await;
|
||||
coin.mine_block().await;
|
||||
}
|
||||
|
||||
for wallet in wallets.iter_mut() {
|
||||
|
|
Loading…
Reference in a new issue