Skip to content

Commit

Permalink
feat: re-use scanned range proofs (#3764)
Browse files Browse the repository at this point in the history
Description
---
- Re-used scanned range proofs for one-sided payments and wallet recovery instead of re-calculating them every time
- Consistent creation of rewindable outputs in the wallet database

Motivation and Context
---
- Scanning of one-sided payments and wallet recovery was inefficient due to wasteful re-calculating of the range proof before adding the output to the wallet database, which in any case would never correspond to the range proof on the blockchain.
- A mixture of rewindable and non-rewindable outputs were created in certain transaction protocols; all outputs need to be rewindable.

How Has This Been Tested?
---
- Unit tests
- Cucumber tests (`npm test -- --tags "@critical"`)
  • Loading branch information
hansieodendaal authored Feb 2, 2022
1 parent c19db92 commit ffd502d
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 109 deletions.
2 changes: 1 addition & 1 deletion base_layer/core/src/transactions/coinbase_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ impl CoinbaseBuilder {
// TODO: Verify bullet proof?
let output = if let Some(rewind_data) = self.rewind_data.as_ref() {
unblinded_output
.as_rewindable_transaction_output(&self.factories, rewind_data)
.as_rewindable_transaction_output(&self.factories, rewind_data, None)
.map_err(|e| CoinbaseBuildError::BuildError(e.to_string()))?
} else {
unblinded_output
Expand Down
2 changes: 1 addition & 1 deletion base_layer/core/src/transactions/transaction/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ fn test_output_rewinding() {
..Default::default()
});
let output = unblinded_output
.as_rewindable_transaction_output(&factories, &rewind_data)
.as_rewindable_transaction_output(&factories, &rewind_data, None)
.unwrap();

assert!(matches!(
Expand Down
24 changes: 14 additions & 10 deletions base_layer/core/src/transactions/transaction/unblinded_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ impl UnblindedOutput {
&self,
factories: &CryptoFactories,
rewind_data: &RewindData,
range_proof: Option<&RangeProof>,
) -> Result<TransactionOutput, TransactionError> {
if factories.range_proof.range() < 64 && self.value >= MicroTari::from(1u64.shl(&factories.range_proof.range()))
{
Expand All @@ -227,16 +228,19 @@ impl UnblindedOutput {
}
let commitment = factories.commitment.commit(&self.spending_key, &self.value.into());

let proof_bytes = factories.range_proof.construct_proof_with_rewind_key(
&self.spending_key,
self.value.into(),
&rewind_data.rewind_key,
&rewind_data.rewind_blinding_key,
&rewind_data.proof_message,
)?;

let proof = RangeProof::from_bytes(&proof_bytes)
.map_err(|_| TransactionError::RangeProofError(RangeProofError::ProofConstructionError))?;
let proof = if let Some(proof) = range_proof {
proof.clone()
} else {
let proof_bytes = factories.range_proof.construct_proof_with_rewind_key(
&self.spending_key,
self.value.into(),
&rewind_data.rewind_key,
&rewind_data.rewind_blinding_key,
&rewind_data.proof_message,
)?;
RangeProof::from_bytes(&proof_bytes)
.map_err(|_| TransactionError::RangeProofError(RangeProofError::ProofConstructionError))?
};

let output = TransactionOutput::new_current_version(
self.features.clone(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ impl SenderTransactionInitializer {
.iter()
.map(|o| {
if let Some(rewind_data) = self.rewind_data.as_ref() {
o.as_rewindable_transaction_output(factories, rewind_data)
o.as_rewindable_transaction_output(factories, rewind_data, None)
} else {
o.as_transaction_output(factories)
}
Expand All @@ -527,7 +527,7 @@ impl SenderTransactionInitializer {
// If rewind data is present we produce a rewindable output, else a standard output
let change_output = if let Some(rewind_data) = self.rewind_data.as_ref() {
// TODO: Should proof be verified?
match change_unblinded_output.as_rewindable_transaction_output(factories, rewind_data) {
match change_unblinded_output.as_rewindable_transaction_output(factories, rewind_data, None) {
Ok(o) => o,
Err(e) => {
return self.build_err(e.to_string().as_str());
Expand Down
26 changes: 25 additions & 1 deletion base_layer/wallet/src/output_manager_service/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use tari_core::{
transactions::{
tari_amount::MicroTari,
transaction::{OutputFeatures, Transaction, TransactionOutput, UnblindedOutput, UnblindedOutputBuilder},
transaction_protocol::sender::TransactionSenderMessage,
transaction_protocol::{sender::TransactionSenderMessage, RewindData},
ReceiverTransactionProtocol,
SenderTransactionProtocol,
},
Expand All @@ -57,6 +57,7 @@ pub enum OutputManagerRequest {
GetBalance,
AddOutput((Box<UnblindedOutput>, Option<SpendingPriority>)),
AddOutputWithTxId((TxId, Box<UnblindedOutput>, Option<SpendingPriority>)),
AddRewindableOutputWithTxId((TxId, Box<UnblindedOutput>, Option<SpendingPriority>, Option<RewindData>)),
AddUnvalidatedOutput((TxId, Box<UnblindedOutput>, Option<SpendingPriority>)),
UpdateOutputMetadataSignature(Box<TransactionOutput>),
GetRecipientTransaction(TransactionSenderMessage),
Expand Down Expand Up @@ -127,6 +128,7 @@ impl fmt::Display for OutputManagerRequest {
GetBalance => write!(f, "GetBalance"),
AddOutput((v, _)) => write!(f, "AddOutput ({})", v.value),
AddOutputWithTxId((t, v, _)) => write!(f, "AddOutputWithTxId ({}: {})", t, v.value),
AddRewindableOutputWithTxId((t, v, _, _)) => write!(f, "AddRewindableOutputWithTxId ({}: {})", t, v.value),
AddUnvalidatedOutput((t, v, _)) => {
write!(f, "AddUnvalidatedOutput ({}: {})", t, v.value)
},
Expand Down Expand Up @@ -315,6 +317,28 @@ impl OutputManagerHandle {
}
}

pub async fn add_rewindable_output_with_tx_id(
&mut self,
tx_id: TxId,
output: UnblindedOutput,
spend_priority: Option<SpendingPriority>,
custom_rewind_data: Option<RewindData>,
) -> Result<(), OutputManagerError> {
match self
.handle
.call(OutputManagerRequest::AddRewindableOutputWithTxId((
tx_id,
Box::new(output),
spend_priority,
custom_rewind_data,
)))
.await??
{
OutputManagerResponse::OutputAdded => Ok(()),
_ => Err(OutputManagerError::UnexpectedApiResponse),
}
}

pub async fn add_unvalidated_output(
&mut self,
tx_id: TxId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use std::{sync::Arc, time::Instant};

use log::*;
use rand::rngs::OsRng;
use tari_common_types::types::{PrivateKey, PublicKey};
use tari_common_types::types::{PrivateKey, PublicKey, RangeProof};
use tari_core::transactions::{
transaction::{TransactionOutput, UnblindedOutput},
CryptoFactories,
Expand Down Expand Up @@ -75,7 +75,7 @@ where TBackend: OutputManagerBackend + 'static
) -> Result<Vec<UnblindedOutput>, OutputManagerError> {
let start = Instant::now();
let outputs_length = outputs.len();
let mut rewound_outputs: Vec<UnblindedOutput> = outputs
let mut rewound_outputs: Vec<(UnblindedOutput, RangeProof)> = outputs
.into_iter()
.filter_map(|output| {
output
Expand All @@ -87,14 +87,15 @@ where TBackend: OutputManagerBackend + 'static
.ok()
.map(|v| ( v, output ) )
})
//Todo this needs some investigation. We assume Nop script here and recovery here might create an unspendable output if the script does not equal Nop.
//TODO: This needs some investigation. We assume Nop script here and recovery here might create an
//TODO: unspendable output if the script does not equal Nop.
.map(
|(rewind_result, output)| {
// Todo we need to look here that we might want to fail a specific output and not recover it as this
// will only work if the script is a Nop script. If this is not a Nop script the recovered input
// will not be spendable.
let script_key = PrivateKey::random(&mut OsRng);
UnblindedOutput::new(
(UnblindedOutput::new(
output.version,
rewind_result.committed_value,
rewind_result.blinding_factor,
Expand All @@ -106,7 +107,8 @@ where TBackend: OutputManagerBackend + 'static
output.metadata_signature,
0,
output.covenant
)
),
output.proof)
},
)
.collect();
Expand All @@ -118,11 +120,17 @@ where TBackend: OutputManagerBackend + 'static
rewind_time.as_millis(),
);

for output in rewound_outputs.iter_mut() {
for (output, proof) in rewound_outputs.iter_mut() {
self.update_outputs_script_private_key_and_update_key_manager_index(output)
.await?;

let db_output = DbUnblindedOutput::from_unblinded_output(output.clone(), &self.factories, None)?;
let db_output = DbUnblindedOutput::rewindable_from_unblinded_output(
output.clone(),
&self.factories,
self.master_key_manager.rewind_data(),
None,
Some(proof),
)?;
let output_hex = db_output.commitment.to_hex();
if let Err(e) = self.db.add_unspent_output(db_output).await {
match e {
Expand All @@ -145,6 +153,7 @@ where TBackend: OutputManagerBackend + 'static
);
}

let rewound_outputs = rewound_outputs.iter().map(|(ro, _)| ro.clone()).collect();
Ok(rewound_outputs)
}

Expand Down
Loading

0 comments on commit ffd502d

Please sign in to comment.