Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support posv #1815

Merged
merged 8 commits into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions mm2src/coins/utxo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,9 @@ pub struct UtxoCoinConf {
/// https://en.bitcoin.it/wiki/Proof_of_work
/// The actual meaning of this is nTime field is used in transaction
pub is_pos: bool,
/// Defines if coin uses PoSV transaction format (Reddcoin, Potcoin, et al).
/// n_time field is appended to end of transaction
pub is_posv: bool,
/// Special field for Zcash and it's forks
/// Defines if Overwinter network upgrade was activated
/// https://z.cash/upgrade/overwinter/
Expand Down Expand Up @@ -791,6 +794,7 @@ impl UtxoCoinFields {
shielded_spends: vec![],
shielded_outputs: vec![],
zcash: self.conf.zcash,
posv: self.conf.is_posv,
str_d_zeel,
hash_algo: self.tx_hash_algo.into(),
}
Expand Down
1 change: 1 addition & 0 deletions mm2src/coins/utxo/slp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,7 @@ impl SlpToken {
join_split_sig: Default::default(),
binding_sig: Default::default(),
zcash: unsigned.zcash,
posv: unsigned.posv,
str_d_zeel: unsigned.str_d_zeel,
tx_hash_algo: self.platform_coin.as_ref().tx_hash_algo,
};
Expand Down
4 changes: 4 additions & 0 deletions mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ impl<'a> UtxoConfBuilder<'a> {
let mature_confirmations = self.mature_confirmations();

let is_pos = self.is_pos();
let is_posv = self.is_posv();
let segwit = self.segwit();
let force_min_relay_fee = self.conf["force_min_relay_fee"].as_bool().unwrap_or(false);
let mtp_block_count = self.mtp_block_count();
Expand All @@ -95,6 +96,7 @@ impl<'a> UtxoConfBuilder<'a> {
Ok(UtxoCoinConf {
ticker: self.ticker.to_owned(),
is_pos,
is_posv,
requires_notarization,
overwintered,
pub_addr_prefix,
Expand Down Expand Up @@ -266,6 +268,8 @@ impl<'a> UtxoConfBuilder<'a> {

fn is_pos(&self) -> bool { self.conf["isPoS"].as_u64() == Some(1) }

fn is_posv(&self) -> bool { self.conf["isPoSV"].as_u64() == Some(1) }

fn segwit(&self) -> bool { self.conf["segwit"].as_bool().unwrap_or(false) }

fn mtp_block_count(&self) -> NonZeroU64 {
Expand Down
2 changes: 2 additions & 0 deletions mm2src/coins/utxo/utxo_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1141,6 +1141,7 @@ pub async fn p2sh_spending_tx<T: UtxoCommonOps>(coin: &T, input: P2SHSpendingTxI
version_group_id: coin.as_ref().conf.version_group_id,
consensus_branch_id: coin.as_ref().conf.consensus_branch_id,
zcash: coin.as_ref().conf.zcash,
posv: coin.as_ref().conf.is_posv,
str_d_zeel,
hash_algo,
};
Expand Down Expand Up @@ -1170,6 +1171,7 @@ pub async fn p2sh_spending_tx<T: UtxoCommonOps>(coin: &T, input: P2SHSpendingTxI
join_split_sig: H512::default(),
join_split_pubkey: H256::default(),
zcash: coin.as_ref().conf.zcash,
posv: coin.as_ref().conf.is_posv,
str_d_zeel: unsigned.str_d_zeel,
tx_hash_algo: unsigned.hash_algo.into(),
})
Expand Down
1 change: 1 addition & 0 deletions mm2src/coins/utxo/utxo_common_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ pub(super) fn utxo_coin_fields_for_test(
UtxoCoinFields {
conf: UtxoCoinConf {
is_pos: false,
is_posv: false,
requires_notarization: false.into(),
overwintered: true,
segwit: true,
Expand Down
1 change: 1 addition & 0 deletions mm2src/coins/utxo_signer/src/sign_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub(crate) fn complete_tx(unsigned: TransactionInputSigner, signed_inputs: Vec<T
join_split_sig: H512::default(),
join_split_pubkey: H256::default(),
zcash: unsigned.zcash,
posv: unsigned.posv,
str_d_zeel: unsigned.str_d_zeel,
tx_hash_algo: unsigned.hash_algo.into(),
}
Expand Down
70 changes: 65 additions & 5 deletions mm2src/mm2_bitcoin/chain/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ pub struct Transaction {
pub join_split_sig: H512,
pub binding_sig: H512,
pub zcash: bool,
pub posv: bool,
/// https://github.com/navcoin/navcoin-core/blob/556250920fef9dc3eddd28996329ba316de5f909/src/primitives/transaction.h#L497
pub str_d_zeel: Option<String>,
pub tx_hash_algo: TxHashAlgo,
Expand Down Expand Up @@ -358,15 +359,23 @@ impl Serializable for Transaction {
stream.append(&self.version_group_id);
}

if let Some(n_time) = self.n_time {
stream.append(&n_time);
if !self.posv {
if let Some(n_time) = self.n_time {
stream.append(&n_time);
}
}

stream
.append_list(&self.inputs)
.append_list(&self.outputs)
.append(&self.lock_time);

if self.posv {
if let Some(n_time) = self.n_time {
stream.append(&n_time);
}
}

if self.overwintered && self.version >= 3 {
stream.append(&self.expiry_height);
if self.version >= 4 {
Expand Down Expand Up @@ -418,6 +427,11 @@ pub enum TxType {
StandardWithWitness,
Zcash,
PosWithNTime,
PosvWithNTime,
}

impl TxType {
fn uses_witness(&self) -> bool { matches!(self, TxType::StandardWithWitness | TxType::PosvWithNTime) }
}

pub fn deserialize_tx<T>(reader: &mut Reader<T>, tx_type: TxType) -> Result<Transaction, Error>
Expand All @@ -433,13 +447,13 @@ where
version_group_id = reader.read()?;
}

let n_time = if tx_type == TxType::PosWithNTime {
let mut n_time = if tx_type == TxType::PosWithNTime {
Some(reader.read()?)
} else {
None
};
let mut inputs: Vec<TransactionInput> = reader.read_list_max(MAX_LIST_SIZE)?;
let read_witness = if inputs.is_empty() && !overwintered && tx_type == TxType::StandardWithWitness {
let read_witness = if inputs.is_empty() && !overwintered && tx_type.uses_witness() {
let witness_flag: u8 = reader.read()?;
if witness_flag != WITNESS_FLAG {
return Err(Error::MalformedData);
Expand All @@ -451,7 +465,7 @@ where
false
};
let outputs = reader.read_list_max(MAX_LIST_SIZE)?;
if outputs.is_empty() && tx_type == TxType::StandardWithWitness {
if outputs.is_empty() && tx_type.uses_witness() {
return Err(Error::Custom("Transaction has no output".into()));
}
if read_witness {
Expand All @@ -462,6 +476,14 @@ where

let lock_time = reader.read()?;

let mut posv = false;
n_time = if tx_type == TxType::PosvWithNTime {
posv = true;
Some(reader.read()?)
} else {
n_time
};

let mut expiry_height = 0;
let mut value_balance = 0;
let mut shielded_spends = vec![];
Expand Down Expand Up @@ -528,6 +550,7 @@ where
shielded_spends,
shielded_outputs,
zcash,
posv,
str_d_zeel,
tx_hash_algo: TxHashAlgo::DSHA256,
})
Expand All @@ -545,6 +568,9 @@ impl Deserializable for Transaction {
// specific use case
let mut buffer = vec![];
reader.read_to_end(&mut buffer)?;
if let Ok(t) = deserialize_tx(&mut Reader::from_read(buffer.as_slice()), TxType::PosvWithNTime) {
return Ok(t);
}
if let Ok(t) = deserialize_tx(&mut Reader::from_read(buffer.as_slice()), TxType::StandardWithWitness) {
return Ok(t);
}
Expand Down Expand Up @@ -836,6 +862,7 @@ mod tests {
}],
lock_time: 0x00000011,
zcash: false,
posv: false,
str_d_zeel: None,
tx_hash_algo: TxHashAlgo::DSHA256,
};
Expand Down Expand Up @@ -1021,6 +1048,7 @@ mod tests {
}],
lock_time: 1632875267,
zcash: false,
posv: false,
str_d_zeel: None,
tx_hash_algo: TxHashAlgo::DSHA256,
};
Expand All @@ -1034,4 +1062,36 @@ mod tests {
let ext_tx = ExtTransaction::from(tx.clone());
assert_eq!(tx.hash().reversed().to_string(), ext_tx.txid().to_string());
}

#[test]
fn n_time_posv_transaction() {
let raw = "0200000001fa402b05b9108ec4762247d74c48a2ff303dd832d24c341c486e32cef0434177010000004847304402207a5283cc0fe6fc384744545cb600206ec730d0cdfa6a5e1479cb509fda536ee402202bec1e79b90638f1c608d805b2877fefc8fa6d0df279f58f0a70883e0e0609ce01ffffffff030000000000000000006a734110a10a0000232102fa0ecb032c7cb7be378efd03a84532b5cf1795996bfad854f042dc521616bfdcacd57f643201000000232103c8fc5c87f00bcc32b5ce5c036957f8befeff05bf4d88d2dcde720249f78d9313ac00000000dfcb3c64";
let t: Transaction = raw.into();

assert_eq!(t.version, 2);
assert_eq!(t.lock_time, 0);
assert_eq!(t.inputs.len(), 1);
assert_eq!(t.outputs.len(), 3);
assert_eq!(t.n_time, Some(1681705951));
assert!(t.posv);

let serialized = serialize(&t);
assert_eq!(Bytes::from(raw), serialized);
}

#[test]
fn n_time_posv_transaction_locktime() {
let raw = "0200000001a471828d6290f5ca7935bc26a9d07cda37227ca3fb0e8d1282296ea839c134810100000048473044022067bbc1176fe4fa8681db854d9fce8d47d5613d01820ef78a6ab76c4f067500990220560d00098fcb69eb8587873f8072c11bcf832308fdcf5712d0e4aea4b761060e01fdffffff02940237c31d0600001976a9147fb8384a3f328148137447cdf6b1c3c1f0a8559588ac00e40b54020000001976a91485ee21a7f8cdd9034fb55004e0d8ed27db1c03c288acbd8d05002b584e63";
let t: Transaction = raw.into();

assert_eq!(t.version, 2);
assert_eq!(t.lock_time, 363965);
assert_eq!(t.inputs.len(), 1);
assert_eq!(t.outputs.len(), 2);
assert_eq!(t.n_time, Some(1666078763));
assert!(t.posv);

let serialized = serialize(&t);
assert_eq!(Bytes::from(raw), serialized);
}
}
62 changes: 61 additions & 1 deletion mm2src/mm2_bitcoin/script/src/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ pub struct TransactionInputSigner {
pub shielded_spends: Vec<ShieldedSpend>,
pub shielded_outputs: Vec<ShieldedOutput>,
pub zcash: bool,
pub posv: bool,
pub str_d_zeel: Option<String>,
pub hash_algo: SignerHashAlgo,
}
Expand All @@ -181,6 +182,7 @@ impl From<Transaction> for TransactionInputSigner {
shielded_spends: t.shielded_spends.clone(),
shielded_outputs: t.shielded_outputs.clone(),
zcash: t.zcash,
posv: t.posv,
str_d_zeel: t.str_d_zeel,
hash_algo: t.tx_hash_algo.into(),
}
Expand Down Expand Up @@ -214,6 +216,7 @@ impl From<TransactionInputSigner> for Transaction {
shielded_spends: t.shielded_spends.clone(),
shielded_outputs: t.shielded_outputs.clone(),
zcash: t.zcash,
posv: t.posv,
binding_sig: H512::default(),
join_split_pubkey: H256::default(),
join_split_sig: H512::default(),
Expand Down Expand Up @@ -344,11 +347,14 @@ impl TransactionInputSigner {
SighashBase::None => Vec::new(),
};

// PoSV transactions have n_time truncated when creating signed inputs
let n_time: Option<u32> = if self.posv { None } else { self.n_time };

let tx = Transaction {
inputs,
outputs,
version: self.version,
n_time: self.n_time,
n_time,
lock_time: self.lock_time,
binding_sig: H512::default(),
expiry_height: 0,
Expand All @@ -361,6 +367,7 @@ impl TransactionInputSigner {
value_balance: 0,
version_group_id: 0,
zcash: self.zcash,
posv: self.posv,
str_d_zeel: self.str_d_zeel.clone(),
tx_hash_algo: self.hash_algo.into(),
};
Expand Down Expand Up @@ -658,6 +665,59 @@ mod tests {
shielded_spends: vec![],
shielded_outputs: vec![],
zcash: false,
posv: false,
str_d_zeel: None,
hash_algo: SignerHashAlgo::DSHA256,
};

let hash = input_signer.signature_hash(0, 0, &previous_output, SignatureVersion::Base, SighashBase::All.into());
assert_eq!(hash, expected_signature_hash);
}

#[test]
fn test_signature_hash_posv() {
let _private: Private = "cSQJp8ymcCUZCceowcTr5L1rr7tRbeB8uj3pDFRfRaMuRP6yDqfa".into();
let previous_tx_hash =
H256::from_reversed_str("0bc54ed426950f50bf2c2776034a03592e844757b42330eb908eb04492dad2c6");
let previous_output_index = 1;
let to: Address = "msj7SEQmH7pUCUx8YU6R87DrAHYzcABdzw".into();
assert!(to.hash.is_address_hash());
let previous_output = "76a914df3bd30160e6c6145baaf2c88a8844c13a00d1d588ac".into();
let current_output: Bytes = "76a91485ee21a7f8cdd9034fb55004e0d8ed27db1c03c288ac".into();
let value = 100000000;
let expected_signature_hash: H256 = "21d91397ba4e4bfaf73584300804cf9f9fd11cabe43f1bb38f7986cea5ef5519".into();

let unsigned_input = UnsignedTransactionInput {
sequence: 0xffff_ffff,
previous_output: OutPoint {
index: previous_output_index,
hash: previous_tx_hash,
},
amount: 100,
witness: vec![Vec::new()],
};

let output = TransactionOutput {
value,
script_pubkey: current_output,
};

let input_signer = TransactionInputSigner {
version: 2,
n_time: Some(1682050928),
overwintered: false,
version_group_id: 0,
consensus_branch_id: 0,
expiry_height: 0,
value_balance: 0,
lock_time: 0,
inputs: vec![unsigned_input],
outputs: vec![output],
join_splits: vec![],
shielded_spends: vec![],
shielded_outputs: vec![],
zcash: false,
posv: true,
str_d_zeel: None,
hash_algo: SignerHashAlgo::DSHA256,
};
Expand Down