From 56227ecf49396c4f56cb46e1a47266f999100a94 Mon Sep 17 00:00:00 2001 From: highlander Date: Thu, 6 Aug 2026 12:23:09 -0300 Subject: [PATCH] feat(zcash): Orchard shielded signing engine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The device-side Orchard engine behind Zcash shielded sends: Orchard key derivation from the wallet seed, note/action handling, RedPallas signing over the Pallas curve, and unified-address encoding (ZIP-316). - Signing is constant-time on the secret scalar path; the Pallas arithmetic comes from the pinned trezor-firmware fork and is compiled in only when the privacy engine is enabled, so the bitcoin-only image carries none of it. - Progress is reported to the host during action signing — multi-action transactions take long enough that a silent device looks hung. - messages-zcash proto/options are registered with the transport generator. Handlers, host-visible messages, unit tests, the constant-time CI gates and the coin-integration docs land in the Zcash wiring branch; this commit adds the engine and its build wiring only. --- deps/crypto/CMakeLists.txt | 12 + include/keepkey/firmware/zcash.h | 430 ++++++ include/keepkey/transport/interface.h | 1 + .../keepkey/transport/messages-zcash.options | 54 + lib/firmware/CMakeLists.txt | 5 + lib/firmware/zcash.c | 1228 +++++++++++++++++ lib/transport/CMakeLists.txt | 9 + 7 files changed, 1739 insertions(+) create mode 100644 include/keepkey/firmware/zcash.h create mode 100644 include/keepkey/transport/messages-zcash.options create mode 100644 lib/firmware/zcash.c diff --git a/deps/crypto/CMakeLists.txt b/deps/crypto/CMakeLists.txt index d726b39bc..dde1951f2 100644 --- a/deps/crypto/CMakeLists.txt +++ b/deps/crypto/CMakeLists.txt @@ -57,6 +57,18 @@ set(sources #trezor-firmware/crypto/aes/aestst.c trezor-firmware/crypto/aes/aestab.c) +# Pallas/Orchard curve arithmetic (~2.4k LOC) -- only the Zcash shielded engine +# uses it. Excluded from the bitcoin-only image. +if(${KK_ZCASH_PRIVACY}) + list(APPEND sources + trezor-firmware/crypto/pallas.c + trezor-firmware/crypto/pallas_ct.c + trezor-firmware/crypto/pallas_sinsemilla.c + trezor-firmware/crypto/pallas_swu.c + trezor-firmware/crypto/redpallas.c + trezor-firmware/crypto/zcash_zip316.c) +endif() + # Clang 5.0 in the docker image (kktech/firmware:v7) is missing # , which breaks these. Until they're needed, we'll just elide # them. diff --git a/include/keepkey/firmware/zcash.h b/include/keepkey/firmware/zcash.h new file mode 100644 index 000000000..b331f52b1 --- /dev/null +++ b/include/keepkey/firmware/zcash.h @@ -0,0 +1,430 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2025 KeepKey + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef KEEPKEY_FIRMWARE_ZCASH_H +#define KEEPKEY_FIRMWARE_ZCASH_H + +#include +#include +#include + +/* Orchard spending keys derived via ZIP-32. + * cppcheck doesn't see these used because the consumers live in + * fsm_msg_zcash.h which is #include'd into fsm.c rather than compiled + * separately, so the struct members appear "unused" in this TU. */ +typedef struct { + // cppcheck-suppress unusedStructMember + uint8_t sk[32]; /* Spending key (master secret at this level) */ + // cppcheck-suppress unusedStructMember + uint8_t ask[32]; /* Spend authorizing key (scalar) */ + // cppcheck-suppress unusedStructMember + uint8_t ak[32]; /* Public spend validating key (compressed, even y) */ + // cppcheck-suppress unusedStructMember + uint8_t nk[32]; /* Nullifier deriving key */ + // cppcheck-suppress unusedStructMember + uint8_t rivk[32]; /* Commitment randomness key */ + // cppcheck-suppress unusedStructMember + uint8_t dk[32]; /* Diversifier key */ +} ZcashOrchardKeys; + +typedef void (*ZcashOrchardProgressCallback)(uint32_t completed, uint32_t total, + void* context); + +typedef struct { + bool has_header_digest; + size_t header_digest_size; + bool has_transparent_digest; + size_t transparent_digest_size; + bool has_sapling_digest; + size_t sapling_digest_size; + bool has_orchard_digest; + size_t orchard_digest_size; + bool is_ironwood; + bool has_ironwood_digest; + size_t ironwood_digest_size; + bool has_orchard_flags; + uint32_t orchard_flags; + bool has_orchard_value_balance; + bool has_orchard_anchor; + size_t orchard_anchor_size; + bool has_header_fields; + uint32_t n_transparent_inputs; + uint32_t n_transparent_outputs; +} ZcashPCZTSigningRequestMeta; + +typedef enum { + ZCASH_PCZT_SIGNING_REQUEST_OK = 0, + ZCASH_PCZT_SIGNING_REQUEST_MISSING_TX_DIGESTS, + ZCASH_PCZT_SIGNING_REQUEST_INVALID_DIGEST_SIZE, + ZCASH_PCZT_SIGNING_REQUEST_MISSING_HEADER_FIELDS, + ZCASH_PCZT_SIGNING_REQUEST_UNSUPPORTED_SAPLING_COMPONENT, + ZCASH_PCZT_SIGNING_REQUEST_MISSING_ORCHARD_METADATA, + ZCASH_PCZT_SIGNING_REQUEST_MISSING_TRANSPARENT_DIGEST, +} ZcashPCZTSigningRequestStatus; + +typedef struct { + const uint8_t* prevout_txid; + uint32_t prevout_index; + uint32_t sequence; + uint64_t value; + const uint8_t* script_pubkey; + size_t script_pubkey_size; +} ZcashTransparentInputDigestInfo; + +typedef struct { + uint64_t value; + const uint8_t* script_pubkey; + size_t script_pubkey_size; +} ZcashTransparentOutputDigestInfo; + +#define ZCASH_ORCHARD_RAW_RECEIVER_SIZE 43 +#define ZCASH_ORCHARD_UNIFIED_ADDRESS_SIZE 128 + +/** + * Validate the clear-signing metadata required before Orchard signatures. + * + * This rejects the legacy flow where the host supplied only a per-action + * sighash. The firmware must assemble the ZIP-244 sighash from transaction + * component digests and verify the Orchard digest against streamed action data + * before returning signatures. + */ +ZcashPCZTSigningRequestStatus zcash_pczt_signing_request_status( + const ZcashPCZTSigningRequestMeta* meta); + +bool zcash_pczt_signing_request_is_clear( + const ZcashPCZTSigningRequestMeta* meta); + +/** + * Derive Orchard spending keys from the device seed via ZIP-32. + * Path: m_orchard / 32' / 133' / account' + * + * Uses BLAKE2b with personalization "ZcashIP32Orchard" for key derivation. + * + * @param seed BIP-39 master seed + * @param seed_len Seed length (typically 64 bytes) + * @param account Account index (0-based, will be hardened) + * @param keys Output: derived Orchard keys + * @return true on success + */ +bool zcash_derive_orchard_keys(const uint8_t* seed, uint32_t seed_len, + uint32_t account, ZcashOrchardKeys* keys); + +/** + * Progress-reporting Orchard key derivation for interactive device flows. + * Progress is driven by the fixed public scalar-multiplication schedule and + * does not depend on the derived secret key. + */ +bool zcash_derive_orchard_keys_with_progress( + const uint8_t* seed, uint32_t seed_len, uint32_t account, + ZcashOrchardKeys* keys, ZcashOrchardProgressCallback progress, + void* progress_context); + +/** + * Compute the ZIP 244 shielded sighash for Orchard spend authorization. + * + * For shielded-only transactions, transparent_sig_digest uses the "no inputs" + * form. For mixed transactions, transparent data must be provided separately. + * + * @param header_digest 32-byte pre-computed header digest + * @param transparent_digest 32-byte transparent sig digest (or empty hash) + * @param sapling_digest 32-byte sapling digest (or empty hash) + * @param orchard_digest 32-byte orchard digest + * @param branch_id Consensus branch ID (LE) + * @param sighash_out 32-byte output sighash + * @return true on success + */ +bool zcash_compute_shielded_sighash(const uint8_t header_digest[32], + const uint8_t transparent_digest[32], + const uint8_t sapling_digest[32], + const uint8_t orchard_digest[32], + uint32_t branch_id, + uint8_t sighash_out[32]); + +/** Compute the five-component ZIP-229 transaction-v6 sighash. */ +bool zcash_compute_v6_shielded_sighash(const uint8_t header_digest[32], + const uint8_t transparent_digest[32], + const uint8_t sapling_digest[32], + const uint8_t orchard_digest[32], + const uint8_t ironwood_digest[32], + uint32_t branch_id, + uint8_t sighash_out[32]); + +/** + * Compute ZIP-244 T.1 header_digest from plaintext transaction header fields. + */ +bool zcash_compute_header_digest(uint32_t version, uint32_t version_group_id, + uint32_t branch_id, uint32_t lock_time, + uint32_t expiry_height, + uint8_t digest_out[32]); + +/** + * Compute ZIP-244 T.2 transparent_digest from plaintext transparent data. + * + * This is the digest mixed into the Orchard/Sapling signing commitment. It is + * not the same as the per-input transparent signature digest. + */ +bool zcash_compute_transparent_digest( + const ZcashTransparentInputDigestInfo* inputs, size_t n_inputs, + const ZcashTransparentOutputDigestInfo* outputs, size_t n_outputs, + uint8_t digest_out[32]); + +/** + * Compute ZIP-244 §4.9 transparent_sig_digest for Orchard spend authorization. + * + * Uses the S.2 form with EMPTY txin_sig_digest when n_inputs > 0 (shield txs), + * or falls back to T.1 when n_inputs == 0 (deshield / private-send). This is + * what the Zcash consensus node uses to verify Orchard spend auth sigs and the + * binding signature in a hybrid (transparent + Orchard) transaction. + */ +bool zcash_compute_orchard_transparent_sig_digest( + const ZcashTransparentInputDigestInfo* inputs, size_t n_inputs, + const ZcashTransparentOutputDigestInfo* outputs, size_t n_outputs, + uint8_t digest_out[32]); + +/** + * Compute ZIP-244 S.2 per-input transparent signature digest. + * + * This currently accepts SIGHASH_ALL only, matching the existing transparent + * signing flow. + */ +bool zcash_compute_transparent_sighash_digest( + const ZcashTransparentInputDigestInfo* inputs, size_t n_inputs, + const ZcashTransparentOutputDigestInfo* outputs, size_t n_outputs, + uint32_t signable_input_index, uint8_t sighash_type, + uint8_t digest_out[32]); + +/** + * Encode a raw Orchard receiver (d || pk_d) as an Orchard-only ZIP-316 Unified + * Address for display. This is for recipient review; it does not derive or + * prove ownership of the receiver. + */ +bool zcash_orchard_receiver_to_unified_address( + const uint8_t receiver[ZCASH_ORCHARD_RAW_RECEIVER_SIZE], const char* hrp, + char* address_out, size_t address_out_len); + +/** + * Recompute an Orchard output note commitment x-coordinate (cmx). + * + * cmx = Extract_P(NoteCommit_rcm^Orchard(g_d, pk_d, v, rho, psi)) + * where receiver = d || pk_d, rho is the action nullifier, and rseed is the + * output note seed. This binds the user-displayed receiver/value to the action + * commitment before any authorization signature is emitted. + */ +bool zcash_orchard_compute_cmx( + const uint8_t receiver[ZCASH_ORCHARD_RAW_RECEIVER_SIZE], uint64_t value, + const uint8_t rho[32], const uint8_t rseed[32], uint8_t cmx_out[32]); + +/** ZIP-2005 V3 note commitment used by the Ironwood pool. */ +bool zcash_ironwood_compute_cmx( + const uint8_t receiver[ZCASH_ORCHARD_RAW_RECEIVER_SIZE], uint64_t value, + const uint8_t rho[32], const uint8_t rseed[32], uint8_t cmx_out[32]); + +bool zcash_ironwood_compute_cmx_with_progress( + const uint8_t receiver[ZCASH_ORCHARD_RAW_RECEIVER_SIZE], uint64_t value, + const uint8_t rho[32], const uint8_t rseed[32], uint8_t cmx_out[32], + ZcashOrchardProgressCallback progress, void* progress_context); + +/** + * Progress-reporting note-commitment verification for interactive PCZT flows. + * The callback exposes only the public Sinsemilla word index and count. + */ +bool zcash_orchard_compute_cmx_with_progress( + const uint8_t receiver[ZCASH_ORCHARD_RAW_RECEIVER_SIZE], uint64_t value, + const uint8_t rho[32], const uint8_t rseed[32], uint8_t cmx_out[32], + ZcashOrchardProgressCallback progress, void* progress_context); + +/** + * Derive an Orchard diversifier from a diversifier key and 88-bit index. + * + * ZIP-32 defines Orchard diversifiers as: + * d_j = FF1-AES256.Encrypt(dk, "", I2LEBSP_88(j)) + * + * Both index_le and diversifier_out are 11-byte LEBS2OSP encodings of the + * 88-bit bitstrings. + * + * @param dk 32-byte Orchard diversifier key + * @param index_le 11-byte little-endian diversifier index bitstring + * @param diversifier_out 11-byte output diversifier + * @return true on success + */ +bool zcash_orchard_derive_diversifier(const uint8_t dk[32], + const uint8_t index_le[11], + uint8_t diversifier_out[11]); + +/** + * Compute DiversifyHash^Orchard(d) as a serialized Pallas point. + * + * g_d = GroupHash^Pallas("z.cash:Orchard-gd", d) + * + * If the group hash ever returns the identity, Orchard falls back to hashing + * the empty message under the same domain. + * + * @param diversifier 11-byte Orchard diversifier + * @param gd_out 32-byte compressed Pallas point + * @return true on success + */ +bool zcash_orchard_diversify_hash(const uint8_t diversifier[11], + uint8_t gd_out[32]); + +/** + * Derive an Orchard diversified transmission key. + * + * g_d = DiversifyHash^Orchard(d) + * pk_d = KA^Orchard.DerivePublic(ivk, g_d) = [ivk] g_d + * + * @param ivk 32-byte nonzero Orchard incoming viewing key encoding + * @param diversifier 11-byte Orchard diversifier + * @param gd_out optional 32-byte compressed g_d output, may be NULL + * @param pkd_out 32-byte compressed diversified transmission key + * @return true on success + */ +bool zcash_orchard_derive_transmission_key(const uint8_t ivk[32], + const uint8_t diversifier[11], + uint8_t gd_out[32], + uint8_t pkd_out[32]); + +/** + * Derive the external Orchard incoming viewing key from FVK components. + * + * ivk = Commit^ivk.Output(ExtractP(ak), nk, rivk) + * + * @param ak 32-byte Orchard spend validating key encoding, sign bit clear + * @param nk 32-byte Orchard nullifier deriving key + * @param rivk 32-byte Orchard IVK commitment randomness + * @param ivk_out 32-byte nonzero Orchard incoming viewing key + * @return true on success + */ +bool zcash_orchard_derive_ivk(const uint8_t ak[32], const uint8_t nk[32], + const uint8_t rivk[32], uint8_t ivk_out[32]); + +/** + * Derive a raw Orchard receiver from external FVK components and index. + * + * d_j = DiversifierKey(dk).get(j) + * ivk = Commit^ivk.Output(ExtractP(ak), nk, rivk) + * pk_dj = KA^Orchard.DerivePublic(ivk, DiversifyHash(d_j)) + * + * @param ak 32-byte Orchard spend validating key encoding + * @param nk 32-byte Orchard nullifier deriving key + * @param rivk 32-byte Orchard IVK commitment randomness + * @param dk 32-byte Orchard diversifier key + * @param index_le 11-byte little-endian diversifier index bitstring + * @param receiver_out 43-byte raw receiver: d_j || pk_dj + * @return true on success + */ +bool zcash_orchard_derive_receiver(const uint8_t ak[32], const uint8_t nk[32], + const uint8_t rivk[32], const uint8_t dk[32], + const uint8_t index_le[11], + uint8_t receiver_out[43]); + +/** + * Derive an Orchard-only ZIP-316 Unified Address from derived Orchard keys. + * + * ak = [ask] G_spendauth + * receiver = d_j || pk_dj + * address = Bech32m(HRP, F4Jumble(Orchard receiver payload)) + * + * @param keys ZIP-32-derived Orchard key material + * @param index_le 11-byte little-endian diversifier index bitstring + * @param hrp ZIP-316 HRP ("u" for mainnet, "utest" for testnet) + * @param address_out NUL-terminated output address + * @param address_out_len Size of address_out + * @return true on success + */ +bool zcash_orchard_derive_unified_address(const ZcashOrchardKeys* keys, + const uint8_t index_le[11], + const char* hrp, char* address_out, + size_t address_out_len); + +/** + * Compute the ZIP-32 §6.1 seed fingerprint. + * + * SeedFingerprint := BLAKE2b-256( + * "Zcash_HD_Seed_FP", I2LEBSP_8(len(seed)) || seed) + * + * The 1-byte length prefix domain-separates seeds of different lengths that + * happen to share a prefix. + * + * 32-byte stable identifier of a seed. Used by host wallets and PCZTs + * (zip32_derivation.seed_fingerprint) to confirm which device seed produced + * a given key, address, or signature. Trivial seeds (all-zero, all-0xFF) + * and seeds outside [32, 252] bytes are rejected per ZIP-32 §6.1. + * + * @param seed Seed bytes (BIP-39 seed or BIP-32 master seed) + * @param seed_len Seed length, must be in [32, 252] + * @param fingerprint_out 32-byte output fingerprint + * @return true on success, false if seed is invalid + */ +bool zcash_calculate_seed_fingerprint(const uint8_t* seed, uint32_t seed_len, + uint8_t fingerprint_out[32]); + +/** + * Validate the wire shape of an optional asserted seed fingerprint. Omission is + * valid; a present assertion must be exactly 32 bytes. + */ +bool zcash_seed_fingerprint_request_valid(bool present, size_t size); + +/* ── Storage-scoped wrappers ─────────────────────────────────────────── + * + * The two functions below own the seed access. Implementations live in + * lib/firmware/storage.c so the raw 64-byte BIP-39 seed never escapes + * that translation unit. Callers (FSM handlers) get only the derived + * material — Orchard keys or the 32-byte fingerprint — never a pointer + * to the seed itself. This is the only sanctioned way for production + * firmware code to consume seed-derived Zcash material. + * + * The bare zcash_derive_orchard_keys() / zcash_calculate_seed_fingerprint() + * functions above remain in the header for unit tests, which feed them + * known test vectors directly. + */ + +/** + * Derive Orchard keys for an account using the device's session seed. + * + * @param account Account index (0-based, will be hardened) + * @param usePassphrase Whether to apply the passphrase (prompts if needed) + * @param keys_out Output: derived Orchard keys + * @return true on success, false if seed unavailable or derivation fails + */ +bool storage_zcashOrchardKeys(uint32_t account, bool usePassphrase, + ZcashOrchardKeys* keys_out); + +/** + * Compute the ZIP-32 §6.1 seed fingerprint for the device's session seed. + * + * @param usePassphrase Whether to apply the passphrase (prompts if needed) + * @param fingerprint_out 32-byte output fingerprint + * @return true on success, false if seed unavailable + */ +bool storage_zcashSeedFingerprint(bool usePassphrase, + uint8_t fingerprint_out[32]); + +/** + * Tear down any in-progress Zcash signing session. + * + * Wipes the static signing state (active flag, derived Orchard keys, + * accumulated signatures, sub-digest contexts, transparent-input + * counters) so a host cannot resume streaming PCZTAction or + * TransparentInput messages against a previously-approved session + * after Initialize, Cancel, or ClearSession. Safe to call when no + * session is active. + */ +void zcash_signing_abort(void); + +#endif diff --git a/include/keepkey/transport/interface.h b/include/keepkey/transport/interface.h index 45e5a09e6..ab435baf8 100644 --- a/include/keepkey/transport/interface.h +++ b/include/keepkey/transport/interface.h @@ -38,6 +38,7 @@ #include "messages-tron.pb.h" #include "messages-ton.pb.h" #include "messages-solana.pb.h" +#include "messages-zcash.pb.h" #include "types.pb.h" #include "trezor_transport.h" diff --git a/include/keepkey/transport/messages-zcash.options b/include/keepkey/transport/messages-zcash.options new file mode 100644 index 000000000..63d1035b4 --- /dev/null +++ b/include/keepkey/transport/messages-zcash.options @@ -0,0 +1,54 @@ +ZcashSignPCZT.address_n max_count:10 +ZcashSignPCZT.pczt_data max_size:0 +ZcashSignPCZT.total_amount int_size:IS_64 +ZcashSignPCZT.fee int_size:IS_64 +ZcashSignPCZT.header_digest max_size:32 +ZcashSignPCZT.transparent_digest max_size:32 +ZcashSignPCZT.sapling_digest max_size:32 +ZcashSignPCZT.orchard_digest max_size:32 +ZcashSignPCZT.ironwood_digest max_size:32 +ZcashSignPCZT.orchard_value_balance int_size:IS_64 +ZcashSignPCZT.orchard_anchor max_size:32 +ZcashSignPCZT.expected_seed_fingerprint max_size:32 + +ZcashPCZTAction.alpha max_size:32 +ZcashPCZTAction.sighash max_size:32 +ZcashPCZTAction.cv_net max_size:32 +ZcashPCZTAction.value int_size:IS_64 +ZcashPCZTAction.nullifier max_size:32 +ZcashPCZTAction.cmx max_size:32 +ZcashPCZTAction.epk max_size:32 +ZcashPCZTAction.enc_compact max_size:52 +ZcashPCZTAction.enc_memo max_size:512 +ZcashPCZTAction.enc_noncompact max_size:564 +ZcashPCZTAction.rk max_size:32 +ZcashPCZTAction.out_ciphertext max_size:80 +ZcashPCZTAction.recipient max_size:43 +ZcashPCZTAction.rseed max_size:32 + +ZcashSignedPCZT.signatures max_count:16, max_size:64 +ZcashSignedPCZT.txid max_size:32 + +ZcashGetOrchardFVK.address_n max_count:10 + +ZcashOrchardFVK.ak max_size:32 +ZcashOrchardFVK.nk max_size:32 +ZcashOrchardFVK.rivk max_size:32 +ZcashOrchardFVK.seed_fingerprint max_size:32 + +ZcashTransparentOutput.amount int_size:IS_64 +ZcashTransparentOutput.script_pubkey max_size:128 + +ZcashTransparentInput.sighash max_size:32 +ZcashTransparentInput.address_n max_count:8 +ZcashTransparentInput.amount int_size:IS_64 +ZcashTransparentInput.prevout_txid max_size:32 +ZcashTransparentInput.script_pubkey max_size:128 + +ZcashTransparentSigned.signatures max_count:8, max_size:73 + +ZcashDisplayAddress.address_n max_count:8 +ZcashDisplayAddress.expected_seed_fingerprint max_size:32 + +ZcashAddress.address max_size:128 +ZcashAddress.seed_fingerprint max_size:32 diff --git a/lib/firmware/CMakeLists.txt b/lib/firmware/CMakeLists.txt index dcf21b9d4..33e977051 100644 --- a/lib/firmware/CMakeLists.txt +++ b/lib/firmware/CMakeLists.txt @@ -45,6 +45,11 @@ set(sources txin_check.c u2f.c) +# Zcash shielded/Orchard engine -- transparent Zcash needs none of this. +if(${KK_ZCASH_PRIVACY}) + list(APPEND sources zcash.c) +endif() + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_revision.h.in" "${CMAKE_CURRENT_BINARY_DIR}/scm_revision.h" @ONLY) diff --git a/lib/firmware/zcash.c b/lib/firmware/zcash.c new file mode 100644 index 000000000..b9379d040 --- /dev/null +++ b/lib/firmware/zcash.c @@ -0,0 +1,1228 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2025 KeepKey + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "keepkey/firmware/zcash.h" + +#include +#include + +#include "trezor/crypto/aes/aes.h" +#include "trezor/crypto/bignum.h" +#include "trezor/crypto/blake2b.h" +#include "trezor/crypto/hasher.h" +#include "trezor/crypto/memzero.h" +#include "trezor/crypto/pallas.h" +#include "trezor/crypto/pallas_ct.h" +#include "trezor/crypto/pallas_sinsemilla.h" +#include "trezor/crypto/pallas_swu.h" +#include "trezor/crypto/redpallas.h" +#include "trezor/crypto/zcash_zip316.h" + +/* + * ZIP-32 Orchard key derivation. + * + * Master key: + * I = BLAKE2b-512("ZcashIP32Orchard", seed) + * sk = I[0..32], chain_code = I[32..64] + * + * Child derivation (hardened only): + * I = BLAKE2b-512("ZcashIP32Orchard", chain_code, + * 0x11 || sk || i_be) + * where 0x11 indicates hardened derivation with Orchard, + * and i_be is the 4-byte big-endian child index with the hardened bit set. + * + * From the spending key sk, subkeys are derived using PRF^expand: + * PRF^expand(sk, t) = BLAKE2b-512("Zcash_ExpandSeed", sk || t) + * + * ask = ToScalar(PRF^expand(sk, [0x06])) + * nk = ToBase(PRF^expand(sk, [0x07])) + * rivk = ToScalar(PRF^expand(sk, [0x08])) + * + * ToScalar: interpret 64 bytes as LE integer, reduce mod order + * ToBase: interpret 64 bytes as LE integer, reduce mod prime + */ + +/* + * BLAKE2b-512 with personalization "ZcashIP32Orchard" — master key only. + * Used for: I = BLAKE2b-512("ZcashIP32Orchard", seed) + * NOT used for child derivation (which uses PRF^expand). + */ +static void zip32_orchard_master(const uint8_t* seed, size_t seed_len, + uint8_t out[64]) { + BLAKE2B_CTX ctx; + blake2b_InitPersonal(&ctx, 64, "ZcashIP32Orchard", 16); + blake2b_Update(&ctx, seed, seed_len); + blake2b_Final(&ctx, out, 64); +} + +/* PRF^expand(sk, t) = BLAKE2b-512("Zcash_ExpandSeed", sk || t) */ +static void prf_expand(const uint8_t sk[32], const uint8_t* t, size_t t_len, + uint8_t out[64]) { + BLAKE2B_CTX ctx; + blake2b_InitPersonal(&ctx, 64, "Zcash_ExpandSeed", 16); + blake2b_Update(&ctx, sk, 32); + blake2b_Update(&ctx, t, t_len); + blake2b_Final(&ctx, out, 64); +} + +/* + * 2^256 mod q (Pallas scalar field order), little-endian. + * q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 + * R = 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF992C350BE34205675B2B3E9CFFFFFFFD + * Verified: R + 3*q == 2^256. + */ +static const uint8_t two_256_mod_q[32] = { + 0xfd, 0xff, 0xff, 0xff, 0x9c, 0x3e, 0x2b, 0x5b, 0x67, 0x05, 0x42, + 0xe3, 0x0b, 0x35, 0x2c, 0x99, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, +}; + +/* + * 2^256 mod p (Pallas base field prime), little-endian. + * p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001 + * R = 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF992C350BE41914AD34786D38FFFFFFFD + * Verified: R + 3*p == 2^256. + */ +static const uint8_t two_256_mod_p[32] = { + 0xfd, 0xff, 0xff, 0xff, 0x38, 0x6d, 0x78, 0x34, 0xad, 0x14, 0x19, + 0xe4, 0x0b, 0x35, 0x2c, 0x99, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, +}; + +/* + * ToScalar: reduce a 512-bit LE integer mod Pallas scalar order. + * + * Uses wide reduction matching the orchard crate's from_uniform_bytes: + * result = (lo + hi * 2^256) mod q + * where lo = input[0..31], hi = input[32..63] (little-endian). + */ +static void to_scalar(const uint8_t input[64], uint8_t output[32]) { + bignum256 lo, hi, t256, result; + + bn_read_le(input, &lo); + pallas_ct_mod_q(&lo); + + bn_read_le(input + 32, &hi); + pallas_ct_mod_q(&hi); + + bn_read_le(two_256_mod_q, &t256); + + /* result = hi * (2^256 mod q) mod q */ + bn_copy(&hi, &result); + pallas_ct_mul_mod_q(&result, &t256); + + /* result = result + lo mod q */ + pallas_ct_add_mod_q(&result, &lo); + + bn_write_le(&result, output); + + memzero(&lo, sizeof(lo)); + memzero(&hi, sizeof(hi)); + memzero(&result, sizeof(result)); +} + +/* + * ToBase: reduce a 512-bit LE integer mod Pallas base field prime. + * + * Uses wide reduction: + * result = (lo + hi * 2^256) mod p + * where lo = input[0..31], hi = input[32..63] (little-endian). + */ +static void to_base(const uint8_t input[64], uint8_t output[32]) { + bignum256 lo, hi, t256, result; + + bn_read_le(input, &lo); + pallas_ct_mod_p(&lo); + + bn_read_le(input + 32, &hi); + pallas_ct_mod_p(&hi); + + bn_read_le(two_256_mod_p, &t256); + + /* result = hi * (2^256 mod p) mod p */ + bn_copy(&hi, &result); + pallas_ct_mul_mod_p(&result, &t256); + + /* result = result + lo mod p */ + bignum256 sum; + pallas_ct_add_mod_p(&result, &lo, &sum); + bn_copy(&sum, &result); + memzero(&sum, sizeof(sum)); + + bn_write_le(&result, output); + + memzero(&lo, sizeof(lo)); + memzero(&hi, sizeof(hi)); + memzero(&result, sizeof(result)); +} + +/* Hardened child index */ +#define ZIP32_HARDENED 0x80000000 + +/* + * ZIP-32 Orchard diversifiers use FF1-AES256 over an 88-bit binary numeral + * string. Parameters are fixed by the Zcash protocol: + * + * radix = 2, minlen = maxlen = n = 88, tweak = "", rounds = 10 + * + * The input and output byte arrays are LEBS2OSP encodings of the 88-bit + * strings, but FF1's NUM/STR operations interpret each half in numeral-string + * order. Keep the bit-order conversion explicit to avoid silently turning this + * into a radix-256 construction, which would be a different permutation. + */ +#define ZCASH_FF1_BITS 88 +#define ZCASH_FF1_HALF_BITS 44 +#define ZCASH_FF1_MASK44 ((UINT64_C(1) << ZCASH_FF1_HALF_BITS) - 1) + +static uint8_t bit_get_le(const uint8_t* bytes, uint32_t bit) { + return (bytes[bit >> 3] >> (bit & 7)) & 1; +} + +static void bit_set_le(uint8_t* bytes, uint32_t bit, uint8_t value) { + if (value) { + bytes[bit >> 3] |= (uint8_t)(1u << (bit & 7)); + } +} + +static uint64_t ff1_bits_to_num(const uint8_t bits[11], uint32_t offset, + uint32_t len) { + uint64_t n = 0; + for (uint32_t i = 0; i < len; i++) { + n = (n << 1) | bit_get_le(bits, offset + i); + } + return n; +} + +static void ff1_num_to_bits(uint64_t n, uint8_t bits[11], uint32_t offset, + uint32_t len) { + for (uint32_t i = 0; i < len; i++) { + uint32_t shift = len - 1 - i; + bit_set_le(bits, offset + i, (uint8_t)((n >> shift) & 1)); + } +} + +static void ff1_store_be48(uint64_t n, uint8_t out[6]) { + for (int i = 5; i >= 0; i--) { + out[i] = (uint8_t)(n & 0xff); + n >>= 8; + } +} + +static bool aes256_encrypt_block(const aes_encrypt_ctx* ctx, + const uint8_t in[16], uint8_t out[16]) { + return aes_encrypt(in, out, ctx) == EXIT_SUCCESS; +} + +static bool ff1_round_y_mod_2_44(const aes_encrypt_ctx* ctx, uint8_t round, + uint64_t b, uint64_t* y_mod) { + static const uint8_t P[16] = { + 0x01, 0x02, 0x01, 0x00, 0x00, 0x02, 0x0a, 0x2c, + 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, + }; + + uint8_t q[16] = {0}; + uint8_t y[16]; + uint8_t block[16]; + uint8_t r[16]; + + /* + * Q = T || [0]^{(-t-b-1) mod 16} || [i]_1 || [NUM(B)]_b + * Here t = 0 and b = ceil(44 / 8) = 6, so padding is 9 bytes. + */ + q[9] = round; + ff1_store_be48(b, q + 10); + + /* PRF(P || Q) = CBC-MAC_AES(P || Q), IV = 0. */ + if (!aes256_encrypt_block(ctx, P, y)) return false; + for (int i = 0; i < 16; i++) { + block[i] = y[i] ^ q[i]; + } + if (!aes256_encrypt_block(ctx, block, r)) return false; + + /* + * d = 4 * ceil(6 / 4) + 4 = 12, so S is the first 12 bytes of R. + * We only need NUM(S) modulo 2^44, i.e. the low 44 bits of R[0..11]. + */ + uint64_t low48 = 0; + for (int i = 6; i < 12; i++) { + low48 = (low48 << 8) | r[i]; + } + *y_mod = low48 & ZCASH_FF1_MASK44; + + memzero(q, sizeof(q)); + memzero(y, sizeof(y)); + memzero(block, sizeof(block)); + memzero(r, sizeof(r)); + return true; +} + +bool zcash_orchard_derive_diversifier(const uint8_t dk[32], + const uint8_t index_le[11], + uint8_t diversifier_out[11]) { + if (!dk || !index_le || !diversifier_out) return false; + + aes_encrypt_ctx ctx; + if (aes_encrypt_key256(dk, &ctx) != EXIT_SUCCESS) { + memzero(&ctx, sizeof(ctx)); + return false; + } + + uint64_t A = + ff1_bits_to_num(index_le, 0, ZCASH_FF1_HALF_BITS) & ZCASH_FF1_MASK44; + uint64_t B = + ff1_bits_to_num(index_le, ZCASH_FF1_HALF_BITS, ZCASH_FF1_HALF_BITS) & + ZCASH_FF1_MASK44; + + for (uint8_t round = 0; round < 10; round++) { + uint64_t y; + if (!ff1_round_y_mod_2_44(&ctx, round, B, &y)) { + memzero(&ctx, sizeof(ctx)); + return false; + } + uint64_t C = (A + y) & ZCASH_FF1_MASK44; + A = B; + B = C; + } + + memset(diversifier_out, 0, 11); + ff1_num_to_bits(A, diversifier_out, 0, ZCASH_FF1_HALF_BITS); + ff1_num_to_bits(B, diversifier_out, ZCASH_FF1_HALF_BITS, ZCASH_FF1_HALF_BITS); + + memzero(&ctx, sizeof(ctx)); + return true; +} + +static bool orchard_diversify_point(const uint8_t diversifier[11], + curve_point* gd) { + if (!diversifier || !gd) return false; + static const char domain[] = "z.cash:Orchard-gd"; + + if (pallas_group_hash(domain, diversifier, 11, gd) != 0) { + return false; + } + + if (pallas_point_is_identity(gd)) { + if (pallas_group_hash(domain, NULL, 0, gd) != 0 || + pallas_point_is_identity(gd)) { + memzero(gd, sizeof(*gd)); + return false; + } + } + + return true; +} + +bool zcash_orchard_diversify_hash(const uint8_t diversifier[11], + uint8_t gd_out[32]) { + if (!gd_out) return false; + + curve_point gd; + if (!orchard_diversify_point(diversifier, &gd)) { + return false; + } + + pallas_point_encode(&gd, gd_out); + memzero(&gd, sizeof(gd)); + return true; +} + +bool zcash_orchard_derive_transmission_key(const uint8_t ivk[32], + const uint8_t diversifier[11], + uint8_t gd_out[32], + uint8_t pkd_out[32]) { + if (!ivk || !pkd_out) return false; + + bignum256 ivk_scalar; + bn_read_le(ivk, &ivk_scalar); + bn_normalize(&ivk_scalar); + if (bn_is_zero(&ivk_scalar) || !bn_is_less(&ivk_scalar, &pallas_prime)) { + memzero(&ivk_scalar, sizeof(ivk_scalar)); + return false; + } + + curve_point gd; + if (!orchard_diversify_point(diversifier, &gd)) { + memzero(&ivk_scalar, sizeof(ivk_scalar)); + return false; + } + + curve_point pkd; + /* ivk is private viewing-key material. Do not use the variable-time + * public-data multiplier that Sinsemilla note verification relies on. */ + pallas_ct_point_mult(&ivk_scalar, &gd, &pkd); + if (pallas_point_is_identity(&pkd)) { + memzero(&ivk_scalar, sizeof(ivk_scalar)); + memzero(&gd, sizeof(gd)); + memzero(&pkd, sizeof(pkd)); + return false; + } + + if (gd_out) { + pallas_point_encode(&gd, gd_out); + } + pallas_point_encode(&pkd, pkd_out); + + memzero(&ivk_scalar, sizeof(ivk_scalar)); + memzero(&gd, sizeof(gd)); + memzero(&pkd, sizeof(pkd)); + return true; +} + +bool zcash_orchard_derive_ivk(const uint8_t ak[32], const uint8_t nk[32], + const uint8_t rivk[32], uint8_t ivk_out[32]) { + if (!ak || !nk || !rivk || !ivk_out) return false; + if ((ak[31] & 0x80) != 0) return false; + + if (pallas_sinsemilla_commit_ivk(ak, nk, rivk, ivk_out) != 0) { + return false; + } + + bignum256 ivk; + bn_read_le(ivk_out, &ivk); + bn_normalize(&ivk); + bool ok = !bn_is_zero(&ivk) && bn_is_less(&ivk, &pallas_prime); + memzero(&ivk, sizeof(ivk)); + if (!ok) { + memzero(ivk_out, 32); + } + return ok; +} + +bool zcash_orchard_derive_receiver(const uint8_t ak[32], const uint8_t nk[32], + const uint8_t rivk[32], const uint8_t dk[32], + const uint8_t index_le[11], + uint8_t receiver_out[43]) { + if (!receiver_out) return false; + + uint8_t diversifier[11]; + uint8_t ivk[32]; + uint8_t pkd[32]; + bool ok = zcash_orchard_derive_diversifier(dk, index_le, diversifier) && + zcash_orchard_derive_ivk(ak, nk, rivk, ivk) && + zcash_orchard_derive_transmission_key(ivk, diversifier, NULL, pkd); + + if (ok) { + memcpy(receiver_out, diversifier, sizeof(diversifier)); + memcpy(receiver_out + sizeof(diversifier), pkd, sizeof(pkd)); + } else { + memzero(receiver_out, 43); + } + + memzero(diversifier, sizeof(diversifier)); + memzero(ivk, sizeof(ivk)); + memzero(pkd, sizeof(pkd)); + return ok; +} + +bool zcash_orchard_derive_unified_address(const ZcashOrchardKeys* keys, + const uint8_t index_le[11], + const char* hrp, char* address_out, + size_t address_out_len) { + if (!keys || !index_le || !hrp || !address_out) return false; + + bignum256 ask_scalar; + curve_point ak_point; + bignum256 ak_x; + uint8_t ak[32]; + uint8_t receiver[43]; + + bn_read_le(keys->ask, &ask_scalar); + redpallas_scalar_mult_spendauth_G(&ask_scalar, &ak_point); + bn_copy(&ak_point.x, &ak_x); + bn_write_le(&ak_x, ak); + + bool ok = zcash_orchard_derive_receiver(ak, keys->nk, keys->rivk, keys->dk, + index_le, receiver); + if (ok) { + ok = zcash_zip316_encode_orchard_unified_address(hrp, receiver, address_out, + address_out_len) == 0; + } + if (!ok && address_out_len > 0) { + address_out[0] = '\0'; + } + + memzero(&ask_scalar, sizeof(ask_scalar)); + memzero(&ak_point, sizeof(ak_point)); + memzero(&ak_x, sizeof(ak_x)); + memzero(ak, sizeof(ak)); + memzero(receiver, sizeof(receiver)); + return ok; +} + +bool zcash_orchard_receiver_to_unified_address( + const uint8_t receiver[ZCASH_ORCHARD_RAW_RECEIVER_SIZE], const char* hrp, + char* address_out, size_t address_out_len) { + if (!receiver || !hrp || !address_out) return false; + return zcash_zip316_encode_orchard_unified_address(hrp, receiver, address_out, + address_out_len) == 0; +} + +static bool zcash_pack_orchard_note_commit_msg(const uint8_t receiver[43], + uint64_t value, + const uint8_t rho[32], + const uint8_t psi[32], + uint8_t msg[136]) { + memset(msg, 0, 136); + + /* bits 0..255: repr_P(g_d) */ + curve_point gd; + if (!orchard_diversify_point(receiver, &gd)) { + memzero(&gd, sizeof(gd)); + return false; + } + pallas_point_encode(&gd, msg); + memzero(&gd, sizeof(gd)); + + /* bits 256..511: repr_P(pk_d) */ + memcpy(msg + 32, receiver + 11, 32); + + /* bits 512..575: I2LEBSP_64(value) */ + for (int i = 0; i < 8; i++) { + msg[64 + i] = (uint8_t)((value >> (8 * i)) & 0xff); + } + + /* bits 576..830: I2LEBSP_255(rho) */ + memcpy(msg + 72, rho, 31); + msg[103] = rho[31] & 0x7f; + + /* bits 831..1085: I2LEBSP_255(psi), packed at bit offset 831. */ + uint8_t psi255[32]; + memcpy(psi255, psi, 32); + psi255[31] &= 0x7f; + for (int i = 0; i < 32; i++) { + msg[103 + i] |= (uint8_t)(psi255[i] << 7); + msg[104 + i] |= (uint8_t)(psi255[i] >> 1); + } + memzero(psi255, sizeof(psi255)); + return true; +} + +static bool zcash_orchard_family_compute_cmx_with_progress( + const uint8_t receiver[ZCASH_ORCHARD_RAW_RECEIVER_SIZE], uint64_t value, + const uint8_t rho[32], const uint8_t rseed[32], uint8_t cmx_out[32], + bool ironwood, ZcashOrchardProgressCallback progress, + void* progress_context) { + if (!receiver || !rho || !rseed || !cmx_out) return false; + + uint8_t msg[136]; + uint8_t prf_in[137]; + uint8_t prf_out[64]; + uint8_t rcm[32]; + uint8_t psi[32]; + curve_point gd, q, r; + bool ok = false; + + /* psi is unchanged between V2 (Orchard) and V3 (Ironwood) notes. */ + memcpy(prf_in + 1, rho, 32); + prf_in[0] = 0x09; + prf_expand(rseed, prf_in, 33, prf_out); + to_base(prf_out, psi); + + if (ironwood) { + /* ZIP-2005 H_rcm binds V3 randomness to every note field. */ + if (!orchard_diversify_point(receiver, &gd)) goto cleanup; + prf_in[0] = 0x0B; + pallas_point_encode(&gd, prf_in + 1); + memcpy(prf_in + 33, receiver + 11, 32); + for (size_t i = 0; i < 8; i++) { + prf_in[65 + i] = (uint8_t)((value >> (8 * i)) & 0xff); + } + memcpy(prf_in + 73, rho, 32); + memcpy(prf_in + 105, psi, 32); + prf_expand(rseed, prf_in, sizeof(prf_in), prf_out); + } else { + prf_in[0] = 0x05; + prf_expand(rseed, prf_in, 33, prf_out); + } + to_scalar(prf_out, rcm); + + ok = zcash_pack_orchard_note_commit_msg(receiver, value, rho, psi, msg) && + pallas_group_hash("z.cash:SinsemillaQ", + (const uint8_t*)"z.cash:Orchard-NoteCommit-M", + strlen("z.cash:Orchard-NoteCommit-M"), &q) == 0 && + pallas_group_hash("z.cash:Orchard-NoteCommit-r", NULL, 0, &r) == 0 && + pallas_sinsemilla_short_commit_progress(&q, &r, msg, 1086, rcm, cmx_out, + progress, progress_context) == 0; + +cleanup: + if (!ok) memzero(cmx_out, 32); + memzero(msg, sizeof(msg)); + memzero(prf_in, sizeof(prf_in)); + memzero(prf_out, sizeof(prf_out)); + memzero(rcm, sizeof(rcm)); + memzero(psi, sizeof(psi)); + memzero(&gd, sizeof(gd)); + memzero(&q, sizeof(q)); + memzero(&r, sizeof(r)); + return ok; +} + +bool zcash_orchard_compute_cmx_with_progress( + const uint8_t receiver[ZCASH_ORCHARD_RAW_RECEIVER_SIZE], uint64_t value, + const uint8_t rho[32], const uint8_t rseed[32], uint8_t cmx_out[32], + ZcashOrchardProgressCallback progress, void* progress_context) { + return zcash_orchard_family_compute_cmx_with_progress( + receiver, value, rho, rseed, cmx_out, false, progress, progress_context); +} + +bool zcash_orchard_compute_cmx( + const uint8_t receiver[ZCASH_ORCHARD_RAW_RECEIVER_SIZE], uint64_t value, + const uint8_t rho[32], const uint8_t rseed[32], uint8_t cmx_out[32]) { + return zcash_orchard_compute_cmx_with_progress(receiver, value, rho, rseed, + cmx_out, NULL, NULL); +} + +bool zcash_ironwood_compute_cmx_with_progress( + const uint8_t receiver[ZCASH_ORCHARD_RAW_RECEIVER_SIZE], uint64_t value, + const uint8_t rho[32], const uint8_t rseed[32], uint8_t cmx_out[32], + ZcashOrchardProgressCallback progress, void* progress_context) { + return zcash_orchard_family_compute_cmx_with_progress( + receiver, value, rho, rseed, cmx_out, true, progress, progress_context); +} + +bool zcash_ironwood_compute_cmx( + const uint8_t receiver[ZCASH_ORCHARD_RAW_RECEIVER_SIZE], uint64_t value, + const uint8_t rho[32], const uint8_t rseed[32], uint8_t cmx_out[32]) { + return zcash_ironwood_compute_cmx_with_progress(receiver, value, rho, rseed, + cmx_out, NULL, NULL); +} + +bool zcash_derive_orchard_keys_with_progress( + const uint8_t* seed, uint32_t seed_len, uint32_t account, + ZcashOrchardKeys* keys, ZcashOrchardProgressCallback progress, + void* progress_context) { + uint8_t I[64]; + uint8_t sk[32], chain_code[32]; + + /* Step 1: Master key from seed + * I = BLAKE2b-512("ZcashIP32Orchard", seed) */ + zip32_orchard_master(seed, seed_len, I); + memcpy(sk, I, 32); + memcpy(chain_code, I + 32, 32); + + /* Step 2: Derive path m_orchard / 32' / 133' / account' + * + * CKDOrchard child derivation (ZIP-32 hardened-only): + * I = PRF^expand(chain_code, [0x81] || sk || I2LEOSP32(index)) + * + * PRF^expand(sk, t) = BLAKE2b-512("Zcash_ExpandSeed", sk || t) + * + * So: I = BLAKE2b-512("Zcash_ExpandSeed", + * chain_code || 0x81 || sk || index_le) + */ + const uint32_t path[3] = { + 32 | ZIP32_HARDENED, /* Purpose (Orchard) */ + 133 | ZIP32_HARDENED, /* Coin type (Zcash) */ + account | ZIP32_HARDENED /* Account */ + }; + + for (int i = 0; i < 3; i++) { + /* Build PRF^expand input: [0x81] || sk || I2LEOSP32(index) */ + uint8_t child_input[1 + 32 + 4]; + child_input[0] = 0x81; /* ORCHARD_ZIP32_CHILD domain separator */ + memcpy(child_input + 1, sk, 32); + /* Little-endian index (I2LEOSP32) */ + uint32_t idx = path[i]; + child_input[33] = idx & 0xff; + child_input[34] = (idx >> 8) & 0xff; + child_input[35] = (idx >> 16) & 0xff; + child_input[36] = (idx >> 24) & 0xff; + + /* PRF^expand(chain_code, child_input) */ + prf_expand(chain_code, child_input, sizeof(child_input), I); + memcpy(sk, I, 32); + memcpy(chain_code, I + 32, 32); + + memzero(child_input, sizeof(child_input)); + } + + /* Step 3: Derive subkeys from final spending key */ + memcpy(keys->sk, sk, 32); + + uint8_t expanded[64]; + + /* ask = ToScalar(PRF^expand(sk, [0x06])) */ + uint8_t t_ask = 0x06; + prf_expand(sk, &t_ask, 1, expanded); + to_scalar(expanded, keys->ask); + uint8_t ak_bytes[32]; + + /* + * Zcash spec (§ 4.2.3): If [ask]*G_spendauth has odd y (ỹ = 1), + * negate ask so that the resulting ak always has ỹ = 0. + * This matches the orchard crate's SpendAuthorizingKey::from() behavior. + */ + { + bignum256 ask_test; + bn_read_le(keys->ask, &ask_test); + curve_point ak_test; + redpallas_scalar_mult_spendauth_G_progress(&ask_test, &ak_test, progress, + progress_context); + bignum256 ak_x; + bn_copy(&ak_test.x, &ak_x); + bn_write_le(&ak_x, ak_bytes); + if (bn_is_odd(&ak_test.y)) { + /* ask = order - ask (negate mod q) */ + bignum256 ask_val, neg_ask; + bn_read_le(keys->ask, &ask_val); + bn_subtract(&pallas_order, &ask_val, &neg_ask); + bn_write_le(&neg_ask, keys->ask); + memzero(&neg_ask, sizeof(neg_ask)); + memzero(&ask_val, sizeof(ask_val)); + } + memzero(&ask_test, sizeof(ask_test)); + memzero(&ak_test, sizeof(ak_test)); + memzero(&ak_x, sizeof(ak_x)); + } + /* Cache the public key produced by the normalization multiplication. This + * avoids repeating the same expensive secret-scalar operation for FVK + * export and lets signing derive rk from public ak + public alpha. */ + memcpy(keys->ak, ak_bytes, sizeof(keys->ak)); + + /* nk = ToBase(PRF^expand(sk, [0x07])) */ + uint8_t t_nk = 0x07; + prf_expand(sk, &t_nk, 1, expanded); + to_base(expanded, keys->nk); + + /* rivk = ToScalar(PRF^expand(sk, [0x08])) */ + uint8_t t_rivk = 0x08; + prf_expand(sk, &t_rivk, 1, expanded); + to_scalar(expanded, keys->rivk); + + /* + * dk = truncate_32(PRF^expand(rivk, [0x82] || I2LEOSP_256(ak) + * || I2LEOSP_256(nk))) + */ + uint8_t dk_input[1 + 32 + 32]; + dk_input[0] = 0x82; + memcpy(dk_input + 1, ak_bytes, 32); + memcpy(dk_input + 33, keys->nk, 32); + prf_expand(keys->rivk, dk_input, sizeof(dk_input), expanded); + memcpy(keys->dk, expanded, 32); + + /* Clean up */ + memzero(I, sizeof(I)); + memzero(sk, sizeof(sk)); + memzero(chain_code, sizeof(chain_code)); + memzero(expanded, sizeof(expanded)); + memzero(ak_bytes, sizeof(ak_bytes)); + memzero(dk_input, sizeof(dk_input)); + + return true; +} + +bool zcash_derive_orchard_keys(const uint8_t* seed, uint32_t seed_len, + uint32_t account, ZcashOrchardKeys* keys) { + return zcash_derive_orchard_keys_with_progress(seed, seed_len, account, keys, + NULL, NULL); +} + +static bool zcash_compute_shielded_sighash_inner( + const uint8_t header_digest[32], const uint8_t transparent_digest[32], + const uint8_t sapling_digest[32], const uint8_t orchard_digest[32], + const uint8_t* ironwood_digest, uint32_t branch_id, + uint8_t sighash_out[32]) { + if (!header_digest || !transparent_digest || !sapling_digest || + !orchard_digest || !sighash_out) { + return false; + } + Hasher h; + uint8_t personal[16]; + + memcpy(personal, "ZcashTxHash_", 12); + memcpy(personal + 12, &branch_id, 4); + + hasher_InitParam(&h, HASHER_BLAKE2B_PERSONAL, personal, 16); + hasher_Update(&h, header_digest, 32); + hasher_Update(&h, transparent_digest, 32); + hasher_Update(&h, sapling_digest, 32); + hasher_Update(&h, orchard_digest, 32); + if (ironwood_digest) hasher_Update(&h, ironwood_digest, 32); + hasher_Final(&h, sighash_out); + memzero(personal, sizeof(personal)); + return true; +} + +bool zcash_compute_shielded_sighash(const uint8_t header_digest[32], + const uint8_t transparent_digest[32], + const uint8_t sapling_digest[32], + const uint8_t orchard_digest[32], + uint32_t branch_id, + uint8_t sighash_out[32]) { + return zcash_compute_shielded_sighash_inner(header_digest, transparent_digest, + sapling_digest, orchard_digest, + NULL, branch_id, sighash_out); +} + +bool zcash_compute_v6_shielded_sighash(const uint8_t header_digest[32], + const uint8_t transparent_digest[32], + const uint8_t sapling_digest[32], + const uint8_t orchard_digest[32], + const uint8_t ironwood_digest[32], + uint32_t branch_id, + uint8_t sighash_out[32]) { + if (!ironwood_digest) return false; + return zcash_compute_shielded_sighash_inner( + header_digest, transparent_digest, sapling_digest, orchard_digest, + ironwood_digest, branch_id, sighash_out); +} + +static void zcash_write_u32_le(uint32_t value, uint8_t out[4]) { + out[0] = (uint8_t)(value & 0xff); + out[1] = (uint8_t)((value >> 8) & 0xff); + out[2] = (uint8_t)((value >> 16) & 0xff); + out[3] = (uint8_t)((value >> 24) & 0xff); +} + +static void zcash_write_u64_le(uint64_t value, uint8_t out[8]) { + for (size_t i = 0; i < 8; i++) { + out[i] = (uint8_t)((value >> (8 * i)) & 0xff); + } +} + +static size_t zcash_write_compact_size(size_t value, uint8_t out[9]) { + if (value < 253) { + out[0] = (uint8_t)value; + return 1; + } + + if (value <= 0xffff) { + out[0] = 0xfd; + out[1] = (uint8_t)(value & 0xff); + out[2] = (uint8_t)((value >> 8) & 0xff); + return 3; + } + + if (value <= 0xffffffff) { + out[0] = 0xfe; + out[1] = (uint8_t)(value & 0xff); + out[2] = (uint8_t)((value >> 8) & 0xff); + out[3] = (uint8_t)((value >> 16) & 0xff); + out[4] = (uint8_t)((value >> 24) & 0xff); + return 5; + } + + out[0] = 0xff; + uint64_t v = (uint64_t)value; + for (size_t i = 0; i < 8; i++) { + out[i + 1] = (uint8_t)((v >> (8 * i)) & 0xff); + } + return 9; +} + +static void zcash_blake2b_personal_256(const char personal[16], + const uint8_t* data, size_t data_len, + uint8_t digest_out[32]) { + BLAKE2B_CTX ctx; + blake2b_InitPersonal(&ctx, 32, personal, 16); + if (data_len > 0) { + blake2b_Update(&ctx, data, data_len); + } + blake2b_Final(&ctx, digest_out, 32); +} + +bool zcash_compute_header_digest(uint32_t version, uint32_t version_group_id, + uint32_t branch_id, uint32_t lock_time, + uint32_t expiry_height, + uint8_t digest_out[32]) { + if (!digest_out) return false; + + uint8_t header[20]; + zcash_write_u32_le(version | 0x80000000u, header); + zcash_write_u32_le(version_group_id, header + 4); + zcash_write_u32_le(branch_id, header + 8); + zcash_write_u32_le(lock_time, header + 12); + zcash_write_u32_le(expiry_height, header + 16); + + zcash_blake2b_personal_256("ZTxIdHeadersHash", header, sizeof(header), + digest_out); + memzero(header, sizeof(header)); + return true; +} + +static bool zcash_validate_transparent_digest_info( + const ZcashTransparentInputDigestInfo* inputs, size_t n_inputs, + const ZcashTransparentOutputDigestInfo* outputs, size_t n_outputs) { + if (n_inputs > 0 && !inputs) return false; + if (n_outputs > 0 && !outputs) return false; + + for (size_t i = 0; i < n_inputs; i++) { + if (!inputs[i].prevout_txid || + (inputs[i].script_pubkey_size > 0 && !inputs[i].script_pubkey)) { + return false; + } + } + + for (size_t i = 0; i < n_outputs; i++) { + if (outputs[i].script_pubkey_size > 0 && !outputs[i].script_pubkey) { + return false; + } + } + + return true; +} + +static void zcash_hash_transparent_prevouts( + const ZcashTransparentInputDigestInfo* inputs, size_t n_inputs, + uint8_t digest_out[32]) { + BLAKE2B_CTX ctx; + uint8_t le[4]; + blake2b_InitPersonal(&ctx, 32, "ZTxIdPrevoutHash", 16); + for (size_t i = 0; i < n_inputs; i++) { + blake2b_Update(&ctx, inputs[i].prevout_txid, 32); + zcash_write_u32_le(inputs[i].prevout_index, le); + blake2b_Update(&ctx, le, sizeof(le)); + } + blake2b_Final(&ctx, digest_out, 32); + memzero(le, sizeof(le)); +} + +static void zcash_hash_transparent_sequences( + const ZcashTransparentInputDigestInfo* inputs, size_t n_inputs, + uint8_t digest_out[32]) { + BLAKE2B_CTX ctx; + uint8_t le[4]; + blake2b_InitPersonal(&ctx, 32, "ZTxIdSequencHash", 16); + for (size_t i = 0; i < n_inputs; i++) { + zcash_write_u32_le(inputs[i].sequence, le); + blake2b_Update(&ctx, le, sizeof(le)); + } + blake2b_Final(&ctx, digest_out, 32); + memzero(le, sizeof(le)); +} + +static void zcash_hash_transparent_amounts( + const ZcashTransparentInputDigestInfo* inputs, size_t n_inputs, + uint8_t digest_out[32]) { + BLAKE2B_CTX ctx; + uint8_t le[8]; + blake2b_InitPersonal(&ctx, 32, "ZTxTrAmountsHash", 16); + for (size_t i = 0; i < n_inputs; i++) { + zcash_write_u64_le(inputs[i].value, le); + blake2b_Update(&ctx, le, sizeof(le)); + } + blake2b_Final(&ctx, digest_out, 32); + memzero(le, sizeof(le)); +} + +static void zcash_hash_transparent_scripts( + const ZcashTransparentInputDigestInfo* inputs, size_t n_inputs, + uint8_t digest_out[32]) { + BLAKE2B_CTX ctx; + uint8_t compact_size[9]; + blake2b_InitPersonal(&ctx, 32, "ZTxTrScriptsHash", 16); + for (size_t i = 0; i < n_inputs; i++) { + size_t compact_size_len = + zcash_write_compact_size(inputs[i].script_pubkey_size, compact_size); + blake2b_Update(&ctx, compact_size, compact_size_len); + if (inputs[i].script_pubkey_size > 0) { + blake2b_Update(&ctx, inputs[i].script_pubkey, + inputs[i].script_pubkey_size); + } + } + blake2b_Final(&ctx, digest_out, 32); + memzero(compact_size, sizeof(compact_size)); +} + +static void zcash_hash_transparent_outputs( + const ZcashTransparentOutputDigestInfo* outputs, size_t n_outputs, + uint8_t digest_out[32]) { + BLAKE2B_CTX ctx; + uint8_t le[8]; + uint8_t compact_size[9]; + blake2b_InitPersonal(&ctx, 32, "ZTxIdOutputsHash", 16); + for (size_t i = 0; i < n_outputs; i++) { + zcash_write_u64_le(outputs[i].value, le); + blake2b_Update(&ctx, le, sizeof(le)); + size_t compact_size_len = + zcash_write_compact_size(outputs[i].script_pubkey_size, compact_size); + blake2b_Update(&ctx, compact_size, compact_size_len); + if (outputs[i].script_pubkey_size > 0) { + blake2b_Update(&ctx, outputs[i].script_pubkey, + outputs[i].script_pubkey_size); + } + } + blake2b_Final(&ctx, digest_out, 32); + memzero(le, sizeof(le)); + memzero(compact_size, sizeof(compact_size)); +} + +static bool zcash_hash_transparent_input( + const ZcashTransparentInputDigestInfo* input, uint8_t digest_out[32]) { + if (!input) return false; + + BLAKE2B_CTX ctx; + uint8_t le4[4]; + uint8_t le8[8]; + uint8_t compact_size[9]; + blake2b_InitPersonal(&ctx, 32, "Zcash___TxInHash", 16); + blake2b_Update(&ctx, input->prevout_txid, 32); + zcash_write_u32_le(input->prevout_index, le4); + blake2b_Update(&ctx, le4, sizeof(le4)); + zcash_write_u64_le(input->value, le8); + blake2b_Update(&ctx, le8, sizeof(le8)); + size_t compact_size_len = + zcash_write_compact_size(input->script_pubkey_size, compact_size); + blake2b_Update(&ctx, compact_size, compact_size_len); + if (input->script_pubkey_size > 0) { + blake2b_Update(&ctx, input->script_pubkey, input->script_pubkey_size); + } + zcash_write_u32_le(input->sequence, le4); + blake2b_Update(&ctx, le4, sizeof(le4)); + blake2b_Final(&ctx, digest_out, 32); + memzero(le4, sizeof(le4)); + memzero(le8, sizeof(le8)); + memzero(compact_size, sizeof(compact_size)); + return true; +} + +bool zcash_compute_transparent_digest( + const ZcashTransparentInputDigestInfo* inputs, size_t n_inputs, + const ZcashTransparentOutputDigestInfo* outputs, size_t n_outputs, + uint8_t digest_out[32]) { + if (!digest_out || !zcash_validate_transparent_digest_info( + inputs, n_inputs, outputs, n_outputs)) { + return false; + } + + if (n_inputs == 0 && n_outputs == 0) { + zcash_blake2b_personal_256("ZTxIdTranspaHash", NULL, 0, digest_out); + return true; + } + + uint8_t prevouts_digest[32], sequence_digest[32], outputs_digest[32]; + zcash_hash_transparent_prevouts(inputs, n_inputs, prevouts_digest); + zcash_hash_transparent_sequences(inputs, n_inputs, sequence_digest); + zcash_hash_transparent_outputs(outputs, n_outputs, outputs_digest); + + BLAKE2B_CTX ctx; + blake2b_InitPersonal(&ctx, 32, "ZTxIdTranspaHash", 16); + blake2b_Update(&ctx, prevouts_digest, 32); + blake2b_Update(&ctx, sequence_digest, 32); + blake2b_Update(&ctx, outputs_digest, 32); + blake2b_Final(&ctx, digest_out, 32); + + memzero(prevouts_digest, sizeof(prevouts_digest)); + memzero(sequence_digest, sizeof(sequence_digest)); + memzero(outputs_digest, sizeof(outputs_digest)); + return true; +} + +/* ZIP-244 §4.9 / §4.10b: transparent_sig_digest for Orchard spend + * authorization. + * + * When n_inputs > 0, the Orchard sighash uses the S.2 form: + * BLAKE2b("ZTxIdTranspaHash", + * hash_type(0x01) || prevouts || amounts || scripts || sequences || + * outputs || empty_txin_digest) + * where empty_txin_digest = BLAKE2b("Zcash___TxInHash", ""). + * + * When n_inputs == 0 (deshield / private-send), falls back to T.1 form + * (no hash_type, amounts, scripts, or txin digest) — same as txid form. + * + * This differs from zcash_compute_transparent_sighash_digest which uses a + * per-input txin_sig_digest for transparent ECDSA signatures. + */ +bool zcash_compute_orchard_transparent_sig_digest( + const ZcashTransparentInputDigestInfo* inputs, size_t n_inputs, + const ZcashTransparentOutputDigestInfo* outputs, size_t n_outputs, + uint8_t digest_out[32]) { + if (!digest_out || !zcash_validate_transparent_digest_info( + inputs, n_inputs, outputs, n_outputs)) { + return false; + } + + /* Empty-vin case (deshield, private): T.1 form is correct per §4.10b. */ + if (n_inputs == 0) { + return zcash_compute_transparent_digest(inputs, n_inputs, outputs, + n_outputs, digest_out); + } + + /* Non-empty vin (shield): S.2 form with empty txin_sig_digest. */ + const uint8_t sighash_type = 0x01; /* SIGHASH_ALL */ + uint8_t prevouts_digest[32], amounts_digest[32], scripts_digest[32]; + uint8_t sequence_digest[32], outputs_digest[32], empty_txin_digest[32]; + + zcash_hash_transparent_prevouts(inputs, n_inputs, prevouts_digest); + zcash_hash_transparent_amounts(inputs, n_inputs, amounts_digest); + zcash_hash_transparent_scripts(inputs, n_inputs, scripts_digest); + zcash_hash_transparent_sequences(inputs, n_inputs, sequence_digest); + zcash_hash_transparent_outputs(outputs, n_outputs, outputs_digest); + + /* Empty txin_sig_digest: BLAKE2b("Zcash___TxInHash", "") */ + zcash_blake2b_personal_256("Zcash___TxInHash", NULL, 0, empty_txin_digest); + + BLAKE2B_CTX ctx; + blake2b_InitPersonal(&ctx, 32, "ZTxIdTranspaHash", 16); + blake2b_Update(&ctx, &sighash_type, 1); + blake2b_Update(&ctx, prevouts_digest, 32); + blake2b_Update(&ctx, amounts_digest, 32); + blake2b_Update(&ctx, scripts_digest, 32); + blake2b_Update(&ctx, sequence_digest, 32); + blake2b_Update(&ctx, outputs_digest, 32); + blake2b_Update(&ctx, empty_txin_digest, 32); + blake2b_Final(&ctx, digest_out, 32); + + memzero(prevouts_digest, sizeof(prevouts_digest)); + memzero(amounts_digest, sizeof(amounts_digest)); + memzero(scripts_digest, sizeof(scripts_digest)); + memzero(sequence_digest, sizeof(sequence_digest)); + memzero(outputs_digest, sizeof(outputs_digest)); + memzero(empty_txin_digest, sizeof(empty_txin_digest)); + return true; +} + +bool zcash_compute_transparent_sighash_digest( + const ZcashTransparentInputDigestInfo* inputs, size_t n_inputs, + const ZcashTransparentOutputDigestInfo* outputs, size_t n_outputs, + uint32_t signable_input_index, uint8_t sighash_type, + uint8_t digest_out[32]) { + if (!digest_out || !zcash_validate_transparent_digest_info( + inputs, n_inputs, outputs, n_outputs)) { + return false; + } + + if (sighash_type != 0x01 || signable_input_index >= n_inputs) { + return false; + } + + uint8_t prevouts_digest[32], amounts_digest[32], scripts_digest[32]; + uint8_t sequence_digest[32], outputs_digest[32], txin_sig_digest[32]; + zcash_hash_transparent_prevouts(inputs, n_inputs, prevouts_digest); + zcash_hash_transparent_amounts(inputs, n_inputs, amounts_digest); + zcash_hash_transparent_scripts(inputs, n_inputs, scripts_digest); + zcash_hash_transparent_sequences(inputs, n_inputs, sequence_digest); + zcash_hash_transparent_outputs(outputs, n_outputs, outputs_digest); + + zcash_hash_transparent_input(&inputs[signable_input_index], txin_sig_digest); + + BLAKE2B_CTX ctx; + blake2b_InitPersonal(&ctx, 32, "ZTxIdTranspaHash", 16); + blake2b_Update(&ctx, &sighash_type, 1); + blake2b_Update(&ctx, prevouts_digest, 32); + blake2b_Update(&ctx, amounts_digest, 32); + blake2b_Update(&ctx, scripts_digest, 32); + blake2b_Update(&ctx, sequence_digest, 32); + blake2b_Update(&ctx, outputs_digest, 32); + blake2b_Update(&ctx, txin_sig_digest, 32); + blake2b_Final(&ctx, digest_out, 32); + + memzero(prevouts_digest, sizeof(prevouts_digest)); + memzero(amounts_digest, sizeof(amounts_digest)); + memzero(scripts_digest, sizeof(scripts_digest)); + memzero(sequence_digest, sizeof(sequence_digest)); + memzero(outputs_digest, sizeof(outputs_digest)); + memzero(txin_sig_digest, sizeof(txin_sig_digest)); + return true; +} + +ZcashPCZTSigningRequestStatus zcash_pczt_signing_request_status( + const ZcashPCZTSigningRequestMeta* meta) { + if (!meta || !meta->has_header_digest || !meta->has_orchard_digest) { + return ZCASH_PCZT_SIGNING_REQUEST_MISSING_TX_DIGESTS; + } + + if (meta->header_digest_size != 32 || meta->orchard_digest_size != 32) { + return ZCASH_PCZT_SIGNING_REQUEST_INVALID_DIGEST_SIZE; + } + + if (meta->is_ironwood && + (!meta->has_ironwood_digest || meta->ironwood_digest_size != 32)) { + return ZCASH_PCZT_SIGNING_REQUEST_MISSING_TX_DIGESTS; + } + + if (meta->has_transparent_digest && meta->transparent_digest_size != 32) { + return ZCASH_PCZT_SIGNING_REQUEST_INVALID_DIGEST_SIZE; + } + + (void)meta->sapling_digest_size; + if (meta->has_sapling_digest) { + return ZCASH_PCZT_SIGNING_REQUEST_UNSUPPORTED_SAPLING_COMPONENT; + } + + if (!meta->has_header_fields) { + return ZCASH_PCZT_SIGNING_REQUEST_MISSING_HEADER_FIELDS; + } + + if ((meta->n_transparent_inputs > 0 || meta->n_transparent_outputs > 0) && + (!meta->has_transparent_digest || meta->transparent_digest_size != 32)) { + return ZCASH_PCZT_SIGNING_REQUEST_MISSING_TRANSPARENT_DIGEST; + } + + if (!meta->has_orchard_flags || meta->orchard_flags > 0xff || + !meta->has_orchard_value_balance || !meta->has_orchard_anchor || + meta->orchard_anchor_size != 32) { + return ZCASH_PCZT_SIGNING_REQUEST_MISSING_ORCHARD_METADATA; + } + + return ZCASH_PCZT_SIGNING_REQUEST_OK; +} + +bool zcash_pczt_signing_request_is_clear( + const ZcashPCZTSigningRequestMeta* meta) { + return zcash_pczt_signing_request_status(meta) == + ZCASH_PCZT_SIGNING_REQUEST_OK; +} + +/* + * ZIP-32 §6.1 seed fingerprint: + * + * SeedFingerprint := BLAKE2b-256( + * "Zcash_HD_Seed_FP", I2LEBSP_8(len(seed)) || seed) + * + * The 1-byte length prefix domain-separates seeds of different lengths that + * happen to share a prefix. + * + * Trivial seeds (all-zero, all-0xFF) and seeds outside [32, 252] bytes are + * rejected — these are nominally seeds but provide no security and are + * almost certainly bugs in the caller. + */ +bool zcash_seed_fingerprint_request_valid(bool present, size_t size) { + return !present || size == 32; +} + +bool zcash_calculate_seed_fingerprint(const uint8_t* seed, uint32_t seed_len, + uint8_t fingerprint_out[32]) { + if (!seed || !fingerprint_out) return false; + if (seed_len < 32 || seed_len > 252) return false; + + bool all_zero = true; + bool all_ff = true; + for (uint32_t i = 0; i < seed_len; i++) { + if (seed[i] != 0x00) all_zero = false; + if (seed[i] != 0xFF) all_ff = false; + if (!all_zero && !all_ff) break; + } + if (all_zero || all_ff) return false; + + BLAKE2B_CTX ctx; + if (blake2b_InitPersonal(&ctx, 32, "Zcash_HD_Seed_FP", 16) != 0) { + return false; + } + uint8_t len_byte = (uint8_t)seed_len; + blake2b_Update(&ctx, &len_byte, 1); + blake2b_Update(&ctx, seed, seed_len); + if (blake2b_Final(&ctx, fingerprint_out, 32) != 0) { + memzero(&ctx, sizeof(ctx)); + return false; + } + + memzero(&ctx, sizeof(ctx)); + return true; +} diff --git a/lib/transport/CMakeLists.txt b/lib/transport/CMakeLists.txt index d42d3e113..805b27762 100644 --- a/lib/transport/CMakeLists.txt +++ b/lib/transport/CMakeLists.txt @@ -18,6 +18,7 @@ set(protoc_pb_sources ${DEVICE_PROTOCOL}/messages-solana.proto ${DEVICE_PROTOCOL}/messages-tron.proto ${DEVICE_PROTOCOL}/messages-ton.proto + ${DEVICE_PROTOCOL}/messages-zcash.proto ${DEVICE_PROTOCOL}/messages.proto) set(protoc_pb_options @@ -35,6 +36,7 @@ set(protoc_pb_options ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages-solana.options ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages-tron.options ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages-ton.options + ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages-zcash.options ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages.options) set(protoc_c_sources @@ -52,6 +54,7 @@ set(protoc_c_sources ${CMAKE_BINARY_DIR}/lib/transport/messages-solana.pb.c ${CMAKE_BINARY_DIR}/lib/transport/messages-tron.pb.c ${CMAKE_BINARY_DIR}/lib/transport/messages-ton.pb.c + ${CMAKE_BINARY_DIR}/lib/transport/messages-zcash.pb.c ${CMAKE_BINARY_DIR}/lib/transport/messages.pb.c) set(protoc_c_headers @@ -69,6 +72,7 @@ set(protoc_c_headers ${CMAKE_BINARY_DIR}/include/messages-solana.pb.h ${CMAKE_BINARY_DIR}/include/messages-tron.pb.h ${CMAKE_BINARY_DIR}/include/messages-ton.pb.h + ${CMAKE_BINARY_DIR}/include/messages-zcash.pb.h ${CMAKE_BINARY_DIR}/include/messages.pb.h) set(protoc_pb_sources_moved @@ -86,6 +90,7 @@ set(protoc_pb_sources_moved ${CMAKE_BINARY_DIR}/lib/transport/messages-solana.proto ${CMAKE_BINARY_DIR}/lib/transport/messages-tron.proto ${CMAKE_BINARY_DIR}/lib/transport/messages-ton.proto + ${CMAKE_BINARY_DIR}/lib/transport/messages-zcash.proto ${CMAKE_BINARY_DIR}/lib/transport/messages.proto) add_custom_command( @@ -163,6 +168,10 @@ add_custom_command( ${PROTOC_BINARY} -I. -I/usr/include --plugin=nanopb=${NANOPB_DIR}/generator/protoc-gen-nanopb "--nanopb_out=-f messages-ton.options:." messages-ton.proto + COMMAND + ${PROTOC_BINARY} -I. -I/usr/include + --plugin=nanopb=${NANOPB_DIR}/generator/protoc-gen-nanopb + "--nanopb_out=-f messages-zcash.options:." messages-zcash.proto COMMAND ${PROTOC_BINARY} -I. -I/usr/include --plugin=nanopb=${NANOPB_DIR}/generator/protoc-gen-nanopb