Bulletproofs+ (#70)

* Initial stab at Bulletproofs+

Does move around the existing Bulletproofs code, does still work as 
expected.

* Make the Clsag RCTPrunable type work with BP and BP+

* Initial set of BP+ bug fixes

* Further bug fixes

* Remove RING_LEN as a constant

* Monero v16 TX support

Doesn't implement view tags, nor going back to v14, nor the updated BP 
clawback logic.

* Support v14 and v16 at the same time
This commit is contained in:
Luke Parker
2022-07-27 04:05:43 -05:00
committed by GitHub
parent 37b8e3c025
commit 023afaf7ce
15 changed files with 384 additions and 132 deletions

View File

@@ -11,7 +11,7 @@ use monero::{consensus::Encodable, PublicKey, blockdata::transaction::SubField};
use frost::FrostError;
use crate::{
Commitment, random_scalar,
Protocol, Commitment, random_scalar,
ringct::{
generate_key_image,
clsag::{ClsagError, ClsagInput, Clsag},
@@ -103,6 +103,7 @@ pub enum TransactionError {
async fn prepare_inputs<R: RngCore + CryptoRng>(
rng: &mut R,
rpc: &Rpc,
ring_len: usize,
inputs: &[SpendableOutput],
spend: &Scalar,
tx: &mut Transaction,
@@ -113,6 +114,7 @@ async fn prepare_inputs<R: RngCore + CryptoRng>(
let decoys = Decoys::select(
rng,
rpc,
ring_len,
rpc.get_height().await.map_err(TransactionError::RpcError)? - 10,
inputs,
)
@@ -159,6 +161,7 @@ impl Fee {
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct SignableTransaction {
protocol: Protocol,
inputs: Vec<SpendableOutput>,
payments: Vec<(Address, u64)>,
outputs: Vec<SendOutput>,
@@ -167,6 +170,7 @@ pub struct SignableTransaction {
impl SignableTransaction {
pub fn new(
protocol: Protocol,
inputs: Vec<SpendableOutput>,
mut payments: Vec<(Address, u64)>,
change_address: Option<Address>,
@@ -200,14 +204,19 @@ impl SignableTransaction {
if change && change_address.is_none() {
Err(TransactionError::NoChange)?;
}
let mut outputs = payments.len() + (if change { 1 } else { 0 });
let outputs = payments.len() + (if change { 1 } else { 0 });
// Calculate the extra length.
// Type, length, value, with 1 field for the first key and 1 field for the rest
let extra = (outputs * (2 + 32)) - (outputs.saturating_sub(2) * 2);
// Calculate the fee.
let mut fee = fee_rate.calculate(Transaction::fee_weight(inputs.len(), outputs, extra));
let mut fee = fee_rate.calculate(Transaction::fee_weight(
protocol.ring_len(),
inputs.len(),
outputs,
extra,
));
// Make sure we have enough funds
let in_amount = inputs.iter().map(|input| input.commitment.amount).sum::<u64>();
@@ -219,25 +228,28 @@ impl SignableTransaction {
// 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;
let change_fee = fee_rate.calculate(Transaction::fee_weight(
protocol.ring_len(),
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)?;
}
if change {
payments.push((change_address.unwrap(), in_amount - out_amount));
}
Ok(SignableTransaction { inputs, payments, outputs: vec![], fee })
if payments.len() > MAX_OUTPUTS {
Err(TransactionError::TooManyOutputs)?;
}
Ok(SignableTransaction { protocol, inputs, payments, outputs: vec![], fee })
}
fn prepare_outputs<R: RngCore + CryptoRng>(
@@ -259,7 +271,14 @@ impl SignableTransaction {
(commitments, sum)
}
fn prepare_transaction(&self, commitments: &[Commitment], bp: Bulletproofs) -> Transaction {
fn prepare_transaction<R: RngCore + CryptoRng>(
&self,
rng: &mut R,
commitments: &[Commitment],
) -> Transaction {
// Safe due to the constructor checking MAX_OUTPUTS
let bp = Bulletproofs::prove(rng, commitments, self.protocol.bp_plus()).unwrap();
// Create the TX extra
// TODO: Review this for canonicity with Monero
let mut extra = vec![];
@@ -275,7 +294,11 @@ impl SignableTransaction {
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() {
tx_outputs.push(Output { amount: 0, key: self.outputs[o].dest, tag: None });
tx_outputs.push(Output {
amount: 0,
key: self.outputs[o].dest,
tag: Some(0).filter(|_| matches!(self.protocol, Protocol::v16)),
});
ecdh_info.push(self.outputs[o].amount);
}
@@ -329,9 +352,10 @@ impl SignableTransaction {
),
);
let mut tx = self.prepare_transaction(&commitments, Bulletproofs::prove(rng, &commitments)?);
let mut tx = self.prepare_transaction(rng, &commitments);
let signable = prepare_inputs(rng, rpc, &self.inputs, spend, &mut tx).await?;
let signable =
prepare_inputs(rng, rpc, self.protocol.ring_len(), &self.inputs, spend, &mut tx).await?;
let clsag_pairs = Clsag::sign(rng, &signable, mask_sum, tx.signature_hash());
match tx.rct_signatures.prunable {