diff --git a/Cargo.lock b/Cargo.lock index 69a91e3..8ffe513 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] name = "async-oneshot" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -250,19 +256,21 @@ name = "dubp_rs" version = "0.1.0" dependencies = [ "allo-isolate", + "assert_matches", "cbindgen", "dart-bindgen", "dup-crypto", "fast-threadpool", "once_cell", + "parking_lot", "thiserror", ] [[package]] name = "dup-crypto" -version = "0.44.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4281497a99e7bb67d54800671558d707c48ca35320f8c5747fb0eda4d8cbc71" +checksum = "58a3b88cbd886b092a7e6c58608d87744706e0fc66a48eaf452c297e5aac3802" dependencies = [ "aes", "arrayvec", @@ -272,7 +280,7 @@ dependencies = [ "byteorder", "cryptoxide", "ed25519-bip32", - "getrandom 0.2.2", + "getrandom", "once_cell", "ring", "serde", @@ -328,24 +336,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -373,6 +370,15 @@ dependencies = [ ] [[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] name = "itoa" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -440,6 +446,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] name = "ppv-lite86" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -465,11 +496,10 @@ dependencies = [ [[package]] name = "rand" -version = "0.7.3" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ - "getrandom 0.1.16", "libc", "rand_chacha", "rand_core", @@ -478,9 +508,9 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" dependencies = [ "ppv-lite86", "rand_core", @@ -488,27 +518,30 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.5.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" dependencies = [ - "getrandom 0.1.16", + "getrandom", ] [[package]] name = "rand_hc" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" dependencies = [ "rand_core", ] [[package]] name = "redox_syscall" -version = "0.1.57" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +dependencies = [ + "bitflags", +] [[package]] name = "remove_dir_all" @@ -578,6 +611,12 @@ dependencies = [ ] [[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -629,11 +668,11 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "libc", "rand", "redox_syscall", @@ -723,12 +762,6 @@ checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" diff --git a/native/dubp_rs/Cargo.toml b/native/dubp_rs/Cargo.toml index 6468d2e..b27959d 100644 --- a/native/dubp_rs/Cargo.toml +++ b/native/dubp_rs/Cargo.toml @@ -11,11 +11,15 @@ crate-type = ["rlib"] [dependencies] allo-isolate = "0.1.6" -dup-crypto = { version = "0.44.0", features = ["bip32-ed25519", "dewif", "mnemonic", "mnemonic_french", "scrypt"] } +dup-crypto = { version = "0.45.0", features = ["bip32-ed25519", "dewif", "mnemonic", "mnemonic_french", "scrypt"] } fast-threadpool = { version = "0.3.0", default-features = false } once_cell = { version = "1.3.1", default-features = false, features = ["std"] } +parking_lot = "0.11.1" thiserror = "1.0.24" [build-dependencies] cbindgen = "0.14.3" -dart-bindgen = "0.1.7" \ No newline at end of file +dart-bindgen = "0.1.7" + +[dev-dependencies] +assert_matches = "1.5.0" diff --git a/native/dubp_rs/src/dewif.rs b/native/dubp_rs/src/dewif.rs index c05ad85..63252a3 100644 --- a/native/dubp_rs/src/dewif.rs +++ b/native/dubp_rs/src/dewif.rs @@ -81,22 +81,38 @@ pub(super) fn get_dewif_meta( } pub(super) fn get_pubkey( + account_index_opt: Option, + address_index_opt: Option, currency: Currency, dewif: &str, + external_opt: Option, secret_code: &str, ) -> Result { - let mut keypairs = dup_crypto::dewif::read_dewif_file_content( - ExpectedCurrency::Specific(currency), - dewif, - &secret_code.to_ascii_uppercase(), - ) - .map_err(DubpError::DewifReadError)?; + if let Some(account_index) = account_index_opt { + dewif::bip32::get_bip32_pubkey( + account_index, + address_index_opt, + currency, + dewif, + external_opt, + secret_code, + ) + } else if address_index_opt.is_none() && external_opt.is_none() { + let mut keypairs = dup_crypto::dewif::read_dewif_file_content( + ExpectedCurrency::Specific(currency), + dewif, + &secret_code.to_ascii_uppercase(), + ) + .map_err(DubpError::DewifReadError)?; - match keypairs.next() { - Some(KeyPairEnum::Ed25519(keypair)) => Ok(keypair.public_key().to_base58()), - Some(KeyPairEnum::Bip32Ed25519(_)) => Err(DubpError::GetMasterPubkeyOfHdWallet), - Some(_) => Err(DubpError::UnsupportedDewifVersion), - None => Err(DubpError::DewifReadError(DewifReadError::CorruptedContent)), + match keypairs.next() { + Some(KeyPairEnum::Ed25519(keypair)) => Ok(keypair.public_key().to_base58()), + Some(KeyPairEnum::Bip32Ed25519(_)) => Err(DubpError::GetMasterPubkeyOfHdWallet), + Some(_) => Err(DubpError::UnsupportedDewifVersion), + None => Err(DubpError::DewifReadError(DewifReadError::CorruptedContent)), + } + } else { + Err(DubpError::GiveExternalBoolOrAddressIndexForLegacyWallet) } } @@ -126,3 +142,51 @@ pub(crate) fn log_n(system_memory: i64) -> u8 { 12 } } + +pub(super) fn sign( + account_index_opt: Option, + address_index_opt: Option, + currency: Currency, + dewif: &str, + external_opt: Option, + secret_code: &str, + msg: &str, +) -> Result { + if let Some(account_index) = account_index_opt { + dewif::bip32::sign_bip32( + account_index, + address_index_opt, + currency, + dewif, + external_opt, + secret_code, + msg, + ) + } else { + dewif::classic::sign(currency, dewif, secret_code, msg) + } +} + +pub(super) fn sign_several( + account_index_opt: Option, + address_index_opt: Option, + currency: Currency, + dewif: &str, + external_opt: Option, + secret_code: &str, + msgs: &[&str], +) -> Result, DubpError> { + if let Some(account_index) = account_index_opt { + dewif::bip32::sign_several_bip32( + account_index, + address_index_opt, + currency, + dewif, + external_opt, + secret_code, + msgs, + ) + } else { + dewif::classic::sign_several(currency, dewif, secret_code, msgs) + } +} diff --git a/native/dubp_rs/src/dewif/bip32.rs b/native/dubp_rs/src/dewif/bip32.rs index 1fa982c..e539285 100644 --- a/native/dubp_rs/src/dewif/bip32.rs +++ b/native/dubp_rs/src/dewif/bip32.rs @@ -15,30 +15,24 @@ use crate::*; -const MEMBER_ACCOUNT_INDEX: u32 = 0; +#[derive(Debug)] +struct OpaqueAccount { + address_index: u32, + chain_code: ChainCode, + public_key: PublicKey, +} + +static CHAINING_EXTERNAL_PUBKEYS: Lazy>>> = + Lazy::new(|| Arc::new(Mutex::new(HashMap::new()))); pub(crate) fn get_accounts_pubkeys( currency: Currency, dewif: &str, secret_code: &str, - accounts_indexs: Vec, + accounts_indexs: Vec, ) -> Result, DubpError> { - if accounts_indexs.contains( - &DerivationIndex::hard(MEMBER_ACCOUNT_INDEX).map_err(DubpError::InvalidDerivationIndex)?, - ) { - if crate::secret_code::is_ascii_letters(secret_code) { - let log_n = - dup_crypto::dewif::read_dewif_log_n(ExpectedCurrency::Specific(currency), dewif) - .map_err(DubpError::DewifReadError)?; - let expected_secret_code_len = - crate::secret_code::compute_secret_code_len(true, SecretCodeType::Letters, log_n)?; - - if secret_code.len() < expected_secret_code_len { - return Err(DubpError::SecretCodeTooShort); - } - } else { - return Err(DubpError::InvalidSecretCodeType); - } + if accounts_indexs.contains(&U31::new(0)?) { + verify_member_secret_code(currency, dewif, secret_code)?; } let mut keypairs = dup_crypto::dewif::read_dewif_file_content( ExpectedCurrency::Specific(currency), @@ -50,25 +44,23 @@ pub(crate) fn get_accounts_pubkeys( Some(KeyPairEnum::Bip32Ed25519(master_keypair)) => Ok(accounts_indexs .into_iter() .map(|account_index| { - master_keypair - .derive(account_index) - .public_key() - .to_base58() + PrivateDerivationPath::transparent(account_index) + .map(|path| master_keypair.derive(path).public_key().to_base58()) }) - .collect()), + .collect::, InvalidAccountIndex>>()?), Some(_) => Err(DubpError::NotHdWallet), None => Err(DubpError::DewifReadError(DewifReadError::CorruptedContent)), } } -pub(crate) fn sign_transparent( - account_index: DerivationIndex, - currency: &str, +pub(crate) fn get_bip32_pubkey( + account_index: u32, + address_index_opt: Option, + currency: Currency, dewif: &str, + external_opt: Option, secret_code: &str, - msg: &str, ) -> Result { - let currency = parse_currency(currency)?; let mut keypairs = dup_crypto::dewif::read_dewif_file_content( ExpectedCurrency::Specific(currency), dewif, @@ -76,41 +68,325 @@ pub(crate) fn sign_transparent( ) .map_err(DubpError::DewifReadError)?; + if account_index == 0 { + verify_member_secret_code(currency, dewif, secret_code)?; + } + match keypairs.next() { - Some(KeyPairEnum::Bip32Ed25519(master_keypair)) => Ok(master_keypair - .derive(account_index) - .generate_signator() - .sign(msg.as_bytes()) - .to_base64()), + Some(KeyPairEnum::Bip32Ed25519(master_keypair)) => get_bip32_pubkey_inner( + account_index, + address_index_opt, + external_opt, + master_keypair, + ), Some(_) => Err(DubpError::NotHdWallet), None => Err(DubpError::DewifReadError(DewifReadError::CorruptedContent)), } } -pub(crate) fn sign_several_transparent( - account_index: DerivationIndex, - currency: &str, +pub(crate) fn get_opaque_account_next_external_address( + account_index: U31, +) -> Result { + let mut guard = CHAINING_EXTERNAL_PUBKEYS.lock(); + if let Some(opaque_account) = guard.get_mut(&account_index.into_u32()) { + let next_address = PublicKeyWithChainCode { + chain_code: opaque_account.chain_code, + public_key: opaque_account.public_key, + } + .derive(U31::new(opaque_account.address_index).expect("unreachable")) + .expect("unreachable") + .public_key; + + opaque_account.address_index += 1; + + Ok(next_address.to_base58()) + } else { + Err(DubpError::OpaqueAccountNotLoaded) + } +} + +pub(crate) fn load_opaque_bip32_accounts( + accounts_indexs: Vec, + currency: Currency, dewif: &str, secret_code: &str, - msgs: &[&str], -) -> Result, DubpError> { - let currency = parse_currency(currency)?; +) -> Result<(), DubpError> { let mut keypairs = dup_crypto::dewif::read_dewif_file_content( ExpectedCurrency::Specific(currency), dewif, &secret_code.to_ascii_uppercase(), ) .map_err(DubpError::DewifReadError)?; - match keypairs.next() { Some(KeyPairEnum::Bip32Ed25519(master_keypair)) => { - let signator = master_keypair.derive(account_index).generate_signator(); - Ok(msgs - .iter() - .map(|msg| signator.sign(msg.as_bytes()).to_base64()) - .collect()) + for account_index in accounts_indexs { + let external_path = PrivateDerivationPath::opaque(account_index, true, None)?; + let external_kp = master_keypair.derive(external_path); + let opaque_account = OpaqueAccount { + address_index: 0, + public_key: external_kp.public_key(), + chain_code: external_kp.chain_code(), + }; + + let mut guard = CHAINING_EXTERNAL_PUBKEYS.lock(); + guard.insert(account_index.into_u32(), opaque_account); + } + Ok(()) } Some(_) => Err(DubpError::NotHdWallet), None => Err(DubpError::DewifReadError(DewifReadError::CorruptedContent)), } } + +pub(crate) fn sign_bip32( + account_index: u32, + address_index_opt: Option, + currency: Currency, + dewif: &str, + external_opt: Option, + secret_code: &str, + msg: &str, +) -> Result { + let mut keypairs = dup_crypto::dewif::read_dewif_file_content( + ExpectedCurrency::Specific(currency), + dewif, + &secret_code.to_ascii_uppercase(), + ) + .map_err(DubpError::DewifReadError)?; + + if account_index == 0 { + verify_member_secret_code(currency, dewif, secret_code)?; + } + + match keypairs.next() { + Some(KeyPairEnum::Bip32Ed25519(master_keypair)) => sign_bip32_inner( + account_index, + address_index_opt, + external_opt, + master_keypair, + msg, + ), + Some(_) => Err(DubpError::NotHdWallet), + None => Err(DubpError::DewifReadError(DewifReadError::CorruptedContent)), + } +} + +pub(crate) fn sign_several_bip32( + account_index: u32, + address_index_opt: Option, + currency: Currency, + dewif: &str, + external_opt: Option, + secret_code: &str, + msgs: &[&str], +) -> Result, DubpError> { + let mut keypairs = dup_crypto::dewif::read_dewif_file_content( + ExpectedCurrency::Specific(currency), + dewif, + &secret_code.to_ascii_uppercase(), + ) + .map_err(DubpError::DewifReadError)?; + + if account_index == 0 { + verify_member_secret_code(currency, dewif, secret_code)?; + } + + match keypairs.next() { + Some(KeyPairEnum::Bip32Ed25519(master_keypair)) => sign_several_bip32_inner( + account_index, + address_index_opt, + external_opt, + master_keypair, + msgs, + ), + Some(_) => Err(DubpError::NotHdWallet), + None => Err(DubpError::DewifReadError(DewifReadError::CorruptedContent)), + } +} + +fn get_bip32_pubkey_inner( + account_index: u32, + address_index_opt: Option, + external_opt: Option, + master_keypair: KeyPair, +) -> Result { + Ok(master_keypair + .derive(z_get_derivation_path( + account_index, + address_index_opt, + external_opt, + )?) + .public_key() + .to_base58()) +} + +fn sign_bip32_inner( + account_index: u32, + address_index_opt: Option, + external_opt: Option, + master_keypair: KeyPair, + msg: &str, +) -> Result { + Ok(master_keypair + .derive(z_get_derivation_path( + account_index, + address_index_opt, + external_opt, + )?) + .generate_signator() + .sign(msg.as_bytes()) + .to_base64()) +} + +fn sign_several_bip32_inner( + account_index: u32, + address_index_opt: Option, + external_opt: Option, + master_keypair: KeyPair, + msgs: &[&str], +) -> Result, DubpError> { + let signator = master_keypair + .derive(z_get_derivation_path( + account_index, + address_index_opt, + external_opt, + )?) + .generate_signator(); + + Ok(msgs + .iter() + .map(|msg| signator.sign(msg.as_bytes()).to_base64()) + .collect()) +} + +fn verify_member_secret_code( + currency: Currency, + dewif: &str, + secret_code: &str, +) -> Result<(), DubpError> { + if crate::secret_code::is_ascii_letters(secret_code) { + let log_n = + dup_crypto::dewif::read_dewif_log_n(ExpectedCurrency::Specific(currency), dewif) + .map_err(DubpError::DewifReadError)?; + let expected_secret_code_len = + crate::secret_code::compute_secret_code_len(true, SecretCodeType::Letters, log_n)?; + + if secret_code.len() < expected_secret_code_len { + Err(DubpError::SecretCodeTooShort) + } else { + Ok(()) + } + } else { + Err(DubpError::InvalidSecretCodeType) + } +} + +fn z_get_derivation_path( + account_index: u32, + address_index_opt: Option, + external_opt: Option, +) -> Result { + let account_index_u31 = U31::new(account_index)?; + match account_index % 3 { + 0 => Ok(PrivateDerivationPath::transparent(account_index_u31)?), + 1 => { + if let Some(external) = external_opt { + if external { + if address_index_opt.is_none() { + Ok(PrivateDerivationPath::semi_opaque_external( + account_index_u31, + )?) + } else { + Err(DubpError::InvalidAccountIndex(InvalidAccountIndex)) + } + } else if address_index_opt.is_some() { + Ok(PrivateDerivationPath::semi_opaque_internal( + account_index_u31, + address_index_opt, + )?) + } else { + Err(DubpError::TryToSignWithInternalChainingAddress) + } + } else { + Err(DubpError::MissingExternalBool) + } + } + 2 => { + if address_index_opt.is_some() { + if let Some(external) = external_opt { + Ok(PrivateDerivationPath::opaque( + account_index_u31, + external, + address_index_opt, + )?) + } else { + Err(DubpError::MissingExternalBool) + } + } else { + Err(DubpError::TryToSignWithChainingAddress) + } + } + _ => unreachable!(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use assert_matches::assert_matches; + + const MNEMONIC: &str = + "tongue cute mail fossil great frozen same social weasel impact brush kind"; + + #[test] + fn test_get_accounts_pubkeys() -> Result<(), DubpError> { + let vec = crate::dewif::gen_dewif( + "g1", + Language::English, + MNEMONIC, + false, + SecretCodeType::Letters, + 1_000, + )?; + let dewif = &vec[0]; + let secret_code = &vec[1]; + //println!("TMP: secret_code={}", secret_code); + + assert_matches!( + get_accounts_pubkeys( + Currency::from(G1_CURRENCY), + dewif, + secret_code, + vec![U31::new(0).expect("unreachable")], + ), + Err(DubpError::SecretCodeTooShort) + ); + + Ok(()) + } + + #[test] + fn test_sign_bip32() { + assert_matches!( + z_get_derivation_path(1, None, None), + Err(DubpError::MissingExternalBool) + ); + assert_matches!( + z_get_derivation_path(2, None, None), + Err(DubpError::TryToSignWithChainingAddress) + ); + assert_matches!(z_get_derivation_path(3, None, None), Ok(_)); + assert_matches!( + z_get_derivation_path(1, None, Some(false)), + Err(DubpError::TryToSignWithInternalChainingAddress) + ); + assert_matches!( + z_get_derivation_path(2, None, Some(true)), + Err(DubpError::TryToSignWithChainingAddress) + ); + assert_matches!( + z_get_derivation_path(1, Some(U31::new(5).expect("unreachable")), Some(true),), + Err(DubpError::InvalidAccountIndex(_)) + ); + } +} diff --git a/native/dubp_rs/src/dewif/classic.rs b/native/dubp_rs/src/dewif/classic.rs index dcfd97a..a0e23f8 100644 --- a/native/dubp_rs/src/dewif/classic.rs +++ b/native/dubp_rs/src/dewif/classic.rs @@ -16,12 +16,11 @@ use crate::*; pub(crate) fn sign( - currency: &str, + currency: Currency, dewif: &str, secret_code: &str, msg: &str, ) -> Result { - let currency = parse_currency(currency)?; let mut keypairs = dup_crypto::dewif::read_dewif_file_content( ExpectedCurrency::Specific(currency), dewif, @@ -36,12 +35,11 @@ pub(crate) fn sign( } pub(crate) fn sign_several( - currency: &str, + currency: Currency, dewif: &str, secret_code: &str, msgs: &[&str], ) -> Result, DubpError> { - let currency = parse_currency(currency)?; let mut keypairs = dup_crypto::dewif::read_dewif_file_content( ExpectedCurrency::Specific(currency), dewif, diff --git a/native/dubp_rs/src/error.rs b/native/dubp_rs/src/error.rs index 33f60a6..29a48bc 100644 --- a/native/dubp_rs/src/error.rs +++ b/native/dubp_rs/src/error.rs @@ -13,8 +13,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use dup_crypto::keys::ed25519::bip32::InvalidDerivationIndex; - use crate::*; /// Dubp error @@ -26,20 +24,30 @@ pub(crate) enum DubpError { DigitsCodeForbidForMemberWallet, #[error("It is forbidden to retrieve the master public key of an HD wallet.")] GetMasterPubkeyOfHdWallet, + #[error("Give external bool or address index for legacy wallet.")] + GiveExternalBoolOrAddressIndexForLegacyWallet, #[error("I/O error: {0}")] IoErr(io::Error), #[error("{0}")] - InvalidDerivationIndex(InvalidDerivationIndex), + InvalidAccountIndex(InvalidAccountIndex), + #[error("{0}")] + InvalidU31(U31Error), #[error("Invalid secret code type")] InvalidSecretCodeType, + #[error("Missing external bool")] + MissingExternalBool, #[error("this wallet is not an HD wallet")] NotHdWallet, - #[error("this account index is not a transparent account index")] - NotTransparentAccountIndex, #[error("A given parameter is null")] NullParamErr, + #[error("Opaque account not loaded")] + OpaqueAccountNotLoaded, #[error("Secret code too short: please change your secret code")] SecretCodeTooShort, + #[error("The chaining address cannot be used to sign with opaque account")] + TryToSignWithChainingAddress, + #[error("The internal chaining address cannot be used to sign with semi-opaque account")] + TryToSignWithInternalChainingAddress, #[error("fail to generate random bytes")] RandErr, #[error("Unknown currency name")] @@ -60,9 +68,15 @@ impl From for DubpError { } } -impl From for DubpError { - fn from(e: InvalidDerivationIndex) -> Self { - Self::InvalidDerivationIndex(e) +impl From for DubpError { + fn from(e: InvalidAccountIndex) -> Self { + Self::InvalidAccountIndex(e) + } +} + +impl From for DubpError { + fn from(e: U31Error) -> Self { + Self::InvalidU31(e) } } @@ -77,6 +91,17 @@ impl IntoDart for DartRes { self.0.into_dart() } } +impl From> for DartRes +where + E: ToString, +{ + fn from(res: Result<(), E>) -> Self { + match res { + Ok(()) => Self("".into_dart()), + Err(e) => Self(format!("DUBP_RS_ERROR: {}", e.to_string()).into_dart()), + } + } +} impl From> for DartRes where E: ToString, diff --git a/native/dubp_rs/src/inputs.rs b/native/dubp_rs/src/inputs.rs index 31ecf37..4aeaee9 100644 --- a/native/dubp_rs/src/inputs.rs +++ b/native/dubp_rs/src/inputs.rs @@ -52,13 +52,13 @@ pub(crate) fn char_ptr_to_str<'a>(c_char_ptr: *const raw::c_char) -> Result<&'a } } -pub(crate) fn char_ptr_prt_to_vec_hard_derivation_index( +pub(crate) fn char_ptr_prt_to_vec_u31( u32_ptr: *const u32, len: u32, -) -> Result, DubpError> { +) -> Result, DubpError> { u32_ptr_to_vec_u32(u32_ptr, len) .into_iter() - .map(|ai| DerivationIndex::hard(ai).map_err(DubpError::InvalidDerivationIndex)) + .map(|ai| U31::new(ai).map_err(DubpError::InvalidU31)) .collect() } @@ -76,6 +76,30 @@ pub(crate) fn char_ptr_prt_to_vec_str<'a>( Ok(str_vec) } +pub(crate) fn i32_to_opt_bool(i: i32) -> Option { + match i { + 1 => Some(true), + 0 => Some(false), + _ => None, + } +} + +pub(crate) fn i32_to_opt_u31(i: i32) -> Result, DubpError> { + if i >= 0 { + Ok(Some(U31::new(i as u32)?)) + } else { + Ok(None) + } +} + +pub(crate) fn i32_to_opt_u32(i: i32) -> Option { + if i >= 0 { + Some(i as u32) + } else { + None + } +} + pub(crate) fn parse_currency(currency: &str) -> Result { let currency_code = match currency { "g1" => G1_CURRENCY, @@ -85,14 +109,6 @@ pub(crate) fn parse_currency(currency: &str) -> Result { Ok(Currency::from(currency_code)) } -pub(crate) fn transparent_account_index(account_index: u32) -> Result { - if account_index % 3 == 0 { - DerivationIndex::hard(account_index).map_err(DubpError::InvalidDerivationIndex) - } else { - Err(DubpError::NotTransparentAccountIndex) - } -} - pub(crate) fn u32_ptr_to_vec_u32(u32_ptr: *const u32, len: u32) -> Vec { let len = len as usize; let u32_slice: &[u32] = unsafe { std::slice::from_raw_parts(u32_ptr, len) }; diff --git a/native/dubp_rs/src/legacy.rs b/native/dubp_rs/src/legacy.rs index 45aef12..92bb737 100644 --- a/native/dubp_rs/src/legacy.rs +++ b/native/dubp_rs/src/legacy.rs @@ -42,14 +42,6 @@ pub(super) fn get_pubkey(salt: &str, password: &str) -> String { .to_base58() } -pub(super) fn sign(salt: &str, password: &str, msg: &str) -> String { - KeyPairFromSaltedPasswordGenerator::with_default_parameters() - .generate(SaltedPassword::new(salt.to_owned(), password.to_owned())) - .generate_signator() - .sign(msg.as_bytes()) - .to_base64() -} - #[cfg(test)] mod tests { use super::*; @@ -73,7 +65,14 @@ mod tests { assert_eq!(get_pubkey("salt", "pass"), pubkey.to_owned()); assert_eq!( - crate::dewif::get_pubkey(Currency::from(G1_CURRENCY), &dewif, &secret_code)?, + crate::dewif::get_pubkey( + None, + None, + Currency::from(G1_CURRENCY), + &dewif, + None, + &secret_code + )?, pubkey.to_owned() ); diff --git a/native/dubp_rs/src/lib.rs b/native/dubp_rs/src/lib.rs index 544d1c4..92b8091 100644 --- a/native/dubp_rs/src/lib.rs +++ b/native/dubp_rs/src/lib.rs @@ -32,14 +32,19 @@ use dup_crypto::{ bases::b58::ToBase58, dewif::{Currency, DewifReadError, ExpectedCurrency, G1_CURRENCY, G1_TEST_CURRENCY}, keys::{ - ed25519::bip32::DerivationIndex, ed25519::KeyPairFromSeed32Generator, KeyPair as _, - KeyPairEnum, Signator as _, Signature as _, + ed25519::bip32::{ + ChainCode, InvalidAccountIndex, KeyPair, PrivateDerivationPath, PublicKeyWithChainCode, + }, + ed25519::{KeyPairFromSeed32Generator, PublicKey}, + KeyPair as _, KeyPairEnum, Signator as _, Signature as _, }, mnemonic::{Language, Mnemonic, MnemonicType}, + utils::{U31Error, U31}, }; use fast_threadpool::{ThreadPool, ThreadPoolConfig, ThreadPoolSyncHandler}; use once_cell::sync::Lazy; -use std::{ffi::CStr, io, os::raw}; +use parking_lot::Mutex; +use std::{collections::HashMap, ffi::CStr, io, os::raw, sync::Arc}; use thiserror::Error; #[no_mangle] @@ -164,6 +169,30 @@ pub extern "C" fn gen_mnemonic(port: i64, language: u32) { } #[no_mangle] +pub extern "C" fn get_bip32_dewif_accounts_pubkeys( + port: i64, + currency: *const raw::c_char, + dewif: *const raw::c_char, + secret_code: *const raw::c_char, + accounts_indexs_len: u32, + accounts_indexs: *const u32, +) { + exec_async( + port, + || { + let currency = parse_currency(char_ptr_to_str(currency)?)?; + let dewif = char_ptr_to_str(dewif)?; + let secret_code = char_ptr_to_str(secret_code)?; + let accounts_indexs = char_ptr_prt_to_vec_u31(accounts_indexs, accounts_indexs_len)?; + Ok((currency, dewif, secret_code, accounts_indexs)) + }, + |(currency, dewif, secret_code, accounts_indexs)| { + dewif::bip32::get_accounts_pubkeys(currency, dewif, secret_code, accounts_indexs) + }, + ) +} + +#[no_mangle] pub extern "C" fn get_dewif_meta( port: i64, dewif: *const raw::c_char, @@ -201,43 +230,76 @@ pub extern "C" fn get_dewif_secret_code_len( #[no_mangle] pub extern "C" fn get_dewif_pubkey( port: i64, + account_index: i32, + address_index: i32, currency: *const raw::c_char, dewif: *const raw::c_char, + external_opt: i32, pin: *const raw::c_char, ) { exec_async( port, || { + let account_index_opt = i32_to_opt_u32(account_index); + let address_index_opt = i32_to_opt_u31(address_index)?; let currency = parse_currency(char_ptr_to_str(currency)?)?; let dewif = char_ptr_to_str(dewif)?; + let external_opt = i32_to_opt_bool(external_opt); let pin = char_ptr_to_str(pin)?; - Ok((currency, dewif, pin)) + Ok(( + account_index_opt, + address_index_opt, + currency, + dewif, + external_opt, + pin, + )) + }, + |(account_index_opt, address_index_opt, currency, dewif, external_opt, pin)| { + dewif::get_pubkey( + account_index_opt, + address_index_opt, + currency, + dewif, + external_opt, + pin, + ) }, - |(currency, dewif, pin)| dewif::get_pubkey(currency, dewif, pin), ) } #[no_mangle] -pub extern "C" fn get_bip32_dewif_accounts_pubkeys( +pub extern "C" fn get_opaque_account_next_external_address(port: i64, account_index: u32) { + exec_async( + port, + || { + let account_index = U31::new(account_index)?; + Ok(account_index) + }, + dewif::bip32::get_opaque_account_next_external_address, + ) +} + +#[no_mangle] +pub extern "C" fn load_opaque_bip32_accounts( port: i64, + accounts_indexs_len: u32, + accounts_indexs: *const u32, currency: *const raw::c_char, dewif: *const raw::c_char, secret_code: *const raw::c_char, - accounts_indexs_len: u32, - accounts_indexs: *const u32, ) { exec_async( port, || { + let accounts_indexs = char_ptr_prt_to_vec_u31(accounts_indexs, accounts_indexs_len)?; let currency = parse_currency(char_ptr_to_str(currency)?)?; let dewif = char_ptr_to_str(dewif)?; let secret_code = char_ptr_to_str(secret_code)?; - let accounts_indexs = - char_ptr_prt_to_vec_hard_derivation_index(accounts_indexs, accounts_indexs_len)?; - Ok((currency, dewif, secret_code, accounts_indexs)) + Ok((accounts_indexs, currency, dewif, secret_code)) }, - |(currency, dewif, secret_code, accounts_indexs)| { - dewif::bip32::get_accounts_pubkeys(currency, dewif, secret_code, accounts_indexs) + |(accounts_indexs, currency, dewif, secret_code)| { + dewif::bip32::load_opaque_bip32_accounts(accounts_indexs, currency, dewif, secret_code) }, ) } @@ -279,112 +341,106 @@ pub extern "C" fn mnemonic_to_pubkey( #[no_mangle] pub extern "C" fn sign( port: i64, + account_index: i32, + address_index: i32, currency: *const raw::c_char, dewif: *const raw::c_char, - pin: *const raw::c_char, - msg: *const raw::c_char, -) { - exec_async( - port, - || { - let currency = char_ptr_to_str(currency)?; - let dewif = char_ptr_to_str(dewif)?; - let pin = char_ptr_to_str(pin)?; - let msg = char_ptr_to_str(msg)?; - Ok((currency, dewif, pin, msg)) - }, - |(currency, dewif, pin, msg)| dewif::classic::sign(currency, dewif, pin, msg), - ) -} - -#[no_mangle] -pub extern "C" fn sign_bip32_transparent( - port: i64, - account_index: u32, - currency: *const raw::c_char, - dewif: *const raw::c_char, + external_opt: i32, secret_code: *const raw::c_char, msg: *const raw::c_char, ) { exec_async( port, || { - let account_index = transparent_account_index(account_index)?; - let currency = char_ptr_to_str(currency)?; + let account_index_opt = i32_to_opt_u32(account_index); + let address_index_opt = i32_to_opt_u31(address_index)?; + let currency = parse_currency(char_ptr_to_str(currency)?)?; let dewif = char_ptr_to_str(dewif)?; - let pin = char_ptr_to_str(secret_code)?; + let external_opt = i32_to_opt_bool(external_opt); + let secret_code = char_ptr_to_str(secret_code)?; let msg = char_ptr_to_str(msg)?; - Ok((currency, dewif, pin, msg, account_index)) + Ok(( + account_index_opt, + address_index_opt, + currency, + dewif, + external_opt, + secret_code, + msg, + )) }, - |(currency, dewif, secret_code, msg, account_index)| { - dewif::bip32::sign_transparent(account_index, currency, dewif, secret_code, msg) + |( + account_index_opt, + address_index_opt, + currency, + dewif, + external_opt, + secret_code, + msg, + )| { + dewif::sign( + account_index_opt, + address_index_opt, + currency, + dewif, + external_opt, + secret_code, + msg, + ) }, ) } #[no_mangle] -pub extern "C" fn sign_legacy( - port: i64, - salt: *const raw::c_char, - password: *const raw::c_char, - msg: *const raw::c_char, -) { - exec_async( - port, - || { - let salt = char_ptr_to_str(salt)?; - let password = char_ptr_to_str(password)?; - let msg = char_ptr_to_str(msg)?; - Ok((salt, password, msg)) - }, - |(salt, password, msg)| Ok::<_, DubpError>(legacy::sign(salt, password, msg)), - ) -} - -#[no_mangle] pub extern "C" fn sign_several( port: i64, + account_index: i32, + address_index: i32, currency: *const raw::c_char, dewif: *const raw::c_char, - pin: *const raw::c_char, + external_opt: i32, + secret_code: *const raw::c_char, msgs_len: u32, msgs: *const *const raw::c_char, ) { exec_async( port, || { - let currency = char_ptr_to_str(currency)?; + let account_index_opt = i32_to_opt_u32(account_index); + let address_index_opt = i32_to_opt_u31(address_index)?; + let currency = parse_currency(char_ptr_to_str(currency)?)?; let dewif = char_ptr_to_str(dewif)?; - let pin = char_ptr_to_str(pin)?; + let external_opt = i32_to_opt_bool(external_opt); + let secret_code = char_ptr_to_str(secret_code)?; let msgs = char_ptr_prt_to_vec_str(msgs, msgs_len)?; - Ok((currency, dewif, pin, msgs)) + Ok(( + account_index_opt, + address_index_opt, + currency, + dewif, + external_opt, + secret_code, + msgs, + )) }, - |(currency, dewif, pin, msgs)| dewif::classic::sign_several(currency, dewif, pin, &msgs), - ) -} - -#[no_mangle] -pub extern "C" fn sign_several_bip32_transparent( - port: i64, - account_index: u32, - currency: *const raw::c_char, - dewif: *const raw::c_char, - pin: *const raw::c_char, - msgs_len: u32, - msgs: *const *const raw::c_char, -) { - exec_async( - port, - || { - let account_index = transparent_account_index(account_index)?; - let currency = char_ptr_to_str(currency)?; - let dewif = char_ptr_to_str(dewif)?; - let pin = char_ptr_to_str(pin)?; - let msgs = char_ptr_prt_to_vec_str(msgs, msgs_len)?; - Ok((currency, dewif, pin, msgs, account_index)) - }, - |(currency, dewif, pin, msgs, account_index)| { - dewif::bip32::sign_several_transparent(account_index, currency, dewif, pin, &msgs) + |( + account_index_opt, + address_index_opt, + currency, + dewif, + external_opt, + secret_code, + msgs, + )| { + dewif::sign_several( + account_index_opt, + address_index_opt, + currency, + dewif, + external_opt, + secret_code, + &msgs, + ) }, ) } diff --git a/packages/dubp_rs/README.md b/packages/dubp_rs/README.md index 958eb82..6be3629 100644 --- a/packages/dubp_rs/README.md +++ b/packages/dubp_rs/README.md @@ -144,10 +144,12 @@ NewWallet new_wallet = await DubpRust.genWalletFromMnemonic( #### Function signature ```dart -static Future signBip32Transparent({ +static Future sign({ int accountIndex, + int addressIndexOpt, String currency = "g1", String dewif, + bool externalOpt, String secretCode, String message }); @@ -158,7 +160,7 @@ If the wallet is not dedicated to the Ğ1 currency, you must indicate the curren #### Usage example ```dart -String signature = await DubpRust.signBip32Transparent( +String signature = await DubpRust.sign( accountIndex: 3, dewif: "AAAAARAAAAGfFDAs+jVZYkfhBlHZZ2fEQIvBqnG16g5+02cY18wSOjW0cUg2JV3SUTJYN2CrbQeRDwGazWnzSFBphchMmiL0", pin: "CDJ4UB", diff --git a/packages/dubp_rs/lib/dubp.dart b/packages/dubp_rs/lib/dubp.dart index ee4ee9b..94382c7 100644 --- a/packages/dubp_rs/lib/dubp.dart +++ b/packages/dubp_rs/lib/dubp.dart @@ -177,7 +177,16 @@ class DubpRust { return Future.value(NewWallet._(newWallet[0], newWallet[1])); } - //get_bip32_dewif_accounts_pubkeys + /// Get next external public key of a specific opaque account + static Future getOpaqueAccountNextExternalPublicKey( + {int accountIndex}) async { + final completer = Completer(); + final sendPort = + singleCompletePort(completer, callback: _handleErr); + native.get_opaque_account_next_external_address( + sendPort.nativePort, accountIndex.toUnsigned(31)); + return completer.future; + } /// Get BIP32 accounts public keys (in base 58) of `dewif` master keypair. static Future> getBip32DewifAccountsPublicKeys( @@ -194,7 +203,7 @@ class DubpRust { Utf8.toUtf8(dewif), Utf8.toUtf8(secretCode), accountsIndex.length, - _listIntToPtr(accountsIndex)); + _listIntToPtrUint32(accountsIndex)); return completer.future; } @@ -215,14 +224,26 @@ class DubpRust { /// Get public key (in base 58) of `dewif` keypair. static Future getDewifPublicKey( - {String currency = "g1", String dewif, String pin}) async { + {int accountIndexOpt, + int addressIndexOpt, + String currency = "g1", + String dewif, + bool externalOpt, + String pin}) async { + var externalOptInt = -1; + if (externalOpt != null) { + externalOptInt = externalOpt ? 1 : 0; + } final completer = Completer(); final sendPort = singleCompletePort(completer, callback: _handleErr); native.get_dewif_pubkey( sendPort.nativePort, + accountIndexOpt ?? -1, + addressIndexOpt ?? -1, Utf8.toUtf8(currency), Utf8.toUtf8(dewif), + externalOptInt, Utf8.toUtf8(pin), ); return completer.future; @@ -261,43 +282,55 @@ class DubpRust { return completer.future; } - /// Sign the message `message` with `dewif` Bip32-Ed25519 keypair encryted - /// in DEWIF format. - /// - /// If you have several messages to sign, use `signSeveralBip32Transparent` - /// method instead. - static Future signBip32Transparent( - {int accountIndex, - String currency = "g1", - String dewif, - String secretCode, - String message}) { - final completer = Completer(); + /// Load opaque accounts + static Future loadOpaqueAccounts( + List accountsIndex, + String currency, + String dewif, + String secretCode, + ) { + final completer = Completer(); final sendPort = - singleCompletePort(completer, callback: _handleErr); - native.sign_bip32_transparent( - sendPort.nativePort, - accountIndex, - Utf8.toUtf8(currency), - Utf8.toUtf8(dewif), - Utf8.toUtf8(secretCode), - Utf8.toUtf8(message), - ); + singleCompletePort(completer, callback: _handleErrVoid); + native.load_opaque_bip32_accounts( + sendPort.nativePort, + accountsIndex.length, + _listIntToPtrUint32(accountsIndex), + Utf8.toUtf8(currency), + Utf8.toUtf8(dewif), + Utf8.toUtf8(secretCode)); return completer.future; } - /// Sign the message `message` with legacy wallet (password + salt) + /// Sign the message `message` with `dewif` keypair encryted + /// in DEWIF format. /// - /// This deprecated method must be used only for compatibility purpose ! - static Future signLegacy( - {String password, String salt, String message}) { + /// If you have several messages to sign, use `signSeveral` + /// method instead. + static Future sign( + {int accountIndexOpt, + int addressIndexOpt, + String currency = "g1", + String dewif, + bool externalOpt, + String secretCode, + String message}) { + var externalOptInt = -1; + if (externalOpt != null) { + externalOptInt = externalOpt ? 1 : 0; + } + final completer = Completer(); final sendPort = singleCompletePort(completer, callback: _handleErr); - native.sign_legacy( + native.sign( sendPort.nativePort, - Utf8.toUtf8(password), - Utf8.toUtf8(salt), + accountIndexOpt ?? -1, + addressIndexOpt ?? -1, + Utf8.toUtf8(currency), + Utf8.toUtf8(dewif), + externalOptInt, + Utf8.toUtf8(secretCode), Utf8.toUtf8(message), ); return completer.future; @@ -309,22 +342,31 @@ class DubpRust { /// This method is optimized to sign several messages at once. If you have /// several messages to sign, avoid calling the `sign` method for each /// message. Use this `signSeveral` method instead. - static Future> signSeveralBip32Transparent( - {int accountIndex, + static Future> signSeveral( + {int accountIndexOpt, + int addressIndexOpt, String currency = "g1", String dewif, - String pin, + bool externalOpt, + String secretCode, List messages}) { + var externalOptInt = -1; + if (externalOpt != null) { + externalOptInt = externalOpt ? 1 : 0; + } + final completer = Completer>(); final sendPort = singleCompletePort, List>(completer, callback: _handleErrList); - native.sign_several_bip32_transparent( + native.sign_several( sendPort.nativePort, - accountIndex, + accountIndexOpt ?? -1, + addressIndexOpt ?? -1, Utf8.toUtf8(currency), Utf8.toUtf8(dewif), - Utf8.toUtf8(pin), + externalOptInt, + Utf8.toUtf8(secretCode), messages.length, _listStringToPtr(messages), ); @@ -332,11 +374,11 @@ class DubpRust { return completer.future; } - static Pointer _listIntToPtr(List list) { - //final listUint32 = list.map(int.toUnsigned).toList(); - final Pointer ptr = allocate(count: list.length); - for (var i = 0; i < list.length; i++) { - ptr[i] = list[i]; + static Pointer _listIntToPtrUint32(List list) { + final listUint32 = list.map((i) => i.toUnsigned(31)).toList(); + final Pointer ptr = allocate(count: listUint32.length); + for (var i = 0; i < listUint32.length; i++) { + ptr[i] = listUint32[i]; } return ptr; } @@ -371,6 +413,14 @@ class DubpRust { } } + static void _handleErrVoid(String res) { + if (res.startsWith('DUBP_RS_ERROR: ')) { + final error = res; + print(error); + throw error; + } + } + /*static int _handleErrInt(String res) { if (res.startsWith('DUBP_RS_ERROR: ')) { final error = res;