mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-22 19:49:22 +00:00
This commit is contained in:
parent
5a1f011db8
commit
5c106cecf6
9 changed files with 258 additions and 155 deletions
|
@ -138,12 +138,9 @@ impl Rpc {
|
||||||
Ok(self.rpc_call::<Option<()>, HeightResponse>("get_height", None).await?.height)
|
Ok(self.rpc_call::<Option<()>, HeightResponse>("get_height", None).await?.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_transactions_core(
|
pub async fn get_transactions(&self, hashes: &[[u8; 32]]) -> Result<Vec<Transaction>, RpcError> {
|
||||||
&self,
|
|
||||||
hashes: &[[u8; 32]],
|
|
||||||
) -> Result<(Vec<Result<Transaction, RpcError>>, Vec<[u8; 32]>), RpcError> {
|
|
||||||
if hashes.is_empty() {
|
if hashes.is_empty() {
|
||||||
return Ok((vec![], vec![]));
|
return Ok(vec![]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
|
@ -168,50 +165,34 @@ impl Rpc {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok((
|
if txs.missed_tx.len() != 0 {
|
||||||
txs
|
Err(RpcError::TransactionsNotFound(
|
||||||
.txs
|
txs.missed_tx.iter().map(|hash| hex::decode(&hash).unwrap().try_into().unwrap()).collect(),
|
||||||
.iter()
|
))?;
|
||||||
.map(|res| {
|
|
||||||
let tx = Transaction::deserialize(&mut std::io::Cursor::new(
|
|
||||||
rpc_hex(if !res.as_hex.is_empty() { &res.as_hex } else { &res.pruned_as_hex }).unwrap(),
|
|
||||||
))
|
|
||||||
.map_err(|_| {
|
|
||||||
RpcError::InvalidTransaction(hex::decode(&res.tx_hash).unwrap().try_into().unwrap())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// https://github.com/monero-project/monero/issues/8311
|
|
||||||
if res.as_hex.is_empty() {
|
|
||||||
match tx.prefix.inputs.get(0) {
|
|
||||||
Some(Input::Gen { .. }) => (),
|
|
||||||
_ => Err(RpcError::PrunedTransaction)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(tx)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
txs.missed_tx.iter().map(|hash| hex::decode(&hash).unwrap().try_into().unwrap()).collect(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_transactions(&self, hashes: &[[u8; 32]]) -> Result<Vec<Transaction>, RpcError> {
|
|
||||||
let (txs, missed) = self.get_transactions_core(hashes).await?;
|
|
||||||
if !missed.is_empty() {
|
|
||||||
Err(RpcError::TransactionsNotFound(missed))?;
|
|
||||||
}
|
}
|
||||||
// This will clone several KB and is accordingly inefficient
|
|
||||||
// TODO: Optimize
|
|
||||||
txs.iter().cloned().collect::<Result<_, _>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove with https://github.com/serai-dex/serai/issues/25
|
txs
|
||||||
pub async fn get_transactions_possible(
|
.txs
|
||||||
&self,
|
.iter()
|
||||||
hashes: &[[u8; 32]],
|
.map(|res| {
|
||||||
) -> Result<Vec<Transaction>, RpcError> {
|
let tx = Transaction::deserialize(&mut std::io::Cursor::new(
|
||||||
let (txs, _) = self.get_transactions_core(hashes).await?;
|
rpc_hex(if !res.as_hex.is_empty() { &res.as_hex } else { &res.pruned_as_hex }).unwrap(),
|
||||||
Ok(txs.iter().cloned().filter_map(|tx| tx.ok()).collect())
|
))
|
||||||
|
.map_err(|_| {
|
||||||
|
RpcError::InvalidTransaction(hex::decode(&res.tx_hash).unwrap().try_into().unwrap())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// https://github.com/monero-project/monero/issues/8311
|
||||||
|
if res.as_hex.is_empty() {
|
||||||
|
match tx.prefix.inputs.get(0) {
|
||||||
|
Some(Input::Gen { .. }) => (),
|
||||||
|
_ => Err(RpcError::PrunedTransaction)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(tx)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_block(&self, height: usize) -> Result<Block, RpcError> {
|
pub async fn get_block(&self, height: usize) -> Result<Block, RpcError> {
|
||||||
|
@ -238,32 +219,13 @@ impl Rpc {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_block_transactions_core(
|
pub async fn get_block_transactions(&self, height: usize) -> Result<Vec<Transaction>, RpcError> {
|
||||||
&self,
|
|
||||||
height: usize,
|
|
||||||
possible: bool,
|
|
||||||
) -> Result<Vec<Transaction>, RpcError> {
|
|
||||||
let block = self.get_block(height).await?;
|
let block = self.get_block(height).await?;
|
||||||
let mut res = vec![block.miner_tx];
|
let mut res = vec![block.miner_tx];
|
||||||
res.extend(if possible {
|
res.extend(self.get_transactions(&block.txs).await?);
|
||||||
self.get_transactions_possible(&block.txs).await?
|
|
||||||
} else {
|
|
||||||
self.get_transactions(&block.txs).await?
|
|
||||||
});
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_block_transactions(&self, height: usize) -> Result<Vec<Transaction>, RpcError> {
|
|
||||||
self.get_block_transactions_core(height, false).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_block_transactions_possible(
|
|
||||||
&self,
|
|
||||||
height: usize,
|
|
||||||
) -> Result<Vec<Transaction>, RpcError> {
|
|
||||||
self.get_block_transactions_core(height, true).await
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
|
|
|
@ -140,8 +140,8 @@ impl Decoys {
|
||||||
let mut real = Vec::with_capacity(inputs.len());
|
let mut real = Vec::with_capacity(inputs.len());
|
||||||
let mut outputs = Vec::with_capacity(inputs.len());
|
let mut outputs = Vec::with_capacity(inputs.len());
|
||||||
for input in inputs {
|
for input in inputs {
|
||||||
real.push(rpc.get_o_indexes(input.tx).await?[usize::from(input.o)]);
|
real.push(input.global_index);
|
||||||
outputs.push((real[real.len() - 1], [input.key, input.commitment.calculate()]));
|
outputs.push((real[real.len() - 1], [input.output.data.key, input.commitment().calculate()]));
|
||||||
}
|
}
|
||||||
|
|
||||||
let distribution_len = {
|
let distribution_len = {
|
||||||
|
|
|
@ -8,22 +8,59 @@ use crate::{
|
||||||
Commitment,
|
Commitment,
|
||||||
serialize::{read_byte, read_u32, read_u64, read_bytes, read_scalar, read_point},
|
serialize::{read_byte, read_u32, read_u64, read_bytes, read_scalar, read_point},
|
||||||
transaction::{Timelock, Transaction},
|
transaction::{Timelock, Transaction},
|
||||||
|
block::Block,
|
||||||
|
rpc::{Rpc, RpcError},
|
||||||
wallet::{PaymentId, Extra, Scanner, uniqueness, shared_key, amount_decryption, commitment_mask},
|
wallet::{PaymentId, Extra, Scanner, uniqueness, shared_key, amount_decryption, commitment_mask},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct SpendableOutput {
|
pub struct AbsoluteId {
|
||||||
pub tx: [u8; 32],
|
pub tx: [u8; 32],
|
||||||
pub o: u8,
|
pub o: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbsoluteId {
|
||||||
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
|
let mut res = Vec::with_capacity(32 + 1);
|
||||||
|
res.extend(&self.tx);
|
||||||
|
res.push(self.o);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<R: std::io::Read>(r: &mut R) -> std::io::Result<AbsoluteId> {
|
||||||
|
Ok(AbsoluteId { tx: read_bytes(r)?, o: read_byte(r)? })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
|
pub struct OutputData {
|
||||||
pub key: EdwardsPoint,
|
pub key: EdwardsPoint,
|
||||||
// Absolute difference between the spend key and the key in this output, inclusive of any
|
// Absolute difference between the spend key and the key in this output
|
||||||
// subaddress offset
|
|
||||||
pub key_offset: Scalar,
|
pub key_offset: Scalar,
|
||||||
pub commitment: Commitment,
|
pub commitment: Commitment,
|
||||||
|
}
|
||||||
|
|
||||||
// Metadata to know how to process this output
|
impl OutputData {
|
||||||
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
|
let mut res = Vec::with_capacity(32 + 32 + 40);
|
||||||
|
res.extend(self.key.compress().to_bytes());
|
||||||
|
res.extend(self.key_offset.to_bytes());
|
||||||
|
res.extend(self.commitment.mask.to_bytes());
|
||||||
|
res.extend(self.commitment.amount.to_le_bytes());
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<R: std::io::Read>(r: &mut R) -> std::io::Result<OutputData> {
|
||||||
|
Ok(OutputData {
|
||||||
|
key: read_point(r)?,
|
||||||
|
key_offset: read_scalar(r)?,
|
||||||
|
commitment: Commitment::new(read_scalar(r)?, read_u64(r)?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
|
pub struct Metadata {
|
||||||
// Does not have to be an Option since the 0 subaddress is the main address
|
// Does not have to be an Option since the 0 subaddress is the main address
|
||||||
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
|
// Can be an Option, as extra doesn't necessarily have a payment ID, yet all Monero TXs should
|
||||||
|
@ -35,14 +72,98 @@ pub struct SpendableOutput {
|
||||||
pub payment_id: [u8; 8],
|
pub payment_id: [u8; 8],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
impl Metadata {
|
||||||
pub struct Timelocked(Timelock, Vec<SpendableOutput>);
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
impl Timelocked {
|
let mut res = Vec::with_capacity(4 + 4 + 8);
|
||||||
|
res.extend(self.subaddress.0.to_le_bytes());
|
||||||
|
res.extend(self.subaddress.1.to_le_bytes());
|
||||||
|
res.extend(self.payment_id);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<R: std::io::Read>(r: &mut R) -> std::io::Result<Metadata> {
|
||||||
|
Ok(Metadata { subaddress: (read_u32(r)?, read_u32(r)?), payment_id: read_bytes(r)? })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
|
pub struct ReceivedOutput {
|
||||||
|
pub absolute: AbsoluteId,
|
||||||
|
pub data: OutputData,
|
||||||
|
pub metadata: Metadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReceivedOutput {
|
||||||
|
pub fn commitment(&self) -> Commitment {
|
||||||
|
self.data.commitment.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
|
let mut serialized = self.absolute.serialize();
|
||||||
|
serialized.extend(&self.data.serialize());
|
||||||
|
serialized.extend(&self.metadata.serialize());
|
||||||
|
serialized
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<R: std::io::Read>(r: &mut R) -> std::io::Result<ReceivedOutput> {
|
||||||
|
Ok(ReceivedOutput {
|
||||||
|
absolute: AbsoluteId::deserialize(r)?,
|
||||||
|
data: OutputData::deserialize(r)?,
|
||||||
|
metadata: Metadata::deserialize(r)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
|
pub struct SpendableOutput {
|
||||||
|
pub output: ReceivedOutput,
|
||||||
|
pub global_index: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpendableOutput {
|
||||||
|
pub async fn refresh_global_index(&mut self, rpc: &Rpc) -> Result<(), RpcError> {
|
||||||
|
self.global_index =
|
||||||
|
rpc.get_o_indexes(self.output.absolute.tx).await?[usize::from(self.output.absolute.o)];
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn from(rpc: &Rpc, output: ReceivedOutput) -> Result<SpendableOutput, RpcError> {
|
||||||
|
let mut output = SpendableOutput { output, global_index: 0 };
|
||||||
|
output.refresh_global_index(rpc).await?;
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commitment(&self) -> Commitment {
|
||||||
|
self.output.commitment()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
|
let mut serialized = self.output.serialize();
|
||||||
|
serialized.extend(&self.global_index.to_le_bytes());
|
||||||
|
serialized
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<R: std::io::Read>(r: &mut R) -> std::io::Result<SpendableOutput> {
|
||||||
|
Ok(SpendableOutput { output: ReceivedOutput::deserialize(r)?, global_index: read_u64(r)? })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Zeroize)]
|
||||||
|
pub struct Timelocked<O: Clone + Zeroize>(Timelock, Vec<O>);
|
||||||
|
impl<O: Clone + Zeroize> Drop for Timelocked<O> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.zeroize();
|
||||||
|
self.1.zeroize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<O: Clone + Zeroize> ZeroizeOnDrop for Timelocked<O> {}
|
||||||
|
|
||||||
|
impl<O: Clone + Zeroize> Timelocked<O> {
|
||||||
pub fn timelock(&self) -> Timelock {
|
pub fn timelock(&self) -> Timelock {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn not_locked(&self) -> Vec<SpendableOutput> {
|
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();
|
||||||
}
|
}
|
||||||
|
@ -50,52 +171,18 @@ impl Timelocked {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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<SpendableOutput>> {
|
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())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ignore_timelock(&self) -> Vec<SpendableOutput> {
|
pub fn ignore_timelock(&self) -> Vec<O> {
|
||||||
self.1.clone()
|
self.1.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpendableOutput {
|
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
|
||||||
let mut res = Vec::with_capacity(32 + 1 + 32 + 32 + 40);
|
|
||||||
|
|
||||||
res.extend(&self.tx);
|
|
||||||
res.push(self.o);
|
|
||||||
|
|
||||||
res.extend(self.key.compress().to_bytes());
|
|
||||||
res.extend(self.key_offset.to_bytes());
|
|
||||||
res.extend(self.commitment.mask.to_bytes());
|
|
||||||
res.extend(self.commitment.amount.to_le_bytes());
|
|
||||||
|
|
||||||
res.extend(self.subaddress.0.to_le_bytes());
|
|
||||||
res.extend(self.subaddress.1.to_le_bytes());
|
|
||||||
res.extend(self.payment_id);
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deserialize<R: std::io::Read>(r: &mut R) -> std::io::Result<SpendableOutput> {
|
|
||||||
Ok(SpendableOutput {
|
|
||||||
tx: read_bytes(r)?,
|
|
||||||
o: read_byte(r)?,
|
|
||||||
|
|
||||||
key: read_point(r)?,
|
|
||||||
key_offset: read_scalar(r)?,
|
|
||||||
commitment: Commitment::new(read_scalar(r)?, read_u64(r)?),
|
|
||||||
|
|
||||||
subaddress: (read_u32(r)?, read_u32(r)?),
|
|
||||||
payment_id: read_bytes(r)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scanner {
|
impl Scanner {
|
||||||
pub fn scan(&mut self, tx: &Transaction) -> Timelocked {
|
pub fn scan_stateless(&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;
|
||||||
let extra = if let Ok(extra) = extra {
|
let extra = if let Ok(extra) = extra {
|
||||||
|
@ -170,16 +257,16 @@ impl Scanner {
|
||||||
}
|
}
|
||||||
|
|
||||||
if commitment.amount != 0 {
|
if commitment.amount != 0 {
|
||||||
res.push(SpendableOutput {
|
res.push(ReceivedOutput {
|
||||||
tx: tx.hash(),
|
absolute: AbsoluteId { tx: tx.hash(), o: o.try_into().unwrap() },
|
||||||
o: o.try_into().unwrap(),
|
|
||||||
|
|
||||||
key: output.key,
|
data: OutputData {
|
||||||
key_offset: key_offset + self.pair.subaddress(*subaddress.unwrap()),
|
key: output.key,
|
||||||
commitment,
|
key_offset: key_offset + self.pair.subaddress(*subaddress.unwrap()),
|
||||||
|
commitment,
|
||||||
|
},
|
||||||
|
|
||||||
subaddress: (0, 0),
|
metadata: Metadata { subaddress: (0, 0), payment_id },
|
||||||
payment_id,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(burning_bug) = self.burning_bug.as_mut() {
|
if let Some(burning_bug) = self.burning_bug.as_mut() {
|
||||||
|
@ -194,4 +281,41 @@ impl Scanner {
|
||||||
|
|
||||||
Timelocked(tx.prefix.timelock, res)
|
Timelocked(tx.prefix.timelock, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn scan(
|
||||||
|
&mut self,
|
||||||
|
rpc: &Rpc,
|
||||||
|
block: &Block,
|
||||||
|
) -> Result<Vec<Timelocked<SpendableOutput>>, RpcError> {
|
||||||
|
let mut index = rpc.get_o_indexes(block.miner_tx.hash()).await?[0];
|
||||||
|
let mut txs = vec![block.miner_tx.clone()];
|
||||||
|
txs.extend(rpc.get_transactions(&block.txs).await?);
|
||||||
|
|
||||||
|
let map = |mut timelock: Timelocked<ReceivedOutput>, index| {
|
||||||
|
if timelock.1.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Timelocked(
|
||||||
|
timelock.0,
|
||||||
|
timelock
|
||||||
|
.1
|
||||||
|
.drain(..)
|
||||||
|
.map(|output| SpendableOutput {
|
||||||
|
global_index: index + u64::from(output.absolute.o),
|
||||||
|
output,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut res = vec![];
|
||||||
|
for tx in txs {
|
||||||
|
if let Some(timelock) = map(self.scan_stateless(&tx), index) {
|
||||||
|
res.push(timelock);
|
||||||
|
}
|
||||||
|
index += u64::try_from(tx.prefix.outputs.len()).unwrap();
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,9 +129,9 @@ async fn prepare_inputs<R: RngCore + CryptoRng>(
|
||||||
|
|
||||||
for (i, input) in inputs.iter().enumerate() {
|
for (i, input) in inputs.iter().enumerate() {
|
||||||
signable.push((
|
signable.push((
|
||||||
spend + input.key_offset,
|
spend + input.output.data.key_offset,
|
||||||
generate_key_image(spend + input.key_offset),
|
generate_key_image(spend + input.output.data.key_offset),
|
||||||
ClsagInput::new(input.commitment.clone(), decoys[i].clone())
|
ClsagInput::new(input.commitment().clone(), decoys[i].clone())
|
||||||
.map_err(TransactionError::ClsagError)?,
|
.map_err(TransactionError::ClsagError)?,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -225,7 +225,7 @@ impl SignableTransaction {
|
||||||
fee_rate.calculate(Transaction::fee_weight(protocol, inputs.len(), outputs, extra));
|
fee_rate.calculate(Transaction::fee_weight(protocol, inputs.len(), outputs, extra));
|
||||||
|
|
||||||
// Make sure we have enough funds
|
// Make sure we have enough funds
|
||||||
let in_amount = inputs.iter().map(|input| input.commitment.amount).sum::<u64>();
|
let in_amount = inputs.iter().map(|input| input.commitment().amount).sum::<u64>();
|
||||||
let mut out_amount = payments.iter().map(|payment| payment.1).sum::<u64>() + fee;
|
let mut out_amount = payments.iter().map(|payment| payment.1).sum::<u64>() + fee;
|
||||||
if in_amount < out_amount {
|
if in_amount < out_amount {
|
||||||
Err(TransactionError::NotEnoughFunds(in_amount, out_amount))?;
|
Err(TransactionError::NotEnoughFunds(in_amount, out_amount))?;
|
||||||
|
@ -345,8 +345,8 @@ impl SignableTransaction {
|
||||||
) -> Result<Transaction, TransactionError> {
|
) -> Result<Transaction, TransactionError> {
|
||||||
let mut images = Vec::with_capacity(self.inputs.len());
|
let mut images = Vec::with_capacity(self.inputs.len());
|
||||||
for input in &self.inputs {
|
for input in &self.inputs {
|
||||||
let mut offset = spend + input.key_offset;
|
let mut offset = spend + input.output.data.key_offset;
|
||||||
if (&offset * &ED25519_BASEPOINT_TABLE) != input.key {
|
if (&offset * &ED25519_BASEPOINT_TABLE) != input.output.data.key {
|
||||||
Err(TransactionError::WrongPrivateKey)?;
|
Err(TransactionError::WrongPrivateKey)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -100,11 +100,11 @@ impl SignableTransaction {
|
||||||
for input in &self.inputs {
|
for input in &self.inputs {
|
||||||
// These outputs can only be spent once. Therefore, it forces all RNGs derived from this
|
// These outputs can only be spent once. Therefore, it forces all RNGs derived from this
|
||||||
// transcript (such as the one used to create one time keys) to be unique
|
// transcript (such as the one used to create one time keys) to be unique
|
||||||
transcript.append_message(b"input_hash", &input.tx);
|
transcript.append_message(b"input_hash", &input.output.absolute.tx);
|
||||||
transcript.append_message(b"input_output_index", &[input.o]);
|
transcript.append_message(b"input_output_index", &[input.output.absolute.o]);
|
||||||
// Not including this, with a doxxed list of payments, would allow brute forcing the inputs
|
// Not including this, with a doxxed list of payments, would allow brute forcing the inputs
|
||||||
// to determine RNG seeds and therefore the true spends
|
// to determine RNG seeds and therefore the true spends
|
||||||
transcript.append_message(b"input_shared_key", &input.key_offset.to_bytes());
|
transcript.append_message(b"input_shared_key", &input.output.data.key_offset.to_bytes());
|
||||||
}
|
}
|
||||||
for payment in &self.payments {
|
for payment in &self.payments {
|
||||||
transcript.append_message(b"payment_address", payment.0.to_string().as_bytes());
|
transcript.append_message(b"payment_address", payment.0.to_string().as_bytes());
|
||||||
|
@ -116,14 +116,14 @@ impl SignableTransaction {
|
||||||
|
|
||||||
for (i, input) in self.inputs.iter().enumerate() {
|
for (i, input) in self.inputs.iter().enumerate() {
|
||||||
// Check this the right set of keys
|
// Check this the right set of keys
|
||||||
let offset = keys.offset(dalek_ff_group::Scalar(input.key_offset));
|
let offset = keys.offset(dalek_ff_group::Scalar(input.output.data.key_offset));
|
||||||
if offset.group_key().0 != input.key {
|
if offset.group_key().0 != input.output.data.key {
|
||||||
Err(TransactionError::WrongPrivateKey)?;
|
Err(TransactionError::WrongPrivateKey)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
clsags.push(
|
clsags.push(
|
||||||
AlgorithmMachine::new(
|
AlgorithmMachine::new(
|
||||||
ClsagMultisig::new(transcript.clone(), input.key, inputs[i].clone())
|
ClsagMultisig::new(transcript.clone(), input.output.data.key, inputs[i].clone())
|
||||||
.map_err(TransactionError::MultisigError)?,
|
.map_err(TransactionError::MultisigError)?,
|
||||||
offset,
|
offset,
|
||||||
&included,
|
&included,
|
||||||
|
@ -331,7 +331,7 @@ impl SignMachine<Transaction> for TransactionSignMachine {
|
||||||
});
|
});
|
||||||
|
|
||||||
*value.3.write().unwrap() = Some(ClsagDetails::new(
|
*value.3.write().unwrap() = Some(ClsagDetails::new(
|
||||||
ClsagInput::new(value.1.commitment.clone(), value.2).map_err(|_| {
|
ClsagInput::new(value.1.commitment().clone(), value.2).map_err(|_| {
|
||||||
panic!("Signing an input which isn't present in the ring we created for it")
|
panic!("Signing an input which isn't present in the ring we created for it")
|
||||||
})?,
|
})?,
|
||||||
mask,
|
mask,
|
||||||
|
|
|
@ -23,7 +23,7 @@ use frost::{
|
||||||
|
|
||||||
use monero_serai::{
|
use monero_serai::{
|
||||||
random_scalar,
|
random_scalar,
|
||||||
wallet::{ViewPair, Scanner, address::Network, SignableTransaction},
|
wallet::{address::Network, ViewPair, Scanner, SpendableOutput, SignableTransaction},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod rpc;
|
mod rpc;
|
||||||
|
@ -98,13 +98,13 @@ async fn send_core(test: usize, multisig: bool) {
|
||||||
|
|
||||||
// Grab the largest output available
|
// Grab the largest output available
|
||||||
let output = {
|
let output = {
|
||||||
let mut outputs = scanner.scan(tx.as_ref().unwrap()).ignore_timelock();
|
let mut outputs = scanner.scan_stateless(tx.as_ref().unwrap()).ignore_timelock();
|
||||||
outputs.sort_by(|x, y| x.commitment.amount.cmp(&y.commitment.amount).reverse());
|
outputs.sort_by(|x, y| x.commitment().amount.cmp(&y.commitment().amount).reverse());
|
||||||
outputs.swap_remove(0)
|
outputs.swap_remove(0)
|
||||||
};
|
};
|
||||||
// Test creating a zero change output and a non-zero change output
|
// Test creating a zero change output and a non-zero change output
|
||||||
amount = output.commitment.amount - u64::try_from(i).unwrap();
|
amount = output.commitment().amount - u64::try_from(i).unwrap();
|
||||||
outputs.push(output);
|
outputs.push(SpendableOutput::from(&rpc, output).await.unwrap());
|
||||||
|
|
||||||
// Test spending multiple inputs
|
// Test spending multiple inputs
|
||||||
} else if test == 1 {
|
} else if test == 1 {
|
||||||
|
@ -122,9 +122,9 @@ async fn send_core(test: usize, multisig: bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in (start + 1) .. (start + 9) {
|
for i in (start + 1) .. (start + 9) {
|
||||||
let tx = rpc.get_block_transactions(i).await.unwrap().swap_remove(0);
|
let mut txs = scanner.scan(&rpc, &rpc.get_block(i).await.unwrap()).await.unwrap();
|
||||||
let output = scanner.scan(&tx).ignore_timelock().swap_remove(0);
|
let output = txs.swap_remove(0).ignore_timelock().swap_remove(0);
|
||||||
amount += output.commitment.amount;
|
amount += output.commitment().amount;
|
||||||
outputs.push(output);
|
outputs.push(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ pub trait Coin {
|
||||||
&self,
|
&self,
|
||||||
block: &Self::Block,
|
block: &Self::Block,
|
||||||
key: <Self::Curve as Curve>::G,
|
key: <Self::Curve as Curve>::G,
|
||||||
) -> Vec<Self::Output>;
|
) -> Result<Vec<Self::Output>, CoinError>;
|
||||||
|
|
||||||
async fn prepare_send(
|
async fn prepare_send(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -8,6 +8,7 @@ use frost::{curve::Ed25519, FrostKeys};
|
||||||
|
|
||||||
use monero_serai::{
|
use monero_serai::{
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
|
block::Block,
|
||||||
rpc::Rpc,
|
rpc::Rpc,
|
||||||
wallet::{
|
wallet::{
|
||||||
ViewPair, Scanner,
|
ViewPair, Scanner,
|
||||||
|
@ -36,11 +37,11 @@ impl OutputTrait for Output {
|
||||||
type Id = [u8; 32];
|
type Id = [u8; 32];
|
||||||
|
|
||||||
fn id(&self) -> Self::Id {
|
fn id(&self) -> Self::Id {
|
||||||
self.0.key.compress().to_bytes()
|
self.0.output.data.key.compress().to_bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn amount(&self) -> u64 {
|
fn amount(&self) -> u64 {
|
||||||
self.0.commitment.amount
|
self.0.commitment().amount
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize(&self) -> Vec<u8> {
|
fn serialize(&self) -> Vec<u8> {
|
||||||
|
@ -97,7 +98,7 @@ impl Coin for Monero {
|
||||||
|
|
||||||
type Fee = Fee;
|
type Fee = Fee;
|
||||||
type Transaction = Transaction;
|
type Transaction = Transaction;
|
||||||
type Block = Vec<Transaction>;
|
type Block = Block;
|
||||||
|
|
||||||
type Output = Output;
|
type Output = Output;
|
||||||
type SignableTransaction = SignableTransaction;
|
type SignableTransaction = SignableTransaction;
|
||||||
|
@ -125,12 +126,25 @@ impl Coin for Monero {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_block(&self, height: usize) -> Result<Self::Block, CoinError> {
|
async fn get_block(&self, height: usize) -> Result<Self::Block, CoinError> {
|
||||||
self.rpc.get_block_transactions_possible(height).await.map_err(|_| CoinError::ConnectionError)
|
self.rpc.get_block(height).await.map_err(|_| CoinError::ConnectionError)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_outputs(&self, block: &Self::Block, key: dfg::EdwardsPoint) -> Vec<Self::Output> {
|
async fn get_outputs(
|
||||||
let mut scanner = self.scanner(key);
|
&self,
|
||||||
block.iter().flat_map(|tx| scanner.scan(tx).not_locked()).map(Output::from).collect()
|
block: &Self::Block,
|
||||||
|
key: dfg::EdwardsPoint,
|
||||||
|
) -> Result<Vec<Self::Output>, CoinError> {
|
||||||
|
Ok(
|
||||||
|
self
|
||||||
|
.scanner(key)
|
||||||
|
.scan(&self.rpc, block)
|
||||||
|
.await
|
||||||
|
.map_err(|_| CoinError::ConnectionError)?
|
||||||
|
.iter()
|
||||||
|
.flat_map(|outputs| outputs.not_locked())
|
||||||
|
.map(Output::from)
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn prepare_send(
|
async fn prepare_send(
|
||||||
|
@ -221,10 +235,13 @@ impl Coin for Monero {
|
||||||
}
|
}
|
||||||
|
|
||||||
let outputs = Self::empty_scanner()
|
let outputs = Self::empty_scanner()
|
||||||
.scan(&self.rpc.get_block_transactions_possible(height).await.unwrap().swap_remove(0))
|
.scan(&self.rpc, &self.rpc.get_block(height).await.unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.swap_remove(0)
|
||||||
.ignore_timelock();
|
.ignore_timelock();
|
||||||
|
|
||||||
let amount = outputs[0].commitment.amount;
|
let amount = outputs[0].commitment().amount;
|
||||||
let fee = 3000000000; // TODO
|
let fee = 3000000000; // TODO
|
||||||
let tx = MSignableTransaction::new(
|
let tx = MSignableTransaction::new(
|
||||||
self.rpc.get_protocol().await.unwrap(),
|
self.rpc.get_protocol().await.unwrap(),
|
||||||
|
|
|
@ -262,7 +262,7 @@ impl<D: CoinDb, C: Coin> Wallet<D, C> {
|
||||||
self
|
self
|
||||||
.coin
|
.coin
|
||||||
.get_outputs(&block, keys.group_key())
|
.get_outputs(&block, keys.group_key())
|
||||||
.await
|
.await?
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|output| self.db.add_output(output)),
|
.filter(|output| self.db.add_output(output)),
|
||||||
|
|
Loading…
Reference in a new issue