Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP]Feat/zcash #1366

Merged
merged 77 commits into from
Dec 20, 2024
Merged

[WIP]Feat/zcash #1366

merged 77 commits into from
Dec 20, 2024

Conversation

soralit
Copy link
Contributor

@soralit soralit commented Sep 26, 2024

Keystone Zcash UR Registries

This protocol is based on the Uniform Resources. It describes the data schemas (UR Registries) used in Zcash integrations.

Introduction

Keystone's QR workflow involves two main steps: linking the wallet and signing data, broken down into three sub-steps:

  1. Wallet Linking: Keystone generates a QR code with public key info for the Watch-Only wallet to scan and import.
  2. Transaction Creation: The Watch-Only wallet creates a transaction and generates a QR code for Keystone to scan, parse, and display.
  3. Signing Authorization: Keystone signs the transaction, displays the result as a QR code for the Watch-Only wallet to scan and broadcast.

Two UR Registries are needed for these steps, utilizing the Partially Created Zcash Transaction structure.

Zcash Accounts

Unified Full Viewing Key (UFVK)

UFVK is a standard account expression format in Zcash as per ZIP-316. It consists of:

  1. Transparent
  2. Sprout
  3. Sapling
  4. Orchard

This protocol focuses on the Transparent and Orchard components.

CDDL for Zcash Accounts

The specification uses CDDL and includes crypto-hdkey and crypto-key-path specs defined in https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-007-hdkey.md.

zcash-accounts = {
    seed-fingerprint: bytes.32, ; the seed fingerprint specified by ZIP-32 to identify the wallet
    accounts: [+ zcash-ufvk],
    ? origin: text, ; source of data, e.g., Keystone
}

zcash-ufvk = {
    ? transparent: crypto-hdkey,
    orchard: zcash-fvk,
    ? name: text,
}

zcash-fvk = {
    key-path: crypto-key-path,
    key-data: bytes,
}

zcash-ufvk describes the UFVK of a Zcash account. Each seed has multiple accounts with different indexes. For index 0, zcash-ufvk should contain a BIP32 extended public key with path M/44'/133'/0' (transparent) and an Orchard FVK with path M_orchard/32'/133'/0' (Orchard).

CDDL for Zcash PCZT

zcash-pczt {
    data: bytes, ; Zcash PCZT, signatures inserted after signing.
}

@soralit soralit marked this pull request as draft September 26, 2024 02:15
@soralit
Copy link
Contributor Author

soralit commented Oct 8, 2024

Latest protocol is updated in the code base.

@daira
Copy link

daira commented Oct 8, 2024

//is versionGroupId still needed?

The purpose of versionGroupId is to unambiguously identify the transaction format even if version numbers are reused across Zcash forks, and that's still relevant for PCZTs.

I think we'd want to include Sapling components even if it isn't supported for Keystone, because it is needed for other applications of PCZTs.

@soralit soralit force-pushed the feat/zcash branch 3 times, most recently from 9cda39e to 90596a9 Compare October 9, 2024 09:38
images/coin/coinZec.png Outdated Show resolved Hide resolved
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably not going to work in light mode, because the Zashi logo and name are in white.

