use crate::{
wallet::ArWallet,
recursive_hash::recursive_hash,
b64::{b64_encode},
hasher::{sha256},
chunks::{Chunks}
};
use base64ct::{Base64UrlUnpadded, Encoding};
use serde::{Deserialize, Serialize};
use std::error::Error;
use openssl::{
sign::{Signer, Verifier},
rsa::{Padding},
pkey::PKey,
hash::MessageDigest,
};
type Bytes = Vec<u8>;
#[derive(Debug, Clone)]
pub enum TxValue {
Bytes(Bytes),
Vec(Vec<TxValue>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tag {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionData {
format: u32,
id: String,
last_tx: String,
owner: String,
tags: Vec<Tag>,
target: String,
quantity: String,
data: String,
data_root: String,
data_size: String,
reward: String,
signature: String,
}
#[derive(Debug, Clone)]
pub struct Transaction {
arwallet: ArWallet,
tx_data: TransactionData,
chunks: Chunks,
}
impl Transaction {
pub async fn new_ar(arwallet: &ArWallet, target: &str, quantity_ar: f64,)
-> Self {
let quantity_winston = quantity_ar * 1000000000000.0;
let quantity_winston = quantity_winston as u64;
let quantity = quantity_winston.to_string();
let tags: Vec<Tag>
= Vec::new();
let reward = arwallet.ar_transaction_price(target)
.await
.unwrap();
let tx_data = TransactionData {
format: 2,
id: "".to_string(),
last_tx: arwallet.tx_anchor().await.unwrap(),
owner: arwallet.owner(),
tags,
target: target.to_string(),
quantity,
data: "".to_string(),
data_root: "".to_string(),
data_size: "0".to_string(),
reward,
signature: "".to_string(),
};
Self {
arwallet: arwallet.clone(),
tx_data,
chunks: Chunks::new(),
}
}
pub async fn new_data(arwallet: &ArWallet, raw_data: &[u8]) -> Self {
let mut tx = Self::new_ar(arwallet, "", 0.0).await.clone();
let mut chunks = Chunks::new();
chunks.finalize(&raw_data);
tx.chunks = chunks.clone();
tx.tx_data.data_root = b64_encode(&tx.chunks.data_root());
tx.tx_data.data_size = tx.chunks.data_size().to_string();
tx.tx_data.reward =arwallet.data_transaction_price(
tx.chunks.data_size()).await.unwrap();
tx.tx_data.last_tx = arwallet.tx_anchor().await.unwrap();
tx
}
pub async fn new_ar_data(
arwallet: &ArWallet,
target: &str,
quantity_ar: f64,
raw_data: &[u8]) -> Self {
let mut tx = Self::new_ar(arwallet, target, quantity_ar).await.clone();
let mut chunks = Chunks::new();
chunks.finalize(&raw_data);
tx.chunks = chunks.clone();
tx.tx_data.data_root = b64_encode(&tx.chunks.data_root());
tx.tx_data.data_size = tx.chunks.data_size().to_string();
tx.tx_data.reward =arwallet.ar_data_transaction_price(
&target,
tx.chunks.data_size(),
).await.unwrap();
tx.tx_data.last_tx = arwallet.tx_anchor().await.unwrap();
tx
}
pub fn sign_data_root(&self) -> Bytes {
let tx_data = &self.tx_data;
let format_b = tx_data.format.to_string().into_bytes();
let format_b = TxValue::Bytes(format_b);
let owner_b = Base64UrlUnpadded::decode_vec(&tx_data.owner).unwrap();
let owner_b = TxValue::Bytes(owner_b);
let target_b = Base64UrlUnpadded::decode_vec(&tx_data.target).unwrap();
let target_b = TxValue::Bytes(target_b);
let data_root_b = Base64UrlUnpadded::decode_vec(&tx_data.data_root)
.unwrap();
let data_root_b = TxValue::Bytes(data_root_b);
let data_size_b = tx_data.data_size.clone().into_bytes();
let data_size_b = TxValue::Bytes(data_size_b);
let quantity_b = tx_data.quantity.clone().into_bytes();
let quantity_b = TxValue::Bytes(quantity_b);
let reward_b = tx_data.reward.clone().into_bytes();
let reward_b = TxValue::Bytes(reward_b);
let last_tx_b = Base64UrlUnpadded::decode_vec(&tx_data.last_tx)
.unwrap();
let last_tx_b = TxValue::Bytes(last_tx_b);
let mut tags_b_vec: TxValue = TxValue::Vec(Vec::new());
let mut i = 0;
while i < tx_data.tags.len() {
let name = tx_data.tags[i].name.clone();
let value = tx_data.tags[i].value.clone();
let name_b = TxValue::Bytes(
Base64UrlUnpadded::decode_vec(&name).unwrap());
let value_b = TxValue::Bytes(
Base64UrlUnpadded::decode_vec(&value).unwrap());
let mut tag_b_vec: TxValue = TxValue::Vec(Vec::new());
if let TxValue::Vec(ref mut real_vec) = tag_b_vec {
real_vec.push(name_b);
real_vec.push(value_b);
}
if let TxValue::Vec(ref mut real_vec) = tags_b_vec {
real_vec.push(tag_b_vec);
}
i = i + 1;
}
let sign_data: TxValue = TxValue::Vec(
vec![
format_b,
owner_b,
target_b,
quantity_b,
reward_b,
last_tx_b,
tags_b_vec,
data_size_b,
data_root_b,
]
);
recursive_hash(&sign_data)
}
pub fn sign(&mut self) {
let sign_data_root = self.sign_data_root();
let private_key = self.arwallet.private_key();
let signing_key = PKey::from_rsa(private_key).unwrap();
let mut signer = Signer::new(MessageDigest::sha256(), &signing_key)
.unwrap();
signer.set_rsa_padding(Padding::PKCS1_PSS).unwrap();
signer.update(&sign_data_root).unwrap();
let signature = signer.sign_to_vec().unwrap();
let signature_b64 = Base64UrlUnpadded
::encode_string(&signature);
self.tx_data.signature = signature_b64;
let tx_id_b = sha256(&signature);
self.tx_data.id = Base64UrlUnpadded
::encode_string(&tx_id_b);
}
pub fn verify(&self) -> Result<String, String> {
let sign_data_root = self.sign_data_root();
let private_key = self.arwallet.private_key();
let signing_key = PKey::from_rsa(private_key).unwrap();
let mut verifier = Verifier::new(MessageDigest::sha256(),
&signing_key)
.unwrap();
verifier.set_rsa_padding(Padding::PKCS1_PSS).unwrap();
verifier.update(&sign_data_root).unwrap();
let signature = Base64UrlUnpadded::decode_vec(&self.tx_data.signature)
.unwrap();
let verify_result = verifier.verify(&signature).unwrap();
if verify_result {
Ok("The Transaction is verified successfully."
.to_string())
} else {
Err("Failed to verify".to_string())
}
}
pub async fn submit(&self) -> Result<String, Box<dyn Error>> {
let verified: bool;
match self.verify() {
Ok(_) => verified = true,
Err(_) => verified = false,
}
if verified {
let tx_data = &self.tx_data;
let tx_data_json = serde_json::to_string_pretty(tx_data).unwrap();
let mut api_url = self.arwallet.gateway();
api_url.push_str("tx");
let res = self.arwallet.http_client().post(&api_url)
.body(tx_data_json).send().await;
match res {
Ok(r) => {
let res_status = r.status().as_u16();
if res_status == 200 {
Ok("Transaction has been submitted successfully."
.to_string())
} else {
let res_text = r.text().await.unwrap();
let mut err_string = "Error: ".to_string();
err_string.push_str(&res_status.to_string());
err_string.push_str(" - ");
err_string.push_str(&res_text);
Err(err_string.into())
}
},
Err(e) => Err(e.into()),
}
} else {
Err("Transaction failed to sign can't be submitted"
.to_string().into())
}
}
pub fn tx_data(&self) -> TransactionData {
self.tx_data.clone()
}
pub fn tx_data_json(&self) -> String {
serde_json::to_string_pretty(&self.tx_data()).unwrap()
}
pub fn add_tag(&mut self, name: &str, value: &str) {
let name = b64_encode(name.as_bytes());
let value = b64_encode(value.as_bytes());
let tag = Tag {
name: name,
value: value,
};
self.tx_data.tags.push(tag);
}
pub fn chunks(&self) -> Chunks {
self.chunks.clone()
}
}