use crate::transactions::{
aggregated_body::AggregateBody,
tari_amount::{uT, MicroTari},
transaction_protocol::{build_challenge, RewindData, TransactionMetadata},
types::{
BlindingFactor,
Commitment,
CommitmentFactory,
CryptoFactories,
HashDigest,
PrivateKey,
PublicKey,
RangeProof,
RangeProofService,
Signature,
},
};
use digest::Input;
use serde::{Deserialize, Serialize};
use std::{
cmp::{max, min, Ordering},
fmt,
fmt::{Display, Formatter},
hash::{Hash, Hasher},
ops::Add,
};
use tari_crypto::{
commitment::HomomorphicCommitmentFactory,
range_proof::{
FullRewindResult as CryptoFullRewindResult,
RangeProofError,
RangeProofService as RangeProofServiceTrait,
RewindResult as CryptoRewindResult,
REWIND_USER_MESSAGE_LENGTH,
},
tari_utilities::{hex::Hex, message_format::MessageFormat, ByteArray, Hashable},
};
use thiserror::Error;
pub const MAX_TRANSACTION_INPUTS: usize = 12_500;
pub const MAX_TRANSACTION_OUTPUTS: usize = 500;
pub const MAX_TRANSACTION_RECIPIENTS: usize = 15;
pub const MINIMUM_TRANSACTION_FEE: MicroTari = MicroTari(100);
bitflags! {
#[derive(Deserialize, Serialize)]
pub struct KernelFeatures: u8 {
const COINBASE_KERNEL = 1u8;
}
}
impl KernelFeatures {
pub fn create_coinbase() -> KernelFeatures {
KernelFeatures::COINBASE_KERNEL
}
}
#[derive(Debug, Clone, Hash, PartialEq, Deserialize, Serialize, Eq)]
pub struct OutputFeatures {
pub flags: OutputFlags,
pub maturity: u64,
}
impl OutputFeatures {
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::new();
bincode::serialize_into(&mut buf, self).unwrap(); buf
}
pub fn create_coinbase(maturity_height: u64) -> OutputFeatures {
OutputFeatures {
flags: OutputFlags::COINBASE_OUTPUT,
maturity: maturity_height,
}
}
pub fn with_maturity(maturity: u64) -> OutputFeatures {
OutputFeatures {
maturity,
..OutputFeatures::default()
}
}
}
impl Default for OutputFeatures {
fn default() -> Self {
OutputFeatures {
flags: OutputFlags::empty(),
maturity: 0,
}
}
}
impl PartialOrd for OutputFeatures {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for OutputFeatures {
fn cmp(&self, other: &Self) -> Ordering {
self.maturity.cmp(&other.maturity)
}
}
impl Display for OutputFeatures {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"OutputFeatures: Flags = {:?}, Maturity = {}",
self.flags, self.maturity
)
}
}
bitflags! {
#[derive(Deserialize, Serialize)]
pub struct OutputFlags: u8 {
const COINBASE_OUTPUT = 0b0000_0001;
}
}
#[derive(Clone, Debug, PartialEq, Error, Deserialize, Serialize)]
pub enum TransactionError {
#[error("Error validating the transaction: {0}")]
ValidationError(String),
#[error("Signature is invalid")]
InvalidSignatureError,
#[error("Transaction kernel does not contain a signature")]
NoSignatureError,
#[error("A range proof construction or verification has produced an error: {0}")]
RangeProofError(#[from] RangeProofError),
#[error("Invalid kernel in body")]
InvalidKernel,
#[error("Invalid coinbase in body")]
InvalidCoinbase,
#[error("Invalid coinbase maturity in body")]
InvalidCoinbaseMaturity,
#[error("More than one coinbase in body")]
MoreThanOneCoinbase,
#[error("No coinbase in body")]
NoCoinbase,
#[error("Input maturity not reached")]
InputMaturity,
}
#[derive(Debug, Clone)]
pub struct UnblindedOutput {
pub value: MicroTari,
pub spending_key: BlindingFactor,
pub features: OutputFeatures,
}
impl UnblindedOutput {
pub fn new(value: MicroTari, spending_key: BlindingFactor, features: Option<OutputFeatures>) -> UnblindedOutput {
UnblindedOutput {
value,
spending_key,
features: features.unwrap_or_default(),
}
}
pub fn as_transaction_input(&self, factory: &CommitmentFactory, features: OutputFeatures) -> TransactionInput {
let commitment = factory.commit(&self.spending_key, &self.value.into());
TransactionInput { commitment, features }
}
pub fn as_transaction_output(&self, factories: &CryptoFactories) -> Result<TransactionOutput, TransactionError> {
let commitment = factories.commitment.commit(&self.spending_key, &self.value.into());
let output = TransactionOutput {
features: self.features.clone(),
commitment,
proof: RangeProof::from_bytes(
&factories
.range_proof
.construct_proof(&self.spending_key, self.value.into())?,
)
.map_err(|_| TransactionError::RangeProofError(RangeProofError::ProofConstructionError))?,
};
if !output.verify_range_proof(&factories.range_proof)? {
return Err(TransactionError::ValidationError(
"Range proof could not be verified".into(),
));
}
Ok(output)
}
pub fn as_rewindable_transaction_output(
&self,
factories: &CryptoFactories,
rewind_data: &RewindData,
) -> Result<TransactionOutput, TransactionError>
{
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 output = TransactionOutput {
features: self.features.clone(),
commitment,
proof,
};
if !output.verify_range_proof(&factories.range_proof)? {
return Err(TransactionError::ValidationError(
"Range proof could not be verified".into(),
));
}
Ok(output)
}
}
impl Eq for UnblindedOutput {}
impl PartialEq for UnblindedOutput {
fn eq(&self, other: &UnblindedOutput) -> bool {
self.value == other.value
}
}
impl Hash for UnblindedOutput {
fn hash<H: Hasher>(&self, state: &mut H) {
self.value.hash(state);
}
}
impl PartialOrd<UnblindedOutput> for UnblindedOutput {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.value.partial_cmp(&other.value)
}
}
impl Ord for UnblindedOutput {
fn cmp(&self, other: &Self) -> Ordering {
self.value.cmp(&other.value)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct TransactionInput {
pub features: OutputFeatures,
pub commitment: Commitment,
}
impl TransactionInput {
pub fn new(features: OutputFeatures, commitment: Commitment) -> TransactionInput {
TransactionInput { features, commitment }
}
pub fn commitment(&self) -> &Commitment {
&self.commitment
}
pub fn opened_by(&self, input: &UnblindedOutput, factory: &CommitmentFactory) -> bool {
factory.open(&input.spending_key, &input.value.into(), &self.commitment)
}
pub fn is_equal_to(&self, output: &TransactionOutput) -> bool {
self.commitment == output.commitment && self.features == output.features
}
}
impl From<TransactionOutput> for TransactionInput {
fn from(item: TransactionOutput) -> Self {
TransactionInput {
features: item.features,
commitment: item.commitment,
}
}
}
impl Hashable for TransactionInput {
fn hash(&self) -> Vec<u8> {
HashDigest::new()
.chain(self.features.to_bytes())
.chain(self.commitment.as_bytes())
.result()
.to_vec()
}
}
impl Display for TransactionInput {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
fmt.write_str(&format!("{} [{:?}]\n", self.commitment.to_hex(), self.features))
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct TransactionOutput {
pub features: OutputFeatures,
pub commitment: Commitment,
pub proof: RangeProof,
}
impl TransactionOutput {
pub fn new(features: OutputFeatures, commitment: Commitment, proof: RangeProof) -> TransactionOutput {
TransactionOutput {
features,
commitment,
proof,
}
}
pub fn commitment(&self) -> &Commitment {
&self.commitment
}
pub fn proof(&self) -> &RangeProof {
&self.proof
}
pub fn verify_range_proof(&self, prover: &RangeProofService) -> Result<bool, TransactionError> {
Ok(prover.verify(&self.proof.0, &self.commitment))
}
pub fn rewind_range_proof_value_only(
&self,
prover: &RangeProofService,
rewind_public_key: &PublicKey,
rewind_blinding_public_key: &PublicKey,
) -> Result<RewindResult, TransactionError>
{
Ok(prover
.rewind_proof_value_only(
&self.proof.0,
&&self.commitment,
rewind_public_key,
rewind_blinding_public_key,
)?
.into())
}
pub fn full_rewind_range_proof(
&self,
prover: &RangeProofService,
rewind_key: &PrivateKey,
rewind_blinding_key: &PrivateKey,
) -> Result<FullRewindResult, TransactionError>
{
Ok(prover
.rewind_proof_commitment_data(&self.proof.0, &&self.commitment, rewind_key, rewind_blinding_key)?
.into())
}
#[inline]
pub fn is_equal_to(&self, output: &TransactionInput) -> bool {
self.commitment == output.commitment && self.features == output.features
}
pub fn is_coinbase(&self) -> bool {
self.features.flags.contains(OutputFlags::COINBASE_OUTPUT)
}
}
impl Hashable for TransactionOutput {
fn hash(&self) -> Vec<u8> {
HashDigest::new()
.chain(self.features.to_bytes())
.chain(self.commitment.as_bytes())
.result()
.to_vec()
}
}
impl Default for TransactionOutput {
fn default() -> Self {
TransactionOutput::new(
OutputFeatures::default(),
CommitmentFactory::default().zero(),
RangeProof::default(),
)
}
}
impl Display for TransactionOutput {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
let proof = self.proof.to_hex();
fmt.write_str(&format!(
"({} [{:?}] Proof: {}..{})",
self.commitment.to_hex(),
self.features,
proof[0..16].to_string(),
proof[proof.len() - 16..proof.len()].to_string()
))
}
}
#[derive(Debug, PartialEq)]
pub struct RewindResult {
pub committed_value: MicroTari,
pub proof_message: [u8; REWIND_USER_MESSAGE_LENGTH],
}
impl RewindResult {
pub fn new(committed_value: MicroTari, proof_message: [u8; REWIND_USER_MESSAGE_LENGTH]) -> Self {
Self {
committed_value,
proof_message,
}
}
}
impl From<CryptoRewindResult> for RewindResult {
fn from(crr: CryptoRewindResult) -> Self {
Self {
committed_value: crr.committed_value.into(),
proof_message: crr.proof_message,
}
}
}
#[derive(Debug, PartialEq)]
pub struct FullRewindResult {
pub committed_value: MicroTari,
pub proof_message: [u8; REWIND_USER_MESSAGE_LENGTH],
pub blinding_factor: BlindingFactor,
}
impl FullRewindResult {
pub fn new(
committed_value: MicroTari,
proof_message: [u8; REWIND_USER_MESSAGE_LENGTH],
blinding_factor: BlindingFactor,
) -> Self
{
Self {
committed_value,
proof_message,
blinding_factor,
}
}
}
impl From<CryptoFullRewindResult<BlindingFactor>> for FullRewindResult {
fn from(crr: CryptoFullRewindResult<BlindingFactor>) -> Self {
Self {
committed_value: crr.committed_value.into(),
proof_message: crr.proof_message,
blinding_factor: crr.blinding_factor,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct TransactionKernel {
pub features: KernelFeatures,
pub fee: MicroTari,
pub lock_height: u64,
pub excess: Commitment,
pub excess_sig: Signature,
}
pub struct KernelBuilder {
features: KernelFeatures,
fee: MicroTari,
lock_height: u64,
excess: Option<Commitment>,
excess_sig: Option<Signature>,
}
impl KernelBuilder {
pub fn new() -> KernelBuilder {
KernelBuilder::default()
}
pub fn with_features(mut self, features: KernelFeatures) -> KernelBuilder {
self.features = features;
self
}
pub fn with_fee(mut self, fee: MicroTari) -> KernelBuilder {
self.fee = fee;
self
}
pub fn with_lock_height(mut self, lock_height: u64) -> KernelBuilder {
self.lock_height = lock_height;
self
}
pub fn with_excess(mut self, excess: &Commitment) -> KernelBuilder {
self.excess = Some(excess.clone());
self
}
pub fn with_signature(mut self, signature: &Signature) -> KernelBuilder {
self.excess_sig = Some(signature.clone());
self
}
pub fn build(self) -> Result<TransactionKernel, TransactionError> {
if self.excess.is_none() || self.excess_sig.is_none() {
return Err(TransactionError::NoSignatureError);
}
Ok(TransactionKernel {
features: self.features,
fee: self.fee,
lock_height: self.lock_height,
excess: self.excess.unwrap(),
excess_sig: self.excess_sig.unwrap(),
})
}
}
impl Default for KernelBuilder {
fn default() -> Self {
KernelBuilder {
features: KernelFeatures::empty(),
fee: MicroTari::from(0),
lock_height: 0,
excess: None,
excess_sig: None,
}
}
}
impl TransactionKernel {
pub fn verify_signature(&self) -> Result<(), TransactionError> {
let excess = self.excess.as_public_key();
let r = self.excess_sig.get_public_nonce();
let m = TransactionMetadata {
lock_height: self.lock_height,
fee: self.fee,
};
let c = build_challenge(r, &m);
if self.excess_sig.verify_challenge(excess, &c) {
Ok(())
} else {
Err(TransactionError::InvalidSignatureError)
}
}
}
impl Hashable for TransactionKernel {
fn hash(&self) -> Vec<u8> {
HashDigest::new()
.chain(&[self.features.bits])
.chain(u64::from(self.fee).to_le_bytes())
.chain(self.lock_height.to_le_bytes())
.chain(self.excess.as_bytes())
.chain(self.excess_sig.get_public_nonce().as_bytes())
.chain(self.excess_sig.get_signature().as_bytes())
.result()
.to_vec()
}
}
impl Display for TransactionKernel {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
let msg = format!(
"Fee: {}\nLock height: {}\nFeatures: {:?}\nExcess: {}\nExcess signature: {}\n",
self.fee,
self.lock_height,
self.features,
self.excess.to_hex(),
self.excess_sig
.to_json()
.unwrap_or_else(|_| "Failed to serialize signature".into()),
);
fmt.write_str(&msg)
}
}
pub struct KernelSum {
pub sum: Commitment,
pub fees: MicroTari,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Transaction {
pub offset: BlindingFactor,
pub body: AggregateBody,
}
impl Transaction {
pub fn new(
inputs: Vec<TransactionInput>,
outputs: Vec<TransactionOutput>,
kernels: Vec<TransactionKernel>,
offset: BlindingFactor,
) -> Transaction
{
let mut body = AggregateBody::new(inputs, outputs, kernels);
body.sort();
Transaction { offset, body }
}
#[allow(clippy::erasing_op)] pub fn validate_internal_consistency(
&self,
factories: &CryptoFactories,
reward: Option<MicroTari>,
) -> Result<(), TransactionError>
{
let reward = reward.unwrap_or_else(|| 0 * uT);
self.body.validate_internal_consistency(&self.offset, reward, factories)
}
pub fn get_body(&self) -> &AggregateBody {
&self.body
}
pub fn calculate_weight(&self) -> u64 {
self.body.calculate_weight()
}
pub fn calculate_ave_fee_per_gram(&self) -> f64 {
(self.body.get_total_fee().0 as f64) / self.calculate_weight() as f64
}
pub fn min_input_maturity(&self) -> u64 {
self.body.inputs().iter().fold(std::u64::MAX, |min_maturity, input| {
min(min_maturity, input.features.maturity)
})
}
pub fn max_input_maturity(&self) -> u64 {
self.body
.inputs()
.iter()
.fold(0, |max_maturity, input| max(max_maturity, input.features.maturity))
}
pub fn max_kernel_timelock(&self) -> u64 {
self.body
.kernels()
.iter()
.fold(0, |max_timelock, kernel| max(max_timelock, kernel.lock_height))
}
pub fn min_spendable_height(&self) -> u64 {
max(self.max_kernel_timelock(), self.max_input_maturity())
}
pub fn add_no_cut_through(mut self, other: Self) -> Self {
self.offset = self.offset + other.offset;
let (mut inputs, mut outputs, mut kernels) = other.body.dissolve();
self.body.add_inputs(&mut inputs);
self.body.add_outputs(&mut outputs);
self.body.add_kernels(&mut kernels);
self
}
pub fn first_kernel_excess_sig(&self) -> Option<&Signature> {
Some(&self.body.kernels().first()?.excess_sig)
}
}
impl Add for Transaction {
type Output = Self;
fn add(mut self, other: Self) -> Self {
self = self.add_no_cut_through(other);
self.body.do_cut_through();
self
}
}
impl Display for Transaction {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
fmt.write_str("-------------- Transaction --------------\n")?;
fmt.write_str("--- Offset ---\n")?;
fmt.write_str(&format!("{}\n", self.offset.to_hex()))?;
fmt.write_str("--- Body ---\n")?;
fmt.write_str(&format!("{}\n", self.body))
}
}
pub struct TransactionBuilder {
body: AggregateBody,
offset: Option<BlindingFactor>,
reward: Option<MicroTari>,
}
impl TransactionBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn add_offset(&mut self, offset: BlindingFactor) -> &mut Self {
self.offset = Some(offset);
self
}
pub fn add_input(&mut self, input: TransactionInput) -> &mut Self {
self.body.add_input(input);
self
}
pub fn add_output(&mut self, output: TransactionOutput) -> &mut Self {
self.body.add_output(output);
self
}
pub fn add_inputs(&mut self, inputs: &mut Vec<TransactionInput>) -> &mut Self {
self.body.add_inputs(inputs);
self
}
pub fn add_outputs(&mut self, outputs: &mut Vec<TransactionOutput>) -> &mut Self {
self.body.add_outputs(outputs);
self
}
pub fn with_kernel(&mut self, kernel: TransactionKernel) -> &mut Self {
self.body.set_kernel(kernel);
self
}
pub fn with_reward(&mut self, reward: MicroTari) -> &mut Self {
self.reward = Some(reward);
self
}
pub fn build(self, factories: &CryptoFactories) -> Result<Transaction, TransactionError> {
if let Some(offset) = self.offset {
let (i, o, k) = self.body.dissolve();
let tx = Transaction::new(i, o, k, offset);
tx.validate_internal_consistency(factories, self.reward)?;
Ok(tx)
} else {
Err(TransactionError::ValidationError(
"Transaction validation failed".into(),
))
}
}
}
impl Default for TransactionBuilder {
fn default() -> Self {
Self {
offset: None,
body: AggregateBody::empty(),
reward: None,
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
transactions::{
helpers::{create_test_kernel, create_tx, spend_utxos},
tari_amount::T,
transaction::OutputFeatures,
types::{BlindingFactor, PrivateKey, PublicKey, RangeProof},
},
txn_schema,
};
use rand::{self, rngs::OsRng};
use tari_crypto::{
keys::{PublicKey as PublicKeyTrait, SecretKey as SecretKeyTrait},
ristretto::pedersen::PedersenCommitmentFactory,
};
#[test]
fn unblinded_input() {
let k = BlindingFactor::random(&mut OsRng);
let factory = PedersenCommitmentFactory::default();
let i = UnblindedOutput::new(10.into(), k, None);
let input = i.as_transaction_input(&factory, OutputFeatures::default());
assert_eq!(input.features, OutputFeatures::default());
assert!(input.opened_by(&i, &factory));
}
#[test]
fn with_maturity() {
let features = OutputFeatures::with_maturity(42);
assert_eq!(features.maturity, 42);
assert_eq!(features.flags, OutputFlags::empty());
}
#[test]
fn range_proof_verification() {
let factories = CryptoFactories::new(32);
let k1 = BlindingFactor::random(&mut OsRng);
let k2 = BlindingFactor::random(&mut OsRng);
let unblinded_output1 = UnblindedOutput::new((2u64.pow(32) - 1u64).into(), k1, None);
let tx_output1 = unblinded_output1.as_transaction_output(&factories).unwrap();
assert!(tx_output1.verify_range_proof(&factories.range_proof).unwrap());
let unblinded_output2 = UnblindedOutput::new((2u64.pow(32) + 1u64).into(), k2.clone(), None);
let tx_output2 = unblinded_output2.as_transaction_output(&factories);
match tx_output2 {
Ok(_) => panic!("Range proof should have failed to verify"),
Err(e) => assert_eq!(
e,
TransactionError::ValidationError("Range proof could not be verified".to_string())
),
}
let v = PrivateKey::from(2u64.pow(32) + 1);
let c = factories.commitment.commit(&k2, &v);
let proof = factories.range_proof.construct_proof(&k2, 2u64.pow(32) + 1).unwrap();
let tx_output3 = TransactionOutput::new(OutputFeatures::default(), c, RangeProof::from_bytes(&proof).unwrap());
assert_eq!(tx_output3.verify_range_proof(&factories.range_proof).unwrap(), false);
}
#[test]
fn kernel_hash() {
let s = PrivateKey::from_hex("6c6eebc5a9c02e1f3c16a69ba4331f9f63d0718401dea10adc4f9d3b879a2c09").unwrap();
let r = PublicKey::from_hex("28e8efe4e5576aac931d358d0f6ace43c55fa9d4186d1d259d1436caa876d43b").unwrap();
let sig = Signature::new(r, s);
let excess = Commitment::from_hex("9017be5092b85856ce71061cadeb20c2d1fabdf664c4b3f082bf44cf5065e650").unwrap();
let k = KernelBuilder::new()
.with_signature(&sig)
.with_fee(100.into())
.with_excess(&excess)
.with_lock_height(500)
.build()
.unwrap();
assert_eq!(
&k.hash().to_hex(),
"fe25e4e961d5efec889c489d43e40a1334bf9b4408be4c2e8035a523f231a732"
);
}
#[test]
fn kernel_metadata() {
let s = PrivateKey::from_hex("df9a004360b1cf6488d8ff7fb625bc5877f4b013f9b2b20d84932172e605b207").unwrap();
let r = PublicKey::from_hex("5c6bfaceaa1c83fa4482a816b5f82ca3975cb9b61b6e8be4ee8f01c5f1bee561").unwrap();
let sig = Signature::new(r, s);
let excess = Commitment::from_hex("e0bd3f743b566272277c357075b0584fc840d79efac49e9b3b6dbaa8a351bc0c").unwrap();
let k = KernelBuilder::new()
.with_signature(&sig)
.with_fee(100.into())
.with_excess(&excess)
.with_lock_height(500)
.build()
.unwrap();
assert_eq!(
&k.hash().to_hex(),
"f1e7348b0952d8afbec6bfaa07a1cbc9c45e51e022242d3faeb0f190e2a9dd07"
)
}
#[test]
fn check_timelocks() {
let factories = CryptoFactories::new(32);
let k = BlindingFactor::random(&mut OsRng);
let v = PrivateKey::from(2u64.pow(32) + 1);
let c = factories.commitment.commit(&k, &v);
let mut input = TransactionInput::new(OutputFeatures::default(), c);
let mut kernel = create_test_kernel(0.into(), 0);
let mut tx = Transaction::new(Vec::new(), Vec::new(), Vec::new(), 0.into());
input.features.maturity = 5;
kernel.lock_height = 2;
tx.body.add_input(input.clone());
tx.body.add_kernel(kernel.clone());
assert_eq!(tx.max_input_maturity(), 5);
assert_eq!(tx.max_kernel_timelock(), 2);
assert_eq!(tx.min_spendable_height(), 5);
input.features.maturity = 4;
kernel.lock_height = 3;
tx.body.add_input(input.clone());
tx.body.add_kernel(kernel.clone());
assert_eq!(tx.max_input_maturity(), 5);
assert_eq!(tx.max_kernel_timelock(), 3);
assert_eq!(tx.min_spendable_height(), 5);
input.features.maturity = 2;
kernel.lock_height = 10;
tx.body.add_input(input.clone());
tx.body.add_kernel(kernel.clone());
assert_eq!(tx.max_input_maturity(), 5);
assert_eq!(tx.max_kernel_timelock(), 10);
assert_eq!(tx.min_spendable_height(), 10);
}
#[test]
fn test_validate_internal_consistency() {
let (tx, _, _) = create_tx(5000.into(), 15.into(), 1, 2, 1, 4);
let factories = CryptoFactories::default();
assert!(tx.validate_internal_consistency(&factories, None).is_ok());
}
#[test]
fn check_cut_through_() {
let (tx, _, outputs) = create_tx(50000000.into(), 15.into(), 1, 2, 1, 2);
assert_eq!(tx.body.inputs().len(), 2);
assert_eq!(tx.body.outputs().len(), 2);
assert_eq!(tx.body.kernels().len(), 1);
let factories = CryptoFactories::default();
assert!(tx.validate_internal_consistency(&factories, None).is_ok());
let schema = txn_schema!(from: vec![outputs[1].clone()], to: vec![1 * T, 2 * T]);
let (tx2, _outputs, _) = spend_utxos(schema);
assert_eq!(tx2.body.inputs().len(), 1);
assert_eq!(tx2.body.outputs().len(), 3);
assert_eq!(tx2.body.kernels().len(), 1);
let mut tx3 = tx.clone().add_no_cut_through(tx2.clone());
let tx = tx + tx2;
assert_eq!(tx3.body.inputs().len(), 3);
assert_eq!(tx3.body.outputs().len(), 5);
assert_eq!(tx3.body.kernels().len(), 2);
assert!(!tx3.body.check_cut_through());
tx3.body.do_cut_through();
assert!(tx.body.check_cut_through());
assert!(tx.validate_internal_consistency(&factories, None).is_ok());
assert_eq!(tx.body.inputs().len(), 2);
assert_eq!(tx.body.outputs().len(), 4);
assert_eq!(tx.body.kernels().len(), 2);
assert!(tx3.body.check_cut_through());
assert!(tx3.validate_internal_consistency(&factories, None).is_ok());
assert_eq!(tx3.body.inputs().len(), 2);
assert_eq!(tx3.body.outputs().len(), 4);
assert_eq!(tx3.body.kernels().len(), 2);
}
#[test]
fn test_output_rewinding() {
let factories = CryptoFactories::new(32);
let k = BlindingFactor::random(&mut OsRng);
let v = MicroTari::from(42);
let rewind_key = PrivateKey::random(&mut OsRng);
let rewind_blinding_key = PrivateKey::random(&mut OsRng);
let random_key = PrivateKey::random(&mut OsRng);
let rewind_public_key = PublicKey::from_secret_key(&rewind_key);
let rewind_blinding_public_key = PublicKey::from_secret_key(&rewind_blinding_key);
let public_random_key = PublicKey::from_secret_key(&random_key);
let proof_message = b"testing12345678910111";
let rewind_data = RewindData {
rewind_key: rewind_key.clone(),
rewind_blinding_key: rewind_blinding_key.clone(),
proof_message: proof_message.clone(),
};
let unblinded_output = UnblindedOutput::new(v, k.clone(), None);
let output = unblinded_output
.as_rewindable_transaction_output(&factories, &rewind_data)
.unwrap();
assert_eq!(
output.rewind_range_proof_value_only(
&factories.range_proof,
&public_random_key,
&rewind_blinding_public_key
),
Err(TransactionError::RangeProofError(RangeProofError::InvalidRewind))
);
assert_eq!(
output.rewind_range_proof_value_only(&factories.range_proof, &rewind_public_key, &public_random_key),
Err(TransactionError::RangeProofError(RangeProofError::InvalidRewind))
);
let rewind_result = output
.rewind_range_proof_value_only(&factories.range_proof, &rewind_public_key, &rewind_blinding_public_key)
.unwrap();
assert_eq!(rewind_result.committed_value, v);
assert_eq!(&rewind_result.proof_message, proof_message);
assert_eq!(
output.full_rewind_range_proof(&factories.range_proof, &random_key, &rewind_blinding_key),
Err(TransactionError::RangeProofError(RangeProofError::InvalidRewind))
);
assert_eq!(
output.full_rewind_range_proof(&factories.range_proof, &rewind_key, &random_key),
Err(TransactionError::RangeProofError(RangeProofError::InvalidRewind))
);
let full_rewind_result = output
.full_rewind_range_proof(&factories.range_proof, &rewind_key, &rewind_blinding_key)
.unwrap();
assert_eq!(full_rewind_result.committed_value, v);
assert_eq!(&full_rewind_result.proof_message, proof_message);
assert_eq!(full_rewind_result.blinding_factor, k);
}
}