@soralit soralit force-pushed the feat/zcash branch 3 times, most recently from 13f57a2 to a809fdc Compare October 14, 2024 05:31
@soralit soralit force-pushed the feat/zcash branch 2 times, most recently from 515890a to ea142af Compare November 5, 2024 10:13
@soralit soralit force-pushed the feat/zcash branch 5 times, most recently from 6453e73 to 011282a Compare December 11, 2024 11:59
rust/apps/zcash/src/pczt/check.rs Outdated Show resolved Hide resolved
rust/apps/zcash/src/pczt/check.rs Outdated Show resolved Hide resolved
@soralit soralit force-pushed the feat/zcash branch 2 times, most recently from 7e801ad to 387adfe Compare December 16, 2024 05:32
rust/Cargo.toml Show resolved Hide resolved
Comment on lines +17 to +22
// TODO: Determine how to correctly map error codes from the underlying hardware
// into the `getrandom` custom error code space `Error::CUSTOM_START..=u32::MAX`.
if ret != 0 {
let error = NonZeroU32::new(Error::CUSTOM_START.saturating_add_signed(ret)).unwrap();
return Err(Error::from(error));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TODO still needs addressing by someone with access to the TRNG documentation, in particular to confirm that ret will never be negative. (Also to confirm that ret = 0 means success, but I think that's the case as otherwise this function would have been consistently returning an error.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checked the ret is asserted in the SE driver level so the error code should always be SUCCESS_CODE(0) here.

Copy link
Contributor

@str4d str4d left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed as of d41a8f2. This is not a full review or audit of the PR, just what I had time to look over.

@@ -45,6 +46,12 @@ typedef struct {
char walletName[WALLET_NAME_MAX_LEN + 1];
} AccountInfo_t;

typedef struct {
uint8_t accountIndex;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This currently puts an upper limit of 256 accounts (2^8, vs the total possible 2^31), which is fine for now (given that only account 0 is supported at present) assuming that this cache struct can be altered later.

@@ -7,6 +7,7 @@
#include "stdio.h"

#define WALLET_NAME_MAX_LEN 16
#define ZCASH_UFVK_MAX_LEN 384
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Keystone device is generating the UFVKs, so the limit here only needs to encompass the data those UFVKs will contain:

  • Orchard: 96 bytes (+ 2 for TLV overhead)
  • Transparent: 65 bytes (+ 2 for TLV overhead)
  • HRP padding: 16 bytes
  • Total: 181 bytes
  • Bech32 encoding: 6 + (181*8/5) + 6 = 302 characters

A limit of 384 bytes leaves plenty of overhead for e.g. metadata.

#![feature(error_in_core)]
extern crate alloc;

pub mod pczt_ext;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is only used by rust/apps/zcash/src/pczt/sign.rs, so if desired we could move this file there, and remove rust/zcash_vendor entirely (moving its relevant imports into the other crates). Non-blocking, I might look at this refactor separately.

Comment on lines +50 to +54
if path.len() == 3
&& path[0] == zip32::ChildIndex::hardened(32)
&& path[1] == zip32::ChildIndex::hardened(coin_type)
{
let account_id = zip32::AccountId::try_from(path[2].index() - (1 << 31)).expect("valid");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm adding an orchard::pczt::Zip32Derivation::extract_account_index API in zcash/orchard#449 that can replace this in future.

Comment on lines +254 to +260
if zip32_derivation.seed_fingerprint() == seed_fingerprint
&& zip32_derivation.derivation_path()
== &[
zip32::ChildIndex::hardened(32),
zip32::ChildIndex::hardened(params.network_type().coin_type()),
account_index.into(),
] =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can in future be replaced by checking the output of Zip32Derivation::extract_account_index once the firmware is upgraded to an orchard release containing zcash/orchard#449.

.map_err(|e| {
ZcashError::InvalidPczt(alloc::format!("invalid Orchard action cmx: {:?}", e))
})?;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user_address field of the Orchard action output is currently checked in pczt::parse::parse_orchard_output. We could instead move that check to here, given that action.output().recipient() is confirmed to be present if verify_note_commitment passes. Then all that parse_orchard_output would be checking is that the decrypted note's recipient matches action.output().recipient() (unless that is also moved here per the TODO below).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant to note that I did later see the comment about CPU management being why this was done later. I'd be interested to know what specifically is the CPU bottleneck.

Comment on lines +183 to +185
_ => Err(ZcashError::InvalidPczt(
"transparent output script pubkey is not a public key hash".to_string(),
)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to prevent users from sending Keystone-controlled funds to P2SH addresses, which should be fixed at some point (the PCZT format does allow it). The same checking should be applied to user_address in that case as above, but instead that it matches Receiver::P2sh(hash).

Comment on lines +108 to +110
_ => Err(ZcashError::InvalidPczt(
"transparent input script pubkey is not a public key hash".to_string(),
)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will prevent creation of transactions where one of the inputs is a P2PKH coin controlled by a Keystone device, and another input is a P2SH coin. It will also prevent users from manually constructing e.g. P2SH multi-sig addresses where one of the pubkeys is controlled by a Keystone device.

Unlike on the output side, I think this limitation is fine for now; Zashi doesn't support creating these kinds of transactions. But it might be something to consider for future firmware updates.

let total_transfer_value = format_zec_value((total_output_value - total_change_value) as f64);
let fee_value = format_zec_value((total_input_value - total_output_value) as f64);

let has_sapling = pczt.sapling().spends().len() > 0 || pczt.sapling().outputs().len() > 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently total_transfer_value will be completely incorrect if has_sapling = true. It should at a minimum include the Sapling bundle's value_sum in the calculation; that value is included in the sighash, and thus even if it hasn't been checked to match the individual Sapling spends and outputs, it can still be "trusted" (because any change to it by the online wallet would invalidate the signature).

Comment on lines 482 to 495
//decode as utf8.
if first <= 0xF4 {
let mut temp_memo = memo_bytes.to_vec();
temp_memo.reverse();
let mut result = vec![];
temp_memo.iter().for_each(|v| {
if *v != 0 {
result.push(*v);
}
});
result.reverse();

return Some(String::from_utf8(result).unwrap());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is subtly incorrect: rather than only stripping trailing nul bytes from the memo bytes, it is stripping every nul byte from the memo bytes. nul is a valid byte in UTF-8 (as part of the NUL character).

If a NUL character cannot be included in rendered memo strings (if Keystone requires C-strings), then it should be replaced with an appropriate Unicode replacement character.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I aim to remove until the first non-zero value but implement a wrong one.

@soralit soralit merged commit 14d54b1 into master Dec 20, 2024
3 of 9 checks passed
@soralit soralit deleted the feat/zcash branch December 20, 2024 01:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants