use crate::constants::*;
use crate::crypto::sha3_256;
use crate::crypto::sign_ed25519::{self as sign, sign_detached, PublicKey, SecretKey};
use crate::primitives::asset::Asset;
use crate::primitives::druid::{DdeValues, DruidExpectation};
use crate::primitives::transaction::*;
use crate::script::lang::Script;
use crate::script::{OpCodes, StackEntry};
use bincode::serialize;
use std::collections::BTreeMap;
use tracing::debug;
pub struct ReceiverInfo {
pub address: String,
pub asset: Asset,
}
pub fn construct_p2sh_address(script: &Script) -> String {
let bytes = match serialize(script) {
Ok(bytes) => bytes,
Err(_) => vec![],
};
let mut addr = hex::encode(sha3_256::digest(&bytes));
addr.insert(ZERO, P2SH_PREPEND as char);
addr.truncate(STANDARD_ADDRESS_LENGTH);
addr
}
pub fn construct_address_for(pub_key: &PublicKey, address_version: Option<u64>) -> String {
match address_version {
Some(NETWORK_VERSION_V0) => construct_address_v0(pub_key),
Some(NETWORK_VERSION_TEMP) => construct_address_temp(pub_key),
_ => construct_address(pub_key),
}
}
pub fn construct_address(pub_key: &PublicKey) -> String {
hex::encode(sha3_256::digest(pub_key.as_ref()))
}
pub fn construct_address_v0(pub_key: &PublicKey) -> String {
let first_pubkey_bytes = {
let mut v = vec![32, 0, 0, 0, 0, 0, 0, 0];
v.extend_from_slice(pub_key.as_ref());
v
};
let mut first_hash = sha3_256::digest(&first_pubkey_bytes).to_vec();
first_hash.truncate(V0_ADDRESS_LENGTH);
hex::encode(first_hash)
}
pub fn construct_address_temp(pub_key: &PublicKey) -> String {
let base64_encoding = base64::encode(pub_key.as_ref());
let hex_decoded = decode_base64_as_hex(&base64_encoding);
hex::encode(sha3_256::digest(&hex_decoded))
}
pub fn decode_base64_as_hex(s: &str) -> Vec<u8> {
(ZERO..s.len())
.step_by(TWO)
.map(|i| {
u8::from_str_radix(&s[i..i + TWO], SIXTEEN as u32)
.or_else(|_| u8::from_str_radix(&s[i..i + ONE], SIXTEEN as u32))
.unwrap_or_default()
})
.collect()
}
pub fn get_out_point_signable_string(out_point: &OutPoint) -> String {
format!("{}-{}", out_point.n, out_point.t_hash)
}
pub fn construct_tx_in_signable_hash(previous_out: &OutPoint) -> String {
hex::encode(sha3_256::digest(
get_out_point_signable_string(previous_out).as_bytes(),
))
}
pub fn get_asset_signable_string(asset: &Asset) -> String {
match asset {
Asset::Token(token_amount) => format!("Token:{}", token_amount.0),
Asset::Item(item) => format!("Item:{}", item.amount),
}
}
pub fn construct_tx_in_signable_asset_hash(asset: &Asset) -> String {
hex::encode(sha3_256::digest(
get_asset_signable_string(asset).as_bytes(),
))
}
pub fn get_stack_entry_signable_string(entry: &StackEntry) -> String {
match entry {
StackEntry::Op(op) => format!("Op:{op}"),
StackEntry::Signature(signature) => {
format!("Signature:{}", hex::encode(signature.as_ref()))
}
StackEntry::PubKey(pub_key) => format!("PubKey:{}", hex::encode(pub_key.as_ref())),
StackEntry::Num(num) => format!("Num:{num}"),
StackEntry::Bytes(bytes) => format!("Bytes:{bytes}"),
}
}
pub fn construct_tx_in_out_signable_hash(tx_in: &TxIn, tx_out: &[TxOut]) -> String {
let mut signable_list = tx_out
.iter()
.map(|tx| {
debug!("txout: {:?}", tx);
serde_json::to_string(tx).unwrap_or("".to_string())
})
.collect::<Vec<String>>();
let tx_in_value = serde_json::to_string(&tx_in.previous_out).unwrap_or("".to_string());
signable_list.push(tx_in_value);
let signable = signable_list.join("");
debug!("Formatted string for signing: {signable}");
debug!(
"Hash: {:?}",
hex::encode(sha3_256::digest(signable.as_bytes()))
);
hex::encode(sha3_256::digest(signable.as_bytes()))
}
pub fn get_script_signable_string(stack: &[StackEntry]) -> String {
stack
.iter()
.map(get_stack_entry_signable_string)
.collect::<Vec<String>>()
.join("-")
}
pub fn get_tx_in_address_signable_string(tx_in: &TxIn) -> String {
let out_point_signable_string = match &tx_in.previous_out {
Some(out_point) => get_out_point_signable_string(out_point),
None => "null".to_owned(),
};
let script_signable_string = get_script_signable_string(&tx_in.script_signature.stack);
debug!("Formatted string: {out_point_signable_string}-{script_signable_string}");
format!("{out_point_signable_string}-{script_signable_string}")
}
pub fn construct_tx_ins_address(tx_ins: &[TxIn]) -> String {
let signable_tx_ins = tx_ins
.iter()
.map(get_tx_in_address_signable_string)
.collect::<Vec<String>>()
.join("-");
hex::encode(sha3_256::digest(signable_tx_ins.as_bytes()))
}
pub fn get_inputs_previous_out_point<'a>(
utxo_entries: impl Iterator<Item = &'a Transaction>,
) -> impl Iterator<Item = &'a OutPoint> {
utxo_entries
.filter(|tx| !tx.is_create_tx())
.flat_map(|val| val.inputs.iter())
.map(|input| input.previous_out.as_ref().unwrap())
}
pub fn get_tx_with_out_point<'a>(
txs: impl Iterator<Item = (&'a String, &'a Transaction)>,
) -> impl Iterator<Item = (OutPoint, &'a Transaction)> {
txs.map(|(hash, tx)| (hash, tx, &tx.outputs))
.flat_map(|(hash, tx, outs)| outs.iter().enumerate().map(move |(idx, _)| (hash, idx, tx)))
.map(|(hash, idx, tx)| (OutPoint::new(hash.clone(), idx as i32), tx))
}
pub fn get_tx_with_out_point_cloned<'a>(
txs: impl Iterator<Item = (&'a String, &'a Transaction)> + 'a,
) -> impl Iterator<Item = (OutPoint, Transaction)> + 'a {
get_tx_with_out_point(txs).map(|(h, tx)| (h, tx.clone()))
}
pub fn get_tx_out_with_out_point<'a>(
txs: impl Iterator<Item = (&'a String, &'a Transaction)>,
) -> impl Iterator<Item = (OutPoint, &'a TxOut)> {
txs.map(|(hash, tx)| (hash, tx.outputs.iter()))
.flat_map(|(hash, outs)| outs.enumerate().map(move |(idx, txo)| (hash, idx, txo)))
.map(|(hash, idx, txo)| (OutPoint::new(hash.clone(), idx as i32), txo))
}
pub fn get_fees_with_out_point<'a>(
txs: impl Iterator<Item = (&'a String, &'a Transaction)>,
) -> impl Iterator<Item = (OutPoint, &'a TxOut)> {
txs.map(|(hash, tx)| (hash, tx.fees.iter()))
.flat_map(|(hash, outs)| outs.enumerate().map(move |(idx, txo)| (hash, idx, txo)))
.map(|(hash, idx, txo)| (OutPoint::new(hash.clone(), idx as i32), txo))
}
pub fn get_fees_with_out_point_cloned<'a>(
txs: impl Iterator<Item = (&'a String, &'a Transaction)> + 'a,
) -> impl Iterator<Item = (OutPoint, TxOut)> + 'a {
txs.map(|(hash, tx)| (hash, tx.fees.iter()))
.flat_map(|(hash, outs)| outs.enumerate().map(move |(idx, txo)| (hash, idx, txo)))
.map(|(hash, idx, txo)| (OutPoint::new(hash.clone(), idx as i32), txo.clone()))
}
pub fn get_tx_out_with_out_point_cloned<'a>(
txs: impl Iterator<Item = (&'a String, &'a Transaction)> + 'a,
) -> impl Iterator<Item = (OutPoint, TxOut)> + 'a {
get_tx_out_with_out_point(txs).map(|(o, txo)| (o, txo.clone()))
}
pub fn update_utxo_set(current_utxo: &mut BTreeMap<OutPoint, Transaction>) {
let value_set: Vec<OutPoint> = get_inputs_previous_out_point(current_utxo.values())
.cloned()
.collect();
value_set.iter().for_each(move |t_hash| {
current_utxo.remove(t_hash);
});
}
pub fn construct_tx_hash(tx: &Transaction) -> String {
let bytes = match serialize(tx) {
Ok(bytes) => bytes,
Err(_) => vec![],
};
let mut hash = hex::encode(sha3_256::digest(&bytes));
hash.insert(ZERO, TX_PREPEND as char);
hash.truncate(TX_HASH_LENGTH);
hash
}
pub fn construct_create_tx_in(
block_num: u64,
asset: &Asset,
public_key: PublicKey,
secret_key: &SecretKey,
) -> Vec<TxIn> {
let asset_hash = construct_tx_in_signable_asset_hash(asset);
let signature = sign::sign_detached(asset_hash.as_bytes(), secret_key);
vec![TxIn {
previous_out: None,
script_signature: Script::new_create_asset(block_num, asset_hash, signature, public_key),
}]
}
pub fn construct_item_create_tx(
block_num: u64,
public_key: PublicKey,
secret_key: &SecretKey,
amount: u64,
genesis_hash_spec: GenesisTxHashSpec,
fee: Option<ReceiverInfo>,
metadata: Option<String>,
) -> Transaction {
let genesis_hash = genesis_hash_spec.get_genesis_hash();
let asset = Asset::item(amount, genesis_hash, metadata);
let receiver_address = construct_address(&public_key);
let tx_ins = construct_create_tx_in(block_num, &asset, public_key, secret_key);
let tx_out = TxOut {
value: asset,
script_public_key: Some(receiver_address),
..Default::default()
};
construct_tx_core(tx_ins, vec![tx_out], fee)
}
pub fn construct_payment_tx(
tx_ins: Vec<TxIn>,
receiver: ReceiverInfo,
fee: Option<ReceiverInfo>,
locktime: u64,
key_material: &BTreeMap<OutPoint, (PublicKey, SecretKey)>,
) -> Transaction {
let tx_out = TxOut {
value: receiver.asset,
locktime,
script_public_key: Some(receiver.address),
};
let tx_outs = vec![tx_out];
let final_tx_ins = update_input_signatures(&tx_ins, &tx_outs, key_material);
construct_tx_core(final_tx_ins, tx_outs, fee)
}
pub fn construct_p2sh_tx(
tx_ins: Vec<TxIn>,
fee: Option<ReceiverInfo>,
script: &Script,
asset: Asset,
locktime: u64,
key_material: &BTreeMap<OutPoint, (PublicKey, SecretKey)>,
) -> Transaction {
let script_hash = construct_p2sh_address(script);
let tx_out = TxOut {
value: asset,
locktime,
script_public_key: Some(script_hash),
};
let tx_outs = vec![tx_out];
let final_tx_ins = update_input_signatures(&tx_ins, &tx_outs, key_material);
construct_tx_core(final_tx_ins, tx_outs, fee)
}
pub fn construct_burn_tx(
tx_ins: Vec<TxIn>,
fee: Option<ReceiverInfo>,
key_material: &BTreeMap<OutPoint, (PublicKey, SecretKey)>,
) -> Transaction {
let s = vec![StackEntry::Op(OpCodes::OP_BURN)];
let script = Script::from(s);
let script_hash = construct_p2sh_address(&script);
let tx_out = TxOut {
script_public_key: Some(script_hash),
..Default::default()
};
let tx_outs = vec![tx_out];
let final_tx_ins = update_input_signatures(&tx_ins, &tx_outs, key_material);
construct_tx_core(final_tx_ins, tx_outs, fee)
}
pub fn construct_tx_core(
tx_ins: Vec<TxIn>,
tx_outs: Vec<TxOut>,
fee: Option<ReceiverInfo>,
) -> Transaction {
let fee_tx_out = match fee {
Some(fee) => vec![TxOut {
value: fee.asset,
locktime: 0,
script_public_key: Some(fee.address),
}],
None => vec![],
};
Transaction {
inputs: tx_ins,
outputs: tx_outs,
fees: fee_tx_out,
..Default::default()
}
}
pub fn construct_rb_tx_core(
tx_ins: Vec<TxIn>,
tx_outs: Vec<TxOut>,
fee: Option<ReceiverInfo>,
druid: String,
druid_expectation: Vec<DruidExpectation>,
key_material: &BTreeMap<OutPoint, (PublicKey, SecretKey)>,
) -> Transaction {
let mut tx = construct_tx_core(tx_ins, tx_outs, fee);
tx.inputs = update_input_signatures(&tx.inputs, &tx.outputs, key_material);
tx.druid_info = Some(DdeValues {
druid,
participants: 2,
expectations: druid_expectation,
genesis_hash: None,
});
tx
}
pub fn update_input_signatures(
tx_ins: &[TxIn],
tx_outs: &[TxOut],
key_material: &BTreeMap<OutPoint, (PublicKey, SecretKey)>,
) -> Vec<TxIn> {
let mut tx_ins = tx_ins.to_vec();
debug!("TxOuts: {:?}", tx_outs);
for tx_in in tx_ins.iter_mut() {
let signable_prev_out = TxIn {
previous_out: tx_in.previous_out.clone(),
script_signature: Script::new(),
};
debug!("Signable prev out: {:?}", signable_prev_out.previous_out);
let signable_hash = construct_tx_in_out_signable_hash(&signable_prev_out, tx_outs);
let previous_out = signable_prev_out.previous_out;
debug!("Signable hash: {:?}", signable_hash);
if previous_out.is_some() && key_material.get(&previous_out.clone().unwrap()).is_some() {
let pk = key_material.get(&previous_out.clone().unwrap()).unwrap().0;
let sk = &key_material.get(&previous_out.unwrap()).unwrap().1;
let script_signature = Script::pay2pkh(
signable_hash.clone(),
sign_detached(signable_hash.as_bytes(), sk),
pk,
None,
);
tx_in.script_signature = script_signature;
}
}
tx_ins
}
pub fn construct_rb_payments_send_tx(
tx_ins: Vec<TxIn>,
mut tx_outs: Vec<TxOut>,
fee: Option<ReceiverInfo>,
receiver: ReceiverInfo,
locktime: u64,
druid_info: DdeValues,
key_material: &BTreeMap<OutPoint, (PublicKey, SecretKey)>,
) -> Transaction {
let out = TxOut {
value: receiver.asset,
locktime,
script_public_key: Some(receiver.address),
};
tx_outs.push(out);
construct_rb_tx_core(
tx_ins,
tx_outs,
fee,
druid_info.druid,
druid_info.expectations,
key_material,
)
}
pub fn construct_rb_receive_payment_tx(
tx_ins: Vec<TxIn>,
mut tx_outs: Vec<TxOut>,
fee: Option<ReceiverInfo>,
sender_address: String,
locktime: u64,
druid_info: DdeValues,
key_material: &BTreeMap<OutPoint, (PublicKey, SecretKey)>,
) -> Transaction {
let out = TxOut {
value: Asset::item(1, druid_info.genesis_hash, None),
locktime,
script_public_key: Some(sender_address),
};
tx_outs.push(out);
construct_rb_tx_core(
tx_ins,
tx_outs,
fee,
druid_info.druid,
druid_info.expectations,
key_material,
)
}
pub fn construct_payment_tx_ins(tx_values: Vec<TxConstructor>) -> Vec<TxIn> {
let mut tx_ins = Vec::new();
for entry in tx_values {
let signable_prev_out = TxIn {
previous_out: Some(entry.previous_out),
script_signature: Script::new(),
};
let previous_out = signable_prev_out.previous_out;
let script_signature = Script::new();
tx_ins.push(TxIn {
previous_out,
script_signature,
});
}
tx_ins
}
pub fn construct_p2sh_redeem_tx_ins(tx_values: TxConstructor, script: Script) -> Vec<TxIn> {
let mut tx_ins = Vec::new();
let previous_out = Some(tx_values.previous_out);
tx_ins.push(TxIn {
previous_out,
script_signature: script,
});
tx_ins
}
pub fn construct_dde_tx(
druid_info: DdeValues,
tx_ins: Vec<TxIn>,
tx_outs: Vec<TxOut>,
fee: Option<ReceiverInfo>,
key_material: &BTreeMap<OutPoint, (PublicKey, SecretKey)>,
) -> Transaction {
let mut tx = construct_tx_core(tx_ins, tx_outs, fee);
tx.inputs = update_input_signatures(&tx.inputs, &tx.outputs, key_material);
tx.druid_info = Some(druid_info);
tx
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::sign_ed25519::{self as sign, Signature};
use crate::primitives::asset::{AssetValues, ItemAsset, TokenAmount};
use crate::script::OpCodes;
use crate::utils::script_utils::{tx_has_valid_p2sh_script, tx_outs_are_valid};
#[test]
fn test_construct_a_valid_payment_tx() {
test_construct_a_valid_payment_tx_common(None);
}
#[test]
fn test_construct_a_valid_payment_tx_v0() {
test_construct_a_valid_payment_tx_common(Some(NETWORK_VERSION_V0));
}
#[test]
fn test_construct_a_valid_payment_tx_temp() {
test_construct_a_valid_payment_tx_common(Some(NETWORK_VERSION_TEMP));
}
fn test_construct_valid_inputs(
address_version: Option<u64>,
) -> (
Vec<TxIn>,
String,
BTreeMap<OutPoint, (PublicKey, SecretKey)>,
) {
let (_pk, sk) = sign::gen_keypair();
let (pk, _sk) = sign::gen_keypair();
let t_hash = vec![0, 0, 0];
let signature = sign::sign_detached(&t_hash, &sk);
let drs_block_hash = hex::encode(vec![1, 2, 3, 4, 5, 6]);
let mut key_material = BTreeMap::new();
let prev_out = OutPoint::new(hex::encode(t_hash), 0);
key_material.insert(prev_out.clone(), (pk, sk));
let tx_const = TxConstructor {
previous_out: prev_out,
signatures: vec![signature],
pub_keys: vec![pk],
address_version,
};
let tx_ins = construct_payment_tx_ins(vec![tx_const]);
(tx_ins, drs_block_hash, key_material)
}
#[test]
fn test_construct_a_valid_p2sh_tx() {
let token_amount = TokenAmount(400000);
let (tx_ins, _drs_block_hash, key_material) =
test_construct_valid_inputs(Some(NETWORK_VERSION_V0));
let mut script = Script::new_for_coinbase(10);
script.stack.push(StackEntry::Op(OpCodes::OP_DROP));
let p2sh_tx = construct_p2sh_tx(
tx_ins,
None,
&script,
Asset::Token(token_amount),
0,
&key_material,
);
let spending_tx_hash = construct_tx_hash(&p2sh_tx);
let tx_const = TxConstructor {
previous_out: OutPoint::new(spending_tx_hash, 0),
signatures: vec![],
pub_keys: vec![],
address_version: Some(NETWORK_VERSION_V0),
};
let redeeming_tx_ins = construct_p2sh_redeem_tx_ins(tx_const, script.clone());
let redeeming_tx = construct_payment_tx(
redeeming_tx_ins,
ReceiverInfo {
address: hex::encode(vec![0; 32]),
asset: Asset::Token(token_amount),
},
None,
0,
&key_material,
);
let p2sh_script_pub_key = p2sh_tx.outputs[0].script_public_key.as_ref().unwrap();
assert_eq!(Asset::Token(token_amount), p2sh_tx.outputs[0].value);
assert_eq!(p2sh_script_pub_key.as_bytes()[0], P2SH_PREPEND);
assert_eq!(p2sh_script_pub_key.len(), STANDARD_ADDRESS_LENGTH);
assert!(tx_has_valid_p2sh_script(
&redeeming_tx.inputs[0].script_signature,
p2sh_tx.outputs[0].script_public_key.as_ref().unwrap()
));
}
#[test]
fn test_construct_a_valid_burn_tx() {
let token_amount = TokenAmount(400000);
let (tx_ins, _drs_block_hash, key_material) =
test_construct_valid_inputs(Some(NETWORK_VERSION_V0));
let burn_tx = construct_burn_tx(tx_ins, None, &key_material);
let spending_tx_hash = construct_tx_hash(&burn_tx);
let tx_const = TxConstructor {
previous_out: OutPoint::new(spending_tx_hash, 0),
signatures: vec![],
pub_keys: vec![],
address_version: Some(NETWORK_VERSION_V0),
};
let s = vec![StackEntry::Op(OpCodes::OP_BURN)];
let script = Script::from(s);
let redeeming_tx_ins = construct_p2sh_redeem_tx_ins(tx_const, script);
let redeeming_tx = construct_payment_tx(
redeeming_tx_ins,
ReceiverInfo {
address: hex::encode(vec![0; 32]),
asset: Asset::Token(token_amount),
},
None,
0,
&key_material,
);
let burn_script_pub_key = burn_tx.outputs[0].script_public_key.as_ref().unwrap();
debug!("{:?}", burn_script_pub_key);
assert_eq!(burn_script_pub_key.as_bytes()[0], P2SH_PREPEND);
assert_eq!(burn_script_pub_key.len(), STANDARD_ADDRESS_LENGTH);
assert!(!redeeming_tx.inputs[0].script_signature.interpret());
assert!(!tx_has_valid_p2sh_script(
&redeeming_tx.inputs[0].script_signature,
burn_tx.outputs[0].script_public_key.as_ref().unwrap()
));
}
fn test_construct_a_valid_payment_tx_common(address_version: Option<u64>) {
let (tx_ins, _drs_block_hash, key_material) = test_construct_valid_inputs(address_version);
let token_amount = TokenAmount(400000);
let payment_tx = construct_payment_tx(
tx_ins,
ReceiverInfo {
address: hex::encode(vec![0; 32]),
asset: Asset::Token(token_amount),
},
None,
0,
&key_material,
);
assert_eq!(Asset::Token(token_amount), payment_tx.outputs[0].value);
assert_eq!(
payment_tx.outputs[0].script_public_key,
Some(hex::encode(vec![0; 32]))
);
}
#[test]
fn test_construct_valid_payment_tx_with_fees() {
let (tx_ins, _drs_block_hash, key_material) = test_construct_valid_inputs(None);
let token_amount = TokenAmount(400000);
let fee_amount = TokenAmount(1000);
let payment_tx = construct_payment_tx(
tx_ins,
ReceiverInfo {
address: hex::encode(vec![0; 32]),
asset: Asset::Token(token_amount),
},
Some(ReceiverInfo {
address: hex::encode(vec![0; 32]),
asset: Asset::Token(fee_amount),
}),
0,
&key_material,
);
assert_eq!(Asset::Token(token_amount), payment_tx.outputs[0].value);
assert_eq!(Asset::Token(fee_amount), payment_tx.fees[0].value);
}
#[test]
fn test_token_onspend_with_fees() {
let (_pk, sk) = sign::gen_keypair();
let (pk, _sk) = sign::gen_keypair();
let t_hash = vec![0, 0, 0];
let signature = sign::sign_detached(&t_hash, &sk);
let tokens = TokenAmount(400000);
let fees = TokenAmount(1000);
let prev_out = OutPoint::new(hex::encode(t_hash), 0);
let mut key_material = BTreeMap::new();
key_material.insert(prev_out.clone(), (pk, sk));
let tx_const = TxConstructor {
previous_out: prev_out.clone(),
signatures: vec![signature],
pub_keys: vec![pk],
address_version: Some(2),
};
let tx_ins = construct_payment_tx_ins(vec![tx_const]);
let payment_tx_valid = construct_payment_tx(
tx_ins,
ReceiverInfo {
address: hex::encode(vec![0; 32]),
asset: Asset::Token(tokens),
},
Some(ReceiverInfo {
address: hex::encode(vec![0; 32]),
asset: Asset::Token(fees),
}),
0,
&key_material,
);
let tx_ins_spent = AssetValues::new(tokens + fees, BTreeMap::new());
assert!(
tx_outs_are_valid(
&payment_tx_valid.outputs,
&payment_tx_valid.fees,
tx_ins_spent
)
.0
);
}
#[test]
fn test_item_onspend_with_fees() {
let (_pk, sk) = sign::gen_keypair();
let (pk, _sk) = sign::gen_keypair();
let t_hash = vec![0, 0, 0];
let signature = sign::sign_detached(&t_hash, &sk);
let fees = TokenAmount(1000);
let prev_out = OutPoint::new(hex::encode(t_hash), 0);
let mut key_material = BTreeMap::new();
key_material.insert(prev_out.clone(), (pk, sk));
let tx_const = TxConstructor {
previous_out: prev_out.clone(),
signatures: vec![signature],
pub_keys: vec![pk],
address_version: Some(2),
};
let drs_tx_hash = "item_tx_hash".to_string();
let item_asset_valid = ItemAsset::new(1000, Some(drs_tx_hash.clone()), None);
let tx_ins = construct_payment_tx_ins(vec![tx_const]);
let payment_tx_valid = construct_payment_tx(
tx_ins,
ReceiverInfo {
address: hex::encode(vec![0; 32]),
asset: Asset::Item(item_asset_valid),
},
Some(ReceiverInfo {
address: hex::encode(vec![0; 32]),
asset: Asset::Token(fees),
}),
0,
&key_material,
);
let mut btree = BTreeMap::new();
btree.insert(drs_tx_hash, 1000);
let tx_ins_spent = AssetValues::new(fees, btree);
assert!(
tx_outs_are_valid(
&payment_tx_valid.outputs,
&payment_tx_valid.fees,
tx_ins_spent
)
.0
);
}
#[test]
fn test_item_onspend_metadata() {
let (_pk, sk) = sign::gen_keypair();
let (pk, _sk) = sign::gen_keypair();
let t_hash = vec![0, 0, 0];
let signature = sign::sign_detached(&t_hash, &sk);
let prev_out = OutPoint::new(hex::encode(t_hash), 0);
let mut key_material = BTreeMap::new();
key_material.insert(prev_out.clone(), (pk, sk));
let tx_const = TxConstructor {
previous_out: prev_out.clone(),
signatures: vec![signature],
pub_keys: vec![pk],
address_version: Some(2),
};
let genesis_hash = "item_tx_hash".to_string();
let item_asset_valid = ItemAsset::new(1000, Some(genesis_hash.clone()), None);
let tx_ins = construct_payment_tx_ins(vec![tx_const]);
let payment_tx_valid = construct_payment_tx(
tx_ins,
ReceiverInfo {
address: hex::encode(vec![0; 32]),
asset: Asset::Item(item_asset_valid),
},
None,
0,
&key_material,
);
let mut btree = BTreeMap::new();
btree.insert(genesis_hash, 1000);
let tx_ins_spent = AssetValues::new(TokenAmount(0), btree);
assert!(tx_outs_are_valid(&payment_tx_valid.outputs, &[], tx_ins_spent).0);
}
#[test]
fn test_construct_valid_utxo_set() {
test_construct_valid_utxo_set_common(None);
}
#[test]
fn test_construct_valid_utxo_set_v0() {
test_construct_valid_utxo_set_common(Some(NETWORK_VERSION_V0));
}
#[test]
fn test_construct_valid_utxo_set_temp() {
test_construct_valid_utxo_set_common(Some(NETWORK_VERSION_TEMP));
}
fn test_construct_valid_utxo_set_common(address_version: Option<u64>) {
let (pk, sk) = sign::gen_keypair();
let t_hash_1 = hex::encode(vec![0, 0, 0]);
let signed = sign::sign_detached(t_hash_1.as_bytes(), &sk);
let prev_out = OutPoint::new(hex::encode(t_hash_1), 0);
let mut key_material = BTreeMap::new();
key_material.insert(prev_out.clone(), (pk, sk.clone()));
let tx_1 = TxConstructor {
previous_out: OutPoint::new("".to_string(), 0),
signatures: vec![signed],
pub_keys: vec![pk],
address_version,
};
let token_amount = TokenAmount(400000);
let tx_ins_1 = construct_payment_tx_ins(vec![tx_1]);
let payment_tx_1 = construct_payment_tx(
tx_ins_1,
ReceiverInfo {
address: hex::encode(vec![0; 32]),
asset: Asset::Token(token_amount),
},
None,
0,
&key_material,
);
let tx_1_hash = construct_tx_hash(&payment_tx_1);
let tx_1_out_p = OutPoint::new(tx_1_hash.clone(), 0);
key_material.insert(tx_1_out_p.clone(), (pk, sk));
let tx_2 = TxConstructor {
previous_out: tx_1_out_p.clone(),
signatures: vec![signed],
pub_keys: vec![pk],
address_version,
};
let tx_ins_2 = construct_payment_tx_ins(vec![tx_2]);
let tx_outs = vec![TxOut::new_token_amount(
hex::encode(vec![0; 32]),
token_amount,
None,
)];
let payment_tx_2 = construct_tx_core(tx_ins_2, tx_outs, None);
let tx_2_hash = construct_tx_hash(&payment_tx_2);
let tx_2_out_p = OutPoint::new(tx_2_hash, 0);
let mut btree = BTreeMap::new();
btree.insert(tx_1_out_p, payment_tx_1);
btree.insert(tx_2_out_p.clone(), payment_tx_2);
update_utxo_set(&mut btree);
assert_eq!(btree.len(), 1);
assert_ne!(btree.get(&tx_2_out_p), None);
}
#[test]
fn test_construct_a_valid_dde_tx() {
test_construct_a_valid_dde_tx_common(None);
}
#[test]
fn test_construct_a_valid_dde_tx_v0() {
test_construct_a_valid_dde_tx_common(Some(NETWORK_VERSION_V0));
}
#[test]
fn test_construct_a_valid_dde_tx_temp() {
test_construct_a_valid_dde_tx_common(Some(NETWORK_VERSION_TEMP));
}
fn test_construct_a_valid_dde_tx_common(address_version: Option<u64>) {
let (_pk, sk) = sign::gen_keypair();
let (pk, _sk) = sign::gen_keypair();
let t_hash = hex::encode(vec![0, 0, 0]);
let signature = sign::sign_detached(t_hash.as_bytes(), &sk);
let prev_out = OutPoint::new(hex::encode(&t_hash), 0);
let mut key_material = BTreeMap::new();
key_material.insert(prev_out.clone(), (pk, sk));
let to_asset = "2222".to_owned();
let data = Asset::Item(ItemAsset {
metadata: Some("hello".to_string()),
amount: 1,
genesis_hash: None,
});
let tx_const = TxConstructor {
previous_out: prev_out.clone(),
signatures: vec![signature],
pub_keys: vec![pk],
address_version,
};
let tx_ins = construct_payment_tx_ins(vec![tx_const]);
let tx_outs = vec![TxOut {
value: data.clone(),
script_public_key: Some(to_asset.clone()),
..Default::default()
}];
let bytes = match serialize(&tx_ins) {
Ok(bytes) => bytes,
Err(_) => vec![],
};
let from_addr = hex::encode(bytes);
let druid = hex::encode(vec![1, 2, 3, 4, 5]);
let participants = 2;
let expects = vec![DruidExpectation {
from: from_addr,
to: to_asset,
asset: data.clone(),
}];
let druid_info = DdeValues {
druid: druid.clone(),
participants,
expectations: expects.clone(),
genesis_hash: None,
};
let dde = construct_dde_tx(druid_info, tx_ins, tx_outs, None, &key_material);
assert_eq!(dde.druid_info.clone().unwrap().druid, druid);
assert_eq!(dde.outputs[0].clone().value, data);
assert_eq!(dde.druid_info.unwrap().participants, participants);
}
#[test]
fn test_construct_a_valid_item_tx_pair() {
let amount = TokenAmount(33);
let payment = TokenAmount(11);
let druid = "VALUE".to_owned();
let tx_input = construct_payment_tx_ins(vec![]);
let from_addr = construct_tx_ins_address(&tx_input);
let alice_addr = "1111".to_owned();
let bob_addr = "00000".to_owned();
let sender_address_excess = "11112".to_owned();
let (pk, sk) = sign::gen_keypair();
let mut key_material = BTreeMap::new();
let send_tx = {
let tx_ins = {
construct_payment_tx_ins(vec![])
};
key_material.insert(OutPoint::new("".to_string(), 0), (pk, sk));
let excess_tx_out =
TxOut::new_token_amount(sender_address_excess, amount - payment, None);
let expectation = DruidExpectation {
from: from_addr.clone(),
to: alice_addr.clone(),
asset: Asset::item(1, Some("genesis_hash".to_owned()), None),
};
let mut tx = construct_rb_payments_send_tx(
tx_ins,
Vec::new(),
None,
ReceiverInfo {
address: bob_addr.clone(),
asset: Asset::Token(payment),
},
0,
DdeValues {
druid: druid.clone(),
participants: 2,
expectations: vec![expectation],
genesis_hash: None,
},
&key_material,
);
tx.outputs.push(excess_tx_out);
tx
};
let recv_tx = {
let tx_ins = {
let tx_ins_constructor = vec![];
construct_payment_tx_ins(tx_ins_constructor)
};
let expectation = DruidExpectation {
from: from_addr,
to: bob_addr,
asset: Asset::Token(payment),
};
let druid_info = DdeValues {
druid: druid.clone(),
participants: 2,
expectations: vec![expectation],
genesis_hash: Some("genesis_hash".to_owned()),
};
construct_rb_receive_payment_tx(
tx_ins,
Vec::new(),
None,
alice_addr,
0,
druid_info,
&key_material,
)
};
assert_eq!(
send_tx
.druid_info
.as_ref()
.map(|v| (&v.druid, v.participants)),
Some((&druid, 2))
);
assert_eq!(
recv_tx
.druid_info
.as_ref()
.map(|v| (&v.druid, v.participants)),
Some((&druid, 2))
);
}
#[test]
fn test_construct_valid_addresses() {
test_construct_valid_addresses_common(None);
}
#[test]
fn test_construct_valid_addresses_v0() {
test_construct_valid_addresses_common(Some(NETWORK_VERSION_V0));
}
#[test]
fn test_construct_valid_addresses_temp() {
test_construct_valid_addresses_common(Some(NETWORK_VERSION_TEMP));
}
fn test_construct_valid_addresses_common(address_version: Option<u64>) {
let pub_keys = [
"5371832122a8e804fa3520ec6861c3fa554a7f6fb617e6f0768452090207e07c",
"6e86cc1fc5efbe64c2690efbb966b9fe1957facc497dce311981c68dac88e08c",
"8b835e00c57ebff6637ec32276f2c6c0df71129c8f0860131a78a4692a0b59dc",
]
.iter()
.map(|v| hex::decode(v).unwrap())
.map(|v| PublicKey::from_slice(&v).unwrap())
.collect::<Vec<PublicKey>>();
let actual_pub_addresses: Vec<String> = pub_keys
.iter()
.map(|pub_key| construct_address_for(pub_key, address_version))
.collect();
let expected_pub_addresses = match address_version {
Some(NETWORK_VERSION_V0) => vec![
"13bd3351b78beb2d0dadf2058dcc926c",
"abc7c0448465c4507faf2ee588728824",
"6ae52e3870884ab66ec49d3bb359c0bf",
],
Some(NETWORK_VERSION_TEMP) => vec![
"6c6b6e8e9df8c63d22d9eb687b9671dd1ce5d89f195bb2316e1b1444848cd2b3",
"8ac2fdcb0688abb2727d63ed230665b275a1d3a28373baa92a9afa5afd610e9f",
"0becdaaf6a855f04961208ee992651c11df0be91c08629dfc079d05d2915ec22",
],
_ => vec![
"5423e6bd848e0ce5cd794e55235c23138d8833633cd2d7de7f4a10935178457b",
"77516e2d91606250e625546f86702510d2e893e4a27edfc932fdba03c955cc1b",
"4cfd64a6692021fc417368a866d33d94e1c806747f61ac85e0b3935e7d5ed925",
],
};
assert_eq!(actual_pub_addresses, expected_pub_addresses);
}
#[test]
fn test_construct_valid_tx_in_signable_hash() {
let out_points = vec![
OutPoint::new("000000".to_owned(), 0),
OutPoint::new("000001".to_owned(), 0),
OutPoint::new("000002".to_owned(), 0),
];
let actual: Vec<String> = out_points
.iter()
.map(construct_tx_in_signable_hash)
.collect();
let expected: Vec<String> = vec![
"927b3411743452e5e0d73e9e40a4fa3c842b3d00dabde7f9af7e44661ce02c88".to_owned(),
"754dc248d1c847e8a10c6f8ded6ccad96381551ebb162583aea2a86b9bb78dfa".to_owned(),
"5585c6f74d5c55f1ab457c31671822ba28c78c397cce1e11680b9f3852f96edb".to_owned(),
];
assert_eq!(actual, expected);
}
#[test]
fn test_construct_valid_tx_in_signable_asset_hash() {
let assets = vec![Asset::token_u64(1), Asset::item(1, None, None)];
let actual: Vec<String> = assets
.iter()
.map(construct_tx_in_signable_asset_hash)
.collect();
let expected: Vec<String> = vec![
"a5b2f5e8dcf824aee45b81294ff8049b680285b976cc6c8fa45eb070acfc5974".to_owned(),
"cb8f6cba3a62cfb7cd14245f19509b800da3dd446b6d902290efbcc91b3cee0d".to_owned(),
];
assert_eq!(actual, expected);
}
#[test]
fn test_construct_valid_tx_ins_address() {
let pub_keys = [
"5e6d463ec66d7999769fa4de56f690dfb62e685b97032f5926b0cb6c93ba83c6",
"58272ba93c1e79df280d4c417de47dbf6a7e330ba52793d7baa8e00ae5c34e59",
"efa9dcba0f3282b3ed4a6aa1ccdb169d6685a30d7b2af7a2171a5682f3112359",
];
let signatures = ["660e4698d817d409feb209699b15935048c8b3c4ac86a23f25b05aa32fb8b87e7cd029b83220d31a0b2717bd63b47a320a7728355d7fae43a665d6e27743e20d",
"fd107c9446cdcbd8fbb0d6b88c73067c9bd15de03fff677b0129acf1bd2d14a5ab8a63c7eb6fe8c5acc4b44b033744760847194a15b006368d178c85243d0605",
"e1a436bbfcb3e411be1ce6088cdb4c39d7e79f8fe427943e74307e43864fd0f6ef26123f1439b92c075edd031d17feb4dd265c6fcc2e5ed571df48a03c396100"];
let signable_data = [
"927b3411743452e5e0d73e9e40a4fa3c842b3d00dabde7f9af7e44661ce02c88",
"754dc248d1c847e8a10c6f8ded6ccad96381551ebb162583aea2a86b9bb78dfa",
"5585c6f74d5c55f1ab457c31671822ba28c78c397cce1e11680b9f3852f96edb",
];
let previous_out_points = vec![
OutPoint::new("000000".to_owned(), 0),
OutPoint::new("000001".to_owned(), 0),
OutPoint::new("000002".to_owned(), 0),
];
let tx_ins: Vec<TxIn> = (0..3)
.map(|n| {
let sig_data = signable_data[n].to_owned();
let sig =
Signature::from_slice(hex::decode(signatures[n]).unwrap().as_ref()).unwrap();
let pk = PublicKey::from_slice(hex::decode(pub_keys[n]).unwrap().as_ref()).unwrap();
let script = Script::pay2pkh(sig_data, sig, pk, None);
let out_p = previous_out_points[n].clone();
TxIn::new_from_input(out_p, script)
})
.collect();
let expected =
"c8b62d379f07602956207ea473ce20d9752d24ad6e6cd43cb042d024d7c6a468".to_owned();
let actual = construct_tx_ins_address(&tx_ins);
assert_eq!(actual, expected);
}
}