Skip to content

Commit

Permalink
feat: add optional range proof types (#5372)
Browse files Browse the repository at this point in the history
Description
---
1. Added optional range proof types where the Bulletproof+ is no longer
the only acceptable proof.
The `RevealedValue ` proof hinges on the prover revealing the commitment
value as equal to the `minimum_value_promise`
and creating a special version of the metadata signature where the
deterministic `ephemeral_commitment` nonce `r_a` is
set equal to zero (see
[RFC-0182](https://rfc.tari.com/RFC-0182_CommitmentSignatures.html)).

1. As a result of the optional range proof, we do not use Bulletproof+
range-proof rewinding anymore. Encrypted value was replaced with
encrypted openings and updated scanning and recovery based on encrypted
openings.

1. Updated the `igor` and `esmeralda` genesis block faucets to make use
of the new optional range proofs.

Motivation and Context
---
This will allow faucet outputs in the genesis block without the data
overhead of a Bulletproof+ range proof for each. Other use cases include
special transactions with revealed value outputs, but subject to the
consensus constants where `RevealedValue` range proofs need to be
switched on explicitly.

How Has This Been Tested?
---
Unit tests
Cucumber tests
System-level tests:
- On `igor` - base nodes, SHA3 mining, merged mining, console wallets
- On `esmeralda` - base nodes, SHA3 mining, merged mining, console
wallets

What process can a PR reviewer use to test or verify this change?
---
Recommend a system-level test

<!-- Checklist -->
<!-- 1. Is the title of your PR in the form that would make nice release
notes? The title, excluding the conventional commit
tag, will be included exactly as is in the CHANGELOG, so please think
about it carefully. -->


Breaking Changes
---

- [ ] None
- [ ] Requires data directory on base node to be deleted
- [X] Requires hard fork
- [ ] Other - Please specify

BREAKING CHANGE: New genesis block for `igor` and `esmeralda`, thus
restart the blockchains.
  • Loading branch information
hansieodendaal committed May 23, 2023
1 parent 53ee32b commit f24784f
Show file tree
Hide file tree
Showing 100 changed files with 13,033 additions and 11,955 deletions.
19 changes: 11 additions & 8 deletions applications/tari_app_grpc/proto/transaction.proto
Expand Up @@ -78,8 +78,8 @@ message TransactionInput {
bytes covenant = 10;
// Version
uint32 version = 11;
// The encrypted value
bytes encrypted_value = 12;
// The encrypted data
bytes encrypted_data = 12;
// The minimum value of the commitment that is proven by the range proof (in MicroTari)
uint64 minimum_value_promise = 13;
}
Expand All @@ -93,7 +93,7 @@ message TransactionOutput {
// The homomorphic commitment representing the output amount
bytes commitment = 2;
// A proof that the commitment is in the right range
bytes range_proof = 3;
RangeProof range_proof = 3;
// The hash of the output, as it appears in the MMR
bytes hash = 4;
// Tari script serialised script
Expand All @@ -107,8 +107,8 @@ message TransactionOutput {
bytes covenant = 8;
// Version
uint32 version = 9;
// The encrypted value
bytes encrypted_value = 10;
// Encrypted Pedersen commitment openings (value and mask) for the output
bytes encrypted_data = 10;
// The minimum value of the commitment that is proven by the range proof (in MicroTari)
uint64 minimum_value_promise = 11;
}
Expand All @@ -122,11 +122,14 @@ message OutputFeatures {
// The maturity of the specific UTXO. This is the min lock height at which an UTXO can be spend. Coinbase UTXO
// require a min maturity of the Coinbase_lock_height, this should be checked on receiving new blocks.
uint64 maturity = 3;
// Additional arbitrary info in coinbase transactions supplied by miners
bytes coinbase_extra = 4;
// Features that are specific to a side chain
SideChainFeature sidechain_feature = 5;
// The type of range proof used in the output
uint32 range_proof_type = 6;
}


// The components of the block or transaction. The same struct can be used for either, since in Mimblewimble,
// cut-through means that blocks and transactions have the same structure. The inputs, outputs and kernels should
// be sorted by their Blake2b-256bit digest hash
Expand Down Expand Up @@ -167,8 +170,8 @@ message UnblindedOutput {
uint64 script_lock_height = 10;
// Covenant
bytes covenant = 11;
// Encrypted Value
bytes encrypted_value = 12;
// Encrypted data
bytes encrypted_data = 12;
// The minimum value of the commitment that is proven by the range proof (in MicroTari)
uint64 minimum_value_promise = 13;
}
Expand Down
13 changes: 13 additions & 0 deletions applications/tari_app_grpc/proto/types.proto
Expand Up @@ -93,6 +93,17 @@ enum OutputType {
CODE_TEMPLATE_REGISTRATION = 4;
}

/// Range proof types
enum RangeProofType {
BULLETPROOF_PLUS = 0;
REVEALED_VALUE = 1;
}

/// Range proof
message RangeProof {
bytes proof_bytes = 1;
}

/// Consensus Constants response
message ConsensusConstants {
/// The min height maturity a coinbase utxo must have
Expand Down Expand Up @@ -157,4 +168,6 @@ message ConsensusConstants {
uint64 validator_node_registration_min_deposit_amount = 31;
uint64 validator_node_registration_min_lock_height = 32;
uint64 validator_node_registration_shuffle_interval_epoch = 33;
/// An allowlist of range proof types
repeated RangeProofType permitted_range_proof_types = 34;
}
Expand Up @@ -27,6 +27,7 @@ use tari_core::{consensus::ConsensusConstants, proof_of_work::PowAlgorithm};
use crate::tari_rpc as grpc;

impl From<ConsensusConstants> for grpc::ConsensusConstants {
#[allow(clippy::too_many_lines)]
fn from(cc: ConsensusConstants) -> Self {
let (emission_initial, emission_decay, emission_tail) = cc.emission_amounts();
let weight_params = cc.transaction_weight().params();
Expand Down Expand Up @@ -79,6 +80,12 @@ impl From<ConsensusConstants> for grpc::ConsensusConstants {
.map(|ot| i32::from(ot.as_byte()))
.collect::<Vec<i32>>();

let permitted_range_proof_types = cc.permitted_range_proof_types();
let permitted_range_proof_types = permitted_range_proof_types
.iter()
.map(|rpt| i32::from(rpt.as_byte()))
.collect::<Vec<i32>>();

let monero_pow = PowAlgorithm::Monero;
let sha3_pow = PowAlgorithm::Sha3;

Expand Down Expand Up @@ -125,6 +132,7 @@ impl From<ConsensusConstants> for grpc::ConsensusConstants {
max_randomx_seed_height: cc.max_randomx_seed_height(),
output_version_range: Some(output_version_range),
permitted_output_types,
permitted_range_proof_types,
validator_node_validity_period: cc.validator_node_validity_period_epochs().as_u64(),
epoch_length: cc.epoch_length(),
validator_node_registration_min_deposit_amount: cc
Expand Down
9 changes: 9 additions & 0 deletions applications/tari_app_grpc/src/conversions/output_features.rs
Expand Up @@ -26,6 +26,7 @@ use tari_core::transactions::transaction_components::{
OutputFeatures,
OutputFeaturesVersion,
OutputType,
RangeProofType,
SideChainFeature,
};

Expand All @@ -46,6 +47,11 @@ impl TryFrom<grpc::OutputFeatures> for OutputFeatures {
.try_into()
.map_err(|_| "Invalid output type: overflow")?;

let range_proof_type = features
.range_proof_type
.try_into()
.map_err(|_| "Invalid range proof type: overflowed")?;

Ok(OutputFeatures::new(
OutputFeaturesVersion::try_from(
u8::try_from(features.version).map_err(|_| "Invalid version: overflowed u8")?,
Expand All @@ -54,6 +60,8 @@ impl TryFrom<grpc::OutputFeatures> for OutputFeatures {
features.maturity,
features.coinbase_extra,
sidechain_feature,
RangeProofType::from_byte(range_proof_type)
.ok_or_else(|| "Invalid or unrecognised range proof type".to_string())?,
))
}
}
Expand All @@ -66,6 +74,7 @@ impl From<OutputFeatures> for grpc::OutputFeatures {
maturity: features.maturity,
coinbase_extra: features.coinbase_extra,
sidechain_feature: features.sidechain_feature.map(Into::into),
range_proof_type: u32::from(features.range_proof_type.as_byte()),
}
}
}
12 changes: 6 additions & 6 deletions applications/tari_app_grpc/src/conversions/transaction_input.rs
Expand Up @@ -27,7 +27,7 @@ use tari_common_types::types::{Commitment, PublicKey};
use tari_core::{
borsh::FromBytes,
covenants::Covenant,
transactions::transaction_components::{EncryptedValue, TransactionInput, TransactionInputVersion},
transactions::transaction_components::{EncryptedData, TransactionInput, TransactionInputVersion},
};
use tari_script::{ExecutionStack, TariScript};
use tari_utilities::ByteArray;
Expand Down Expand Up @@ -68,7 +68,7 @@ impl TryFrom<grpc::TransactionInput> for TransactionInput {
let sender_offset_public_key =
PublicKey::from_bytes(input.sender_offset_public_key.as_bytes()).map_err(|err| format!("{:?}", err))?;

let encrypted_value = EncryptedValue::from_bytes(&input.encrypted_value).map_err(|err| err.to_string())?;
let encrypted_data = EncryptedData::from_bytes(&input.encrypted_data).map_err(|err| err.to_string())?;
let minimum_value_promise = input.minimum_value_promise.into();

Ok(TransactionInput::new_with_output_data(
Expand All @@ -82,7 +82,7 @@ impl TryFrom<grpc::TransactionInput> for TransactionInput {
script_signature,
sender_offset_public_key,
Covenant::borsh_from_bytes(&mut input.covenant.as_bytes()).map_err(|err| err.to_string())?,
encrypted_value,
encrypted_data,
minimum_value_promise,
))
}
Expand Down Expand Up @@ -137,10 +137,10 @@ impl TryFrom<TransactionInput> for grpc::TransactionInput {
.try_to_vec()
.map_err(|err| err.to_string())?,
version: input.version as u32,
encrypted_value: input
.encrypted_value()
encrypted_data: input
.encrypted_data()
.map_err(|_| "Non-compact Transaction input should contain encrypted value".to_string())?
.to_vec(),
.to_byte_vec(),
minimum_value_promise: input
.minimum_value_promise()
.map_err(|_| "Non-compact Transaction input should contain the minimum value promise".to_string())?
Expand Down
21 changes: 15 additions & 6 deletions applications/tari_app_grpc/src/conversions/transaction_output.rs
Expand Up @@ -26,7 +26,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
use tari_common_types::types::{BulletRangeProof, Commitment, PublicKey};
use tari_core::transactions::{
tari_amount::MicroTari,
transaction_components::{EncryptedValue, TransactionOutput, TransactionOutputVersion},
transaction_components::{EncryptedData, TransactionOutput, TransactionOutputVersion},
};
use tari_script::TariScript;
use tari_utilities::ByteArray;
Expand All @@ -47,6 +47,12 @@ impl TryFrom<grpc::TransactionOutput> for TransactionOutput {
let sender_offset_public_key = PublicKey::from_bytes(output.sender_offset_public_key.as_bytes())
.map_err(|err| format!("Invalid sender_offset_public_key {:?}", err))?;

let range_proof = if let Some(proof) = output.range_proof {
Some(BulletRangeProof::from_bytes(&proof.proof_bytes).map_err(|err| err.to_string())?)
} else {
None
};

let script = TariScript::from_bytes(output.script.as_slice())
.map_err(|err| format!("Script deserialization: {:?}", err))?;

Expand All @@ -57,20 +63,20 @@ impl TryFrom<grpc::TransactionOutput> for TransactionOutput {
.map_err(|_| "Metadata signature could not be converted".to_string())?;
let mut covenant = output.covenant.as_bytes();
let covenant = BorshDeserialize::deserialize(&mut covenant).map_err(|err| err.to_string())?;
let encrypted_value = EncryptedValue::from_bytes(&output.encrypted_value).map_err(|err| err.to_string())?;
let encrypted_data = EncryptedData::from_bytes(&output.encrypted_data).map_err(|err| err.to_string())?;
let minimum_value_promise = MicroTari::from(output.minimum_value_promise);
Ok(Self::new(
TransactionOutputVersion::try_from(
u8::try_from(output.version).map_err(|_| "Invalid version: overflowed u8")?,
)?,
features,
commitment,
BulletRangeProof(output.range_proof),
range_proof,
script,
sender_offset_public_key,
metadata_signature,
covenant,
encrypted_value,
encrypted_data,
minimum_value_promise,
))
}
Expand All @@ -83,11 +89,14 @@ impl TryFrom<TransactionOutput> for grpc::TransactionOutput {
let hash = output.hash().to_vec();
let mut covenant = Vec::new();
BorshSerialize::serialize(&output.covenant, &mut covenant).map_err(|err| err.to_string())?;
let range_proof = output.proof.map(|proof| grpc::RangeProof {
proof_bytes: proof.to_vec(),
});
Ok(grpc::TransactionOutput {
hash,
features: Some(output.features.into()),
commitment: Vec::from(output.commitment.as_bytes()),
range_proof: Vec::from(output.proof.as_bytes()),
range_proof,
script: output.script.to_bytes(),
sender_offset_public_key: output.sender_offset_public_key.as_bytes().to_vec(),
metadata_signature: Some(grpc::ComAndPubSignature {
Expand All @@ -99,7 +108,7 @@ impl TryFrom<TransactionOutput> for grpc::TransactionOutput {
}),
covenant,
version: output.version as u32,
encrypted_value: output.encrypted_value.to_vec(),
encrypted_data: output.encrypted_data.to_byte_vec(),
minimum_value_promise: output.minimum_value_promise.into(),
})
}
Expand Down
Expand Up @@ -26,7 +26,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
use tari_common_types::types::{PrivateKey, PublicKey};
use tari_core::transactions::{
tari_amount::MicroTari,
transaction_components::{EncryptedValue, TransactionOutputVersion, UnblindedOutput},
transaction_components::{EncryptedData, TransactionOutputVersion, UnblindedOutput},
};
use tari_script::{ExecutionStack, TariScript};
use tari_utilities::ByteArray;
Expand Down Expand Up @@ -57,7 +57,7 @@ impl TryFrom<UnblindedOutput> for grpc::UnblindedOutput {
}),
script_lock_height: output.script_lock_height,
covenant,
encrypted_value: output.encrypted_value.to_vec(),
encrypted_data: output.encrypted_data.to_byte_vec(),
minimum_value_promise: output.minimum_value_promise.into(),
})
}
Expand Down Expand Up @@ -95,7 +95,7 @@ impl TryFrom<grpc::UnblindedOutput> for UnblindedOutput {
let mut buffer = output.covenant.as_bytes();
let covenant = BorshDeserialize::deserialize(&mut buffer).map_err(|err| err.to_string())?;

let encrypted_value = EncryptedValue::from_bytes(&output.encrypted_value).map_err(|err| err.to_string())?;
let encrypted_data = EncryptedData::from_bytes(&output.encrypted_data).map_err(|err| err.to_string())?;

let minimum_value_promise = MicroTari::from(output.minimum_value_promise);

Expand All @@ -115,7 +115,7 @@ impl TryFrom<grpc::UnblindedOutput> for UnblindedOutput {
metadata_signature,
output.script_lock_height,
covenant,
encrypted_value,
encrypted_data,
minimum_value_promise,
))
}
Expand Down
4 changes: 2 additions & 2 deletions applications/tari_console_wallet/src/automation/commands.rs
Expand Up @@ -1055,7 +1055,7 @@ fn write_utxos_to_csv_file(utxos: Vec<UnblindedOutput>, file_path: PathBuf) -> R
let mut csv_file = LineWriter::new(file);
writeln!(
csv_file,
r##""index","version","value","spending_key","commitment","flags","maturity","coinbase_extra","script","covenant","input_data","script_private_key","sender_offset_public_key","ephemeral_commitment","ephemeral_nonce","signature_u_x","signature_u_a","signature_u_y","script_lock_height","encrypted_value","minimum_value_promise""##
r##""index","version","value","spending_key","commitment","flags","maturity","coinbase_extra","script","covenant","input_data","script_private_key","sender_offset_public_key","ephemeral_commitment","ephemeral_nonce","signature_u_x","signature_u_a","signature_u_y","script_lock_height","encrypted_data","minimum_value_promise""##
)
.map_err(|e| CommandError::CSVFile(e.to_string()))?;
for (i, utxo) in utxos.iter().enumerate() {
Expand Down Expand Up @@ -1085,7 +1085,7 @@ fn write_utxos_to_csv_file(utxos: Vec<UnblindedOutput>, file_path: PathBuf) -> R
utxo.metadata_signature.u_a().to_hex(),
utxo.metadata_signature.u_y().to_hex(),
utxo.script_lock_height,
utxo.encrypted_value.to_hex(),
utxo.encrypted_data.to_byte_vec().to_hex(),
utxo.minimum_value_promise.as_u64()
)
.map_err(|e| CommandError::CSVFile(e.to_string()))?;
Expand Down
Expand Up @@ -784,7 +784,7 @@ where B: BlockchainBackend + 'static
output.script,
output.sender_offset_public_key,
output.covenant,
output.encrypted_value,
output.encrypted_data,
output.minimum_value_promise,
);
},
Expand Down
6 changes: 6 additions & 0 deletions base_layer/core/src/base_node/rpc/service.rs
Expand Up @@ -382,6 +382,12 @@ impl<B: BlockchainBackend + 'static> BaseNodeWalletService for BaseNodeWalletRpc
.map(|hash| hash.try_into().map_err(|_| "Malformed pruned hash".to_string()))
.collect::<Result<_, _>>()
.map_err(|_| RpcStatus::bad_request(&"Malformed block hash received".to_string()))?;
trace!(
target: LOG_TARGET,
"UTXO hashes queried from wallet: {:?}",
hashes.iter().map(|h| h.to_hex()).collect::<Vec<String>>()
);

let mined_info_resp = db
.fetch_utxos_and_mined_info(hashes)
.await
Expand Down

0 comments on commit f24784f

Please sign in to comment.