diff --git a/Cargo.toml b/Cargo.toml index 35bf0a04..d2379a9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,11 @@ parking_lot = "0.12" lru = "0.7.1" dashmap = "5.4" dyn-clone = "1.0" +faster-hex = "0.9.0" +ripemd = "0.1.3" +sha2 = "0.10.6" +ed25519-dalek = "2.1.1" +openssl = "0.10" ckb-types = "0.115.0-rc2" ckb-dao-utils = "0.115.0-rc2" diff --git a/src/constants.rs b/src/constants.rs index 0ed38ce3..5d453210 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -50,6 +50,11 @@ pub const ACP_TYPE_HASH_AGGRON: H256 = /// cheque withdraw since value pub const CHEQUE_CELL_SINCE: u64 = 0xA000000000000006; +pub const COMMON_PREFIX: &str = "CKB transaction: 0x"; +pub const BTC_PREFIX: &str = "CKB (Bitcoin Layer) transaction: 0x"; + +pub const SECP256K1_TAG_PUBKEY_UNCOMPRESSED: u8 = 0x04; + #[cfg(test)] mod test { use super::*; diff --git a/src/tests/transaction/omnilock.rs b/src/tests/transaction/omnilock.rs index 2e070905..d8be48dc 100644 --- a/src/tests/transaction/omnilock.rs +++ b/src/tests/transaction/omnilock.rs @@ -1,3 +1,5 @@ +use std::convert::TryInto; + use ckb_crypto::secp::{Pubkey, SECP256K1}; use ckb_hash::blake2b_256; use ckb_types::{ @@ -66,183 +68,240 @@ fn test_omnilock_config(omnilock_outpoint: OutPoint) -> TransactionBuilderConfig #[test] fn test_omnilock_ethereum() { - omnilock_ethereum(false); - omnilock_ethereum(true) + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + let mut cfg = OmniLockConfig::new_ethereum(keccak160(Pubkey::from(pubkey).as_ref())); + let sign_context = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg, &sign_context_2); } -fn omnilock_ethereum(cobuild: bool) { - let network_info = NetworkInfo::testnet(); +#[test] +fn test_omnilock_pubkeyhash() { let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); - let mut cfg = OmniLockConfig::new_ethereum(keccak160(Pubkey::from(pubkey).as_ref())); - cfg.enable_cobuild(cobuild); + let mut cfg = OmniLockConfig::new_pubkey_hash(blake160(&pubkey.serialize())); - let sender = build_omnilock_script(&cfg); - let receiver = build_sighash_script(ACCOUNT2_ARG); + let sign_context = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg.clone(), &sign_context); - let (ctx, mut outpoints) = init_context( - vec![(OMNILOCK_BIN, true)], - vec![ - (sender.clone(), Some(100 * ONE_CKB)), - (sender.clone(), Some(200 * ONE_CKB)), - (sender.clone(), Some(300 * ONE_CKB)), - ], - ); + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg, &sign_context_2); +} - let configuration = test_omnilock_config(outpoints.pop().unwrap()); +#[test] +fn test_omnilock_multisign() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let account1_key = secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap(); + let lock_args = vec![ + ACCOUNT0_ARG.clone(), + ACCOUNT1_ARG.clone(), + ACCOUNT2_ARG.clone(), + ]; + let multi_cfg = MultisigConfig::new_with(lock_args, 0, 2).unwrap(); + let mut cfg = OmniLockConfig::new_multisig(multi_cfg); + let sign_context = SignContexts::new_omnilock(vec![account0_key, account1_key], cfg.clone()); + omnilock_test(cfg.clone(), &sign_context); - let iterator = InputIterator::new_with_cell_collector( - vec![sender.clone()], - Box::new(ctx.to_live_cells_context()) as Box<_>, - ); - let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock(vec![account0_key, account1_key], cfg.clone()); + omnilock_test(cfg, &sign_context_2); +} - let output = CellOutput::new_builder() - .capacity((120 * ONE_CKB).pack()) - .lock(receiver) - .build(); - builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default()); - builder.set_change_lock(sender.clone()); +#[test] +fn test_omnilock_ethereum_display() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + let mut cfg = OmniLockConfig::new_ethereum_display(keccak160(Pubkey::from(pubkey).as_ref())); + let sign_context = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg.clone(), &sign_context); - let context = OmnilockScriptContext::new(cfg.clone(), network_info.url.clone()); - let mut contexts = HandlerContexts::default(); - contexts.add_context(Box::new(context) as Box<_>); + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg, &sign_context_2); +} - let mut tx_with_groups = builder.build(&contexts).expect("build failed"); +#[test] +fn test_omnilock_btc() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + // uncompressed + let mut cfg = OmniLockConfig::new_btc_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ); + let sign_context = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg.clone(), &sign_context); - // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); - // println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg, &sign_context_2); - TransactionSigner::new(&network_info) - // use unitest lock to verify - .insert_unlocker( - crate::ScriptId::new_data1(H256::from(blake2b_256(OMNILOCK_BIN))), - crate::transaction::signer::omnilock::OmnilockSigner {}, - ) - .sign_transaction( - &mut tx_with_groups, - &SignContexts::new_omnilock(vec![account0_key], cfg), - ) - .unwrap(); + // compressed + let mut cfg = OmniLockConfig::new_btc_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ); + let sign_context = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg.clone(), &sign_context); - // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); - // println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg, &sign_context_2); - let tx = tx_with_groups.get_tx_view().clone(); - let script_groups = tx_with_groups.script_groups.clone(); - assert_eq!(script_groups.len(), 1); - assert_eq!(tx.header_deps().len(), 0); - assert_eq!(tx.cell_deps().len(), 2); - assert_eq!(tx.inputs().len(), 2); - for out_point in tx.input_pts_iter() { - assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); - } - assert_eq!(tx.outputs().len(), 2); - assert_eq!(tx.output(0).unwrap(), output); - assert_eq!(tx.output(1).unwrap().lock(), sender); - let change_capacity: u64 = tx.output(1).unwrap().capacity().unpack(); - let fee = (100 + 200 - 120) * ONE_CKB - change_capacity; - assert_eq!(tx.data().as_reader().serialized_size_in_block() as u64, fee); + // segwitp2sh + let mut cfg = OmniLockConfig::new_btc_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::SegwitP2SH, + ); + let sign_context = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg.clone(), &sign_context); - ctx.verify(tx, FEE_RATE).unwrap(); -} + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg, &sign_context_2); -#[test] -fn test_omnilock_pubkeyhash() { - omnilock_pubkeyhash(false); - omnilock_pubkeyhash(true) + // segwitbech32 + let mut cfg = OmniLockConfig::new_btc_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::SegwitBech32, + ); + let sign_context = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg, &sign_context_2); } -fn omnilock_pubkeyhash(cobuild: bool) { - let network_info = NetworkInfo::testnet(); +#[test] +fn test_omnilock_dog() { let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); - let mut cfg = OmniLockConfig::new_pubkey_hash(blake160(&pubkey.serialize())); - cfg.enable_cobuild(cobuild); - let sender = build_omnilock_script(&cfg); - let receiver = build_sighash_script(ACCOUNT2_ARG); - let (ctx, mut outpoints) = init_context( - vec![(OMNILOCK_BIN, true)], - vec![ - (sender.clone(), Some(100 * ONE_CKB)), - (sender.clone(), Some(200 * ONE_CKB)), - (sender.clone(), Some(300 * ONE_CKB)), - ], + // uncompress + let mut cfg = OmniLockConfig::new_dogcoin_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, ); + let sign_context = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg.clone(), &sign_context); - let configuration = test_omnilock_config(outpoints.pop().unwrap()); + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg, &sign_context_2); - let iterator = InputIterator::new_with_cell_collector( - vec![sender.clone()], - Box::new(ctx.to_live_cells_context()) as Box<_>, + // compress + let mut cfg = OmniLockConfig::new_dogcoin_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, ); - let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + let sign_context = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg.clone(), &sign_context); - let output = CellOutput::new_builder() - .capacity((120 * ONE_CKB).pack()) - .lock(receiver) - .build(); - builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default()); - builder.set_change_lock(sender.clone()); + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg, &sign_context_2); - let context = OmnilockScriptContext::new(cfg.clone(), network_info.url.clone()); - let mut contexts = HandlerContexts::default(); - contexts.add_context(Box::new(context) as Box<_>); + // SegwitP2SH + let mut cfg = OmniLockConfig::new_dogcoin_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::SegwitP2SH, + ); + let sign_context = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg.clone(), &sign_context); - let mut tx_with_groups = builder.build(&contexts).expect("build failed"); + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg, &sign_context_2); - // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + // SegwitBech32 + let mut cfg = OmniLockConfig::new_dogcoin_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::SegwitBech32, + ); + let sign_context = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg.clone(), &sign_context); - TransactionSigner::new(&network_info) - // use unitest lock to verify - .insert_unlocker( - crate::ScriptId::new_data1(H256::from(blake2b_256(OMNILOCK_BIN))), - crate::transaction::signer::omnilock::OmnilockSigner {}, - ) - .sign_transaction( - &mut tx_with_groups, - &SignContexts::new_omnilock(vec![account0_key], cfg), - ) - .unwrap(); + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg, &sign_context_2); +} - let tx = tx_with_groups.get_tx_view().clone(); - let script_groups = tx_with_groups.script_groups.clone(); - assert_eq!(script_groups.len(), 1); - assert_eq!(tx.header_deps().len(), 0); - assert_eq!(tx.cell_deps().len(), 2); - assert_eq!(tx.inputs().len(), 2); - for out_point in tx.input_pts_iter() { - assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); - } - assert_eq!(tx.outputs().len(), 2); - assert_eq!(tx.output(0).unwrap(), output); - assert_eq!(tx.output(1).unwrap().lock(), sender); - let change_capacity: u64 = tx.output(1).unwrap().capacity().unpack(); - let fee = (100 + 200 - 120) * ONE_CKB - change_capacity; - assert_eq!(tx.data().as_reader().serialized_size_in_block() as u64, fee); +#[test] +fn test_omnilock_eos() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); - ctx.verify(tx, FEE_RATE).unwrap(); + // uncompressed + let mut cfg = OmniLockConfig::new_eos_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ); + let sign_context = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg, &sign_context_2); + + // compressed + let mut cfg = OmniLockConfig::new_eos_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ); + let sign_context = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg, &sign_context_2); } #[test] -fn test_omnilock_multisign() { - omnilock_multisign(false); - omnilock_multisign(true) +fn test_omnilock_tron() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + let mut cfg = OmniLockConfig::new_tron_from_pubkey(&Pubkey::from(pubkey)); + let sign_context = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg, &sign_context_2); } -fn omnilock_multisign(cobuild: bool) { - let network_info = NetworkInfo::testnet(); +#[test] +fn test_omnilock_solana() { + let account0_key = + ed25519_dalek::SigningKey::from_bytes(&ACCOUNT0_KEY.as_bytes().try_into().unwrap()); + let pubkey = account0_key.verifying_key(); + let mut cfg = OmniLockConfig::new_solana_from_pubkey(&pubkey); + let sign_context = SignContexts::new_omnilock_solana(account0_key.clone(), cfg.clone()); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock_solana(account0_key, cfg.clone()); + omnilock_test(cfg, &sign_context_2); +} + +#[ignore] +#[test] +fn test_omnilock_owner() { let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); - let account1_key = secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap(); - let lock_args = vec![ - ACCOUNT0_ARG.clone(), - ACCOUNT1_ARG.clone(), - ACCOUNT2_ARG.clone(), - ]; - let multi_cfg = MultisigConfig::new_with(lock_args, 0, 2).unwrap(); - let mut cfg = OmniLockConfig::new_multisig(multi_cfg); - cfg.enable_cobuild(cobuild); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + let cfg = OmniLockConfig::new_ownerlock(blake160(&Pubkey::from(pubkey).serialize())); + // cfg.enable_cobuild(true); + let sign_context = SignContexts::new_omnilock(vec![account0_key], cfg.clone()); + omnilock_test(cfg, &sign_context) +} + +fn omnilock_test(cfg: OmniLockConfig, sign_context: &SignContexts) { + let network_info = NetworkInfo::testnet(); let sender = build_omnilock_script(&cfg); let receiver = build_sighash_script(ACCOUNT2_ARG); @@ -278,6 +337,7 @@ fn omnilock_multisign(cobuild: bool) { let mut tx_with_groups = builder.build(&contexts).expect("build failed"); // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + // println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); TransactionSigner::new(&network_info) // use unitest lock to verify @@ -285,12 +345,12 @@ fn omnilock_multisign(cobuild: bool) { crate::ScriptId::new_data1(H256::from(blake2b_256(OMNILOCK_BIN))), crate::transaction::signer::omnilock::OmnilockSigner {}, ) - .sign_transaction( - &mut tx_with_groups, - &SignContexts::new_omnilock(vec![account0_key, account1_key], cfg), - ) + .sign_transaction(&mut tx_with_groups, &sign_context) .unwrap(); + // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + // println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + let tx = tx_with_groups.get_tx_view().clone(); let script_groups = tx_with_groups.script_groups.clone(); assert_eq!(script_groups.len(), 1); diff --git a/src/traits/default_impls.rs b/src/traits/default_impls.rs index dfde0c54..fbcc4ec6 100644 --- a/src/traits/default_impls.rs +++ b/src/traits/default_impls.rs @@ -5,6 +5,7 @@ use std::time::Duration; use anyhow::anyhow; use ckb_crypto::secp::Pubkey; +use ed25519_dalek::ed25519::signature::SignerMut; use lru::LruCache; use parking_lot::Mutex; use thiserror::Error; @@ -18,20 +19,15 @@ use ckb_types::{ prelude::*, H160, }; +use openssl::pkey::{PKey, Private}; use super::{ offchain_impls::CollectResult, OffchainCellCollector, OffchainCellDepResolver, OffchainTransactionDependencyProvider, }; -use crate::rpc::ckb_indexer::{Order, SearchKey, Tip}; -use crate::rpc::{CkbRpcClient, IndexerRpcClient}; -use crate::traits::{ - CellCollector, CellCollectorError, CellDepResolver, CellQueryOptions, HeaderDepResolver, - LiveCell, QueryOrder, Signer, SignerError, TransactionDependencyError, - TransactionDependencyProvider, +use crate::util::{ + get_max_mature_number, iso9796_2_batch_sign, serialize_signature, zeroize_privkey, }; -use crate::types::ScriptId; -use crate::util::{get_max_mature_number, serialize_signature, zeroize_privkey}; use crate::SECP256K1; use crate::{ constants::{ @@ -40,6 +36,24 @@ use crate::{ }, util::keccak160, }; +use crate::{ + rpc::ckb_indexer::{Order, SearchKey, Tip}, + unlock::omni_lock::BTCSignVtype, + util::btc_auth, +}; +use crate::{ + rpc::{CkbRpcClient, IndexerRpcClient}, + util::eos_auth, +}; +use crate::{ + traits::{ + CellCollector, CellCollectorError, CellDepResolver, CellQueryOptions, HeaderDepResolver, + LiveCell, QueryOrder, Signer, SignerError, TransactionDependencyError, + TransactionDependencyProvider, + }, + util::blake160, +}; +use crate::{types::ScriptId, util::rsa_sign}; use ckb_resource::{ CODE_HASH_DAO, CODE_HASH_SECP256K1_BLAKE160_MULTISIG_ALL, CODE_HASH_SECP256K1_BLAKE160_SIGHASH_ALL, @@ -79,7 +93,7 @@ impl DefaultCellDepResolver { .map(|(tx_index, tx)| { tx.outputs() .into_iter() - .zip(tx.outputs_data().into_iter()) + .zip(tx.outputs_data()) .enumerate() .map(|(index, (output, data))| { if tx_index == SIGHASH_OUTPUT_LOC.0 && index == SIGHASH_OUTPUT_LOC.1 { @@ -569,7 +583,7 @@ impl TransactionDependencyProvider for DefaultTransactionDependencyProvider { } } -/// A signer use secp256k1 raw key, the id is `blake160(pubkey)`. +/// A signer use secp256k1 raw key. #[derive(Default, Clone)] pub struct SecpCkbRawKeySigner { keys: HashMap, @@ -579,6 +593,7 @@ impl SecpCkbRawKeySigner { pub fn new(keys: HashMap) -> SecpCkbRawKeySigner { SecpCkbRawKeySigner { keys } } + pub fn new_with_secret_keys(keys: Vec) -> SecpCkbRawKeySigner { let mut signer = SecpCkbRawKeySigner::default(); for key in keys { @@ -586,6 +601,7 @@ impl SecpCkbRawKeySigner { } signer } + pub fn add_secret_key(&mut self, key: secp256k1::SecretKey) { let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &key); let hash160 = H160::from_slice(&blake2b_256(&pubkey.serialize()[..])[0..20]) @@ -607,6 +623,34 @@ impl SecpCkbRawKeySigner { let hash160 = keccak160(Pubkey::from(pubkey).as_ref()); self.keys.insert(hash160, key); } + + pub fn new_with_btc_secret_key(keys: Vec, vtype: BTCSignVtype) -> Self { + let mut signer = SecpCkbRawKeySigner::default(); + for key in keys { + signer.add_btc_secret_key(key, vtype); + } + signer + } + + pub fn add_btc_secret_key(&mut self, key: secp256k1::SecretKey, vtype: BTCSignVtype) { + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &key); + let hash160 = btc_auth(&pubkey.into(), vtype); + self.keys.insert(hash160.into(), key); + } + + pub fn new_with_eos_secret_key(keys: Vec, vtype: BTCSignVtype) -> Self { + let mut signer = SecpCkbRawKeySigner::default(); + for key in keys { + signer.add_eos_secret_key(key, vtype); + } + signer + } + + pub fn add_eos_secret_key(&mut self, key: secp256k1::SecretKey, vtype: BTCSignVtype) { + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &key); + let hash160 = eos_auth(&pubkey.into(), vtype); + self.keys.insert(hash160.into(), key); + } } impl Signer for SecpCkbRawKeySigner { @@ -649,6 +693,98 @@ impl Drop for SecpCkbRawKeySigner { } } } + +pub struct RsaSigner { + key: PKey, + use_rsa: bool, + use_iso9796_2: bool, +} + +impl RsaSigner { + pub fn new(key: PKey, use_rsa: bool, use_iso9796_2: bool) -> Self { + Self { + key, + use_rsa, + use_iso9796_2, + } + } +} + +impl Signer for RsaSigner { + fn match_id(&self, id: &[u8]) -> bool { + id.len() == 20 && id == blake160(&self.key.public_key_to_pem().unwrap()).as_bytes() + } + + fn sign( + &self, + id: &[u8], + message: &[u8], + _recoverable: bool, + _tx: &TransactionView, + ) -> Result { + if !self.match_id(id) { + return Err(SignerError::IdNotFound); + } + if message.len() != 32 { + return Err(SignerError::InvalidMessage(format!( + "expected length: 32, got: {}", + message.len() + ))); + } + if self.use_rsa { + Ok(rsa_sign(message, &self.key).into()) + } else if self.use_iso9796_2 { + Ok(iso9796_2_batch_sign(message, &self.key).into()) + } else { + Ok(Default::default()) + } + } +} + +/// A signer use ed25519 raw key +#[derive(Clone)] +pub struct Ed25519Signer { + key: ed25519_dalek::SigningKey, +} + +impl Ed25519Signer { + pub fn new(key: ed25519_dalek::SigningKey) -> Self { + Self { key } + } +} + +impl Signer for Ed25519Signer { + fn match_id(&self, id: &[u8]) -> bool { + id.len() == 20 && blake160(&self.key.verifying_key().as_bytes()[..]).as_bytes() == id + } + + fn sign( + &self, + id: &[u8], + message: &[u8], + _recoverable: bool, + _tx: &TransactionView, + ) -> Result { + if !self.match_id(id) { + return Err(SignerError::IdNotFound); + } + if message.len() != 83 { + return Err(SignerError::InvalidMessage(format!( + "expected length: 83, got: {}", + message.len() + ))); + } + + let sig = self.key.clone().sign(message); + + let verifying_key = self.key.verifying_key(); + // 64 + 32 + let mut sig_plus_pubkey = sig.to_vec(); + sig_plus_pubkey.extend(verifying_key.as_bytes()); + Ok(Bytes::from(sig_plus_pubkey)) + } +} + #[cfg(test)] mod anyhow_tests { use anyhow::anyhow; diff --git a/src/transaction/signer/mod.rs b/src/transaction/signer/mod.rs index ecae3db9..830997d4 100644 --- a/src/transaction/signer/mod.rs +++ b/src/transaction/signer/mod.rs @@ -3,6 +3,7 @@ use ckb_types::{ packed::{CellOutput, OutPoint}, H256, }; +use openssl::pkey::{PKey, Private}; use std::collections::HashMap; use crate::{ @@ -82,6 +83,25 @@ impl SignContexts { } } + pub fn new_omnilock_solana( + key: ed25519_dalek::SigningKey, + omnilock_config: OmniLockConfig, + ) -> Self { + let omnilock_context = + omnilock::OmnilockSignerContext::new_with_ed25519_key(key, omnilock_config); + Self { + contexts: vec![Box::new(omnilock_context)], + } + } + + pub fn new_omnilock_dl(key: PKey, omnilock_config: OmniLockConfig) -> Self { + let omnilock_context = + omnilock::OmnilockSignerContext::new_with_rsa_key(key, omnilock_config); + Self { + contexts: vec![Box::new(omnilock_context)], + } + } + #[inline] pub fn add_context(&mut self, context: Box) { self.contexts.push(context); diff --git a/src/transaction/signer/omnilock.rs b/src/transaction/signer/omnilock.rs index 8c5ed80b..62108d14 100644 --- a/src/transaction/signer/omnilock.rs +++ b/src/transaction/signer/omnilock.rs @@ -1,12 +1,16 @@ use std::collections::HashMap; use ckb_types::{core, packed}; +use openssl::pkey::{PKey, Private}; use crate::{ - traits::SecpCkbRawKeySigner, + traits::{ + default_impls::{Ed25519Signer, RsaSigner}, + SecpCkbRawKeySigner, Signer, + }, unlock::{ - OmniLockConfig, OmniLockScriptSigner, OmniLockUnlocker, OmniUnlockMode, ScriptUnlocker, - UnlockError, + IdentityFlag, OmniLockConfig, OmniLockScriptSigner, OmniLockUnlocker, OmniUnlockMode, + ScriptUnlocker, UnlockError, }, }; @@ -16,6 +20,8 @@ pub struct OmnilockSigner {} pub struct OmnilockSignerContext { keys: Vec, + ed25519_key: Option, + rsa_key: Option>, cfg: OmniLockConfig, unlock_mode: OmniUnlockMode, } @@ -24,19 +30,61 @@ impl OmnilockSignerContext { pub fn new(keys: Vec, cfg: OmniLockConfig) -> Self { Self { keys, + ed25519_key: None, + rsa_key: None, + cfg, + unlock_mode: OmniUnlockMode::Normal, + } + } + + pub fn new_with_ed25519_key(key: ed25519_dalek::SigningKey, cfg: OmniLockConfig) -> Self { + Self { + keys: Default::default(), + ed25519_key: Some(key), + rsa_key: None, + cfg, + unlock_mode: OmniUnlockMode::Normal, + } + } + + pub fn new_with_rsa_key(key: PKey, cfg: OmniLockConfig) -> Self { + Self { + keys: Default::default(), + ed25519_key: None, + rsa_key: Some(key), cfg, unlock_mode: OmniUnlockMode::Normal, } } pub fn build_omnilock_unlocker(&self) -> OmniLockUnlocker { - let signer = if self.cfg.is_ethereum() { - SecpCkbRawKeySigner::new_with_ethereum_secret_keys(self.keys.clone()) - } else { - SecpCkbRawKeySigner::new_with_secret_keys(self.keys.clone()) + let signer: Box = match self.cfg.id().flag() { + IdentityFlag::Ethereum | IdentityFlag::EthereumDisplaying | IdentityFlag::Tron => { + Box::new(SecpCkbRawKeySigner::new_with_ethereum_secret_keys( + self.keys.clone(), + )) + } + IdentityFlag::Bitcoin | IdentityFlag::Dogecoin => { + Box::new(SecpCkbRawKeySigner::new_with_btc_secret_key( + self.keys.clone(), + self.cfg.btc_sign_vtype, + )) + } + IdentityFlag::Eos => Box::new(SecpCkbRawKeySigner::new_with_eos_secret_key( + self.keys.clone(), + self.cfg.btc_sign_vtype, + )), + IdentityFlag::Solana => Box::new(Ed25519Signer::new( + self.ed25519_key.clone().expect("must have ed25519"), + )), + IdentityFlag::Dl => Box::new(RsaSigner::new( + self.rsa_key.clone().expect("muse have rsa"), + self.cfg.use_rsa, + self.cfg.use_iso9796_2, + )), + _ => Box::new(SecpCkbRawKeySigner::new_with_secret_keys(self.keys.clone())), }; - let omnilock_signer = - OmniLockScriptSigner::new(Box::new(signer), self.cfg.clone(), self.unlock_mode); + let omnilock_signer = OmniLockScriptSigner::new(signer, self.cfg.clone(), self.unlock_mode); OmniLockUnlocker::new(omnilock_signer, self.cfg.clone()) } } diff --git a/src/tx_builder/dao.rs b/src/tx_builder/dao.rs index a7f77de0..37c514c6 100644 --- a/src/tx_builder/dao.rs +++ b/src/tx_builder/dao.rs @@ -358,12 +358,7 @@ impl TxBuilder for DaoWithdrawBuilder { inputs.push(input); witnesses.push(witness.pack()); } - header_deps.extend( - prepare_block_hashes - .into_iter() - .collect::>() - .into_iter(), - ); + header_deps.extend(prepare_block_hashes.into_iter().collect::>()); let (outputs, outputs_data) = match &self.receiver { DaoWithdrawReceiver::LockScript { script, fee_rate } => { diff --git a/src/unlock/omni_lock.rs b/src/unlock/omni_lock.rs index c1150917..22a0f02b 100644 --- a/src/unlock/omni_lock.rs +++ b/src/unlock/omni_lock.rs @@ -3,6 +3,7 @@ use std::convert::TryFrom; use std::fmt::Display; use super::{MultisigConfig, OmniUnlockMode}; +use crate::util::{blake160, eos_auth, keccak160}; use crate::{ tx_builder::SinceSource, types::{ @@ -10,6 +11,7 @@ use crate::{ omni_lock::{Auth, Identity as IdentityType, IdentityOpt, OmniLockWitnessLock}, xudt_rce_mol::SmtProofEntryVec, }, + util::btc_auth, Address, }; @@ -22,6 +24,7 @@ use ckb_types::{ H160, H256, }; use enum_repr_derive::{FromEnumToRepr, TryFromReprToEnum}; +use openssl::pkey::{PKey, Public}; use serde::{de::Unexpected, Deserialize, Serialize}; use thiserror::Error; @@ -60,6 +63,9 @@ pub enum IdentityFlag { /// New ethereum with signing message with prefix EthereumDisplaying = 18, + /// It follows the same unlocking methods used by Solana. + Solana = 19, + /// The auth content that represents the blake160 hash of a lock script. /// The lock script will check if the current transaction contains an input cell with a matching lock script. /// Otherwise, it would return with an error. It's similar to P2SH in BTC. @@ -453,8 +459,13 @@ pub struct OmniLockConfig { // cobuild message pub(crate) cobuild_message: Option, - pub(crate) enable_rc: bool, - pub(crate) use_rc_identify: bool, + pub(crate) btc_sign_vtype: BTCSignVtype, + + pub(crate) use_rsa: bool, + + pub(crate) use_iso9796_2: bool, + + pub(crate) rsa_pubkey: Option>, } impl OmniLockConfig { @@ -510,8 +521,10 @@ impl OmniLockConfig { info_cell, enable_cobuild: false, cobuild_message: Some(Message::default().as_bytes()), - enable_rc: false, - use_rc_identify: false, + btc_sign_vtype: BTCSignVtype::P2PKHUncompressed, + use_rsa: false, + use_iso9796_2: false, + rsa_pubkey: None, }) } @@ -534,8 +547,10 @@ impl OmniLockConfig { info_cell: None, enable_cobuild: false, cobuild_message: Some(Message::default().as_bytes()), - enable_rc: false, - use_rc_identify: false, + btc_sign_vtype: BTCSignVtype::P2PKHUncompressed, + use_rsa: false, + use_iso9796_2: false, + rsa_pubkey: None, } } @@ -553,6 +568,45 @@ impl OmniLockConfig { OmniLockConfig::new_ethereum(pubkey_hash) } + pub fn new_ethereum_display(pubkey_hash: H160) -> Self { + Self::new(IdentityFlag::EthereumDisplaying, pubkey_hash) + } + + pub fn new_btc_from_pubkey(pubkey: &ckb_crypto::secp::Pubkey, vtype: BTCSignVtype) -> Self { + let mut c = Self::new(IdentityFlag::Bitcoin, H160::from(btc_auth(pubkey, vtype))); + c.btc_sign_vtype = vtype; + c + } + + pub fn new_dogcoin_from_pubkey(pubkey: &ckb_crypto::secp::Pubkey, vtype: BTCSignVtype) -> Self { + let mut c = Self::new(IdentityFlag::Dogecoin, H160::from(btc_auth(pubkey, vtype))); + c.btc_sign_vtype = vtype; + c + } + + pub fn new_eos_from_pubkey(pubkey: &ckb_crypto::secp::Pubkey, vtype: BTCSignVtype) -> Self { + let mut c = Self::new(IdentityFlag::Eos, H160::from(eos_auth(pubkey, vtype))); + c.btc_sign_vtype = vtype; + c + } + + pub fn new_tron_from_pubkey(pubkey: &ckb_crypto::secp::Pubkey) -> Self { + Self::new(IdentityFlag::Tron, keccak160(pubkey.as_bytes())) + } + + pub fn new_solana_from_pubkey(pubkey: &ed25519_dalek::VerifyingKey) -> Self { + Self::new(IdentityFlag::Solana, blake160(pubkey.as_bytes())) + } + + pub fn new_rsa_from_pubkey(pubkey: &PKey) -> Self { + let mut c = Self::new( + IdentityFlag::Dl, + blake160(&pubkey.public_key_to_pem().unwrap()), + ); + c.rsa_pubkey = Some(pubkey.public_key_to_pem().unwrap()); + c + } + /// Create an ownerlock omnilock with according script hash. /// # Arguments /// * `script_hash` the proper blake160 hash of according ownerlock script. @@ -561,11 +615,18 @@ impl OmniLockConfig { } /// Create a new OmniLockConfig - pub fn new(flag: IdentityFlag, auth_content: H160) -> Self { + pub fn new(flag: IdentityFlag, hash: H160) -> Self { let auth_content = match flag { - IdentityFlag::PubkeyHash | IdentityFlag::Ethereum | IdentityFlag::OwnerLock => { - auth_content - } + IdentityFlag::PubkeyHash + | IdentityFlag::Ethereum + | IdentityFlag::EthereumDisplaying + | IdentityFlag::Bitcoin + | IdentityFlag::Dogecoin + | IdentityFlag::Eos + | IdentityFlag::Tron + | IdentityFlag::Solana + | IdentityFlag::OwnerLock + | IdentityFlag::Dl => hash, _ => H160::from_slice(&[0; 20]).unwrap(), }; @@ -579,8 +640,10 @@ impl OmniLockConfig { info_cell: None, enable_cobuild: false, cobuild_message: Some(Message::default().as_bytes()), - enable_rc: false, - use_rc_identify: false, + btc_sign_vtype: BTCSignVtype::P2PKHUncompressed, + use_rsa: false, + use_iso9796_2: false, + rsa_pubkey: None, } } @@ -752,7 +815,14 @@ impl OmniLockConfig { unlock_mode: OmniUnlockMode, ) -> Result { let mut builder = match self.id.flag { - IdentityFlag::PubkeyHash | IdentityFlag::Ethereum => OmniLockWitnessLock::new_builder() + IdentityFlag::PubkeyHash + | IdentityFlag::Ethereum + | IdentityFlag::EthereumDisplaying + | IdentityFlag::Bitcoin + | IdentityFlag::Dogecoin + | IdentityFlag::Eos + | IdentityFlag::Tron + | IdentityFlag::OwnerLock => OmniLockWitnessLock::new_builder() .signature(Some(Bytes::from(vec![0u8; 65])).pack()), IdentityFlag::Multisig => { let multisig_config = match unlock_mode { @@ -774,7 +844,9 @@ impl OmniLockConfig { omni_sig[..config_data.len()].copy_from_slice(&config_data); OmniLockWitnessLock::new_builder().signature(Some(Bytes::from(omni_sig)).pack()) } - IdentityFlag::OwnerLock => OmniLockWitnessLock::new_builder(), + // IdentityFlag::OwnerLock => OmniLockWitnessLock::new_builder(), + IdentityFlag::Solana => OmniLockWitnessLock::new_builder() + .signature(Some(Bytes::from(vec![0u8; 96])).pack()), _ => todo!("to support other placeholder_witness_lock implementions"), }; @@ -810,17 +882,21 @@ impl OmniLockConfig { unlock_mode: OmniUnlockMode, ) -> Result { match self.id.flag { - IdentityFlag::PubkeyHash | IdentityFlag::Ethereum | IdentityFlag::Multisig => { + IdentityFlag::PubkeyHash + | IdentityFlag::Ethereum + | IdentityFlag::Multisig + | IdentityFlag::EthereumDisplaying + | IdentityFlag::Bitcoin + | IdentityFlag::Dogecoin + | IdentityFlag::Eos + | IdentityFlag::Tron + | IdentityFlag::Solana => { let lock = self.placeholder_witness_lock(unlock_mode)?; Ok(WitnessArgs::new_builder().lock(Some(lock).pack()).build()) } IdentityFlag::OwnerLock => { - if self.admin_config.is_some() { - let lock = self.placeholder_witness_lock(unlock_mode)?; - Ok(WitnessArgs::new_builder().lock(Some(lock).pack()).build()) - } else { - Ok(WitnessArgs::default()) - } + let lock = self.placeholder_witness_lock(unlock_mode)?; + Ok(WitnessArgs::new_builder().lock(Some(lock).pack()).build()) } _ => todo!("to support other placeholder_witness implementions"), } @@ -839,6 +915,28 @@ impl OmniLockConfig { } } +#[derive( + Clone, + Copy, + Serialize, + Deserialize, + Debug, + Hash, + Eq, + PartialEq, + TryFromReprToEnum, + FromEnumToRepr, +)] +#[repr(u8)] +#[derive(Default)] +pub enum BTCSignVtype { + #[default] + P2PKHUncompressed = 27, + P2PKHCompressed = 31, + SegwitP2SH = 35, + SegwitBech32 = 39, +} + #[cfg(test)] mod tests { use ckb_types::packed::Byte; diff --git a/src/unlock/signer.rs b/src/unlock/signer.rs index 0ab425af..914e1bbd 100644 --- a/src/unlock/signer.rs +++ b/src/unlock/signer.rs @@ -3,7 +3,7 @@ use std::collections::HashSet; use anyhow::anyhow; use ckb_hash::{blake2b_256, new_blake2b, Blake2b, Blake2bBuilder}; use ckb_types::{ - bytes::{Bytes, BytesMut}, + bytes::{BufMut, Bytes, BytesMut}, core::{ScriptHashType, TransactionView}, error::VerificationError, packed::{self, BytesOpt, Script, WitnessArgs}, @@ -11,15 +11,20 @@ use ckb_types::{ H160, }; use serde::{Deserialize, Serialize}; +use sha3::{Digest, Keccak256}; use thiserror::Error; use crate::{ - constants::MULTISIG_TYPE_HASH, + constants::{COMMON_PREFIX, MULTISIG_TYPE_HASH}, types::{cobuild::top_level::WitnessLayoutUnion, omni_lock::OmniLockWitnessLock}, + util::{ + btc_convert_message, btc_convert_sign, iso9796_2_signning_prepare_pubkey, + rsa_signning_prepare_pubkey, + }, }; use crate::{ traits::{Signer, SignerError, TransactionDependencyProvider}, - util::convert_keccak256_hash, + util::{calculate_sha256, convert_keccak256_hash, hex_encode}, }; use crate::{ types::{ @@ -460,7 +465,6 @@ impl ScriptSigner for ChequeScriptSigner { pub const PERSONALIZATION_SIGHASH_ALL: &[u8] = b"ckb-tcob-sighash"; pub const PERSONALIZATION_SIGHASH_ALL_ONLY: &[u8] = b"ckb-tcob-sgohash"; -pub const PERSONALIZATION_OTX: &[u8] = b"ckb-tcob-otxhash"; /// return a blake2b instance with personalization for SighashAll pub fn new_sighash_all_blake2b() -> Blake2b { @@ -476,13 +480,6 @@ pub fn new_sighash_all_only_blake2b() -> Blake2b { .build() } -/// return a blake2b instance with personalization for OTX -pub fn new_otx_blake2b() -> Blake2b { - Blake2bBuilder::new(32) - .personal(PERSONALIZATION_OTX) - .build() -} - pub fn cobuild_generate_signing_message_hash( message: &Option, tx_dep_provider: &dyn TransactionDependencyProvider, @@ -492,7 +489,7 @@ pub fn cobuild_generate_signing_message_hash( let mut hasher = match message { Some(m) => { let mut hasher = new_sighash_all_blake2b(); - hasher.update(&m); + hasher.update(m); hasher } None => new_sighash_all_only_blake2b(), @@ -784,7 +781,14 @@ impl ScriptSigner for OmniLockScriptSigner { return false; } match self.config.id().flag() { - IdentityFlag::PubkeyHash | IdentityFlag::Ethereum => self + IdentityFlag::PubkeyHash + | IdentityFlag::Ethereum + | IdentityFlag::Bitcoin + | IdentityFlag::Dogecoin + | IdentityFlag::EthereumDisplaying + | IdentityFlag::Tron + | IdentityFlag::Solana + | IdentityFlag::Eos => self .signer .match_id(self.config.id().auth_content().as_ref()), IdentityFlag::Multisig => { @@ -855,10 +859,133 @@ impl ScriptSigner for OmniLockScriptSigner { witnesses[witness_idx].raw_data(), self.config.enable_cobuild, )?, - // IdentityFlag::OwnerLock => { - // // should not reach here, just return a clone for compatible reason. - // Ok(tx.clone()) - // } + IdentityFlag::Bitcoin => { + let msg = btc_convert_message(&message); + + let sign = + self.signer + .sign(id.auth_content().as_ref(), msg.as_slice(), true, tx)?; + btc_convert_sign(sign, self.config.btc_sign_vtype) + } + IdentityFlag::EthereumDisplaying => { + let eth_prefix = b"\x19Ethereum Signed Message:\n"; + let mut hasher = Keccak256::new(); + hasher.update(eth_prefix); + hasher.update(Bytes::from(format!( + "{}", + COMMON_PREFIX.len() + message.len() * 2 + ))); + hasher.update(Bytes::from(COMMON_PREFIX)); + + hasher.update(hex_encode(&message)); + let r = hasher.finalize(); + + self.signer + .sign(id.auth_content().as_ref(), r.as_slice(), true, tx)? + } + IdentityFlag::Tron => { + let tron_prefix: &[u8; 24] = b"\x19TRON Signed Message:\n32"; + let mut hasher = Keccak256::new(); + hasher.update(tron_prefix); + hasher.update(message); + let r = hasher.finalize(); + self.signer + .sign(id.auth_content().as_ref(), r.as_slice(), true, tx)? + } + IdentityFlag::Eos => { + let sign = self + .signer + .sign(id.auth_content().as_ref(), &message, true, tx)?; + btc_convert_sign(sign, self.config.btc_sign_vtype) + } + IdentityFlag::Dogecoin => { + let message_magic = b"\x19Dogecoin Signed Message:\n\x40"; + let msg_hex = hex_encode(&message); + assert_eq!(msg_hex.len(), 64); + + let mut temp2 = Vec::with_capacity(message_magic.len() + msg_hex.len()); + temp2.put(message_magic.as_slice()); + temp2.put(msg_hex.as_bytes()); + + let msg = calculate_sha256(&temp2); + let r = calculate_sha256(&msg); + let sign = self + .signer + .sign(id.auth_content().as_ref(), r.as_slice(), true, tx)?; + btc_convert_sign(sign, self.config.btc_sign_vtype) + } + IdentityFlag::Solana => { + // should we impl phantom signature mode? + let msg = { + let mut preifx = b"CKB transaction: 0x".to_vec(); + preifx.extend(hex_encode(&message).as_bytes()); + preifx + }; + self.signer + .sign(id.auth_content().as_ref(), msg.as_slice(), true, tx)? + } + IdentityFlag::OwnerLock => { + self.signer + .sign(id.auth_content().as_ref(), message.as_ref(), true, tx)? + } + IdentityFlag::Dl => { + let (sig, _pubkey) = if self.config.use_rsa { + ( + self.signer.sign( + self.config + .rsa_pubkey + .as_ref() + .expect("rsa pubkey must exist"), + &message, + true, + tx, + )?, + { + let pubkey = openssl::pkey::PKey::public_key_from_pem( + self.config + .rsa_pubkey + .as_ref() + .expect("rsa pubkey must exist"), + ) + .unwrap(); + rsa_signning_prepare_pubkey(&pubkey) + }, + ) + } else if self.config.use_iso9796_2 { + ( + self.signer.sign( + self.config + .rsa_pubkey + .as_ref() + .expect("rsa pubkey must exist"), + &message, + true, + tx, + )?, + { + let pubkey = openssl::pkey::PKey::public_key_from_pem( + self.config + .rsa_pubkey + .as_ref() + .expect("rsa pubkey must exist"), + ) + .unwrap(); + iso9796_2_signning_prepare_pubkey(&pubkey) + }, + ) + } else { + (Default::default(), Default::default()) + }; + + // Question? + // + // let hash = blake160(pubkey.as_ref()); + // let preimage = gen_exec_preimage(&self.config.rsa_script, &hash); + // preimage_hash = blake160(preimage.as_ref()); + // write_back_preimage_hash(dummy, IDENTITY_FLAGS_DL, preimage_hash); + + sig + } _ => { todo!("not supported yet"); } diff --git a/src/util.rs b/src/util.rs index ecb3ce1b..21d4688b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,16 +1,28 @@ use std::{convert::TryInto, ptr, sync::atomic}; +use bytes::{BufMut, Bytes, BytesMut}; +use ckb_crypto::secp::Pubkey; use ckb_dao_utils::extract_dao_data; +use ckb_hash::blake2b_256; use ckb_types::{ core::{Capacity, EpochNumber, EpochNumberWithFraction, HeaderView}, - packed::CellOutput, + packed::{CellOutput, Script}, prelude::*, H160, H256, U256, }; +use openssl::{ + hash::MessageDigest, + pkey::{PKey, Private, Public}, + sign::Signer, +}; use sha3::{Digest, Keccak256}; -use crate::rpc::CkbRpcClient; use crate::traits::LiveCell; +use crate::{ + constants::{BTC_PREFIX, SECP256K1_TAG_PUBKEY_UNCOMPRESSED}, + rpc::CkbRpcClient, + unlock::omni_lock::BTCSignVtype, +}; pub fn zeroize_privkey(key: &mut secp256k1::SecretKey) { let key_ptr = key.as_mut_ptr(); @@ -154,6 +166,189 @@ pub fn convert_keccak256_hash(message: &[u8]) -> H256 { H256::from_slice(r.as_slice()).expect("convert_keccak256_hash") } +pub fn calculate_sha256(buf: &[u8]) -> [u8; 32] { + use sha2::Sha256; + + let mut c = Sha256::new(); + c.update(buf); + c.finalize().into() +} + +pub fn calculate_ripemd160(buf: &[u8]) -> [u8; 20] { + use ripemd::Ripemd160; + + let mut hasher = Ripemd160::new(); + hasher.update(buf); + let buf = hasher.finalize().to_vec(); + + buf.try_into().unwrap() +} + +pub fn bitcoin_hash160(buf: &[u8]) -> [u8; 20] { + calculate_ripemd160(&calculate_sha256(buf)) +} + +pub fn btc_auth(pubkey: &Pubkey, vtype: BTCSignVtype) -> [u8; 20] { + match vtype { + BTCSignVtype::P2PKHUncompressed => { + let mut pk_data = vec![0; 65]; + pk_data[0] = SECP256K1_TAG_PUBKEY_UNCOMPRESSED; + pk_data[1..].copy_from_slice(pubkey.as_bytes()); + + bitcoin_hash160(&pk_data) + } + BTCSignVtype::P2PKHCompressed | BTCSignVtype::SegwitBech32 => { + bitcoin_hash160(&pubkey.serialize()) + } + BTCSignVtype::SegwitP2SH => { + let mut pk_data = vec![0; 22]; + pk_data[0] = 0; + pk_data[1] = 20; + pk_data[2..].copy_from_slice(&bitcoin_hash160(&pubkey.serialize())); + bitcoin_hash160(&pk_data) + } + } +} + +pub fn eos_auth(pubkey: &Pubkey, vtype: BTCSignVtype) -> [u8; 20] { + let buf = match vtype { + BTCSignVtype::P2PKHUncompressed => { + let mut temp = Vec::with_capacity(65); + temp.put_u8(SECP256K1_TAG_PUBKEY_UNCOMPRESSED); + temp.put(pubkey.as_bytes()); + temp + } + BTCSignVtype::P2PKHCompressed => pubkey.serialize(), + _ => panic!("unsupport vtype"), + }; + blake2b_256(buf)[..20].try_into().unwrap() +} + +pub fn btc_convert_message(msg: &[u8]) -> [u8; 32] { + let message_magic = b"Bitcoin Signed Message:\n"; + let msg_hex = hex_encode(msg); + assert_eq!(msg_hex.len(), 64); + + let mut temp2 = + Vec::with_capacity(1 + message_magic.len() + 1 + BTC_PREFIX.len() + msg_hex.len()); + temp2.put_u8(message_magic.len() as u8); + temp2.put(message_magic.as_slice()); + + temp2.put_u8(0x40 + BTC_PREFIX.len() as u8); + + temp2.put(format!("{}{}", BTC_PREFIX, msg_hex).as_bytes()); + + let msg = calculate_sha256(&temp2); + calculate_sha256(&msg) +} + +pub fn btc_convert_sign(sign: Bytes, vtype: BTCSignVtype) -> Bytes { + let recid = sign[64]; + + let mark = recid + vtype as u8; + + let mut ret = BytesMut::with_capacity(65); + ret.put_u8(mark); + ret.put(&sign[0..64]); + ret.freeze() +} + +pub fn hex_encode(message: &[u8]) -> String { + let mut res = vec![0; message.len() * 2]; + faster_hex::hex_encode(message, &mut res).unwrap(); + String::from_utf8(res).unwrap() +} + +pub fn rsa_signning_prepare_pubkey(pubkey: &PKey) -> Vec { + let mut sig = vec![ + 1, // algorithm id + 1, // key size, 1024 + 0, // padding, PKCS# 1.5 + 6, // hash type SHA256 + ]; + + let pubkey2 = pubkey.rsa().unwrap(); + let mut e = pubkey2.e().to_vec(); + let mut n = pubkey2.n().to_vec(); + e.reverse(); + n.reverse(); + + while e.len() < 4 { + e.push(0); + } + while n.len() < 128 { + n.push(0); + } + sig.append(&mut e); // 4 bytes E + sig.append(&mut n); // N + + sig +} + +pub fn rsa_sign(msg: &[u8], key: &PKey) -> Vec { + let pem: Vec = key.public_key_to_pem().unwrap(); + let pubkey = PKey::public_key_from_pem(&pem).unwrap(); + + let mut sig = rsa_signning_prepare_pubkey(&pubkey); + + let mut signer = Signer::new(MessageDigest::sha256(), key).unwrap(); + signer.update(msg).unwrap(); + sig.extend(signer.sign_to_vec().unwrap()); // sig + + sig +} + +pub fn iso9796_2_signning_prepare_pubkey(pubkey: &PKey) -> Vec { + let mut sig = vec![ + 3, // algorithm id, CKB_VERIFY_ISO9796_2_BATCH + 1, // key size, 1024 + 0, // padding, PKCS# 1.5 + 6, // hash type SHA256 + ]; + + let pubkey2 = pubkey.rsa().unwrap(); + let mut e = pubkey2.e().to_vec(); + let mut n = pubkey2.n().to_vec(); + e.reverse(); + n.reverse(); + + while e.len() < 4 { + e.push(0); + } + while n.len() < 128 { + n.push(0); + } + sig.append(&mut e); // 4 bytes E + sig.append(&mut n); // N + + sig +} + +pub fn iso9796_2_batch_sign(msg: &[u8], key: &PKey) -> Vec { + let pem: Vec = key.public_key_to_pem().unwrap(); + let pubkey = PKey::public_key_from_pem(&pem).unwrap(); + + let mut sig = iso9796_2_signning_prepare_pubkey(&pubkey); + + let mut signer = Signer::new(MessageDigest::sha256(), key).unwrap(); + signer.update(msg).unwrap(); + sig.extend(signer.sign_to_vec().unwrap()); // sig + sig.extend(signer.sign_to_vec().unwrap()); // sig + sig.extend(signer.sign_to_vec().unwrap()); // sig + sig.extend(signer.sign_to_vec().unwrap()); // sig + + sig +} + +pub fn gen_exec_preimage(script: &Script, blake160: &Bytes) -> Bytes { + let mut result = BytesMut::new(); + result.put_slice(script.code_hash().as_slice()); + result.put_slice(script.hash_type().as_slice()); + result.put_slice(blake160.clone().as_ref()); + + result.freeze() +} + #[cfg(test)] mod tests { use super::*;