Skip to content

Commit bda731c

Browse files
committed
refactor: optimize zcash
1 parent 7c449f3 commit bda731c

File tree

9 files changed

+210
-26
lines changed

9 files changed

+210
-26
lines changed

rust/Cargo.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/keystore/src/algorithms/zcash/mod.rs

+36-6
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,43 @@ use zcash_vendor::{
2222

2323
use crate::errors::{KeystoreError, Result};
2424

25-
use super::utils::normalize_path;
26-
27-
pub fn derive_ufvk<P: consensus::Parameters>(params: &P, seed: &[u8]) -> Result<String> {
28-
let usk = UnifiedSpendingKey::from_seed(params, &seed, AccountId::ZERO)
25+
pub fn derive_ufvk<P: consensus::Parameters>(
26+
params: &P,
27+
seed: &[u8],
28+
account_path: &str,
29+
) -> Result<String> {
30+
let account_path = DerivationPath::from_str(account_path.to_lowercase().as_str())
2931
.map_err(|e| KeystoreError::DerivationError(e.to_string()))?;
30-
let ufvk = usk.to_unified_full_viewing_key();
31-
Ok(ufvk.encode(params))
32+
if account_path.len() != 3 {
33+
return Err(KeystoreError::DerivationError(format!(
34+
"invalid account path: {}",
35+
account_path
36+
)));
37+
}
38+
//should be hardened(32) hardened(133) hardened(account_id)
39+
let purpose = account_path[0];
40+
let coin_type = account_path[1];
41+
let account_id = account_path[2];
42+
match (purpose, coin_type, account_id) {
43+
(
44+
ChildNumber::Hardened { index: 32 },
45+
ChildNumber::Hardened { index: 133 },
46+
ChildNumber::Hardened { index: account_id },
47+
) => {
48+
let account_index = zip32::AccountId::try_from(account_id)
49+
.map_err(|_e| KeystoreError::DerivationError(format!("invalid account index")))?;
50+
let usk = UnifiedSpendingKey::from_seed(params, &seed, account_index)
51+
.map_err(|e| KeystoreError::DerivationError(e.to_string()))?;
52+
let ufvk = usk.to_unified_full_viewing_key();
53+
Ok(ufvk.encode(params))
54+
}
55+
_ => {
56+
return Err(KeystoreError::DerivationError(format!(
57+
"invalid account path: {}",
58+
account_path
59+
)));
60+
}
61+
}
3262
}
3363

3464
pub fn calculate_seed_fingerprint(seed: &[u8]) -> Result<[u8; 32]> {

rust/rust_c/src/common/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ rsa = { workspace = true }
2222
cryptoxide = { workspace = true }
2323
ur-parse-lib = { workspace = true }
2424
sha1 = { workspace = true }
25+
aes = { workspace = true }
26+
cbc = { workspace = true }
2527

2628
app_bitcoin = { workspace = true }
2729
app_wallets = { workspace = true }

rust/rust_c/src/common/src/lib.rs

+124-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@
44
#![allow(unused_unsafe)]
55
extern crate alloc;
66

7+
use aes::cipher::block_padding::Pkcs7;
8+
use aes::cipher::generic_array::GenericArray;
9+
use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit};
710
use alloc::boxed::Box;
8-
use alloc::string::ToString;
11+
use alloc::string::{String, ToString};
12+
use core::ptr::null_mut;
913
use core::slice;
10-
use keystore::algorithms::zcash;
14+
use cryptoxide::hashing::sha256;
15+
use ffi::VecFFI;
16+
use keystore::algorithms::ed25519::slip10_ed25519::get_private_key_by_seed;
1117

1218
use bitcoin::hex::Case;
1319
use bitcoin_hashes::hex::DisplayHex;
@@ -16,7 +22,7 @@ use hex;
1622
use hex::ToHex;
1723

1824
use errors::ErrorCodes;
19-
use structs::TransactionCheckResult;
25+
use structs::{Response, TransactionCheckResult};
2026
use types::Ptr;
2127

2228
use crate::errors::RustCError;
@@ -297,3 +303,118 @@ pub extern "C" fn pbkdf2_rust_64(
297303
pub extern "C" fn tx_check_pass() -> Ptr<TransactionCheckResult> {
298304
TransactionCheckResult::new().c_ptr()
299305
}
306+
307+
type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
308+
type Aes256CbcDec = cbc::Decryptor<aes::Aes256>;
309+
310+
#[no_mangle]
311+
pub extern "C" fn rust_aes256_cbc_encrypt(
312+
data: PtrString,
313+
password: PtrString,
314+
iv: PtrBytes,
315+
iv_len: u32,
316+
) -> *mut SimpleResponse<c_char> {
317+
let data = recover_c_char(data);
318+
let data = data.as_bytes();
319+
let password = recover_c_char(password);
320+
let iv = unsafe { slice::from_raw_parts(iv, iv_len as usize) };
321+
let key = sha256(password.as_bytes());
322+
let iv = GenericArray::from_slice(&iv);
323+
let key = GenericArray::from_slice(&key);
324+
let ct = Aes256CbcEnc::new(key, iv).encrypt_padded_vec_mut::<Pkcs7>(data);
325+
SimpleResponse::success(convert_c_char(hex::encode(ct))).simple_c_ptr()
326+
}
327+
328+
#[no_mangle]
329+
pub extern "C" fn rust_aes256_cbc_decrypt(
330+
hex_data: PtrString,
331+
password: PtrString,
332+
iv: PtrBytes,
333+
iv_len: u32,
334+
) -> *mut SimpleResponse<c_char> {
335+
let hex_data = recover_c_char(hex_data);
336+
let data = hex::decode(hex_data).unwrap();
337+
let password = recover_c_char(password);
338+
let iv = unsafe { slice::from_raw_parts(iv, iv_len as usize) };
339+
let key = sha256(password.as_bytes());
340+
let iv = GenericArray::from_slice(&iv);
341+
let key = GenericArray::from_slice(&key);
342+
343+
match Aes256CbcDec::new(key, iv).decrypt_padded_vec_mut::<Pkcs7>(&data) {
344+
Ok(pt) => {
345+
SimpleResponse::success(convert_c_char(String::from_utf8(pt).unwrap())).simple_c_ptr()
346+
}
347+
Err(_e) => SimpleResponse::from(RustCError::InvalidHex("decrypt failed".to_string()))
348+
.simple_c_ptr(),
349+
}
350+
}
351+
352+
#[no_mangle]
353+
pub extern "C" fn rust_derive_iv_from_seed(
354+
seed: PtrBytes,
355+
seed_len: u32,
356+
) -> *mut SimpleResponse<u8> {
357+
let seed = unsafe { slice::from_raw_parts(seed, seed_len as usize) };
358+
let iv_path = "m/44'/1557192335'/0'/2'/0'".to_string();
359+
let iv = get_private_key_by_seed(seed, &iv_path).unwrap();
360+
let mut iv_bytes = [0; 16];
361+
iv_bytes.copy_from_slice(&iv[..16]);
362+
SimpleResponse::success(Box::into_raw(Box::new(iv_bytes)) as *mut u8).simple_c_ptr()
363+
}
364+
365+
#[cfg(test)]
366+
mod tests {
367+
use alloc::{string::String, vec::Vec};
368+
369+
use super::*;
370+
371+
#[test]
372+
fn test_aes256_cbc_encrypt() {
373+
let mut data = convert_c_char("hello world".to_string());
374+
let mut password = convert_c_char("password".to_string());
375+
let mut seed = hex::decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap();
376+
let iv = rust_derive_iv_from_seed(seed.as_mut_ptr(), 64);
377+
let mut iv = unsafe { slice::from_raw_parts_mut((*iv).data, 16) };
378+
let iv_len = 16;
379+
let ct = rust_aes256_cbc_encrypt(data, password, iv.as_mut_ptr(), iv_len as u32);
380+
let ct_vec = unsafe { (*ct).data };
381+
let value = recover_c_char(ct_vec);
382+
assert_eq!(value, "639194f4bf964e15d8ea9c9bd9d96918");
383+
}
384+
385+
#[test]
386+
fn test_aes256_cbc_decrypt() {
387+
let data = convert_c_char("639194f4bf964e15d8ea9c9bd9d96918".to_string());
388+
let password = convert_c_char("password".to_string());
389+
let mut seed = hex::decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap();
390+
let iv = rust_derive_iv_from_seed(seed.as_mut_ptr(), 64);
391+
let iv = unsafe { slice::from_raw_parts_mut((*iv).data, 16) };
392+
let iv_len = 16;
393+
let pt = rust_aes256_cbc_decrypt(data, password, iv.as_mut_ptr(), iv_len as u32);
394+
assert!(!pt.is_null());
395+
let ct_vec = unsafe { (*pt).data };
396+
let value = recover_c_char(ct_vec);
397+
assert_eq!(value, "hello world");
398+
}
399+
400+
#[test]
401+
fn test_dep_aes256() {
402+
let mut data = b"hello world";
403+
let seed = hex::decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap();
404+
let iv_path = "m/44'/1557192335'/0'/2'/0'".to_string();
405+
let iv = get_private_key_by_seed(&seed, &iv_path).unwrap();
406+
let mut iv_bytes = [0; 16];
407+
iv_bytes.copy_from_slice(&iv[..16]);
408+
let key = sha256(b"password");
409+
let iv = GenericArray::from_slice(&iv_bytes);
410+
let key = GenericArray::from_slice(&key);
411+
412+
let encrypter = Aes256CbcEnc::new(key, iv);
413+
let decrypter = Aes256CbcDec::new(key, iv);
414+
415+
let ct = encrypter.encrypt_padded_vec_mut::<Pkcs7>(data);
416+
let pt = decrypter.decrypt_padded_vec_mut::<Pkcs7>(&ct).unwrap();
417+
418+
assert_eq!(String::from_utf8(pt).unwrap(), "hello world");
419+
}
420+
}

rust/rust_c/src/common/src/structs.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ make_free_method!(TransactionCheckResult);
8282

8383
#[repr(C)]
8484
pub struct SimpleResponse<T> {
85-
data: *mut T,
85+
pub data: *mut T,
8686
error_code: u32,
8787
error_message: PtrString,
8888
}

rust/rust_c/src/zcash/src/lib.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ use ur_registry::{traits::RegistryItem, zcash::zcash_pczt::ZcashPczt};
2424
use zcash_vendor::zcash_protocol::consensus::MainNetwork;
2525

2626
#[no_mangle]
27-
pub extern "C" fn derive_zcash_ufvk(seed: PtrBytes, seed_len: u32) -> *mut SimpleResponse<c_char> {
27+
pub extern "C" fn derive_zcash_ufvk(seed: PtrBytes, seed_len: u32, account_path: PtrString) -> *mut SimpleResponse<c_char> {
2828
let seed = unsafe { slice::from_raw_parts(seed, seed_len as usize) };
29-
let ufvk_text = derive_ufvk(&MainNetwork, seed);
29+
let account_path = recover_c_char(account_path);
30+
let ufvk_text = derive_ufvk(&MainNetwork, seed, &account_path);
3031
match ufvk_text {
3132
Ok(text) => SimpleResponse::success(convert_c_char(text)).simple_c_ptr(),
3233
Err(e) => SimpleResponse::from(e).simple_c_ptr(),

src/crypto/account_public_info.c

+33-6
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ typedef enum {
5050
TON_NATIVE,
5151
TON_CHECKSUM,
5252
LEDGER_BITBOX02,
53+
ZCASH_UFVK_ENCRYPTED,
5354
} PublicInfoType_t;
5455

5556
typedef struct {
@@ -289,6 +290,12 @@ static const ChainItem_t g_chainTable[] = {
289290
{XPUB_TYPE_TON_BIP39, ED25519, "ton_bip39", "M/44'/607'/0'" },
290291
{XPUB_TYPE_TON_NATIVE, TON_NATIVE, "ton", "" },
291292
{PUBLIC_INFO_TON_CHECKSUM, TON_CHECKSUM, "ton_checksum", "" },
293+
//this path is not 100% correct because UFVK is a combination of
294+
//a k1 key with path M/44'/133'/x'
295+
//and
296+
//a redpallas key with path M/32'/133'/x'
297+
//but we use 32 to identify it for now
298+
{ZCASH_UFVK_ENCRYPTED_0, ZCASH_UFVK_ENCRYPTED, "zcash_ufvk_0", "M/32'/133'/0'" },
292299

293300
#else
294301
{XPUB_TYPE_BTC, SECP256K1, "btc_nested_segwit", "M/49'/0'/0'" },
@@ -590,7 +597,6 @@ int32_t AccountPublicSavePublicInfo(uint8_t accountIndex, const char *password,
590597
ledgerBitbox02Key = ledger_bitbox02_response->data;
591598
}
592599

593-
594600
#ifndef BTC_ONLY
595601
if (isTon) {
596602
//store public key for ton wallet;
@@ -613,16 +619,34 @@ int32_t AccountPublicSavePublicInfo(uint8_t accountIndex, const char *password,
613619
} else {
614620
#endif
615621
for (int i = 0; i < NUMBER_OF_ARRAYS(g_chainTable); i++) {
616-
// slip39 wallet does not support ADA
617-
if (isSlip39 && (g_chainTable[i].cryptoKey == BIP32_ED25519 || g_chainTable[i].cryptoKey == LEDGER_BITBOX02)) {
622+
// slip39 wallet does not support:
623+
// ADA
624+
// Zcash
625+
if (isSlip39 && (g_chainTable[i].cryptoKey == BIP32_ED25519 || g_chainTable[i].cryptoKey == LEDGER_BITBOX02 || g_chainTable[i].cryptoKey == ZCASH_UFVK_ENCRYPTED)) {
618626
continue;
619627
}
620-
// do not generate public keys for ton wallet;
628+
// do not generate public keys for ton-only wallet;
621629
if (g_chainTable[i].cryptoKey == TON_CHECKSUM || g_chainTable[i].cryptoKey == TON_NATIVE) {
622630
continue;
623631
}
624-
625-
xPubResult = ProcessKeyType(seed, len, g_chainTable[i].cryptoKey, g_chainTable[i].path, icarusMasterKey, ledgerBitbox02Key);
632+
//encrypt zcash ufvk
633+
if (g_chainTable[i].cryptoKey == ZCASH_UFVK_ENCRYPTED) {
634+
char* zcashUfvk = NULL;
635+
SimpleResponse_c_char *zcash_ufvk_response = NULL;
636+
zcash_ufvk_response = derive_zcash_ufvk(seed, len, g_chainTable[i].path);
637+
CHECK_AND_FREE_XPUB(zcash_ufvk_response)
638+
zcashUfvk = zcash_ufvk_response->data;
639+
printf("zcash ufvk: %s\r\n", zcashUfvk);
640+
SimpleResponse_u8 *iv_response = rust_derive_iv_from_seed(seed, len);
641+
//iv_response won't fail
642+
uint8_t iv_bytes[16];
643+
memcpy_s(iv_bytes, 16, iv_response->data, 16);
644+
free_simple_response_u8(iv_response);
645+
xPubResult = rust_aes256_cbc_encrypt(zcashUfvk, password, iv_bytes, 16);
646+
}
647+
else {
648+
xPubResult = ProcessKeyType(seed, len, g_chainTable[i].cryptoKey, g_chainTable[i].path, icarusMasterKey, ledgerBitbox02Key);
649+
}
626650
if (g_chainTable[i].cryptoKey == RSA_KEY && xPubResult == NULL) {
627651
continue;
628652
}
@@ -773,6 +797,9 @@ int32_t TempAccountPublicInfo(uint8_t accountIndex, const char *password, bool s
773797
if (g_chainTable[i].cryptoKey == TON_CHECKSUM || g_chainTable[i].cryptoKey == TON_NATIVE) {
774798
continue;
775799
}
800+
if (g_chainTable[i].cryptoKey == ZCASH_UFVK_ENCRYPTED) {
801+
continue;
802+
}
776803

777804
xPubResult = ProcessKeyType(seed, len, g_chainTable[i].cryptoKey, g_chainTable[i].path, icarusMasterKey, ledgerBitbox02Key);
778805
if (g_chainTable[i].cryptoKey == RSA_KEY && xPubResult == NULL) {

src/crypto/account_public_info.h

+1
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ typedef enum {
222222
XPUB_TYPE_TON_BIP39,
223223
XPUB_TYPE_TON_NATIVE,
224224
PUBLIC_INFO_TON_CHECKSUM,
225+
ZCASH_UFVK_ENCRYPTED_0,
225226
#else
226227
XPUB_TYPE_BTC_TEST,
227228
XPUB_TYPE_BTC_LEGACY_TEST,

src/managers/account_manager.c

+8-8
Original file line numberDiff line numberDiff line change
@@ -600,18 +600,18 @@ int32_t CalculateZcashUFVK(uint8_t accountIndex, const char* password) {
600600
int len = GetMnemonicType() == MNEMONIC_TYPE_BIP39 ? sizeof(seed) : GetCurrentAccountEntropyLen();
601601
int32_t ret = GetAccountSeed(accountIndex, &seed, password);
602602

603-
SimpleResponse_c_char *response = derive_zcash_ufvk(seed, len);
604-
if (response->error_code != 0)
605-
{
606-
ret = response->error_code;
607-
printf("error: %s\r\n", response->error_message);
608-
return ret;
609-
}
603+
SimpleResponse_u8 *iv_response = rust_derive_iv_from_seed(seed, len);
604+
605+
uint8_t iv_bytes[16];
606+
memcpy_s(iv_bytes, 16, iv_response->data, 16);
607+
free_simple_response_u8(iv_response);
608+
609+
char *zcashEncrypted = GetCurrentAccountPublicKey(ZCASH_UFVK_ENCRYPTED_0);
610+
SimpleResponse_c_char *response = rust_aes256_cbc_decrypt(zcashEncrypted, password, iv_bytes, 16);
610611

611612
char ufvk[ZCASH_UFVK_MAX_LEN] = {'\0'};
612613
strcpy_s(ufvk, ZCASH_UFVK_MAX_LEN, response->data);
613614
free_simple_response_c_char(response);
614-
615615
SimpleResponse_u8 *responseSFP = calculate_zcash_seed_fingerprint(seed, len);
616616
if (responseSFP->error_code != 0)
617617
{

0 commit comments

Comments
 (0)