use std::{
convert::{TryFrom, TryInto},
sync::Arc,
};
use borsh::{BorshDeserialize, BorshSerialize};
use tari_common_types::types::{BulletRangeProof, CompressedCommitment, CompressedPublicKey, PrivateKey};
use tari_crypto::tari_utilities::{ByteArray, ByteArrayError};
use tari_script::{ExecutionStack, TariScript};
use tari_transaction_components::{
MicroMinotari,
aggregated_body::AggregateBody,
transaction_components::{
CoinBaseExtra,
EncryptedData,
KernelFeatures,
OutputFeatures,
OutputFeaturesVersion,
OutputType,
RangeProofType,
SideChainFeature,
Transaction,
TransactionInput,
TransactionInputVersion,
TransactionKernel,
TransactionKernelVersion,
TransactionOutput,
TransactionOutputVersion,
},
};
use tari_utilities::convert::try_convert_all;
use crate::proto;
impl TryFrom<proto::types::TransactionKernel> for TransactionKernel {
type Error = String;
fn try_from(kernel: proto::types::TransactionKernel) -> Result<Self, Self::Error> {
let excess = CompressedCommitment::from_canonical_bytes(
&kernel
.excess
.ok_or_else(|| "Excess not provided in kernel".to_string())?
.data,
)
.map_err(|err| err.to_string())?;
let excess_sig = kernel
.excess_sig
.ok_or_else(|| "excess_sig not provided".to_string())?
.try_into()?;
let kernel_features = u8::try_from(kernel.features).map_err(|_| "Kernel features must be a single byte")?;
let commitment = match kernel.burn_commitment {
Some(burn_commitment) => {
Some(CompressedCommitment::from_canonical_bytes(&burn_commitment.data).map_err(|e| e.to_string())?)
},
None => None,
};
Ok(TransactionKernel::new(
TransactionKernelVersion::try_from(
u8::try_from(kernel.version).map_err(|_| "Invalid version: overflowed u8")?,
)?,
KernelFeatures::from_bits(kernel_features)
.ok_or_else(|| "Invalid or unrecognised kernel feature flag".to_string())?,
MicroMinotari::from(kernel.fee),
kernel.lock_height,
excess,
excess_sig,
commitment,
))
}
}
impl From<TransactionKernel> for proto::types::TransactionKernel {
fn from(kernel: TransactionKernel) -> Self {
let commitment = kernel.burn_commitment.map(|commitment| commitment.into());
Self {
features: u32::from(kernel.features.bits()),
excess: Some(kernel.excess.into()),
excess_sig: Some(kernel.excess_sig.into()),
fee: kernel.fee.into(),
lock_height: kernel.lock_height,
version: kernel.version as u32,
burn_commitment: commitment,
}
}
}
impl TryFrom<proto::types::TransactionInput> for TransactionInput {
type Error = String;
fn try_from(input: proto::types::TransactionInput) -> Result<Self, Self::Error> {
let script_signature = input
.script_signature
.ok_or_else(|| "script_signature not provided".to_string())?
.try_into()
.map_err(|err: ByteArrayError| err.to_string())?;
if let Some(commitment) = input.commitment {
let commitment = CompressedCommitment::from_canonical_bytes(&commitment.data).map_err(|e| e.to_string())?;
let features = input
.features
.map(TryInto::try_into)
.ok_or_else(|| "transaction output features not provided".to_string())??;
let sender_offset_public_key =
CompressedPublicKey::from_canonical_bytes(input.sender_offset_public_key.as_bytes())
.map_err(|err| format!("{err:?}"))?;
let metadata_signature = input
.metadata_signature
.ok_or_else(|| "Metadata signature not provided".to_string())?
.try_into()
.map_err(|_| "Metadata signature could not be converted".to_string())?;
let rangeproof_hash = input
.rangeproof_hash
.try_into()
.map_err(|_| "Invalid rangeproof hash")?;
let mut buffer_input_covenant = input.covenant.as_bytes();
Ok(TransactionInput::new_with_output_data(
TransactionInputVersion::try_from(
u8::try_from(input.version).map_err(|_| "Invalid version: overflowed u8")?,
)?,
features,
commitment,
TariScript::from_bytes(input.script.as_slice()).map_err(|err| format!("{err:?}"))?,
ExecutionStack::from_bytes(input.input_data.as_slice()).map_err(|err| format!("{err:?}"))?,
script_signature,
sender_offset_public_key,
BorshDeserialize::deserialize(&mut buffer_input_covenant).map_err(|err| err.to_string())?,
EncryptedData::from_bytes(&input.encrypted_data).map_err(|err| err.to_string())?,
metadata_signature,
rangeproof_hash,
input.minimum_value_promise.into(),
))
} else {
if input.output_hash.is_empty() {
return Err("Compact Transaction Input does not contain `output_hash`".to_string());
}
let hash = input.output_hash.try_into().map_err(|_| "Invalid transaction hash")?;
Ok(TransactionInput::new_with_output_hash(
hash,
ExecutionStack::from_bytes(input.input_data.as_slice()).map_err(|err| format!("{err:?}"))?,
script_signature,
))
}
}
}
impl TryFrom<TransactionInput> for proto::types::TransactionInput {
type Error = String;
fn try_from(input: TransactionInput) -> Result<Self, Self::Error> {
if input.is_compact() {
let output_hash = input.output_hash();
Ok(Self {
input_data: input.input_data.to_bytes(),
script_signature: Some(input.script_signature.into()),
output_hash: output_hash.to_vec(),
..Default::default()
})
} else {
let mut covenant = Vec::new();
BorshSerialize::serialize(
&input
.covenant()
.map_err(|_| "Non-compact Transaction input should contain covenant".to_string())?,
&mut covenant,
)
.map_err(|err| err.to_string())?;
Ok(Self {
features: Some(
input
.features()
.map_err(|_| "Non-compact Transaction input should contain features".to_string())?
.clone()
.into(),
),
commitment: Some(
input
.commitment()
.map_err(|_| "Non-compact Transaction input should contain commitment".to_string())?
.clone()
.into(),
),
script: input
.script()
.map_err(|_| "Non-compact Transaction input should contain script".to_string())?
.to_bytes(),
input_data: input.input_data.to_bytes(),
script_signature: Some(input.script_signature.clone().into()),
sender_offset_public_key: input
.sender_offset_public_key()
.map_err(|_| "Non-compact Transaction input should contain sender_offset_public_key".to_string())?
.as_bytes()
.to_vec(),
output_hash: Vec::new(),
covenant,
version: input.version as u32,
encrypted_data: input
.encrypted_data()
.map_err(|_| "Non-compact Transaction input should contain encrypted value".to_string())?
.to_byte_vec(),
metadata_signature: Some(
input
.metadata_signature()
.map_err(|_| "Non-compact Transaction input should contain a metadata_signature".to_string())?
.clone()
.into(),
),
rangeproof_hash: input
.rangeproof_hash()
.map_err(|_| "Non-compact Transaction input should contain a rangeproof hash".to_string())?
.to_vec(),
minimum_value_promise: input
.minimum_value_promise()
.map_err(|_| "Non-compact Transaction input should contain the minimum value promise".to_string())?
.as_u64(),
})
}
}
}
impl TryFrom<proto::types::TransactionOutput> for TransactionOutput {
type Error = String;
fn try_from(output: proto::types::TransactionOutput) -> Result<Self, Self::Error> {
let features = output
.features
.map(TryInto::try_into)
.ok_or_else(|| "Transaction output features not provided".to_string())??;
let commitment = output
.commitment
.map(|commit| CompressedCommitment::from_canonical_bytes(&commit.data))
.ok_or_else(|| "Transaction output commitment not provided".to_string())?
.map_err(|err| err.to_string())?;
let sender_offset_public_key =
CompressedPublicKey::from_canonical_bytes(output.sender_offset_public_key.as_bytes())
.map_err(|err| format!("{err:?}"))?;
let range_proof = if let Some(proof) = output.range_proof {
Some(BulletRangeProof::from_canonical_bytes(&proof.proof_bytes).map_err(|err| err.to_string())?)
} else {
None
};
let script = TariScript::from_bytes(&output.script).map_err(|err| err.to_string())?;
let metadata_signature = output
.metadata_signature
.ok_or_else(|| "Metadata signature not provided".to_string())?
.try_into()
.map_err(|_| "Metadata signature could not be converted".to_string())?;
let mut buffer = output.covenant.as_bytes();
let covenant = BorshDeserialize::deserialize(&mut buffer).map_err(|e| e.to_string())?;
let encrypted_data = EncryptedData::from_bytes(&output.encrypted_data).map_err(|err| err.to_string())?;
let minimum_value_promise = output.minimum_value_promise.into();
Ok(Self::new(
TransactionOutputVersion::try_from(
u8::try_from(output.version).map_err(|_| "Invalid version: overflowed u8")?,
)?,
features,
commitment,
range_proof,
script,
sender_offset_public_key,
metadata_signature,
covenant,
encrypted_data,
minimum_value_promise,
))
}
}
impl TryFrom<TransactionOutput> for proto::types::TransactionOutput {
type Error = String;
fn try_from(output: TransactionOutput) -> Result<Self, Self::Error> {
let mut covenant = Vec::new();
BorshSerialize::serialize(&output.covenant, &mut covenant).map_err(|err| err.to_string())?;
let range_proof = output.proof.map(|proof| proto::types::RangeProof {
proof_bytes: proof.to_vec(),
});
Ok(Self {
features: Some(output.features.into()),
commitment: Some(output.commitment.into()),
range_proof,
script: output.script.to_bytes(),
sender_offset_public_key: output.sender_offset_public_key.as_bytes().to_vec(),
metadata_signature: Some(output.metadata_signature.into()),
covenant,
version: output.version as u32,
encrypted_data: output.encrypted_data.to_byte_vec(),
minimum_value_promise: output.minimum_value_promise.into(),
})
}
}
impl TryFrom<proto::types::OutputFeatures> for OutputFeatures {
type Error = String;
fn try_from(features: proto::types::OutputFeatures) -> Result<Self, Self::Error> {
let sidechain_feature = features.sidechain_feature.map(SideChainFeature::try_from).transpose()?;
let output_type = features
.output_type
.try_into()
.map_err(|_| "Invalid output type: overflowed")?;
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")?,
)?,
OutputType::from_byte(output_type).ok_or_else(|| "Invalid or unrecognised output type".to_string())?,
features.maturity,
CoinBaseExtra::try_from(features.coinbase_extra).map_err(|e| e.to_string())?,
sidechain_feature,
RangeProofType::from_byte(range_proof_type)
.ok_or_else(|| "Invalid or unrecognised range proof type".to_string())?,
))
}
}
impl From<OutputFeatures> for proto::types::OutputFeatures {
fn from(features: OutputFeatures) -> Self {
Self {
output_type: u32::from(features.output_type.as_byte()),
maturity: features.maturity,
coinbase_extra: features.coinbase_extra.to_vec(),
version: features.version as u32,
sidechain_feature: features.sidechain_feature.map(Into::into),
range_proof_type: u32::from(features.range_proof_type.as_byte()),
}
}
}
impl TryFrom<proto::types::AggregateBody> for AggregateBody {
type Error = String;
fn try_from(body: proto::types::AggregateBody) -> Result<Self, Self::Error> {
let inputs = try_convert_all(body.inputs)?;
let outputs = try_convert_all(body.outputs)?;
let kernels = try_convert_all(body.kernels)?;
let body = AggregateBody::new(inputs, outputs, kernels);
Ok(body)
}
}
impl TryFrom<AggregateBody> for proto::types::AggregateBody {
type Error = String;
fn try_from(body: AggregateBody) -> Result<Self, Self::Error> {
let (i, o, k) = body.dissolve();
Ok(Self {
inputs: i
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<proto::types::TransactionInput>, _>>()?,
outputs: o
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, String>>()?,
kernels: k.into_iter().map(Into::into).collect(),
})
}
}
impl TryFrom<proto::types::Transaction> for Transaction {
type Error = String;
fn try_from(tx: proto::types::Transaction) -> Result<Self, Self::Error> {
let offset = tx
.offset
.map(|offset| PrivateKey::from_canonical_bytes(&offset.data))
.ok_or_else(|| "Blinding factor offset not provided".to_string())?
.map_err(|err| err.to_string())?;
let body = tx
.body
.map(TryInto::try_into)
.ok_or_else(|| "Body not provided".to_string())??;
let script_offset = tx
.script_offset
.map(|script_offset| PrivateKey::from_canonical_bytes(&script_offset.data))
.ok_or_else(|| "Script offset not provided".to_string())?
.map_err(|err| err.to_string())?;
Ok(Self {
offset,
body,
script_offset,
})
}
}
impl TryFrom<Transaction> for proto::types::Transaction {
type Error = String;
fn try_from(tx: Transaction) -> Result<Self, Self::Error> {
Ok(Self {
offset: Some(tx.offset.into()),
body: Some(tx.body.try_into()?),
script_offset: Some(tx.script_offset.into()),
})
}
}
impl TryFrom<Arc<Transaction>> for proto::types::Transaction {
type Error = String;
fn try_from(tx: Arc<Transaction>) -> Result<Self, Self::Error> {
match Arc::try_unwrap(tx) {
Ok(tx) => Ok(tx.try_into()?),
Err(tx) => Ok(Self {
offset: Some(tx.offset.clone().into()),
body: Some(tx.body.clone().try_into()?),
script_offset: Some(tx.script_offset.clone().into()),
}),
}
}
}