mirror of
https://github.com/serai-dex/serai.git
synced 2025-02-02 19:26:26 +00:00
Add fee handling code to Monero
Updates how change outputs are handled, with a far more logical construction offering greater flexibility. prepare_outputs can not longer error. SignaableTransaction::new will.
This commit is contained in:
parent
71fca06120
commit
f50f249468
11 changed files with 231 additions and 107 deletions
|
@ -6,6 +6,8 @@ use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint};
|
|||
|
||||
use crate::{Commitment, wallet::TransactionError, serialize::*};
|
||||
|
||||
pub(crate) const MAX_OUTPUTS: usize = 16;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct Bulletproofs {
|
||||
pub A: EdwardsPoint,
|
||||
|
@ -22,8 +24,22 @@ pub struct Bulletproofs {
|
|||
}
|
||||
|
||||
impl Bulletproofs {
|
||||
pub(crate) fn fee_weight(outputs: usize) -> usize {
|
||||
let proofs = 6 + usize::try_from(usize::BITS - (outputs - 1).leading_zeros()).unwrap();
|
||||
let len = (9 + (2 * proofs)) * 32;
|
||||
|
||||
let mut clawback = 0;
|
||||
let padded = 1 << (proofs - 6);
|
||||
if padded > 2 {
|
||||
const BP_BASE: usize = 368;
|
||||
clawback = ((BP_BASE * padded) - len) * 4 / 5;
|
||||
}
|
||||
|
||||
len + clawback
|
||||
}
|
||||
|
||||
pub fn new<R: RngCore + CryptoRng>(rng: &mut R, outputs: &[Commitment]) -> Result<Bulletproofs, TransactionError> {
|
||||
if outputs.len() > 16 {
|
||||
if outputs.len() > MAX_OUTPUTS {
|
||||
return Err(TransactionError::TooManyOutputs)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,8 @@ use crate::{
|
|||
Commitment,
|
||||
wallet::decoys::Decoys,
|
||||
random_scalar, hash_to_scalar, hash_to_point,
|
||||
serialize::*
|
||||
serialize::*,
|
||||
transaction::RING_LEN
|
||||
};
|
||||
|
||||
#[cfg(feature = "multisig")]
|
||||
|
@ -287,6 +288,10 @@ impl Clsag {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn fee_weight() -> usize {
|
||||
(RING_LEN * 32) + 32 + 32
|
||||
}
|
||||
|
||||
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
|
||||
write_raw_vec(write_scalar, &self.s, w)?;
|
||||
w.write_all(&self.c1.to_bytes())?;
|
||||
|
|
|
@ -16,6 +16,10 @@ pub struct RctBase {
|
|||
}
|
||||
|
||||
impl RctBase {
|
||||
pub(crate) fn fee_weight(outputs: usize) -> usize {
|
||||
1 + 8 + (outputs * (8 + 32))
|
||||
}
|
||||
|
||||
pub fn serialize<W: std::io::Write>(&self, w: &mut W, rct_type: u8) -> std::io::Result<()> {
|
||||
w.write_all(&[rct_type])?;
|
||||
match rct_type {
|
||||
|
@ -69,6 +73,10 @@ impl RctPrunable {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn fee_weight(inputs: usize, outputs: usize) -> usize {
|
||||
1 + Bulletproofs::fee_weight(outputs) + (inputs * (Clsag::fee_weight() + 32))
|
||||
}
|
||||
|
||||
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
|
||||
match self {
|
||||
RctPrunable::Null => Ok(()),
|
||||
|
@ -114,6 +122,10 @@ pub struct RctSignatures {
|
|||
}
|
||||
|
||||
impl RctSignatures {
|
||||
pub(crate) fn fee_weight(inputs: usize, outputs: usize) -> usize {
|
||||
RctBase::fee_weight(outputs) + RctPrunable::fee_weight(inputs, outputs)
|
||||
}
|
||||
|
||||
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
|
||||
self.base.serialize(w, self.prunable.rct_type())?;
|
||||
self.prunable.serialize(w)
|
||||
|
|
|
@ -9,7 +9,7 @@ use serde_json::json;
|
|||
|
||||
use reqwest;
|
||||
|
||||
use crate::{transaction::{Input, Timelock, Transaction}, block::Block};
|
||||
use crate::{transaction::{Input, Timelock, Transaction}, block::Block, wallet::Fee};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct EmptyResponse {}
|
||||
|
@ -34,9 +34,6 @@ pub enum RpcError {
|
|||
InvalidTransaction([u8; 32])
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Rpc(String);
|
||||
|
||||
fn rpc_hex(value: &str) -> Result<Vec<u8>, RpcError> {
|
||||
hex::decode(value).map_err(|_| RpcError::InternalError("Monero returned invalid hex".to_string()))
|
||||
}
|
||||
|
@ -47,6 +44,9 @@ fn rpc_point(point: &str) -> Result<EdwardsPoint, RpcError> {
|
|||
).decompress().ok_or(RpcError::InvalidPoint(point.to_string()))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Rpc(String);
|
||||
|
||||
impl Rpc {
|
||||
pub fn new(daemon: String) -> Rpc {
|
||||
Rpc(daemon)
|
||||
|
@ -233,6 +233,32 @@ impl Rpc {
|
|||
Ok(indexes.o_indexes)
|
||||
}
|
||||
|
||||
pub async fn get_output_distribution(&self, height: usize) -> Result<Vec<u64>, RpcError> {
|
||||
#[allow(dead_code)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Distribution {
|
||||
distribution: Vec<u64>
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Distributions {
|
||||
distributions: Vec<Distribution>
|
||||
}
|
||||
|
||||
let mut distributions: JsonRpcResponse<Distributions> = self.rpc_call("json_rpc", Some(json!({
|
||||
"method": "get_output_distribution",
|
||||
"params": {
|
||||
"binary": false,
|
||||
"amounts": [0],
|
||||
"cumulative": true,
|
||||
"to_height": height
|
||||
}
|
||||
}))).await?;
|
||||
|
||||
Ok(distributions.result.distributions.swap_remove(0).distribution)
|
||||
}
|
||||
|
||||
pub async fn get_outputs(
|
||||
&self,
|
||||
indexes: &[u64],
|
||||
|
@ -278,30 +304,19 @@ impl Rpc {
|
|||
).collect()
|
||||
}
|
||||
|
||||
pub async fn get_output_distribution(&self, height: usize) -> Result<Vec<u64>, RpcError> {
|
||||
pub async fn get_fee(&self) -> Result<Fee, RpcError> {
|
||||
#[allow(dead_code)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Distribution {
|
||||
distribution: Vec<u64>
|
||||
struct FeeResponse {
|
||||
fee: u64,
|
||||
quantization_mask: u64
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Distributions {
|
||||
distributions: Vec<Distribution>
|
||||
}
|
||||
|
||||
let mut distributions: JsonRpcResponse<Distributions> = self.rpc_call("json_rpc", Some(json!({
|
||||
"method": "get_output_distribution",
|
||||
"params": {
|
||||
"binary": false,
|
||||
"amounts": [0],
|
||||
"cumulative": true,
|
||||
"to_height": height
|
||||
}
|
||||
let res: JsonRpcResponse<FeeResponse> = self.rpc_call("json_rpc", Some(json!({
|
||||
"method": "get_fee_estimate"
|
||||
}))).await?;
|
||||
|
||||
Ok(distributions.result.distributions.swap_remove(0).distribution)
|
||||
Ok(Fee { per_weight: res.result.fee, mask: res.result.quantization_mask })
|
||||
}
|
||||
|
||||
pub async fn publish_transaction(&self, tx: &Transaction) -> Result<(), RpcError> {
|
||||
|
|
|
@ -4,6 +4,10 @@ use curve25519_dalek::{scalar::Scalar, edwards::{EdwardsPoint, CompressedEdwards
|
|||
|
||||
pub const VARINT_CONTINUATION_MASK: u8 = 0b1000_0000;
|
||||
|
||||
pub fn varint_len(varint: usize) -> usize {
|
||||
((usize::try_from(usize::BITS - varint.leading_zeros()).unwrap().saturating_sub(1)) / 7) + 1
|
||||
}
|
||||
|
||||
pub fn write_varint<W: io::Write>(varint: &u64, w: &mut W) -> io::Result<()> {
|
||||
let mut varint = *varint;
|
||||
while {
|
||||
|
|
|
@ -2,6 +2,8 @@ use curve25519_dalek::edwards::EdwardsPoint;
|
|||
|
||||
use crate::{hash, serialize::*, ringct::{RctPrunable, RctSignatures}};
|
||||
|
||||
pub const RING_LEN: usize = 11;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum Input {
|
||||
Gen(u64),
|
||||
|
@ -14,6 +16,13 @@ pub enum Input {
|
|||
}
|
||||
|
||||
impl Input {
|
||||
// Worst-case predictive len
|
||||
pub(crate) fn fee_weight() -> usize {
|
||||
// Uses 1 byte for the VarInt amount due to amount being 0
|
||||
// Uses 1 byte for the VarInt encoding of the length of the ring as well
|
||||
1 + 1 + 1 + (8 * RING_LEN) + 32
|
||||
}
|
||||
|
||||
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
|
||||
match self {
|
||||
Input::Gen(height) => {
|
||||
|
@ -56,6 +65,10 @@ pub struct Output {
|
|||
}
|
||||
|
||||
impl Output {
|
||||
pub(crate) fn fee_weight() -> usize {
|
||||
1 + 1 + 32 + 1
|
||||
}
|
||||
|
||||
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
|
||||
write_varint(&self.amount, w)?;
|
||||
w.write_all(&[2 + (if self.tag.is_some() { 1 } else { 0 })])?;
|
||||
|
@ -102,6 +115,10 @@ impl Timelock {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn fee_weight() -> usize {
|
||||
8
|
||||
}
|
||||
|
||||
fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
|
||||
write_varint(
|
||||
&match self {
|
||||
|
@ -124,6 +141,15 @@ pub struct TransactionPrefix {
|
|||
}
|
||||
|
||||
impl TransactionPrefix {
|
||||
pub(crate) fn fee_weight(inputs: usize, outputs: usize, extra: usize) -> usize {
|
||||
// Assumes Timelock::None since this library won't let you create a TX with a timelock
|
||||
1 + 1 +
|
||||
varint_len(inputs) + (inputs * Input::fee_weight()) +
|
||||
// Only 16 outputs are possible under transactions by this lib
|
||||
1 + (outputs * Output::fee_weight()) +
|
||||
varint_len(extra) + extra
|
||||
}
|
||||
|
||||
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
|
||||
write_varint(&self.version, w)?;
|
||||
self.timelock.serialize(w)?;
|
||||
|
@ -157,6 +183,10 @@ pub struct Transaction {
|
|||
}
|
||||
|
||||
impl Transaction {
|
||||
pub(crate) fn fee_weight(inputs: usize, outputs: usize, extra: usize) -> usize {
|
||||
TransactionPrefix::fee_weight(inputs, outputs, extra) + RctSignatures::fee_weight(inputs, outputs)
|
||||
}
|
||||
|
||||
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
|
||||
self.prefix.serialize(w)?;
|
||||
self.rct_signatures.serialize(w)
|
||||
|
|
|
@ -7,7 +7,7 @@ use rand_distr::{Distribution, Gamma};
|
|||
|
||||
use curve25519_dalek::edwards::EdwardsPoint;
|
||||
|
||||
use crate::{wallet::SpendableOutput, rpc::{RpcError, Rpc}};
|
||||
use crate::{transaction::RING_LEN, wallet::SpendableOutput, rpc::{RpcError, Rpc}};
|
||||
|
||||
const LOCK_WINDOW: usize = 10;
|
||||
const MATURITY: u64 = 60;
|
||||
|
@ -16,7 +16,6 @@ const BLOCK_TIME: usize = 120;
|
|||
const BLOCKS_PER_YEAR: usize = 365 * 24 * 60 * 60 / BLOCK_TIME;
|
||||
const TIP_APPLICATION: f64 = (LOCK_WINDOW * BLOCK_TIME) as f64;
|
||||
|
||||
const RING_LEN: usize = 11;
|
||||
const DECOYS: usize = RING_LEN - 1;
|
||||
|
||||
lazy_static! {
|
||||
|
|
|
@ -13,7 +13,7 @@ pub(crate) mod decoys;
|
|||
pub(crate) use decoys::Decoys;
|
||||
|
||||
mod send;
|
||||
pub use send::{TransactionError, SignableTransaction};
|
||||
pub use send::{Fee, TransactionError, SignableTransaction};
|
||||
#[cfg(feature = "multisig")]
|
||||
pub use send::TransactionMachine;
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ use crate::{
|
|||
generate_key_image,
|
||||
ringct::{
|
||||
clsag::{ClsagError, ClsagInput, Clsag},
|
||||
bulletproofs::Bulletproofs,
|
||||
bulletproofs::{MAX_OUTPUTS, Bulletproofs},
|
||||
RctBase, RctPrunable, RctSignatures
|
||||
},
|
||||
transaction::{Input, Output, Timelock, TransactionPrefix, Transaction},
|
||||
|
@ -44,53 +44,53 @@ pub use multisig::TransactionMachine;
|
|||
struct SendOutput {
|
||||
R: EdwardsPoint,
|
||||
dest: EdwardsPoint,
|
||||
mask: Scalar,
|
||||
commitment: Commitment,
|
||||
amount: [u8; 8]
|
||||
}
|
||||
|
||||
impl SendOutput {
|
||||
fn new<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
unique: Option<[u8; 32]>,
|
||||
output: (Address, u64),
|
||||
unique: [u8; 32],
|
||||
output: (Address, u64, bool),
|
||||
o: usize
|
||||
) -> Result<SendOutput, TransactionError> {
|
||||
) -> SendOutput {
|
||||
let r = random_scalar(rng);
|
||||
let shared_key = shared_key(
|
||||
unique,
|
||||
Some(unique).filter(|_| output.2),
|
||||
r,
|
||||
&output.0.public_view.point.decompress().ok_or(TransactionError::InvalidAddress)?,
|
||||
&output.0.public_view.point.decompress().expect("SendOutput::new requires valid addresses"),
|
||||
o
|
||||
);
|
||||
|
||||
let spend = output.0.public_spend.point.decompress().ok_or(TransactionError::InvalidAddress)?;
|
||||
Ok(
|
||||
SendOutput {
|
||||
R: match output.0.addr_type {
|
||||
AddressType::Standard => Ok(&r * &ED25519_BASEPOINT_TABLE),
|
||||
AddressType::SubAddress => Ok(&r * spend),
|
||||
AddressType::Integrated(_) => Err(TransactionError::InvalidAddress)
|
||||
}?,
|
||||
dest: (&shared_key * &ED25519_BASEPOINT_TABLE) + spend,
|
||||
mask: commitment_mask(shared_key),
|
||||
amount: amount_encryption(output.1, shared_key)
|
||||
}
|
||||
)
|
||||
let spend = output.0.public_spend.point.decompress().expect("SendOutput::new requires valid addresses");
|
||||
SendOutput {
|
||||
R: match output.0.addr_type {
|
||||
AddressType::Standard => &r * &ED25519_BASEPOINT_TABLE,
|
||||
AddressType::SubAddress => &r * spend,
|
||||
AddressType::Integrated(_) => panic!("SendOutput::new doesn't support Integrated addresses")
|
||||
},
|
||||
dest: ((&shared_key * &ED25519_BASEPOINT_TABLE) + spend),
|
||||
commitment: Commitment::new(commitment_mask(shared_key), output.1),
|
||||
amount: amount_encryption(output.1, shared_key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Error, Debug)]
|
||||
pub enum TransactionError {
|
||||
#[error("invalid address")]
|
||||
InvalidAddress,
|
||||
#[error("no inputs")]
|
||||
NoInputs,
|
||||
#[error("no outputs")]
|
||||
NoOutputs,
|
||||
#[error("only one output and no change address")]
|
||||
NoChange,
|
||||
#[error("too many outputs")]
|
||||
TooManyOutputs,
|
||||
#[error("not enough funds (in {0}, out {1})")]
|
||||
NotEnoughFunds(u64, u64),
|
||||
#[error("invalid address")]
|
||||
InvalidAddress,
|
||||
#[error("wrong spend private key")]
|
||||
WrongPrivateKey,
|
||||
#[error("rpc error ({0})")]
|
||||
|
@ -154,24 +154,56 @@ async fn prepare_inputs<R: RngCore + CryptoRng>(
|
|||
Ok(signable)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct Fee {
|
||||
pub per_weight: u64,
|
||||
pub mask: u64
|
||||
}
|
||||
|
||||
impl Fee {
|
||||
pub fn calculate(&self, weight: usize) -> u64 {
|
||||
((((self.per_weight * u64::try_from(weight).unwrap()) - 1) / self.mask) + 1) * self.mask
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct SignableTransaction {
|
||||
inputs: Vec<SpendableOutput>,
|
||||
payments: Vec<(Address, u64)>,
|
||||
change: Address,
|
||||
fee_per_byte: u64,
|
||||
|
||||
fee: u64,
|
||||
outputs: Vec<SendOutput>
|
||||
payments: Vec<(Address, u64, bool)>,
|
||||
outputs: Vec<SendOutput>,
|
||||
fee: u64
|
||||
}
|
||||
|
||||
impl SignableTransaction {
|
||||
pub fn new(
|
||||
inputs: Vec<SpendableOutput>,
|
||||
payments: Vec<(Address, u64)>,
|
||||
change: Address,
|
||||
fee_per_byte: 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 {
|
||||
AddressType::Standard => Ok(()),
|
||||
AddressType::Integrated(..) => Err(TransactionError::InvalidAddress),
|
||||
AddressType::SubAddress => Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
for payment in &payments {
|
||||
test(payment.0)?;
|
||||
}
|
||||
if let Some(change) = change_address {
|
||||
test(change)?;
|
||||
}
|
||||
|
||||
if inputs.len() == 0 {
|
||||
Err(TransactionError::NoInputs)?;
|
||||
}
|
||||
|
@ -179,15 +211,55 @@ impl SignableTransaction {
|
|||
Err(TransactionError::NoOutputs)?;
|
||||
}
|
||||
|
||||
// TODO TX MAX SIZE
|
||||
|
||||
// If we don't have two outputs, as required by Monero, add a second
|
||||
let mut change = payments.len() == 1;
|
||||
if change && change_address.is_none() {
|
||||
Err(TransactionError::NoChange)?;
|
||||
}
|
||||
let mut outputs = payments.len() + (if change { 1 } else { 0 });
|
||||
|
||||
// Calculate the fee.
|
||||
let extra = 0;
|
||||
let mut fee = fee_rate.calculate(Transaction::fee_weight(inputs.len(), outputs, extra));
|
||||
|
||||
// Make sure we have enough funds
|
||||
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;
|
||||
if in_amount < out_amount {
|
||||
Err(TransactionError::NotEnoughFunds(in_amount, out_amount))?;
|
||||
}
|
||||
|
||||
// If we have yet to add a change output, do so if it's economically viable
|
||||
if (!change) && change_address.is_some() && (in_amount != out_amount) {
|
||||
// Check even with the new fee, there's remaining funds
|
||||
let change_fee = fee_rate.calculate(Transaction::fee_weight(inputs.len(), outputs + 1, extra)) - fee;
|
||||
if (out_amount + change_fee) < in_amount {
|
||||
change = true;
|
||||
outputs += 1;
|
||||
out_amount += change_fee;
|
||||
fee += change_fee;
|
||||
}
|
||||
}
|
||||
|
||||
if outputs > MAX_OUTPUTS {
|
||||
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));
|
||||
}
|
||||
|
||||
Ok(
|
||||
SignableTransaction {
|
||||
inputs,
|
||||
payments,
|
||||
change,
|
||||
fee_per_byte,
|
||||
|
||||
fee: 0,
|
||||
outputs: vec![]
|
||||
outputs: vec![],
|
||||
fee
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -196,39 +268,19 @@ impl SignableTransaction {
|
|||
&mut self,
|
||||
rng: &mut R,
|
||||
uniqueness: [u8; 32]
|
||||
) -> Result<(Vec<Commitment>, Scalar), TransactionError> {
|
||||
self.fee = self.fee_per_byte * 2000; // TODO
|
||||
|
||||
// TODO TX MAX SIZE
|
||||
|
||||
// Make sure we have enough funds
|
||||
let in_amount = self.inputs.iter().map(|input| input.commitment.amount).sum();
|
||||
let out_amount = self.fee + self.payments.iter().map(|payment| payment.1).sum::<u64>();
|
||||
if in_amount < out_amount {
|
||||
Err(TransactionError::NotEnoughFunds(in_amount, out_amount))?;
|
||||
}
|
||||
|
||||
let mut temp_outputs = Vec::with_capacity(self.payments.len() + 1);
|
||||
// Add the payments to the outputs
|
||||
for payment in &self.payments {
|
||||
temp_outputs.push((None, (payment.0, payment.1)));
|
||||
}
|
||||
temp_outputs.push((Some(uniqueness), (self.change, in_amount - out_amount)));
|
||||
|
||||
// Shuffle the outputs
|
||||
temp_outputs.shuffle(rng);
|
||||
) -> (Vec<Commitment>, Scalar) {
|
||||
// Shuffle the payments
|
||||
self.payments.shuffle(rng);
|
||||
|
||||
// Actually create the outputs
|
||||
self.outputs = Vec::with_capacity(temp_outputs.len());
|
||||
let mut commitments = Vec::with_capacity(temp_outputs.len());
|
||||
let mut mask_sum = Scalar::zero();
|
||||
for (o, output) in temp_outputs.iter().enumerate() {
|
||||
self.outputs.push(SendOutput::new(rng, output.0, output.1, o)?);
|
||||
commitments.push(Commitment::new(self.outputs[o].mask, output.1.1));
|
||||
mask_sum += self.outputs[o].mask;
|
||||
self.outputs = Vec::with_capacity(self.payments.len() + 1);
|
||||
for (o, output) in self.payments.iter().enumerate() {
|
||||
self.outputs.push(SendOutput::new(rng, uniqueness, *output, o));
|
||||
}
|
||||
|
||||
Ok((commitments, mask_sum))
|
||||
let commitments = self.outputs.iter().map(|output| output.commitment).collect::<Vec<_>>();
|
||||
let sum = commitments.iter().map(|commitment| commitment.mask).sum();
|
||||
(commitments, sum)
|
||||
}
|
||||
|
||||
fn prepare_transaction(
|
||||
|
@ -246,7 +298,6 @@ impl SignableTransaction {
|
|||
self.outputs[1 ..].iter().map(|output| PublicKey { point: output.R.compress() }).collect()
|
||||
).consensus_encode(&mut extra).unwrap();
|
||||
|
||||
// Format it for monero-rs
|
||||
let mut tx_outputs = Vec::with_capacity(self.outputs.len());
|
||||
let mut ecdh_info = Vec::with_capacity(self.outputs.len());
|
||||
for o in 0 .. self.outputs.len() {
|
||||
|
@ -307,7 +358,7 @@ impl SignableTransaction {
|
|||
key_image: *image
|
||||
}).collect::<Vec<_>>()
|
||||
)
|
||||
)?;
|
||||
);
|
||||
|
||||
let mut tx = self.prepare_transaction(&commitments, Bulletproofs::new(rng, &commitments)?);
|
||||
|
||||
|
|
|
@ -35,9 +35,8 @@ pub struct TransactionMachine {
|
|||
}
|
||||
|
||||
impl SignableTransaction {
|
||||
pub async fn multisig<R: RngCore + CryptoRng>(
|
||||
mut self,
|
||||
rng: &mut R,
|
||||
pub async fn multisig(
|
||||
self,
|
||||
rpc: &Rpc,
|
||||
keys: MultisigKeys<Ed25519>,
|
||||
mut transcript: Transcript,
|
||||
|
@ -80,8 +79,8 @@ impl SignableTransaction {
|
|||
for payment in &self.payments {
|
||||
transcript.append_message(b"payment_address", &payment.0.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] }));
|
||||
}
|
||||
transcript.append_message(b"change", &self.change.as_bytes());
|
||||
|
||||
// Sort included before cloning it around
|
||||
included.sort_unstable();
|
||||
|
@ -105,9 +104,6 @@ impl SignableTransaction {
|
|||
);
|
||||
}
|
||||
|
||||
// Verify these outputs by a dummy prep
|
||||
self.prepare_outputs(rng, [0; 32])?;
|
||||
|
||||
// Select decoys
|
||||
// Ideally, this would be done post entropy, instead of now, yet doing so would require sign
|
||||
// to be async which isn't preferable. This should be suitably competent though
|
||||
|
@ -228,7 +224,6 @@ impl StateMachine for TransactionMachine {
|
|||
let mut images = self.images.clone();
|
||||
images.sort_by(key_image_sort);
|
||||
|
||||
// Not invalid outputs due to already doing a dummy prep
|
||||
let (commitments, output_masks) = self.signable.prepare_outputs(
|
||||
&mut ChaCha12Rng::from_seed(self.transcript.rng_seed(b"tx_keys")),
|
||||
uniqueness(
|
||||
|
@ -238,7 +233,7 @@ impl StateMachine for TransactionMachine {
|
|||
key_image: *image
|
||||
}).collect::<Vec<_>>()
|
||||
)
|
||||
).expect("Couldn't prepare outputs despite already doing a dummy prep");
|
||||
);
|
||||
self.output_masks = Some(output_masks);
|
||||
|
||||
self.signable.prepare_transaction(
|
||||
|
|
|
@ -80,9 +80,7 @@ async fn send_core(test: usize, multisig: bool) {
|
|||
PublicKey { point: (&view * &ED25519_BASEPOINT_TABLE).compress() }
|
||||
);
|
||||
|
||||
// TODO
|
||||
let fee_per_byte = 50000000;
|
||||
let fee = fee_per_byte * 2000;
|
||||
let fee = rpc.get_fee().await.unwrap();
|
||||
|
||||
let start = rpc.get_height().await.unwrap();
|
||||
for _ in 0 .. 7 {
|
||||
|
@ -134,7 +132,7 @@ async fn send_core(test: usize, multisig: bool) {
|
|||
}
|
||||
|
||||
let mut signable = SignableTransaction::new(
|
||||
outputs, vec![(addr, amount - fee)], addr, fee_per_byte
|
||||
outputs, vec![(addr, amount - 10000000000)], Some(addr), fee
|
||||
).unwrap();
|
||||
|
||||
if !multisig {
|
||||
|
@ -147,7 +145,6 @@ async fn send_core(test: usize, multisig: bool) {
|
|||
machines.insert(
|
||||
i,
|
||||
signable.clone().multisig(
|
||||
&mut OsRng,
|
||||
&rpc,
|
||||
(*keys[&i]).clone(),
|
||||
Transcript::new(b"Monero Serai Test Transaction"),
|
||||
|
|
Loading…
Reference in a new issue