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

fix: fixed the issue of Legacy and Nested Segwit transaction failure [R2D2-12387] #128

Merged
merged 3 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion imkey-core/ikc-device/src/device_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ pub fn bind_test() {
// pub const TEST_KEY_PATH: &str = "/tmp/";
// pub const TEST_BIND_CODE: &str = "MCYNK5AH";
pub const TEST_KEY_PATH: &str = "/tmp/";
pub const TEST_BIND_CODE: &str = "5PJT7223";
pub const TEST_BIND_CODE: &str = "CM3SH5QE";

#[cfg(test)]
mod test {
Expand Down
19 changes: 18 additions & 1 deletion imkey-core/ikc-device/src/device_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,20 @@ pub fn is_bl_status() -> Result<bool> {
Ok(true)
}

pub fn get_btc_apple_version() -> Result<String> {
select_isd()?;
let res = send_apdu("00a4040005695f62746300".to_string())?;
ApduCheck::check_response(res.as_str())?;
let btc_version = hex::decode(&res[0..(res.len() - 4)])?;
let btc_version = String::from_utf8(btc_version)?;
Ok(btc_version)
}

#[cfg(test)]
mod test {
use crate::device_manager::{
active_device, app_delete, app_download, app_update, bind_check, is_bl_status,
active_device, app_delete, app_download, app_update, bind_check, get_btc_apple_version,
is_bl_status,
};
use ikc_common::constants;
use ikc_transport::hid_api::hid_connect;
Expand Down Expand Up @@ -296,4 +306,11 @@ mod test {
let result = active_device();
assert!(result.is_ok());
}

#[test]
fn get_btc_version_test() {
assert!(hid_connect(constants::DEVICE_MODEL_NAME).is_ok());
let result = get_btc_apple_version();
assert!(result.is_ok());
}
}
220 changes: 193 additions & 27 deletions imkey-core/ikc-wallet/coin-bitcoin/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ use bitcoin_hashes::hash160;
use bitcoin_hashes::hex::ToHex;
use bitcoin_hashes::Hash;
use ikc_common::apdu::{ApduCheck, BtcApdu};
use ikc_common::constants::{MAX_OPRETURN_SIZE, MAX_UTXO_NUMBER, MIN_NONDUST_OUTPUT, TIMEOUT_LONG};
use ikc_common::constants::{
BTC_SEG_WIT_TYPE_P2WPKH, EACH_ROUND_NUMBER, MAX_OPRETURN_SIZE, MAX_UTXO_NUMBER,
MIN_NONDUST_OUTPUT, TIMEOUT_LONG,
};
use ikc_common::error::CoinError;
use ikc_common::path::{check_path_validity, get_account_path};
use ikc_common::utility::{bigint_to_byte_vec, hex_to_bytes, secp256k1_sign};
use ikc_device::device_binding::KEY_MANAGER;
use ikc_device::device_manager::get_btc_apple_version;
use ikc_transport::message::{send_apdu, send_apdu_timeout};
use secp256k1::ecdsa::Signature;
use secp256k1::PublicKey;
Expand Down Expand Up @@ -66,6 +70,9 @@ impl BtcTransaction {
return Err(CoinError::ImkeyInsufficientFunds.into());
}

//get current btc applet version
let btc_version = get_btc_apple_version()?;

//utxo address verify
let utxo_pub_key_vec = get_utxo_pub_key(&self.unspents)?;

Expand All @@ -78,29 +85,62 @@ impl BtcTransaction {
output,
};

self.calc_tx_hash(&mut tx_to_sign)?;

self.tx_preview(&tx_to_sign, network)?;

let input_with_sigs: Vec<TxIn> = vec![];
for (idx, utxo) in self.unspents.iter().enumerate() {
let script = Script::from_str(&utxo.script_pubkey)?;
if script.is_p2pkh() {
self.sign_p2pkh_input(idx, &utxo_pub_key_vec[idx], &mut tx_to_sign)?;
} else if script.is_p2sh() {
self.sign_p2sh_nested_p2wpkh_input(idx, &utxo_pub_key_vec[idx], &mut tx_to_sign)?;
} else if script.is_v0_p2wpkh() {
self.sign_p2wpkh_input(idx, &utxo_pub_key_vec[idx], &mut tx_to_sign)?;
} else if script.is_v1_p2tr() {
self.sign_p2tr_input(
idx,
&utxo_pub_key_vec[idx],
&mut tx_to_sign,
SchnorrSighashType::Default,
)?;
} else {
return Err(CoinError::InvalidUtxo.into());
};
self.calc_tx_hash(&mut tx_to_sign, &btc_version)?;

//Compatible with Legacy and Nested Segwit transactions without upgrading btc apples
if btc_version.as_str() >= "1.6.00" {
self.tx_preview(&tx_to_sign, network)?;
for (idx, utxo) in self.unspents.iter().enumerate() {
let script = Script::from_str(&utxo.script_pubkey)?;
if script.is_p2pkh() {
self.sign_p2pkh_input(idx, &utxo_pub_key_vec[idx], &mut tx_to_sign)?;
} else if script.is_p2sh() {
self.sign_p2sh_nested_p2wpkh_input(
idx,
&utxo_pub_key_vec[idx],
&mut tx_to_sign,
)?;
} else if script.is_v0_p2wpkh() {
self.sign_p2wpkh_input(idx, &utxo_pub_key_vec[idx], &mut tx_to_sign)?;
} else if script.is_v1_p2tr() {
self.sign_p2tr_input(
idx,
&utxo_pub_key_vec[idx],
&mut tx_to_sign,
SchnorrSighashType::Default,
)?;
} else {
return Err(CoinError::InvalidUtxo.into());
};
}
} else {
for utxo in self.unspents.iter() {
let script = Script::from_str(&utxo.script_pubkey)?;
if !script.is_p2pkh() && !script.is_p2sh() {
return Err(CoinError::InvalidUtxo.into());
}
}
let address_version = get_address_version(network, &self.to.to_string())?;
match address_version {
0 | 111 | 5 | 96 => {
if BTC_SEG_WIT_TYPE_P2WPKH.eq(&seg_wit.to_uppercase()) {
self.original_tx_preview(&tx_to_sign, network)?;
for (idx, _) in self.unspents.iter().enumerate() {
self.sign_p2sh_nested_p2wpkh_input(
idx,
&utxo_pub_key_vec[idx],
&mut tx_to_sign,
)?;
}
} else {
self.tx_preview(&tx_to_sign, network)?;
self.sign_p2pkh_inputs(&utxo_pub_key_vec, &mut tx_to_sign)?;
}
}
_ => {
return Err(CoinError::InvalidAddress.into());
}
}
}

let tx_bytes = serialize(&tx_to_sign);
Expand All @@ -112,6 +152,76 @@ impl BtcTransaction {
})
}

pub fn sign_p2pkh_inputs(
&self,
utxo_pub_key_vec: &Vec<String>,
transaction: &mut Transaction,
) -> Result<()> {
let mut lock_script_ver: Vec<Script> = vec![];
let count = (self.unspents.len() - 1) / EACH_ROUND_NUMBER + 1;
for i in 0..count {
for (x, temp_utxo) in self.unspents.iter().enumerate() {
let mut input_data_vec = vec![];
input_data_vec.push(x as u8);

let mut temp_serialize_txin = TxIn {
previous_output: OutPoint {
txid: bitcoin::hash_types::Txid::from_hex(temp_utxo.txhash.as_str())?,
vout: temp_utxo.vout as u32,
},
script_sig: Script::default(),
sequence: Sequence::MAX,
witness: Witness::default(),
};
if (x >= i * EACH_ROUND_NUMBER) && (x < (i + 1) * EACH_ROUND_NUMBER) {
temp_serialize_txin.script_sig =
Script::from(Vec::from_hex(temp_utxo.script_pubkey.as_str())?);
}
input_data_vec.extend_from_slice(serialize(&temp_serialize_txin).as_slice());
let btc_perpare_apdu = BtcApdu::btc_perpare_input(0x80, &input_data_vec);
//send perpare apdu to device
ApduCheck::check_response(&send_apdu(btc_perpare_apdu)?)?;
}
for y in i * EACH_ROUND_NUMBER..(i + 1) * EACH_ROUND_NUMBER {
if y >= utxo_pub_key_vec.len() {
break;
}
let btc_sign_apdu = BtcApdu::btc_sign(
y as u8,
EcdsaSighashType::All.to_u32() as u8,
self.unspents.get(y).unwrap().derive_path.as_str(),
);
//sign data
let btc_sign_apdu_return = send_apdu(btc_sign_apdu)?;
ApduCheck::check_response(&btc_sign_apdu_return)?;
let btc_sign_apdu_return =
&btc_sign_apdu_return[..btc_sign_apdu_return.len() - 4].to_string();
let sign_result_str =
btc_sign_apdu_return[2..btc_sign_apdu_return.len() - 2].to_string();

lock_script_ver.push(self.build_unlock_script(
sign_result_str.as_str(),
utxo_pub_key_vec.get(y).unwrap(),
)?)
}
}
let mut txinputs: Vec<TxIn> = Vec::new();
for (index, unspent) in self.unspents.iter().enumerate() {
let txin = TxIn {
previous_output: OutPoint {
txid: bitcoin::hash_types::Txid::from_hex(&unspent.txhash)?,
vout: unspent.vout as u32,
},
script_sig: lock_script_ver.get(index).unwrap().clone(),
sequence: Sequence::MAX,
witness: Witness::default(),
};
txinputs.push(txin);
}
transaction.input = txinputs;
Ok(())
}

fn sign_p2pkh_input(
&self,
idx: usize,
Expand Down Expand Up @@ -462,7 +572,11 @@ impl BtcTransaction {
Ok(outputs)
}

pub fn calc_tx_hash(&self, transaction: &mut Transaction) -> Result<()> {
pub fn calc_tx_hash(
&self,
transaction: &mut Transaction,
btc_applet_version: &str,
) -> Result<()> {
let mut txhash_vout_vec = vec![];
let mut sequence_vec = vec![];
let mut amount_vec = vec![];
Expand Down Expand Up @@ -491,8 +605,10 @@ impl BtcTransaction {
let mut calc_hash_apdu = vec![];
calc_hash_apdu.extend(BtcApdu::btc_prepare(0x31, 0x40, &txhash_vout_vec));
calc_hash_apdu.extend(BtcApdu::btc_prepare(0x31, 0x80, &sequence_vec));
calc_hash_apdu.extend(BtcApdu::btc_prepare(0x31, 0x20, &amount_vec));
calc_hash_apdu.extend(BtcApdu::btc_prepare(0x31, 0x21, &script_pubkeys_vec));
if btc_applet_version >= "1.6.00" {
calc_hash_apdu.extend(BtcApdu::btc_prepare(0x31, 0x20, &amount_vec));
calc_hash_apdu.extend(BtcApdu::btc_prepare(0x31, 0x21, &script_pubkeys_vec));
}
for apdu in calc_hash_apdu {
ApduCheck::check_response(&send_apdu(apdu)?)?;
}
Expand Down Expand Up @@ -544,6 +660,56 @@ impl BtcTransaction {

Ok(())
}

/**
*original Nested Segwit transaction preview
**/
pub fn original_tx_preview(&self, transaction: &Transaction, network: Network) -> Result<()> {
let mut output_serialize_data = serialize(&transaction);

output_serialize_data.remove(5);
output_serialize_data.remove(5);

//add sign type
let mut encoder_hash = Vec::new();
let len = EcdsaSighashType::All
.to_u32()
.consensus_encode(&mut encoder_hash)
.unwrap();
debug_assert_eq!(len, encoder_hash.len());
output_serialize_data.extend(encoder_hash);

//set input number
output_serialize_data.remove(4);
output_serialize_data.insert(4, self.unspents.len() as u8);

//add fee amount
output_serialize_data.extend(bigint_to_byte_vec(self.fee));

//add address version
let address_version = get_address_version(network, self.to.to_string().as_str())?;
output_serialize_data.push(address_version);

//set 01 tag and length
output_serialize_data.insert(0, output_serialize_data.len() as u8);
output_serialize_data.insert(0, 0x01);

//use local private key sign data
let key_manager_obj = KEY_MANAGER.lock();
let mut output_pareper_data =
secp256k1_sign(&key_manager_obj.pri_key, &output_serialize_data)?;
output_pareper_data.insert(0, output_pareper_data.len() as u8);
output_pareper_data.insert(0, 0x00);
output_pareper_data.extend(output_serialize_data.iter());

let btc_prepare_apdu_vec = BtcApdu::btc_prepare(0x31, 0x00, &output_pareper_data);
//send output pareper command
for temp_str in btc_prepare_apdu_vec {
ApduCheck::check_response(&send_apdu_timeout(temp_str, TIMEOUT_LONG)?)?;
}

Ok(())
}
}

#[cfg(test)]
Expand Down
2 changes: 1 addition & 1 deletion imkey-core/ikc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1863,7 +1863,7 @@ mod tests {
let bind_result: BindCheckRes = BindCheckRes::decode(ret_bytes.as_slice()).unwrap();
if "bound_other".eq(&bind_result.bind_status) {
let param = BindAcquireReq {
bind_code: "5PJT7223".to_string(),
bind_code: "CM3SH5QE".to_string(),
};
let action: ImkeyAction = ImkeyAction {
method: "bind_acquire".to_string(),
Expand Down
Loading