diff --git a/Cargo.toml b/Cargo.toml index 22b1a1a4..c40fcd6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ license = "MIT" description = "Rust SDK for CKB" homepage = "https://github.com/nervosnetwork/ckb-sdk-rust" repository = "https://github.com/nervosnetwork/ckb-sdk-rust" +exclude = ["/test-data"] [dependencies] serde = { version = "1.0", features = ["derive"] } @@ -62,16 +63,17 @@ sparse-merkle-tree = "0.6.1" lazy_static = "1.3.0" [features] -default = ["default-tls", "test", "rand"] +default = ["default-tls"] default-tls = ["reqwest/default-tls"] native-tls-vendored = ["reqwest/native-tls-vendored"] rustls-tls = ["reqwest/rustls-tls"] test = [] [dev-dependencies] -clap = { version = "=4.4.18", features = [ - "derive", -] } # TODO clap v4.5 requires rustc v1.74.0+ +clap = { version = "4.4.18", features = ["derive"] } httpmock = "0.6" async-global-executor = "2.3.1" -hex = "0.4" + + +[target.'cfg(unix)'.dev-dependencies] +openssl = "0.10" diff --git a/src/lib.rs b/src/lib.rs index f4745518..4854259f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,10 +9,8 @@ pub mod types; pub mod unlock; pub mod util; -#[cfg(feature = "test")] pub mod test_util; -#[cfg(feature = "test")] #[cfg(test)] mod tests; diff --git a/src/tests/mod.rs b/src/tests/mod.rs index e1353007..b48421e1 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -44,13 +44,18 @@ const ACCOUNT3_KEY: H256 = const ACCOUNT3_ARG: H160 = h160!("0xdabe88a65760c662ee3f07dee162409f7b20b694"); const FEE_RATE: u64 = 1000; -const GENESIS_JSON: &str = include_str!("../test-data/genesis_block.json"); -const SUDT_BIN: &[u8] = include_bytes!("../test-data/simple_udt"); -const ACP_BIN: &[u8] = include_bytes!("../test-data/anyone_can_pay"); -const CHEQUE_BIN: &[u8] = include_bytes!("../test-data/ckb-cheque-script"); -const ALWAYS_SUCCESS_BIN: &[u8] = include_bytes!("../test-data/always_success"); -const OMNILOCK_BIN: &[u8] = include_bytes!("../test-data/omni_lock"); -// const ALWAYS_SUCCESS_BIN_DL: &[u8] = include_bytes!("../test-data/always_success_dl"); +const GENESIS_JSON: &str = include_str!("../../test-data/genesis_block.json"); +const SUDT_BIN: &[u8] = include_bytes!("../../test-data/simple_udt"); +const ACP_BIN: &[u8] = include_bytes!("../../test-data/anyone_can_pay"); +const CHEQUE_BIN: &[u8] = include_bytes!("../../test-data/ckb-cheque-script"); +const ALWAYS_SUCCESS_BIN: &[u8] = include_bytes!("../../test-data/always_success"); +// https://github.com/XuJiandong/ckb-production-scripts/commit/f692e01ead9378093b57b47023f3408e4c35349f +#[cfg(not(unix))] +const ALWAYS_SUCCESS_DL_BIN: &[u8] = include_bytes!("../../test-data/always_success_dl"); +const OMNILOCK_BIN: &[u8] = include_bytes!("../../test-data/omni_lock"); +// https://github.com/nervosnetwork/ckb-production-scripts/blob/410b16c499a8888781d9ab03160eeef93182d8e6/c/validate_signature_rsa.c +#[cfg(unix)] +const RSA_DL_BIN: &[u8] = include_bytes!("../../test-data/validate_signature_rsa"); fn build_sighash_script(args: H160) -> Script { Script::new_builder() @@ -76,13 +81,23 @@ fn build_always_success_script() -> Script { .build() } -// fn build_always_success_script_dl() -> Script { -// let data_hash = H256::from(blake2b_256(ALWAYS_SUCCESS_BIN_DL)); -// Script::new_builder() -// .code_hash(data_hash.pack()) -// .hash_type(ScriptHashType::Data1.into()) -// .build() -// } +#[cfg(not(unix))] +fn build_always_success_dl_script() -> Script { + let data_hash = H256::from(blake2b_256(ALWAYS_SUCCESS_DL_BIN)); + Script::new_builder() + .code_hash(data_hash.pack()) + .hash_type(ScriptHashType::Data1.into()) + .build() +} + +#[cfg(unix)] +fn build_rsa_script_dl() -> Script { + let data_hash = H256::from(blake2b_256(RSA_DL_BIN)); + Script::new_builder() + .code_hash(data_hash.pack()) + .hash_type(ScriptHashType::Data1.into()) + .build() +} fn build_dao_script() -> Script { Script::new_builder() diff --git a/src/tests/transaction/omnilock.rs b/src/tests/transaction/omnilock.rs index 990e5014..2511644a 100644 --- a/src/tests/transaction/omnilock.rs +++ b/src/tests/transaction/omnilock.rs @@ -456,24 +456,163 @@ fn test_omnilock_owner_lock_tranfer(cobuild: bool) { ctx.verify(tx, FEE_RATE).unwrap(); } +#[cfg(unix)] +mod rsa_dl_test { + use super::test_omnilock_dl_exec; + use crate::{ + tests::build_rsa_script_dl, + traits::{Signer, SignerError}, + unlock::omni_lock::{ExecDlConfig, Preimage}, + util::blake160, + }; + + use ckb_types::core::TransactionView; + use openssl::{ + hash::MessageDigest, + pkey::{PKey, Private, Public}, + rsa::Rsa, + sign::Signer as RSASigner, + }; + + #[derive(Clone)] + struct RSASinger { + key: PKey, + } + + impl Signer for RSASinger { + fn match_id(&self, id: &[u8]) -> bool { + let rsa_script = build_rsa_script_dl(); + let public_key_pem: Vec = self.key.public_key_to_pem().unwrap(); + let rsa_pubkey = PKey::public_key_from_pem(&public_key_pem).unwrap(); + let signning_pubkey = rsa_signning_prepare_pubkey(&rsa_pubkey); + + let preimage = Preimage::new_with_dl(rsa_script, blake160(&signning_pubkey)); + id.len() == 20 && id == preimage.auth().as_bytes() + } + + fn sign( + &self, + id: &[u8], + message: &[u8], + _recoverable: bool, + _tx: &TransactionView, + ) -> Result { + if !self.match_id(id) { + return Err(SignerError::IdNotFound); + } + Ok(bytes::Bytes::from(rsa_sign(message, &self.key))) + } + } + + 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 = RSASigner::new(MessageDigest::sha256(), key).unwrap(); + signer.update(msg).unwrap(); + sig.extend(signer.sign_to_vec().unwrap()); // sig + + sig + } + + #[test] + fn test_omnilock_dl() { + let rsa_script = build_rsa_script_dl(); + let bits = 1024; + let rsa = Rsa::generate(bits).unwrap(); + let rsa_private_key = PKey::from_rsa(rsa).unwrap(); + let public_key_pem: Vec = rsa_private_key.public_key_to_pem().unwrap(); + let rsa_pubkey = PKey::public_key_from_pem(&public_key_pem).unwrap(); + let signning_pubkey = rsa_signning_prepare_pubkey(&rsa_pubkey); + + let preimage = Preimage::new_with_dl(rsa_script, blake160(&signning_pubkey)); + let config = ExecDlConfig::new(preimage, 264); + let signer = RSASinger { + key: rsa_private_key, + }; + test_omnilock_dl_exec(config.clone(), signer.clone(), false); + test_omnilock_dl_exec(config, signer.clone(), true); + } +} + #[derive(Clone)] struct DummySinger {} impl Signer for DummySinger { fn match_id(&self, id: &[u8]) -> bool { - let always_success_script = build_always_success_script(); - let preimage = - Preimage::new_with_exec(always_success_script, 0, [0; 8], blake160(&[0u8; 20])); - id.len() == 20 && id == preimage.auth().as_bytes() + let (preimage, preimage_dl) = if cfg!(unix) { + let always_success_script = build_always_success_script(); + + ( + Preimage::new_with_exec( + always_success_script.clone(), + 0, + [0; 8], + blake160(&[0u8; 20]), + ), + Preimage::new_with_exec(always_success_script, 0, [0; 8], blake160(&[0u8; 20])), + ) + } else { + #[cfg(not(unix))] + { + use crate::tests::build_always_success_dl_script; + let always_success_script_dl = build_always_success_dl_script(); + let always_success_script = build_always_success_script(); + ( + Preimage::new_with_exec( + always_success_script.clone(), + 0, + [0; 8], + blake160(&[0u8; 20]), + ), + Preimage::new_with_dl(always_success_script_dl, H160::from([0u8; 20])), + ) + } + #[cfg(unix)] + unreachable!() + }; + + id.len() == 20 && (id == preimage.auth().as_bytes() || id == preimage_dl.auth().as_bytes()) } fn sign( &self, - _id: &[u8], + id: &[u8], _message: &[u8], _recoverable: bool, _tx: &TransactionView, ) -> Result { + if !self.match_id(id) { + return Err(SignerError::IdNotFound); + } Ok(bytes::Bytes::from(vec![0; 65])) } } @@ -484,34 +623,63 @@ fn test_omnilock_exec() { let preimage = Preimage::new_with_exec(always_success_script, 0, [0; 8], blake160(&[0u8; 20])); let config = ExecDlConfig::new(preimage, 65); - test_omnilock_dl_exec(config.clone(), false); - test_omnilock_dl_exec(config, true) + test_omnilock_dl_exec(config.clone(), DummySinger {}, false); + test_omnilock_dl_exec(config, DummySinger {}, true) } -#[ignore] +#[cfg(not(unix))] #[test] fn test_omnilock_dl() { - // let always_success_script = build_always_success_script_dl(); - // let preimage = Preimage::new_with_dl(always_success_script, blake160(&[0u8; 20])); - // test_omnilock_dl_exec(preimage) + use crate::tests::build_always_success_dl_script; + let always_success_script = build_always_success_dl_script(); + let preimage = Preimage::new_with_dl(always_success_script, H160::from([0u8; 20])); + let config = ExecDlConfig::new(preimage, 65); + + test_omnilock_dl_exec(config.clone(), DummySinger {}, false); + test_omnilock_dl_exec(config, DummySinger {}, true) } -fn test_omnilock_dl_exec(config: ExecDlConfig, cobuild: bool) { +#[cfg(unix)] +fn dl_exec_cfg(config: ExecDlConfig) -> (OmniLockConfig, &'static [u8]) { + use crate::tests::RSA_DL_BIN; + if config.preimage().len() == 32 + 1 + 1 + 8 + 20 { + ( + OmniLockConfig::new_with_exec_preimage(config), + ALWAYS_SUCCESS_BIN, + ) + } else { + (OmniLockConfig::new_with_dl_preimage(config), RSA_DL_BIN) + } +} + +#[cfg(not(unix))] +fn dl_exec_cfg(config: ExecDlConfig) -> (OmniLockConfig, &'static [u8]) { + use crate::tests::ALWAYS_SUCCESS_DL_BIN; + if config.preimage().len() == 32 + 1 + 1 + 8 + 20 { + ( + OmniLockConfig::new_with_exec_preimage(config), + ALWAYS_SUCCESS_BIN, + ) + } else { + ( + OmniLockConfig::new_with_dl_preimage(config), + ALWAYS_SUCCESS_DL_BIN, + ) + } +} + +fn test_omnilock_dl_exec(config: ExecDlConfig, signer: T, cobuild: bool) { let network_info = NetworkInfo::testnet(); let receiver = build_sighash_script(ACCOUNT2_ARG); - let mut cfg = if config.preimage().len() == 32 + 1 + 1 + 8 + 20 { - OmniLockConfig::new_with_exec_preimage(config) - } else { - OmniLockConfig::new_with_dl_preimage(config) - }; + let (mut cfg, bin) = dl_exec_cfg(config); cfg.enable_cobuild(cobuild); let sender = build_omnilock_script(&cfg); - let sign_context = SignContexts::new_omnilock_exec_dl_custom(DummySinger {}, cfg.clone()); + let sign_context = SignContexts::new_omnilock_exec_dl_custom(signer, cfg.clone()); let (ctx, outpoints) = init_context( - vec![(OMNILOCK_BIN, true), (ALWAYS_SUCCESS_BIN, true)], + vec![(OMNILOCK_BIN, true), (bin, true)], vec![(sender.clone(), Some(300 * ONE_CKB))], ); diff --git a/src/tests/tx_builder/cycle.rs b/src/tests/tx_builder/cycle.rs index 2fa3753b..dd67eb18 100644 --- a/src/tests/tx_builder/cycle.rs +++ b/src/tests/tx_builder/cycle.rs @@ -22,7 +22,7 @@ use crate::{ ScriptGroup, ScriptId, }; -const CYCLE_BIN: &[u8] = include_bytes!("../../test-data/cycle"); +const CYCLE_BIN: &[u8] = include_bytes!("../../../test-data/cycle"); pub struct CycleUnlocker { loops: u64, diff --git a/src/transaction/signer/omnilock.rs b/src/transaction/signer/omnilock.rs index 51863208..e00b3de6 100644 --- a/src/transaction/signer/omnilock.rs +++ b/src/transaction/signer/omnilock.rs @@ -1,6 +1,4 @@ -use std::collections::HashMap; - -use ckb_types::{core, packed}; +use ckb_types::core; use crate::{ traits::{ @@ -117,56 +115,3 @@ impl CKBScriptSigner for OmnilockSigner { } } } - -struct InputsProvider<'a> { - inputs: &'a HashMap, -} - -impl<'a> crate::traits::TransactionDependencyProvider for InputsProvider<'a> { - /// For verify certain cell belong to certain transaction - fn get_transaction( - &self, - _tx_hash: &packed::Byte32, - ) -> Result { - Err(crate::traits::TransactionDependencyError::NotFound( - "not support".to_string(), - )) - } - /// For get the output information of inputs or cell_deps, those cell should be live cell - fn get_cell( - &self, - out_point: &packed::OutPoint, - ) -> Result { - self.inputs.get(out_point).map(|a| a.0.clone()).ok_or( - crate::traits::TransactionDependencyError::NotFound("not found".to_string()), - ) - } - /// For get the output data information of inputs or cell_deps - fn get_cell_data( - &self, - out_point: &packed::OutPoint, - ) -> Result { - self.inputs.get(out_point).map(|a| a.1.clone()).ok_or( - crate::traits::TransactionDependencyError::NotFound("not found".to_string()), - ) - } - /// For get the header information of header_deps - fn get_header( - &self, - _block_hash: &packed::Byte32, - ) -> Result { - Err(crate::traits::TransactionDependencyError::NotFound( - "not support".to_string(), - )) - } - - /// For get_block_extension - fn get_block_extension( - &self, - _block_hash: &packed::Byte32, - ) -> Result, crate::traits::TransactionDependencyError> { - Err(crate::traits::TransactionDependencyError::NotFound( - "not support".to_string(), - )) - } -} diff --git a/src/types/address.rs b/src/types/address.rs index 4fa6ad26..fbfb7db8 100644 --- a/src/types/address.rs +++ b/src/types/address.rs @@ -555,6 +555,8 @@ mod old_addr { #[cfg(test)] mod test { + use crate::util::hex_decode; + use super::*; use ckb_types::{h160, h256}; @@ -651,7 +653,7 @@ mod test { assert_eq!(addr.payload().hash_type(), ScriptHashType::Type); assert_eq!( addr.payload().args().as_ref(), - hex::decode("0c4bec5862af847a2d852cb939c6dfb70c25e52e").unwrap() + hex_decode("0c4bec5862af847a2d852cb939c6dfb70c25e52e".as_bytes()) ); } @@ -678,9 +680,9 @@ mod test { let mut data = vec![0u8; 23]; data[0] = 0x01; data[1] = CodeHashIndex::Sighash as u8; - data[2..].copy_from_slice( - &hex::decode("4fb2be2e5d0c1a3b8694f832350a33c1685d477a33").unwrap(), - ); + data[2..].copy_from_slice(&hex_decode( + "4fb2be2e5d0c1a3b8694f832350a33c1685d477a33".as_bytes(), + )); let variant = bech32::Variant::Bech32; let addr = bech32::encode("ckb", data.to_base32(), variant).unwrap(); let expected_addr = "ckb1qyqylv479ewscx3ms620sv34pgeuz6zagaarxdzvx03"; @@ -712,7 +714,7 @@ mod test { fn test_invalid_old_full_address() { // INVALID bech32 encoding { - let args = hex::decode("4fb2be2e5d0c1a3b86").unwrap(); + let args = hex_decode("4fb2be2e5d0c1a3b86".as_bytes()); let mut data = vec![0u8; 33 + args.len()]; data[0] = AddressType::FullData as u8; data[1..33].copy_from_slice( @@ -751,7 +753,7 @@ mod test { ] { let code_hash = h256!("0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"); - let args = hex::decode("4fb2be2e5d0c1a3b86").unwrap(); + let args = hex_decode("4fb2be2e5d0c1a3b86".as_bytes()); let mut data = vec![0u8; 34 + args.len()]; data[0] = 0x00; data[1..33].copy_from_slice(code_hash.as_bytes()); diff --git a/src/unlock/omni_lock.rs b/src/unlock/omni_lock.rs index 8ea1e862..05ade729 100644 --- a/src/unlock/omni_lock.rs +++ b/src/unlock/omni_lock.rs @@ -688,10 +688,16 @@ impl OmniLockConfig { } } + /// Enable cobuild's build transaction standards pub fn enable_cobuild(&mut self, enable: bool) { self.enable_cobuild = enable } + /// Set cobuild message value + pub fn cobuild_message(&mut self, message: Option) { + self.cobuild_message = message.map(|i| i.as_bytes()); + } + /// Set the admin cofiguration, and set the OmniLockFlags::ADMIN flag. /// # Arguments /// * `admin_config` The new admin config. @@ -740,6 +746,7 @@ impl OmniLockConfig { self.info_cell = None; } + /// Set multisignature config pub fn set_multisig_config(&mut self, multisig_config: Option) { self.multisig_config = multisig_config; } @@ -885,7 +892,6 @@ 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::Solana => OmniLockWitnessLock::new_builder() .signature(Some(Bytes::from(vec![0u8; 96])).pack()), IdentityFlag::Dl | IdentityFlag::Exec => { diff --git a/src/unlock/signer.rs b/src/unlock/signer.rs index dc204a34..d13eac0a 100644 --- a/src/unlock/signer.rs +++ b/src/unlock/signer.rs @@ -751,7 +751,7 @@ impl ScriptSigner for OmniLockScriptSigner { if args.len() != self.config.get_args_len() { return false; } - dbg!(1); + if self.unlock_mode == OmniUnlockMode::Admin { if let Some(admin_config) = self.config.get_admin_config() { if args.len() < 54 { @@ -785,7 +785,9 @@ impl ScriptSigner for OmniLockScriptSigner { | IdentityFlag::EthereumDisplaying | IdentityFlag::Tron | IdentityFlag::Solana - | IdentityFlag::Eos => self + | IdentityFlag::Eos + | IdentityFlag::Dl + | IdentityFlag::Exec => self .signer .match_id(self.config.id().auth_content().as_ref()), IdentityFlag::Multisig => { @@ -803,7 +805,6 @@ impl ScriptSigner for OmniLockScriptSigner { // should not reach here, return true for compatible reason true } - _ => todo!("other auth type not supported yet"), } } diff --git a/src/util.rs b/src/util.rs index 2f171ecd..0ebf7b83 100644 --- a/src/util.rs +++ b/src/util.rs @@ -256,6 +256,12 @@ pub fn hex_encode(message: &[u8]) -> String { String::from_utf8(res).unwrap() } +pub fn hex_decode(message: &[u8]) -> Vec { + let mut res = vec![0; message.len() / 2]; + faster_hex::hex_decode(message, &mut res).unwrap(); + res +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/test-data/Makefile b/test-data/Makefile similarity index 100% rename from src/test-data/Makefile rename to test-data/Makefile diff --git a/src/test-data/always_success b/test-data/always_success similarity index 100% rename from src/test-data/always_success rename to test-data/always_success diff --git a/test-data/always_success_dl b/test-data/always_success_dl new file mode 100755 index 00000000..0113a2e4 Binary files /dev/null and b/test-data/always_success_dl differ diff --git a/src/test-data/anyone_can_pay b/test-data/anyone_can_pay similarity index 100% rename from src/test-data/anyone_can_pay rename to test-data/anyone_can_pay diff --git a/src/test-data/ckb-cheque-script b/test-data/ckb-cheque-script similarity index 100% rename from src/test-data/ckb-cheque-script rename to test-data/ckb-cheque-script diff --git a/src/test-data/ckb_syscalls.h b/test-data/ckb_syscalls.h similarity index 100% rename from src/test-data/ckb_syscalls.h rename to test-data/ckb_syscalls.h diff --git a/src/test-data/cycle b/test-data/cycle similarity index 100% rename from src/test-data/cycle rename to test-data/cycle diff --git a/src/test-data/cycle.c b/test-data/cycle.c similarity index 100% rename from src/test-data/cycle.c rename to test-data/cycle.c diff --git a/src/test-data/cycle.debug b/test-data/cycle.debug similarity index 100% rename from src/test-data/cycle.debug rename to test-data/cycle.debug diff --git a/src/test-data/cycle.md b/test-data/cycle.md similarity index 100% rename from src/test-data/cycle.md rename to test-data/cycle.md diff --git a/src/test-data/genesis_block.json b/test-data/genesis_block.json similarity index 100% rename from src/test-data/genesis_block.json rename to test-data/genesis_block.json diff --git a/src/test-data/omni_lock b/test-data/omni_lock similarity index 100% rename from src/test-data/omni_lock rename to test-data/omni_lock diff --git a/src/test-data/simple_udt b/test-data/simple_udt similarity index 100% rename from src/test-data/simple_udt rename to test-data/simple_udt diff --git a/test-data/validate_signature_rsa b/test-data/validate_signature_rsa new file mode 100755 index 00000000..28ed0b31 Binary files /dev/null and b/test-data/validate_signature_rsa differ