From f9e608196cd18d9649554ff4c9374364b966f5e5 Mon Sep 17 00:00:00 2001 From: highlander Date: Tue, 30 Jun 2026 00:47:24 -0500 Subject: [PATCH 01/23] feat: protocol additions for the firmware 7.x release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the device-protocol messages required by the upcoming firmware release: - thorchain: ThorchainMsgSend.denom (field 11) — non-RUNE assets (TCY, RUJI, IBC) - ripple: RippleSignTx.memo (field 7) — XRP->THORChain swap routing - hive: full Hive support — HiveGetPublicKey(s), HiveSignTx, HiveSignAccountCreate/Update (MessageType 1600-1609) - zcash: clear-signing + Orchard shielded protocol (transparent in/out/ack, PCZT, FVK, display-address) All additions are new optional fields / new message types — additive and backward-compatible. lib/ bindings are gitignored build artifacts; package.json build:js/json updated to include messages-hive.proto. --- messages-hive.options | 46 ++++++++++++ messages-hive.proto | 149 +++++++++++++++++++++++++++++++++++++++ messages-ripple.proto | 1 + messages-thorchain.proto | 1 + messages-zcash.options | 16 +++-- messages-zcash.proto | 120 +++++++++++++++++++++++-------- messages.proto | 16 ++++- package.json | 4 +- 8 files changed, 317 insertions(+), 36 deletions(-) create mode 100644 messages-hive.options create mode 100644 messages-hive.proto diff --git a/messages-hive.options b/messages-hive.options new file mode 100644 index 00000000..e878f3e3 --- /dev/null +++ b/messages-hive.options @@ -0,0 +1,46 @@ +HiveGetPublicKey.address_n max_count:8 + +HivePublicKey.public_key max_size:64 +HivePublicKey.raw_public_key max_size:33 + +HiveGetPublicKeys.account_index int_size:IS_32 + +HivePublicKeys.owner_key max_size:64 +HivePublicKeys.active_key max_size:64 +HivePublicKeys.memo_key max_size:64 +HivePublicKeys.posting_key max_size:64 + +HiveSignTx.address_n max_count:8 +HiveSignTx.chain_id max_size:32 +HiveSignTx.from max_size:16 +HiveSignTx.to max_size:16 +HiveSignTx.amount int_size:IS_64 +HiveSignTx.asset_symbol max_size:10 +HiveSignTx.memo max_size:2048 + +HiveSignedTx.signature max_size:65 +HiveSignedTx.serialized_tx max_size:512 + +HiveSignAccountCreate.address_n max_count:8 +HiveSignAccountCreate.chain_id max_size:32 +HiveSignAccountCreate.creator max_size:16 +HiveSignAccountCreate.new_account_name max_size:16 +HiveSignAccountCreate.owner_key max_size:64 +HiveSignAccountCreate.active_key max_size:64 +HiveSignAccountCreate.posting_key max_size:64 +HiveSignAccountCreate.memo_key max_size:64 +HiveSignAccountCreate.fee_amount int_size:IS_64 + +HiveSignedAccountCreate.signature max_size:65 +HiveSignedAccountCreate.serialized_tx max_size:512 + +HiveSignAccountUpdate.address_n max_count:8 +HiveSignAccountUpdate.chain_id max_size:32 +HiveSignAccountUpdate.account max_size:16 +HiveSignAccountUpdate.new_owner_key max_size:64 +HiveSignAccountUpdate.new_active_key max_size:64 +HiveSignAccountUpdate.new_posting_key max_size:64 +HiveSignAccountUpdate.new_memo_key max_size:64 + +HiveSignedAccountUpdate.signature max_size:65 +HiveSignedAccountUpdate.serialized_tx max_size:512 diff --git a/messages-hive.proto b/messages-hive.proto new file mode 100644 index 00000000..7612ec47 --- /dev/null +++ b/messages-hive.proto @@ -0,0 +1,149 @@ +syntax = "proto2"; + +option java_package = "com.shapeshift.keepkey.lib.protobuf"; +option java_outer_classname = "KeepKeyMessageHive"; + +/** + * Request: Ask device for a single Hive public key at a given SLIP-0048 path. + * Path format: m/48'/13'/role'/account'/0' + * role: 0'=owner 1'=active 3'=memo 4'=posting + * @start + * @next HivePublicKey + * @next Failure + */ +message HiveGetPublicKey { + repeated uint32 address_n = 1; // Full SLIP-0048 path (all 5 components hardened) + optional bool show_display = 2; // Confirm on device before returning + optional uint32 role = 3; // 0=owner 1=active 3=memo 4=posting (for display label only) +} + +/** + * Response: Single Hive public key + * @end + */ +message HivePublicKey { + optional string public_key = 1; // STM-prefixed base58check public key + optional bytes raw_public_key = 2; // 33-byte compressed secp256k1 public key +} + +/** + * Request: Ask device for all four Hive role keys in one interaction. + * Derives owner/active/memo/posting keys for the given account index. + * Paths: + * owner: m/48'/13'/0'/account_index'/0' + * active: m/48'/13'/1'/account_index'/0' + * memo: m/48'/13'/3'/account_index'/0' + * posting: m/48'/13'/4'/account_index'/0' + * @start + * @next HivePublicKeys + * @next Failure + */ +message HiveGetPublicKeys { + optional uint32 account_index = 1 [default = 0]; // Hive account slot (0 = first account) + optional bool show_display = 2; // Confirm on device before returning +} + +/** + * Response: All four Hive role public keys + * @end + */ +message HivePublicKeys { + optional string owner_key = 1; // STM... owner public key + optional string active_key = 2; // STM... active public key + optional string memo_key = 3; // STM... memo public key + optional string posting_key = 4; // STM... posting public key +} + +/** + * Request: Sign a Hive transfer transaction (op type 2). + * Signing key should be the active key: m/48'/13'/1'/account'/0' + * @start + * @next HiveSignedTx + * @next Failure + */ +message HiveSignTx { + repeated uint32 address_n = 1; // Full SLIP-0048 path of signing key + optional bytes chain_id = 2; // 32-byte chain ID (mainnet = beeab0de...) + optional uint32 ref_block_num = 3; // Reference block number (uint16) + optional uint32 ref_block_prefix = 4; // Reference block prefix (uint32) + optional uint32 expiration = 5; // Expiration Unix timestamp (uint32) + optional string from = 6; // Sender account name + optional string to = 7; // Recipient account name + optional uint64 amount = 8; // Amount in milliHIVE (1000 = 1.000 HIVE) + optional uint32 decimals = 9; // Decimal places (3 for HIVE/HBD) + optional string asset_symbol = 10; // "HIVE" or "HBD" + optional string memo = 11; // Optional transfer memo +} + +/** + * Response: Signed Hive transfer transaction + * @end + */ +message HiveSignedTx { + optional bytes signature = 1; // 65-byte recoverable secp256k1 signature + optional bytes serialized_tx = 2; // Serialized Graphene transaction bytes +} + +/** + * Request: Sign a Hive account_create operation (op type 9). + * All four role public keys become the account authorities at genesis. + * No software keys are generated. KeepKey is sole root of trust from block 1. + * Signing key: owner key at m/48'/13'/0'/account_index'/0' + * @start + * @next HiveSignedAccountCreate + * @next Failure + */ +message HiveSignAccountCreate { + repeated uint32 address_n = 1; // Owner key path (m/48'/13'/0'/account'/0') + optional bytes chain_id = 2; // 32-byte chain ID + optional uint32 ref_block_num = 3; + optional uint32 ref_block_prefix = 4; + optional uint32 expiration = 5; + optional string creator = 6; // Pioneer sponsor account name + optional string new_account_name = 7; // Desired Hive username + optional string owner_key = 8; // STM... owner public key (from device) + optional string active_key = 9; // STM... active public key (from device) + optional string posting_key = 10; // STM... posting public key (from device) + optional string memo_key = 11; // STM... memo public key (from device) + optional uint64 fee_amount = 12; // Creation fee in milliHIVE (3000 = 3.000 HIVE) +} + +/** + * Response: Signed Hive account_create transaction + * @end + */ +message HiveSignedAccountCreate { + optional bytes signature = 1; // 65-byte recoverable signature + optional bytes serialized_tx = 2; // Serialized Graphene transaction bytes +} + +/** + * Request: Sign a Hive account_update operation (op type 10). + * Replaces all account authorities with KeepKey-derived keys. + * Used to secure an existing Hive account. + * Signing key: owner key at m/48'/13'/0'/account_index'/0' + * @start + * @next HiveSignedAccountUpdate + * @next Failure + */ +message HiveSignAccountUpdate { + repeated uint32 address_n = 1; // Owner key path (must match account's current owner) + optional bytes chain_id = 2; // 32-byte chain ID + optional uint32 ref_block_num = 3; + optional uint32 ref_block_prefix = 4; + optional uint32 expiration = 5; + optional string account = 6; // Hive account name to update + optional string new_owner_key = 7; // STM... new owner public key + optional string new_active_key = 8; // STM... new active public key + optional string new_posting_key = 9; // STM... new posting public key + optional string new_memo_key = 10; // STM... new memo public key +} + +/** + * Response: Signed Hive account_update transaction + * @end + */ +message HiveSignedAccountUpdate { + optional bytes signature = 1; // 65-byte recoverable signature + optional bytes serialized_tx = 2; // Serialized Graphene transaction bytes +} diff --git a/messages-ripple.proto b/messages-ripple.proto index 0fc012d2..bf526ef7 100644 --- a/messages-ripple.proto +++ b/messages-ripple.proto @@ -34,6 +34,7 @@ message RippleSignTx { optional uint32 sequence = 4; // transaction sequence number optional uint32 last_ledger_sequence = 5; // see https://developers.ripple.com/reliable-transaction-submission.html#lastledgersequence optional RipplePayment payment = 6; // Payment transaction type + optional string memo = 7; // transaction memo (e.g. THORChain swap routing memo) } /** diff --git a/messages-thorchain.proto b/messages-thorchain.proto index acde357a..00183623 100644 --- a/messages-thorchain.proto +++ b/messages-thorchain.proto @@ -60,6 +60,7 @@ message ThorchainMsgSend { optional uint64 amount = 8 [jstype = JS_STRING]; optional OutputAddressType address_type = 9; reserved 10; + optional string denom = 11; // asset denom, e.g. "rune" or IBC denom } message ThorchainMsgDeposit { diff --git a/messages-zcash.options b/messages-zcash.options index 89fcdc36..2ebcb224 100644 --- a/messages-zcash.options +++ b/messages-zcash.options @@ -5,6 +5,7 @@ ZcashSignPCZT.transparent_digest max_size:32 ZcashSignPCZT.sapling_digest max_size:32 ZcashSignPCZT.orchard_digest max_size:32 ZcashSignPCZT.orchard_anchor max_size:32 +ZcashSignPCZT.expected_seed_fingerprint max_size:32 ZcashPCZTAction.alpha max_size:32 ZcashPCZTAction.sighash max_size:32 @@ -17,6 +18,8 @@ ZcashPCZTAction.enc_memo max_size:512 ZcashPCZTAction.enc_noncompact max_size:612 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:64 max_size:64 ZcashSignedPCZT.txid max_size:32 @@ -26,16 +29,19 @@ ZcashGetOrchardFVK.address_n max_count:8 ZcashOrchardFVK.ak max_size:32 ZcashOrchardFVK.nk max_size:32 ZcashOrchardFVK.rivk max_size:32 +ZcashOrchardFVK.seed_fingerprint max_size:32 + +ZcashTransparentOutput.script_pubkey max_size:128 ZcashTransparentInput.sighash max_size:32 ZcashTransparentInput.address_n max_count:8 +ZcashTransparentInput.prevout_txid max_size:32 +ZcashTransparentInput.script_pubkey max_size:128 -ZcashTransparentSig.signature max_size:73 +ZcashTransparentSigned.signatures max_count:8 max_size:73 ZcashDisplayAddress.address_n max_count:8 -ZcashDisplayAddress.address max_size:256 -ZcashDisplayAddress.ak max_size:32 -ZcashDisplayAddress.nk max_size:32 -ZcashDisplayAddress.rivk max_size:32 +ZcashDisplayAddress.expected_seed_fingerprint max_size:32 ZcashAddress.address max_size:256 +ZcashAddress.seed_fingerprint max_size:32 diff --git a/messages-zcash.proto b/messages-zcash.proto index e6e14445..c9659dd3 100644 --- a/messages-zcash.proto +++ b/messages-zcash.proto @@ -18,6 +18,7 @@ option java_outer_classname = "KeepKeyMessageZcash"; * The device derives spend authorization keys, computes the sighash, * and returns RedPallas signatures for each Orchard action. * + * @next ZcashTransparentAck * @next ZcashPCZTActionAck * @next Failure */ @@ -32,14 +33,26 @@ message ZcashSignPCZT { // Phase 2a: sub-digests for on-device sighash computation optional bytes header_digest = 8; // 32-byte pre-computed header digest optional bytes transparent_digest = 9; // 32-byte transparent digest (or empty) - optional bytes sapling_digest = 10; // 32-byte sapling digest (or empty) + optional bytes sapling_digest = 10; // Reserved for future Sapling support; currently rejected optional bytes orchard_digest = 11; // 32-byte orchard digest // Phase 2b: bundle metadata for orchard digest verification optional uint32 orchard_flags = 12; // Orchard bundle flags byte optional int64 orchard_value_balance = 13; // Orchard value balance (LE i64) optional bytes orchard_anchor = 14; // 32-byte orchard anchor + // Phase 4: plaintext header fields for on-device header digest verification + optional uint32 tx_version = 15; // Transaction version (without overwinter bit) + optional uint32 version_group_id = 16; // Version group ID + optional uint32 lock_time = 17; // Transaction lock time + optional uint32 expiry_height = 18; // Transaction expiry height // Phase 3: transparent shielding support + optional uint32 n_transparent_outputs = 29; // 0 for shielded-only (default) optional uint32 n_transparent_inputs = 30; // 0 for shielded-only (default), >0 for hybrid shielding tx + // Seed identity binding (ZIP-32 §6.1) + optional bytes expected_seed_fingerprint = 31; // 32-byte ZIP-32 §6.1 seed fingerprint: + // BLAKE2b-256("Zcash_HD_Seed_FP", + // I2LEBSP_8(len(seed)) || seed) + // If present, device verifies match against its own + // seed fingerprint and rejects with Failure on mismatch. } /** @@ -66,6 +79,11 @@ message ZcashPCZTAction { optional bytes enc_noncompact = 12; // Remaining encrypted note bytes optional bytes rk = 13; // 32-byte randomized verification key optional bytes out_ciphertext = 14; // 80-byte output ciphertext + // Phase 4: plaintext Orchard output metadata for trusted display. + // Firmware recomputes cmx from recipient/value/rseed and nullifier before + // displaying the receiver/value and before emitting any signature. + optional bytes recipient = 15; // 43-byte Orchard receiver: d || pk_d + optional bytes rseed = 16; // 32-byte output note rseed } /** @@ -116,64 +134,110 @@ message ZcashOrchardFVK { optional bytes ak = 1; // 32-byte authorizing key (Pallas point) optional bytes nk = 2; // 32-byte nullifier deriving key optional bytes rivk = 3; // 32-byte commitment randomness key + optional bytes seed_fingerprint = 4; // 32-byte ZIP-32 §6.1 seed fingerprint: + // BLAKE2b-256("Zcash_HD_Seed_FP", + // I2LEBSP_8(len(seed)) || seed) + // Stable identity of the device's seed; lets a host pin an FVK + // to a specific seed across sessions. +} + +/** + * Request: Transparent output data for hybrid transactions. + * Sent before transparent inputs so the device can review standard + * transparent recipients before any signature is emitted. + * + * @next ZcashTransparentAck + * @next ZcashPCZTActionAck + * @next Failure + */ +message ZcashTransparentOutput { + required uint32 index = 1; // Output index within the transaction + optional uint64 amount = 2; // Output value in zatoshis + optional bytes script_pubkey = 3; // Standard P2PKH/P2SH scriptPubKey } /** * Request: Transparent input data for hybrid shielding transactions. - * Sent one per transparent input during the transparent signing phase. - * The device ECDSA-signs the per-input sighash with the secp256k1 key - * at the provided BIP44 path. + * Sent after all transparent outputs have been streamed. * - * Flow: after ZcashSignPCZT with n_transparent_inputs > 0, the device - * responds with ZcashPCZTActionAck. For each transparent input, the host - * sends ZcashTransparentInput and receives ZcashTransparentSig. After - * all transparent inputs, the device transitions to the Orchard phase. + * The device stores every input first because ZIP-244 per-input transparent + * sighashes commit to all transparent prevouts, values, scripts, sequences, + * and outputs. Host-provided sighash is legacy and rejected when present. * - * @next ZcashTransparentSig + * @next ZcashTransparentAck + * @next ZcashTransparentSigned * @next Failure */ message ZcashTransparentInput { required uint32 index = 1; // Input index within the transaction - required bytes sighash = 2; // 32-byte per-input sighash (host-computed, ZIP-244) + optional bytes sighash = 2; // Legacy host-computed sighash; rejected when present repeated uint32 address_n = 3; // BIP44 path [44', 133', 0', 0, 0] - optional uint64 amount = 4; // Input value in zatoshis (for display verification) + optional uint64 amount = 4; // Input value in zatoshis + optional bytes prevout_txid = 5; // Previous transaction ID + optional uint32 prevout_index = 6; // Previous output index + optional uint32 sequence = 7; // Input sequence + optional bytes script_pubkey = 8; // Previous output scriptPubKey +} + +/** + * Response: Acknowledgment requesting the next transparent item. + * + * @prev ZcashSignPCZT + * @prev ZcashTransparentOutput + * @prev ZcashTransparentInput + */ +message ZcashTransparentAck { + optional uint32 next_output_index = 1; // Next transparent output index + optional uint32 next_input_index = 2; // Next transparent input index } /** - * Response: ECDSA signature for a transparent input. + * Response: ECDSA signatures for transparent inputs. * * @prev ZcashTransparentInput */ -message ZcashTransparentSig { - required bytes signature = 1; // DER ECDSA signature (72-73 bytes) - optional uint32 next_index = 2; // Next transparent input index, or 0xFF = done +message ZcashTransparentSigned { + repeated bytes signatures = 1; // DER ECDSA signatures, one per transparent input } /** - * Request: Display and verify a Zcash unified address on device. + * Request: Display the device-derived Orchard unified address on screen. * - * The host provides the unified address string and the FVK components - * (ak, nk, rivk). The device re-derives its own Orchard keys from seed - * and compares them against the provided FVK to verify the Orchard - * receiver belongs to this device. + * The device derives the Orchard-only Unified Address (Sinsemilla + SWU + * hash-to-curve, default diversifier index 0) from its own seed at the + * requested account and shows it on the OLED with a QR code. What appears + * on screen is bound to this device — there is no host-supplied address + * to validate. + * + * Either account or a complete address_n path (m/32'/133'/account', all + * hardened) is REQUIRED. The device rejects requests that omit both. + * + * Fields 3–6 (host-supplied address, ak, nk, rivk) were removed when the + * device gained on-device UA derivation: FVK-match attestation against a + * host-built UA is strictly weaker than device-derived display and was + * dropped. Field numbers are reserved to prevent reuse. * * @next ZcashAddress * @next Failure */ message ZcashDisplayAddress { - repeated uint32 address_n = 1; // ZIP-32 derivation path [32', 133', account'] - optional uint32 account = 2; // Account index (alternative to full path) - optional string address = 3; // Unified address string (u1...) - optional bytes ak = 4; // 32-byte authorizing key for verification - optional bytes nk = 5; // 32-byte nullifier deriving key for verification - optional bytes rivk = 6; // 32-byte commitment randomness key for verification + reserved 3, 4, 5, 6; + reserved "address", "ak", "nk", "rivk"; + repeated uint32 address_n = 1; // ZIP-32 path [32', 133', account'] — required if account omitted + optional uint32 account = 2; // Account index — required if address_n omitted + optional bytes expected_seed_fingerprint = 7; // 32-byte ZIP-32 §6.1 seed fingerprint. + // If present, device verifies match against its own + // seed fingerprint and rejects with Failure on mismatch. } /** - * Response: Verified Zcash address. + * Response: Confirmed Zcash address after user approval on device. * * @prev ZcashDisplayAddress */ message ZcashAddress { - optional string address = 1; // Verified unified address string + optional string address = 1; // Confirmed unified address + optional bytes seed_fingerprint = 2; // 32-byte ZIP-32 §6.1 seed fingerprint of the attesting + // device. Returned alongside the confirmed address so a host + // can record that this UA is bound to this device's seed. } diff --git a/messages.proto b/messages.proto index ca35898c..1a5466af 100644 --- a/messages.proto +++ b/messages.proto @@ -217,9 +217,11 @@ enum MessageType { MessageType_ZcashGetOrchardFVK = 1304 [ (wire_in) = true ]; MessageType_ZcashOrchardFVK = 1305 [ (wire_out) = true ]; MessageType_ZcashTransparentInput = 1306 [ (wire_in) = true ]; - MessageType_ZcashTransparentSig = 1307 [ (wire_out) = true ]; + MessageType_ZcashTransparentSigned = 1307 [ (wire_out) = true ]; MessageType_ZcashDisplayAddress = 1308 [ (wire_in) = true ]; MessageType_ZcashAddress = 1309 [ (wire_out) = true ]; + MessageType_ZcashTransparentOutput = 1310 [ (wire_in) = true ]; + MessageType_ZcashTransparentAck = 1311 [ (wire_out) = true ]; // TRON MessageType_TronGetAddress = 1400 [ (wire_in) = true ]; @@ -239,6 +241,18 @@ enum MessageType { MessageType_TonSignedTx = 1503 [ (wire_out) = true ]; MessageType_TonSignMessage = 1504 [ (wire_in) = true ]; MessageType_TonMessageSignature = 1505 [ (wire_out) = true ]; + + // Hive + MessageType_HiveGetPublicKey = 1600 [ (wire_in) = true ]; + MessageType_HivePublicKey = 1601 [ (wire_out) = true ]; + MessageType_HiveSignTx = 1602 [ (wire_in) = true ]; + MessageType_HiveSignedTx = 1603 [ (wire_out) = true ]; + MessageType_HiveGetPublicKeys = 1604 [ (wire_in) = true ]; + MessageType_HivePublicKeys = 1605 [ (wire_out) = true ]; + MessageType_HiveSignAccountCreate = 1606 [ (wire_in) = true ]; + MessageType_HiveSignedAccountCreate = 1607 [ (wire_out) = true ]; + MessageType_HiveSignAccountUpdate = 1608 [ (wire_in) = true ]; + MessageType_HiveSignedAccountUpdate = 1609 [ (wire_out) = true ]; } //////////////////// diff --git a/package.json b/package.json index 51f6d535..9e82e419 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "scripts": { "clean": "rm -rf ./lib/*.js ./lib/*.ts", "build": "npm run build:js && npm run build:json && npm run build:postprocess", - "build:js": "protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --js_out=import_style=commonjs,binary:./lib --ts_out=./lib types.proto messages.proto messages-ethereum.proto messages-eos.proto messages-nano.proto messages-cosmos.proto messages-binance.proto messages-ripple.proto messages-tendermint.proto messages-thorchain.proto messages-osmosis.proto messages-mayachain.proto messages-solana.proto messages-tron.proto messages-ton.proto messages-zcash.proto", - "build:json": "pbjs --keep-case -t json ./types.proto ./messages.proto ./messages-ethereum.proto ./messages-eos.proto ./messages-nano.proto ./messages-cosmos.proto ./messages-binance.proto ./messages-ripple.proto ./messages-tendermint.proto ./messages-thorchain.proto ./messages-osmosis.proto ./messages-mayachain.proto ./messages-solana.proto ./messages-tron.proto ./messages-ton.proto ./messages-zcash.proto > ./lib/proto.json", + "build:js": "protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --js_out=import_style=commonjs,binary:./lib --ts_out=./lib types.proto messages.proto messages-ethereum.proto messages-eos.proto messages-nano.proto messages-cosmos.proto messages-binance.proto messages-ripple.proto messages-tendermint.proto messages-thorchain.proto messages-osmosis.proto messages-mayachain.proto messages-solana.proto messages-tron.proto messages-ton.proto messages-zcash.proto messages-hive.proto", + "build:json": "pbjs --keep-case -t json ./types.proto ./messages.proto ./messages-ethereum.proto ./messages-eos.proto ./messages-nano.proto ./messages-cosmos.proto ./messages-binance.proto ./messages-ripple.proto ./messages-tendermint.proto ./messages-thorchain.proto ./messages-osmosis.proto ./messages-mayachain.proto ./messages-solana.proto ./messages-tron.proto ./messages-ton.proto ./messages-zcash.proto ./messages-hive.proto > ./lib/proto.json", "build:postprocess": "find ./lib -name \"*.js\" -exec sed -i '' -e \"s/var global = Function(\\'return this\\')();/var global = (function(){ return this }).call(null);/g\" {} \\;", "prepublishOnly": "npm run build", "test": "echo \"Error: no test specified\" && exit 1" From 2ec999a9b2e5174da5981e85f66845a97cdaa877 Mon Sep 17 00:00:00 2001 From: highlander Date: Thu, 2 Jul 2026 02:10:27 -0500 Subject: [PATCH 02/23] =?UTF-8?q?feat:=20LoadClearsignSigner=20(117)=20?= =?UTF-8?q?=E2=80=94=20runtime=20clearsign=20signer=20with=20alias,=20user?= =?UTF-8?q?-confirmed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- messages-ethereum.options | 2 ++ messages-ethereum.proto | 16 ++++++++++++++++ messages.proto | 1 + 3 files changed, 19 insertions(+) diff --git a/messages-ethereum.options b/messages-ethereum.options index 1759d0ac..9cd03b4f 100644 --- a/messages-ethereum.options +++ b/messages-ethereum.options @@ -1,2 +1,4 @@ EthereumTxMetadata.signed_payload max_size:1024 EthereumMetadataAck.display_summary max_size:32 +LoadClearsignSigner.pubkey max_size:33 +LoadClearsignSigner.alias max_size:32 diff --git a/messages-ethereum.proto b/messages-ethereum.proto index b8719d34..40e7925e 100644 --- a/messages-ethereum.proto +++ b/messages-ethereum.proto @@ -113,6 +113,22 @@ message EthereumMetadataAck { optional string display_summary = 2; // Brief result for host logging } +/** + * Request: Load a clearsign signer public key + alias into a runtime key slot. + * The device shows a mandatory confirmation (alias + key fingerprint) before + * accepting; there is no way to load a signer without user consent. Loaded + * signers live in RAM only and are cleared on reboot. Every transaction whose + * metadata was verified by a loaded (non built-in) signer is preceded by a + * warning screen naming the alias during transaction confirmation. + * @next Success + * @next Failure + */ +message LoadClearsignSigner { + optional uint32 key_id = 1; // target key slot (0-3); must not hold a built-in key + optional bytes pubkey = 2; // 33-byte compressed secp256k1 public key + optional string alias = 3; // short display name shown on load confirm + per-tx warning +} + //////////////////////////////////////// // Ethereum: Message signing messages // //////////////////////////////////////// diff --git a/messages.proto b/messages.proto index 1a5466af..2f8e5b30 100644 --- a/messages.proto +++ b/messages.proto @@ -100,6 +100,7 @@ enum MessageType { // Ethereum Clear Signing MessageType_EthereumTxMetadata = 115 [ (wire_in) = true ]; MessageType_EthereumMetadataAck = 116 [ (wire_out) = true ]; + MessageType_LoadClearsignSigner = 117 [ (wire_in) = true ]; // BIP-85 MessageType_GetBip85Mnemonic = 120 [ (wire_in) = true ]; From 33521a8fd6f012c8bbca3a8902eea6c1f5aa3389 Mon Sep 17 00:00:00 2001 From: highlander Date: Tue, 7 Jul 2026 14:50:04 -0300 Subject: [PATCH 03/23] feat(clearsign): identity icon + persist fields on LoadClearsignSigner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends msg 117 for the persistent-identity model ("KeepKey + identity"): - icon (bytes, <=384): 1bpp mono identity logo, shown on load-confirm, at the start of every clearsign it vouches for, and on boot. - icon_width / icon_height: icon dims (<=64). - persist (bool): store the identity in flash so it survives reboot; default RAM-only (backward compatible with current behavior). Proto + nanopb options only; the firmware handler + flash storage consume these in a follow-up. Fork only — never PR to keepkey/device-protocol. --- messages-ethereum.options | 1 + messages-ethereum.proto | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/messages-ethereum.options b/messages-ethereum.options index 9cd03b4f..8a1b3ff3 100644 --- a/messages-ethereum.options +++ b/messages-ethereum.options @@ -2,3 +2,4 @@ EthereumTxMetadata.signed_payload max_size:1024 EthereumMetadataAck.display_summary max_size:32 LoadClearsignSigner.pubkey max_size:33 LoadClearsignSigner.alias max_size:32 +LoadClearsignSigner.icon max_size:384 diff --git a/messages-ethereum.proto b/messages-ethereum.proto index 40e7925e..790fdc27 100644 --- a/messages-ethereum.proto +++ b/messages-ethereum.proto @@ -127,6 +127,10 @@ message LoadClearsignSigner { optional uint32 key_id = 1; // target key slot (0-3); must not hold a built-in key optional bytes pubkey = 2; // 33-byte compressed secp256k1 public key optional string alias = 3; // short display name shown on load confirm + per-tx warning + optional bytes icon = 4; // optional identity logo: 1bpp mono row-major bitmap, <= 384 bytes + optional uint32 icon_width = 5; // icon pixel width (<= 64; icon+dims omitted => text-only identity) + optional uint32 icon_height = 6; // icon pixel height (<= 64) + optional bool persist = 7; // store in flash so the identity survives reboot (shown on boot); default RAM-only } //////////////////////////////////////// From 9e46aeb6de95c3d12272bb6b48dcc06d40f1b436 Mon Sep 17 00:00:00 2001 From: highlander Date: Tue, 14 Jul 2026 23:56:38 -0300 Subject: [PATCH 04/23] =?UTF-8?q?feat(hive):=20HiveSignMessage/HiveSignedM?= =?UTF-8?q?essage=20(1614/1615)=20=E2=80=94=20Keychain=20signBuffer=20cont?= =?UTF-8?q?ract?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signature over SHA256(raw message bytes) only: no chain_id prepend, no message prefix. 1610-1613 skipped: NEAR holds them on master. --- messages-hive.options | 6 ++++++ messages-hive.proto | 27 +++++++++++++++++++++++++++ messages.proto | 3 +++ 3 files changed, 36 insertions(+) diff --git a/messages-hive.options b/messages-hive.options index e878f3e3..04ba85d6 100644 --- a/messages-hive.options +++ b/messages-hive.options @@ -44,3 +44,9 @@ HiveSignAccountUpdate.new_memo_key max_size:64 HiveSignedAccountUpdate.signature max_size:65 HiveSignedAccountUpdate.serialized_tx max_size:512 + +HiveSignMessage.address_n max_count:8 +HiveSignMessage.message max_size:1024 + +HiveSignedMessage.signature max_size:65 +HiveSignedMessage.public_key max_size:33 diff --git a/messages-hive.proto b/messages-hive.proto index 7612ec47..c08e2385 100644 --- a/messages-hive.proto +++ b/messages-hive.proto @@ -147,3 +147,30 @@ message HiveSignedAccountUpdate { optional bytes signature = 1; // 65-byte recoverable signature optional bytes serialized_tx = 2; // Serialized Graphene transaction bytes } + +/** + * Request: Sign an arbitrary message with a Hive role key. + * Implements the Hive Keychain requestSignBuffer contract (hive-js + * Signature.signBuffer): signature over SHA256(message bytes) — a single + * hash of the raw bytes only, NO chain_id prepend (unlike transactions) + * and NO Bitcoin/Solana-style message prefix. This is the Hive dApp login + * primitive (Aioha / Keychain SDK). + * Signing key: any SLIP-0048 role; dApp login uses posting + * (m/48'/13'/4'/account'/0'). + * @start + * @next HiveSignedMessage + * @next Failure + */ +message HiveSignMessage { + repeated uint32 address_n = 1; // Full SLIP-0048 path of signing key + optional bytes message = 2; // Raw message bytes (max 1024) +} + +/** + * Response: Signed Hive message + * @end + */ +message HiveSignedMessage { + optional bytes signature = 1; // 65-byte recoverable secp256k1 signature (27+recid+4, r, s) + optional bytes public_key = 2; // 33-byte compressed public key of the signing key +} diff --git a/messages.proto b/messages.proto index 2f8e5b30..f7ad68ad 100644 --- a/messages.proto +++ b/messages.proto @@ -254,6 +254,9 @@ enum MessageType { MessageType_HiveSignedAccountCreate = 1607 [ (wire_out) = true ]; MessageType_HiveSignAccountUpdate = 1608 [ (wire_in) = true ]; MessageType_HiveSignedAccountUpdate = 1609 [ (wire_out) = true ]; + // 1610-1613 reserved: NEAR (NearGetAddress..NearSignedTx) on master + MessageType_HiveSignMessage = 1614 [ (wire_in) = true ]; + MessageType_HiveSignedMessage = 1615 [ (wire_out) = true ]; } //////////////////// From a793934d09a883c9944ba3b9da15d23969906343 Mon Sep 17 00:00:00 2001 From: highlander Date: Wed, 15 Jul 2026 00:46:17 -0300 Subject: [PATCH 05/23] =?UTF-8?q?docs(hive):=20HiveSignMessage=20roles=20a?= =?UTF-8?q?re=20posting/active/memo=20=E2=80=94=20owner'=20rejected?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- messages-hive.proto | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/messages-hive.proto b/messages-hive.proto index c08e2385..853134e8 100644 --- a/messages-hive.proto +++ b/messages-hive.proto @@ -155,7 +155,8 @@ message HiveSignedAccountUpdate { * hash of the raw bytes only, NO chain_id prepend (unlike transactions) * and NO Bitcoin/Solana-style message prefix. This is the Hive dApp login * primitive (Aioha / Keychain SDK). - * Signing key: any SLIP-0048 role; dApp login uses posting + * Signing key: posting/active/memo role (Keychain's requestSignBuffer + * surface — owner' is rejected); dApp login uses posting * (m/48'/13'/4'/account'/0'). * @start * @next HiveSignedMessage From f0b454981e093ac4f7c157281ed5cc1bfa395f73 Mon Sep 17 00:00:00 2001 From: highlander Date: Wed, 15 Jul 2026 12:42:47 -0300 Subject: [PATCH 06/23] =?UTF-8?q?feat(hive):=20HiveSignOperations/HiveSign?= =?UTF-8?q?edOperations=20(1616/1617)=20=E2=80=94=20parsed=20generic=20op?= =?UTF-8?q?=20signing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Host serializes; firmware parses the Graphene bytes and clear-signs the phase-1 op table (vote 0, comment 1, custom_json 18). Op types 2/9/10 are permanently excluded — dedicated messages keep their stronger invariants. --- messages-hive.options | 6 ++++++ messages-hive.proto | 29 +++++++++++++++++++++++++++++ messages.proto | 2 ++ 3 files changed, 37 insertions(+) diff --git a/messages-hive.options b/messages-hive.options index 04ba85d6..d39d5921 100644 --- a/messages-hive.options +++ b/messages-hive.options @@ -50,3 +50,9 @@ HiveSignMessage.message max_size:1024 HiveSignedMessage.signature max_size:65 HiveSignedMessage.public_key max_size:33 + +HiveSignOperations.address_n max_count:8 +HiveSignOperations.chain_id max_size:32 +HiveSignOperations.serialized_tx max_size:2048 + +HiveSignedOperations.signature max_size:65 diff --git a/messages-hive.proto b/messages-hive.proto index 853134e8..1566d5fb 100644 --- a/messages-hive.proto +++ b/messages-hive.proto @@ -175,3 +175,32 @@ message HiveSignedMessage { optional bytes signature = 1; // 65-byte recoverable secp256k1 signature (27+recid+4, r, s) optional bytes public_key = 2; // 33-byte compressed public key of the signing key } + +/** + * Request: Sign a host-serialized Graphene transaction after parsing and + * clear-signing every operation in it. The firmware re-derives everything it + * displays from the bytes themselves; transactions containing operations + * outside the supported table are refused (no blind-sign fallback). + * Phase-1 table: vote (0), comment (1), custom_json (18). + * Op types 2/9/10 (transfer, account_create, account_update) are PERMANENTLY + * excluded — they keep their dedicated message types and invariants. + * Signing key: posting (m/48'/13'/4'/account'/0') for posting-tier txs, + * active (m/48'/13'/1'/account'/0') when custom_json carries required_auths. + * Digest: SHA256(chain_id || serialized_tx), same as HiveSignTx. + * @start + * @next HiveSignedOperations + * @next Failure + */ +message HiveSignOperations { + repeated uint32 address_n = 1; // Full SLIP-0048 path of signing key + optional bytes chain_id = 2; // 32-byte chain ID (mainnet = beeab0de...) + optional bytes serialized_tx = 3; // Graphene tx bytes: header..extensions, NO chain_id prefix (max 2048) +} + +/** + * Response: Signed Hive operations transaction + * @end + */ +message HiveSignedOperations { + optional bytes signature = 1; // 65-byte recoverable secp256k1 signature (27+recid+4, r, s) +} diff --git a/messages.proto b/messages.proto index f7ad68ad..93b318e6 100644 --- a/messages.proto +++ b/messages.proto @@ -257,6 +257,8 @@ enum MessageType { // 1610-1613 reserved: NEAR (NearGetAddress..NearSignedTx) on master MessageType_HiveSignMessage = 1614 [ (wire_in) = true ]; MessageType_HiveSignedMessage = 1615 [ (wire_out) = true ]; + MessageType_HiveSignOperations = 1616 [ (wire_in) = true ]; + MessageType_HiveSignedOperations = 1617 [ (wire_out) = true ]; } //////////////////// From f7b458078cf9249ac706bd1089f109a5a2ea8696 Mon Sep 17 00:00:00 2001 From: highlander Date: Thu, 16 Jul 2026 17:47:54 -0300 Subject: [PATCH 07/23] =?UTF-8?q?fix(clearsign):=20specify=20the=20icon=20?= =?UTF-8?q?RLE=20wire=20grammar=20=E2=80=94=20the=20packed-bitmap=20doc=20?= =?UTF-8?q?was=20wrong?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LoadClearsignSigner.icon was documented as a "1bpp mono row-major bitmap", but firmware feeds it to draw_bitmap_mono_rle() (lib/board/draw.c), which reads signed int8 RLE packets and byte-valued pixels. A client following the protobuf comment would ship packed bits into an RLE decoder and render a garbled or missing identity logo on a trust screen. The RLE is intentional, not the doc: firmware permits icon_width/icon_height up to 64, but a packed 1bpp 64x64 needs 512 bytes and the cap is 384 — the documented format is arithmetically impossible at the dimensions the firmware itself accepts. So specify the grammar rather than change the decoder. Documents the exact packet grammar (RUN [n][v] for n>0, LITERAL [n][v1..v-n] for n<0, n==0 invalid, row-major fill, int8 bounds), states that pixels are byte-valued intensity rendered as value*color/100 (not a 1-bit mask), names draw_bitmap_mono_rle() as the decoder of record, and adds a golden vector (03 FF FF 00, w=2 h=2 -> FF FF FF 00) verified against that decoder. Comment-only: no field, number, or wire behaviour changes. --- messages-ethereum.proto | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/messages-ethereum.proto b/messages-ethereum.proto index 790fdc27..db64824d 100644 --- a/messages-ethereum.proto +++ b/messages-ethereum.proto @@ -127,7 +127,28 @@ message LoadClearsignSigner { optional uint32 key_id = 1; // target key slot (0-3); must not hold a built-in key optional bytes pubkey = 2; // 33-byte compressed secp256k1 public key optional string alias = 3; // short display name shown on load confirm + per-tx warning - optional bytes icon = 4; // optional identity logo: 1bpp mono row-major bitmap, <= 384 bytes + /* + * Optional identity logo, <= 384 bytes, run-length encoded (NOT a packed + * bitmap — a packed 1bpp 64x64 would need 512 bytes and cannot fit the cap). + * Pixels are BYTE-VALUED intensity, one byte per pixel after decoding; the + * device renders each as (value * color / 100). Decoder of record: + * keepkey-firmware lib/board/draw.c: draw_bitmap_mono_rle(). + * + * Grammar — the stream is a sequence of packets. Read n = (int8)data[i++]: + * n > 0 RUN : one value byte follows; emit it n times. [n][v] + * n < 0 LITERAL : (-n) value bytes follow; emit each once. [n][v1]..[v-n] + * n == 0 : invalid. + * Runs may not straddle the end of the image. Packets are decoded until + * exactly icon_width*icon_height pixels have been emitted, filling row-major + * (left->right, top->bottom). n is a signed 8-bit value, so a RUN emits at + * most 127 pixels and a LITERAL at most 128. + * + * Golden vector (2x2, w=2 h=2): bytes 03 FF FF 00 + * 03 -> RUN of 3, value FF => pixels [FF, FF, FF] + * FF -> n = -1, LITERAL of 1 => next byte 00 => pixel [00] + * decoded = FF FF FF 00 (row0 = FF FF, row1 = FF 00) + */ + optional bytes icon = 4; optional uint32 icon_width = 5; // icon pixel width (<= 64; icon+dims omitted => text-only identity) optional uint32 icon_height = 6; // icon pixel height (<= 64) optional bool persist = 7; // store in flash so the identity survives reboot (shown on boot); default RAM-only From 7182973919e88ac49cc219f30f17ec17488f9fde Mon Sep 17 00:00:00 2001 From: highlander Date: Thu, 16 Jul 2026 18:10:15 -0300 Subject: [PATCH 08/23] =?UTF-8?q?fix(clearsign):=20icon=20spec=20was=20uns?= =?UTF-8?q?afe=20=E2=80=94=200x80=20literal=20undecodable,=20width=20cap?= =?UTF-8?q?=20allows=20text=20overwrite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two contract errors found in review of the RLE grammar added in f7b4580. 1) The doc claimed a LITERAL may carry up to 128 bytes, i.e. n = -128 (0x80). The decoder's counter is `int8_t nonsequence` and it computes `nonsequence = -sequence`, so -(-128) = 128 does not fit and wraps back to -128 — failing the `nonsequence > 0` invariant. Under NDEBUG (release) the assert is compiled out and decoding proceeds with a negative counter (signed-overflow UB); on debug/emulator builds it asserts. The load handler validates only length and dimensions, so a spec-valid icon could fault the device during its own mandatory confirmation. Restrict to [-127,-1] and mark 0x80 explicitly invalid; encoders split a 128-byte literal in two. 2) icon_width was documented (and enforced) as <= 64, but the confirm screen's icon column is LEFT_MARGIN_WITH_ICON = 40 and title/body text starts at x=40. stage_runtime_icon() places any icon wider than the column at x=0 ("would clip the title/body" — its own comment), and confirm_sm draws the icon AFTER the text. So a host-supplied 64px icon overwrites 24 columns of the alias, fingerprint and the "NOT verified by KeepKey" warning — on the screen whose entire purpose is that warning. Cap width at 40. Firmware must enforce both independently — a hostile host is not bound by this comment. Companion firmware change rejects 0x80 in the decoder and caps icon_width at 40 in fsm_msgLoadClearsignSigner. Comment-only: no field, number, or wire behaviour changes. --- messages-ethereum.proto | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/messages-ethereum.proto b/messages-ethereum.proto index db64824d..530a5284 100644 --- a/messages-ethereum.proto +++ b/messages-ethereum.proto @@ -135,13 +135,18 @@ message LoadClearsignSigner { * keepkey-firmware lib/board/draw.c: draw_bitmap_mono_rle(). * * Grammar — the stream is a sequence of packets. Read n = (int8)data[i++]: - * n > 0 RUN : one value byte follows; emit it n times. [n][v] - * n < 0 LITERAL : (-n) value bytes follow; emit each once. [n][v1]..[v-n] - * n == 0 : invalid. + * n in [1, 127] RUN : one value byte follows; emit it n times. [n][v] + * n in [-127, -1] LITERAL : (-n) value bytes follow; emit each once. [n][v1]..[v-n] + * n == 0 : invalid. + * n == -128 (0x80) : INVALID. The decoder's run counter is int8_t, + * so it cannot represent -(-128) = 128; a 0x80 + * packet is undecodable. Encoders MUST split a + * 128-byte literal into two packets. Firmware + * rejects 0x80 rather than rendering. * Runs may not straddle the end of the image. Packets are decoded until * exactly icon_width*icon_height pixels have been emitted, filling row-major - * (left->right, top->bottom). n is a signed 8-bit value, so a RUN emits at - * most 127 pixels and a LITERAL at most 128. + * (left->right, top->bottom). So a RUN emits at most 127 pixels and a + * LITERAL at most 127. * * Golden vector (2x2, w=2 h=2): bytes 03 FF FF 00 * 03 -> RUN of 3, value FF => pixels [FF, FF, FF] @@ -149,8 +154,15 @@ message LoadClearsignSigner { * decoded = FF FF FF 00 (row0 = FF FF, row1 = FF 00) */ optional bytes icon = 4; - optional uint32 icon_width = 5; // icon pixel width (<= 64; icon+dims omitted => text-only identity) - optional uint32 icon_height = 6; // icon pixel height (<= 64) + /* + * Icon pixel width, 1..40. The cap is the confirm screen's left icon column + * (LEFT_MARGIN_WITH_ICON = 40); title/body text begins at x=40 and the icon + * is drawn AFTER the text, so a wider icon would paint over the alias, + * fingerprint and the "NOT verified by KeepKey" warning on the trust screen. + * Icon + both dimensions omitted => text-only identity. + */ + optional uint32 icon_width = 5; + optional uint32 icon_height = 6; // icon pixel height (1..64; the icon column is 64px tall) optional bool persist = 7; // store in flash so the identity survives reboot (shown on boot); default RAM-only } From 4eb7d5e4f30ab75930df59cb19e40500217a68e8 Mon Sep 17 00:00:00 2001 From: highlander Date: Thu, 16 Jul 2026 19:21:30 -0300 Subject: [PATCH 09/23] fix(clearsign): drop the obsolete packed-size rationale; correct the RAM-only claim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two contract errors found in review. 1) The "a packed 1bpp 64x64 would need 512 bytes and cannot fit the cap" rationale is obsolete and misleading. Once icon_width was capped at 40, that arithmetic stopped applying: a packed icon at the LEGAL maximum geometry (40x64) is 320 bytes and WOULD fit the 384-byte cap. RLE is the format because draw_bitmap_mono_rle() is the decoder of record and every bundled image already uses it — not because packed wouldn't fit. Say that instead. 2) "Loaded signers live in RAM only and are cleared on reboot" contradicts persist=7 in the same message, which writes the identity to flash to survive reboot. State the actual behaviour: RAM-only by default, durable with persist=true. Also documents the exactness rule the firmware now enforces (no run straddling the image, entire input consumed), so encoders learn it from the spec rather than from a silently-missing logo. Comment-only: no field, number, or wire behaviour changes. --- messages-ethereum.proto | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/messages-ethereum.proto b/messages-ethereum.proto index 530a5284..6d8144d5 100644 --- a/messages-ethereum.proto +++ b/messages-ethereum.proto @@ -114,12 +114,14 @@ message EthereumMetadataAck { } /** - * Request: Load a clearsign signer public key + alias into a runtime key slot. + * Request: Load a clearsign signer public key + alias into a key slot. * The device shows a mandatory confirmation (alias + key fingerprint) before - * accepting; there is no way to load a signer without user consent. Loaded - * signers live in RAM only and are cleared on reboot. Every transaction whose - * metadata was verified by a loaded (non built-in) signer is preceded by a - * warning screen naming the alias during transaction confirmation. + * accepting; there is no way to load a signer without user consent. + * A signer is RAM-only by default and is cleared on reboot; set persist=true + * to also write it to flash, where it survives reboot and is reloaded + * automatically. Every transaction whose metadata was verified by a loaded + * (non built-in) signer is preceded by a warning screen naming the alias + * during transaction confirmation. * @next Success * @next Failure */ @@ -128,8 +130,11 @@ message LoadClearsignSigner { optional bytes pubkey = 2; // 33-byte compressed secp256k1 public key optional string alias = 3; // short display name shown on load confirm + per-tx warning /* - * Optional identity logo, <= 384 bytes, run-length encoded (NOT a packed - * bitmap — a packed 1bpp 64x64 would need 512 bytes and cannot fit the cap). + * Optional identity logo, <= 384 bytes, run-length encoded — NOT a packed + * bitmap. (This is the format because draw_bitmap_mono_rle() is the decoder + * of record and every bundled image already uses it; it is NOT a size + * workaround — a packed 1bpp icon at the legal maximum geometry, 40x64, + * would be 320 bytes and would fit the cap.) * Pixels are BYTE-VALUED intensity, one byte per pixel after decoding; the * device renders each as (value * color / 100). Decoder of record: * keepkey-firmware lib/board/draw.c: draw_bitmap_mono_rle(). @@ -143,10 +148,12 @@ message LoadClearsignSigner { * packet is undecodable. Encoders MUST split a * 128-byte literal into two packets. Firmware * rejects 0x80 rather than rendering. - * Runs may not straddle the end of the image. Packets are decoded until - * exactly icon_width*icon_height pixels have been emitted, filling row-major - * (left->right, top->bottom). So a RUN emits at most 127 pixels and a - * LITERAL at most 127. + * The stream must decode EXACTLY, and the device validates this before the + * icon is shown or stored: no run may straddle the end of the image, + * exactly icon_width*icon_height pixels are emitted (row-major, + * left->right, top->bottom), and the ENTIRE input must be consumed — + * trailing packets after the final pixel are rejected. So a RUN emits at + * most 127 pixels and a LITERAL at most 127. * * Golden vector (2x2, w=2 h=2): bytes 03 FF FF 00 * 03 -> RUN of 3, value FF => pixels [FF, FF, FF] From 47e19d8b0816db20e15d9b1e27da83c70d5ed88d Mon Sep 17 00:00:00 2001 From: highlander Date: Fri, 17 Jul 2026 17:57:22 -0300 Subject: [PATCH 10/23] feat(solana): optional signed token-definition fields on SolanaTokenInfo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add optional signature + signer_key_id so a host can attest a token's mint->symbol mapping with a clear-sign signer key (LoadClearsignSigner). The firmware verifies; producing these signatures is a follow-up. Backward compatible — existing hosts set neither field. --- messages-solana.proto | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/messages-solana.proto b/messages-solana.proto index a8f4eea2..c169817e 100644 --- a/messages-solana.proto +++ b/messages-solana.proto @@ -37,6 +37,13 @@ message SolanaTokenInfo { optional bytes mint = 1; // 32-byte mint public key optional string symbol = 2; // Token symbol e.g. "USDC" (max 12) optional uint32 decimals = 3; // Token decimals e.g. 6 + // Optional attestation: ECDSA(secp256k1) signature over a domain-separated + // digest of (mint, decimals, symbol), signed by a clear-sign signer the + // user loaded via LoadClearsignSigner. When present and valid, the device + // trusts the symbol; when absent it falls back to displaying the raw mint. + // (Firmware verifies; the host/SDK signing side is a follow-up.) + optional bytes signature = 4; // 64-byte compact ECDSA signature + optional uint32 signer_key_id = 5; // which loaded clear-sign signer } /** From e31cddfe7f5c72c983d06a889ac7db649b9811df Mon Sep 17 00:00:00 2001 From: highlander Date: Sat, 25 Jul 2026 16:15:16 -0300 Subject: [PATCH 11/23] docs(clearsign): reserve persistence pending authenticated storage --- messages-ethereum.proto | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages-ethereum.proto b/messages-ethereum.proto index 6d8144d5..e69010a3 100644 --- a/messages-ethereum.proto +++ b/messages-ethereum.proto @@ -117,9 +117,9 @@ message EthereumMetadataAck { * Request: Load a clearsign signer public key + alias into a key slot. * The device shows a mandatory confirmation (alias + key fingerprint) before * accepting; there is no way to load a signer without user consent. - * A signer is RAM-only by default and is cleared on reboot; set persist=true - * to also write it to flash, where it survives reboot and is reloaded - * automatically. Every transaction whose metadata was verified by a loaded + * A signer is RAM-only and is cleared on reboot. The persist field is retained + * for wire compatibility and future authenticated storage; firmware 7.15 + * rejects persist=true. Every transaction whose metadata was verified by a loaded * (non built-in) signer is preceded by a warning screen naming the alias * during transaction confirmation. * @next Success @@ -170,7 +170,7 @@ message LoadClearsignSigner { */ optional uint32 icon_width = 5; optional uint32 icon_height = 6; // icon pixel height (1..64; the icon column is 64px tall) - optional bool persist = 7; // store in flash so the identity survives reboot (shown on boot); default RAM-only + optional bool persist = 7; // reserved for future authenticated persistence; firmware 7.15 rejects true } //////////////////////////////////////// From 6d0ae670e287a75338244fe82c4bef33a920a2ee Mon Sep 17 00:00:00 2001 From: highlander Date: Sun, 26 Jul 2026 14:47:27 -0300 Subject: [PATCH 12/23] test(zcash): pin RC18 compact-signature contract --- .github/workflows/ci.yml | 35 ++++++++++++++++++ messages-zcash.proto | 14 ++++--- tools/check_zcash_contract.py | 70 +++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 tools/check_zcash_contract.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..e99fc06f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: Protocol CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + + - name: Install protobuf compiler + run: sudo apt-get update && sudo apt-get install -y protobuf-compiler + + - name: Compile protocol descriptors + run: | + protoc --proto_path=. --include_imports \ + --descriptor_set_out=/tmp/keepkey-device-protocol.pb \ + types.proto messages.proto messages-binance.proto \ + messages-cosmos.proto messages-eos.proto messages-ethereum.proto \ + messages-hive.proto messages-mayachain.proto messages-nano.proto \ + messages-osmosis.proto messages-ripple.proto messages-solana.proto \ + messages-tendermint.proto messages-thorchain.proto messages-ton.proto \ + messages-tron.proto messages-zcash.proto + + - name: Check RC18 Zcash wire contract + run: python3 tools/check_zcash_contract.py diff --git a/messages-zcash.proto b/messages-zcash.proto index c9659dd3..0be76f47 100644 --- a/messages-zcash.proto +++ b/messages-zcash.proto @@ -15,8 +15,9 @@ option java_outer_classname = "KeepKeyMessageZcash"; * Request: Sign a Zcash shielded transaction (PCZT format) * * The PCZT contains pre-constructed transaction data with proofs. - * The device derives spend authorization keys, computes the sighash, - * and returns RedPallas signatures for each Orchard action. + * The device derives spend authorization keys, computes the sighash, validates + * every Orchard action, and returns compact RedPallas signatures only for + * actions explicitly marked is_spend=true. * * @next ZcashTransparentAck * @next ZcashPCZTActionAck @@ -26,7 +27,7 @@ message ZcashSignPCZT { repeated uint32 address_n = 1; // ZIP-32 derivation path [32', 133', account'] optional uint32 account = 2; // Account index (alternative to full path) optional bytes pczt_data = 3; // Serialized PCZT data (may be chunked) - optional uint32 n_actions = 4; // Number of Orchard actions to sign + optional uint32 n_actions = 4; // Number of Orchard actions to stream and validate optional uint64 total_amount = 5; // Total ZEC amount (zatoshis) for user confirmation optional uint64 fee = 6; // Transaction fee (zatoshis) optional uint32 branch_id = 7; // Consensus branch ID @@ -69,7 +70,8 @@ message ZcashPCZTAction { optional bytes sighash = 3; // 32-byte transaction sighash (ZIP 244) - legacy mode optional bytes cv_net = 4; // 32-byte value commitment optional uint64 value = 5; // Action value in zatoshis (for display) - optional bool is_spend = 6; // True if this action spends a note + optional bool is_spend = 6; // REQUIRED by firmware 7.15: true only for a real spend; + // false for dummy spends/output-only actions // Phase 2b: action fields for incremental orchard digest verification optional bytes nullifier = 7; // 32-byte nullifier optional bytes cmx = 8; // 32-byte note commitment @@ -102,7 +104,9 @@ message ZcashPCZTActionAck { * @prev ZcashPCZTAction */ message ZcashSignedPCZT { - repeated bytes signatures = 1; // 64-byte RedPallas signatures, one per action + repeated bytes signatures = 1; // Compact 64-byte RedPallas signatures: one per + // is_spend=true action, in ascending action-index order; + // an all-dummy shield transaction returns zero optional bytes txid = 2; // 32-byte computed transaction ID } diff --git a/tools/check_zcash_contract.py b/tools/check_zcash_contract.py new file mode 100644 index 00000000..bc64d57c --- /dev/null +++ b/tools/check_zcash_contract.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""Pin the RC18 Zcash wire identifiers and compact-signature contract.""" + +import re +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +ZCASH = (ROOT / "messages-zcash.proto").read_text() +MESSAGES = (ROOT / "messages.proto").read_text() + + +def message_body(name): + match = re.search(r"message\s+%s\s*\{(.*?)\n\}" % name, ZCASH, re.S) + if not match: + raise AssertionError("missing message %s" % name) + return match.group(1) + + +def require_field(message, declaration): + body = message_body(message) + if not re.search(r"^\s*%s\s*(?://.*)?$" % declaration, body, re.M): + raise AssertionError("%s is missing field contract: %s" % (message, declaration)) + + +FIELDS = [ + ("ZcashSignPCZT", r"optional uint32 n_actions = 4;"), + ("ZcashSignPCZT", r"optional uint32 n_transparent_outputs = 29;"), + ("ZcashSignPCZT", r"optional uint32 n_transparent_inputs = 30;"), + ("ZcashSignPCZT", r"optional bytes expected_seed_fingerprint = 31;"), + ("ZcashPCZTAction", r"optional bool is_spend = 6;"), + ("ZcashPCZTAction", r"optional bytes recipient = 15;"), + ("ZcashPCZTAction", r"optional bytes rseed = 16;"), + ("ZcashSignedPCZT", r"repeated bytes signatures = 1;"), + ("ZcashTransparentOutput", r"required uint32 index = 1;"), + ("ZcashTransparentInput", r"required uint32 index = 1;"), +] + +for field in FIELDS: + require_field(*field) + + +MESSAGE_IDS = { + "ZcashSignPCZT": (1300, "wire_in"), + "ZcashPCZTAction": (1301, "wire_in"), + "ZcashPCZTActionAck": (1302, "wire_out"), + "ZcashSignedPCZT": (1303, "wire_out"), + "ZcashGetOrchardFVK": (1304, "wire_in"), + "ZcashOrchardFVK": (1305, "wire_out"), + "ZcashTransparentInput": (1306, "wire_in"), + "ZcashTransparentSigned": (1307, "wire_out"), + "ZcashDisplayAddress": (1308, "wire_in"), + "ZcashAddress": (1309, "wire_out"), + "ZcashTransparentOutput": (1310, "wire_in"), + "ZcashTransparentAck": (1311, "wire_out"), +} + +for name, (number, direction) in MESSAGE_IDS.items(): + pattern = ( + r"MessageType_%s\s*=\s*%d\s*\[\s*\(%s\)\s*=\s*true\s*\]\s*;" + % (name, number, direction) + ) + if not re.search(pattern, MESSAGES): + raise AssertionError("wrong message ID or wire direction for %s" % name) + + +if not re.search(r"one per\s*// is_spend=true action", ZCASH): + raise AssertionError("ZcashSignedPCZT must document compact real-spend signatures") + +print("RC18 Zcash protocol contract: ok") From 41c59abb9393f06d9199392f2c081b346e5b3e35 Mon Sep 17 00:00:00 2001 From: highlander Date: Mon, 27 Jul 2026 01:16:00 -0300 Subject: [PATCH 13/23] feat(solana): reusable instruction-schema fields on SolanaSignTx --- messages-solana.options | 2 ++ messages-solana.proto | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/messages-solana.options b/messages-solana.options index ee812da1..510df1cf 100644 --- a/messages-solana.options +++ b/messages-solana.options @@ -4,6 +4,8 @@ SolanaSignTx.address_n max_count:8 SolanaSignTx.coin_name max_size:21 SolanaSignTx.raw_tx max_size:1232 SolanaSignTx.token_info max_count:4 +SolanaSignTx.schema_payload max_size:256 +SolanaSignTx.schema_signature max_size:64 SolanaTokenInfo.mint max_size:32 SolanaTokenInfo.symbol max_size:13 SolanaAddress.address max_size:45 diff --git a/messages-solana.proto b/messages-solana.proto index c169817e..5f404e8f 100644 --- a/messages-solana.proto +++ b/messages-solana.proto @@ -56,6 +56,22 @@ message SolanaSignTx { optional string coin_name = 2 [default = "Solana"]; optional bytes raw_tx = 3; // Serialized Solana transaction bytes repeated SolanaTokenInfo token_info = 4; // Token metadata for display (max 4) + /* + * KKSOLSC1 instruction schema (see solana.h). A schema describes how to + * read ONE program instruction: program id, discriminator, and the + * labelled args/accounts to display. It carries no amounts and no + * transaction hash, so a signer attests it ONCE per program+instruction + * and every later transaction reuses it — the device decodes the values + * out of the raw_tx bytes it is about to sign. + * + * Safety comes from structural completeness rather than binding to one + * transaction: firmware requires the schema to account for the + * instruction data exactly, and every other instruction in the + * transaction to be a program it already recognises. + */ + optional bytes schema_payload = 5; + optional bytes schema_signature = 6; // 64-byte compact secp256k1 over SHA256(payload) + optional uint32 schema_signer_key_id = 7; // trusted clearsign signer slot (0-3) } /** From 844a9b970e334fc556caa1ad4f1f225280d36801 Mon Sep 17 00:00:00 2001 From: highlander Date: Sun, 26 Jul 2026 21:47:52 -0300 Subject: [PATCH 14/23] feat(clearsign): attestor messages (1700-1703) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ClearsignAttestorGetPublicKey/PublicKey provision the seed-derived attestation key; ClearsignAttestorSign/Signature validate a canonical descriptor payload on-device before attesting it with plain secp256k1 ECDSA over SHA256(payload) — the format signed_metadata_verify_attestation checks on verifying devices. --- messages.proto | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/messages.proto b/messages.proto index 93b318e6..f8a7bafe 100644 --- a/messages.proto +++ b/messages.proto @@ -259,6 +259,12 @@ enum MessageType { MessageType_HiveSignedMessage = 1615 [ (wire_out) = true ]; MessageType_HiveSignOperations = 1616 [ (wire_in) = true ]; MessageType_HiveSignedOperations = 1617 [ (wire_out) = true ]; + + // Clearsign attestor (server/emulator attestor tier) + MessageType_ClearsignAttestorGetPublicKey = 1700 [ (wire_in) = true ]; + MessageType_ClearsignAttestorPublicKey = 1701 [ (wire_out) = true ]; + MessageType_ClearsignAttestorSign = 1702 [ (wire_in) = true ]; + MessageType_ClearsignAttestorSignature = 1703 [ (wire_out) = true ]; } //////////////////// @@ -1040,3 +1046,48 @@ message DebugLinkFillConfig {} message ChangeWipeCode { optional bool remove = 1; // is wipe code removal requested? } + +////////////////////////// +// Clearsign attestor // +////////////////////////// + +/** + * Request: derive and return the clearsign attestation public key. + * The attestation key is derived from the seed at a dedicated hardened path + * and is unrelated to any coin key. Used to provision verifying devices + * (METADATA_PUBKEYS baking, or LoadClearsignSigner for test slots) and to + * let hosts confirm which attestor they are talking to. + * @next ClearsignAttestorPublicKey + * @next Failure + */ +message ClearsignAttestorGetPublicKey {} + +/** + * Response: compressed attestation public key + * @prev ClearsignAttestorGetPublicKey + */ +message ClearsignAttestorPublicKey { + optional bytes public_key = 1; // 33-byte compressed secp256k1 +} + +/** + * Request: validate a clearsign descriptor payload and attest it. + * The device parses the payload with the SAME validator verifying devices + * run (currently KKSOLSW1 cross-chain swap descriptors) and refuses + * anything malformed — this message can never sign arbitrary bytes. + * Requires an unlocked session and an on-device confirmation. + * @next ClearsignAttestorSignature + * @next Failure + */ +message ClearsignAttestorSign { + optional bytes payload = 1; // canonical descriptor payload (magic-prefixed) +} + +/** + * Response: attestation over the validated payload + * @prev ClearsignAttestorSign + */ +message ClearsignAttestorSignature { + optional bytes signature = 1; // 64-byte compact secp256k1 ECDSA over SHA256(payload) + optional bytes public_key = 2; // 33-byte compressed attestation pubkey +} From 81c398d868781971daf5443796546d4226501c98 Mon Sep 17 00:00:00 2001 From: highlander Date: Wed, 29 Jul 2026 02:02:04 -0300 Subject: [PATCH 15/23] docs(clearsign): attestor validates KKSOLSC1, not the abandoned KKSOLSW1 --- messages.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages.proto b/messages.proto index f8a7bafe..9b688a8e 100644 --- a/messages.proto +++ b/messages.proto @@ -1073,7 +1073,7 @@ message ClearsignAttestorPublicKey { /** * Request: validate a clearsign descriptor payload and attest it. * The device parses the payload with the SAME validator verifying devices - * run (currently KKSOLSW1 cross-chain swap descriptors) and refuses + * run (currently KKSOLSC1 reusable Solana instruction schemas) and refuses * anything malformed — this message can never sign arbitrary bytes. * Requires an unlocked session and an on-device confirmation. * @next ClearsignAttestorSignature From 8856334eaad4cb8af67fab5ba62fc146543f79b1 Mon Sep 17 00:00:00 2001 From: highlander Date: Wed, 29 Jul 2026 18:37:34 -0300 Subject: [PATCH 16/23] fix(clearsign): reserve additive schema wire fields --- messages-ethereum.proto | 2 +- messages-solana.proto | 10 ++++++---- messages.proto | 7 +++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/messages-ethereum.proto b/messages-ethereum.proto index e69010a3..f04424f5 100644 --- a/messages-ethereum.proto +++ b/messages-ethereum.proto @@ -170,7 +170,7 @@ message LoadClearsignSigner { */ optional uint32 icon_width = 5; optional uint32 icon_height = 6; // icon pixel height (1..64; the icon column is 64px tall) - optional bool persist = 7; // reserved for future authenticated persistence; firmware 7.15 rejects true + optional bool persist = 7; // reserved compatibility field; firmware rejects true (RAM-only) } //////////////////////////////////////// diff --git a/messages-solana.proto b/messages-solana.proto index 5f404e8f..15b7647f 100644 --- a/messages-solana.proto +++ b/messages-solana.proto @@ -67,11 +67,13 @@ message SolanaSignTx { * Safety comes from structural completeness rather than binding to one * transaction: firmware requires the schema to account for the * instruction data exactly, and every other instruction in the - * transaction to be a program it already recognises. + * transaction to be a program it already recognises. Runtime-loaded + * signers are annotation-only, so the normal Advanced-mode unverified + * transaction warning remains additive. */ - optional bytes schema_payload = 5; - optional bytes schema_signature = 6; // 64-byte compact secp256k1 over SHA256(payload) - optional uint32 schema_signer_key_id = 7; // trusted clearsign signer slot (0-3) + optional bytes schema_payload = 9; + optional bytes schema_signature = 10; // 64-byte compact secp256k1 over SHA256(payload) + optional uint32 schema_signer_key_id = 11; // trusted clearsign signer slot (0-3) } /** diff --git a/messages.proto b/messages.proto index 9b688a8e..1cf8bbb4 100644 --- a/messages.proto +++ b/messages.proto @@ -260,7 +260,7 @@ enum MessageType { MessageType_HiveSignOperations = 1616 [ (wire_in) = true ]; MessageType_HiveSignedOperations = 1617 [ (wire_out) = true ]; - // Clearsign attestor (server/emulator attestor tier) + // Advanced-mode ClearSign studio / schema attestation MessageType_ClearsignAttestorGetPublicKey = 1700 [ (wire_in) = true ]; MessageType_ClearsignAttestorPublicKey = 1701 [ (wire_out) = true ]; MessageType_ClearsignAttestorSign = 1702 [ (wire_in) = true ]; @@ -1054,9 +1054,8 @@ message ChangeWipeCode { /** * Request: derive and return the clearsign attestation public key. * The attestation key is derived from the seed at a dedicated hardened path - * and is unrelated to any coin key. Used to provision verifying devices - * (METADATA_PUBKEYS baking, or LoadClearsignSigner for test slots) and to - * let hosts confirm which attestor they are talking to. + * and is unrelated to any coin key. Available in regular firmware only while + * AdvancedMode is enabled. * @next ClearsignAttestorPublicKey * @next Failure */ From 4cc8b717517c79ee3ac436161141dd033db286fd Mon Sep 17 00:00:00 2001 From: highlander Date: Wed, 29 Jul 2026 23:53:50 -0300 Subject: [PATCH 17/23] fix(clearsign): reserve Solana transaction metadata tags --- messages-solana.proto | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/messages-solana.proto b/messages-solana.proto index 15b7647f..865b5cfb 100644 --- a/messages-solana.proto +++ b/messages-solana.proto @@ -56,6 +56,10 @@ message SolanaSignTx { optional string coin_name = 2 [default = "Solana"]; optional bytes raw_tx = 3; // Serialized Solana transaction bytes repeated SolanaTokenInfo token_info = 4; // Token metadata for display (max 4) + // Reserved for the transaction-bound KKSOLSW1 descriptor and one-request + // opaque-signing consent. Keeping this reservation in the canonical + // protocol makes reusing a planned tag an explicit review decision. + reserved 5 to 8; /* * KKSOLSC1 instruction schema (see solana.h). A schema describes how to * read ONE program instruction: program id, discriminator, and the From f2246cebea8f96fcd7ec2883588a784a60b430ae Mon Sep 17 00:00:00 2001 From: highlander Date: Thu, 30 Jul 2026 15:29:27 -0300 Subject: [PATCH 18/23] feat(zcash): add Ironwood signing metadata --- messages-zcash.options | 1 + messages-zcash.proto | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/messages-zcash.options b/messages-zcash.options index 2ebcb224..4ab6a997 100644 --- a/messages-zcash.options +++ b/messages-zcash.options @@ -5,6 +5,7 @@ ZcashSignPCZT.transparent_digest max_size:32 ZcashSignPCZT.sapling_digest max_size:32 ZcashSignPCZT.orchard_digest max_size:32 ZcashSignPCZT.orchard_anchor max_size:32 +ZcashSignPCZT.ironwood_digest max_size:32 ZcashSignPCZT.expected_seed_fingerprint max_size:32 ZcashPCZTAction.alpha max_size:32 diff --git a/messages-zcash.proto b/messages-zcash.proto index 0be76f47..4dd54d38 100644 --- a/messages-zcash.proto +++ b/messages-zcash.proto @@ -7,6 +7,11 @@ syntax = "proto2"; +enum ZcashShieldedPool { + ZCASH_SHIELDED_POOL_ORCHARD = 0; + ZCASH_SHIELDED_POOL_IRONWOOD = 1; +} + // Sugar for easier handling in Java option java_package = "com.keepkey.deviceprotocol"; option java_outer_classname = "KeepKeyMessageZcash"; @@ -45,6 +50,11 @@ message ZcashSignPCZT { optional uint32 version_group_id = 16; // Version group ID optional uint32 lock_time = 17; // Transaction lock time optional uint32 expiry_height = 18; // Transaction expiry height + // NU6.3 / transaction-v6 Orchard-family pool selection. The existing + // orchard_* metadata fields describe the selected action bundle for wire + // compatibility; ironwood_digest is the fifth v6 transaction component. + optional ZcashShieldedPool shielded_pool = 19 [default = ZCASH_SHIELDED_POOL_ORCHARD]; + optional bytes ironwood_digest = 20; // 32-byte Ironwood component digest (v6) // Phase 3: transparent shielding support optional uint32 n_transparent_outputs = 29; // 0 for shielded-only (default) optional uint32 n_transparent_inputs = 30; // 0 for shielded-only (default), >0 for hybrid shielding tx From cc858ef3db390b57dd291f6d966b21b107a516a1 Mon Sep 17 00:00:00 2001 From: highlander Date: Fri, 31 Jul 2026 14:41:53 -0300 Subject: [PATCH 19/23] feat(solana): add verified token recipient owner hints --- messages-solana.options | 2 ++ messages-solana.proto | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/messages-solana.options b/messages-solana.options index 510df1cf..fd945238 100644 --- a/messages-solana.options +++ b/messages-solana.options @@ -6,6 +6,8 @@ SolanaSignTx.raw_tx max_size:1232 SolanaSignTx.token_info max_count:4 SolanaSignTx.schema_payload max_size:256 SolanaSignTx.schema_signature max_size:64 +SolanaSignTx.token_recipient_owner max_count:4 +SolanaSignTx.token_recipient_owner max_size:32 SolanaTokenInfo.mint max_size:32 SolanaTokenInfo.symbol max_size:13 SolanaAddress.address max_size:45 diff --git a/messages-solana.proto b/messages-solana.proto index 865b5cfb..d377a6aa 100644 --- a/messages-solana.proto +++ b/messages-solana.proto @@ -78,6 +78,15 @@ message SolanaSignTx { optional bytes schema_payload = 9; optional bytes schema_signature = 10; // 64-byte compact secp256k1 over SHA256(payload) optional uint32 schema_signer_key_id = 11; // trusted clearsign signer slot (0-3) + /* + * Candidate owners for SPL associated-token-account destinations. For a + * TransferChecked instruction, firmware may display an owner only after + * independently deriving ATA(owner, token_program, mint) and matching it + * to the signed destination account. An unmatched candidate is never + * treated as a recipient. This lets payment protocols such as x402 show + * their payTo address without trusting host-side decoding or chain RPC. + */ + repeated bytes token_recipient_owner = 12; // 32-byte Solana public keys (max 4) } /** From dafb567241f57dcf24a4a15b53afecd5177c2d8f Mon Sep 17 00:00:00 2001 From: highlander Date: Fri, 31 Jul 2026 20:15:23 -0300 Subject: [PATCH 20/23] fix(ci): make protobuf JavaScript codegen portable --- .github/workflows/ci.yml | 16 ++- package-lock.json | 183 +++++++++++++++++++++++++++++++-- package.json | 5 +- yarn.lock | 211 +++++++++++++++++++++++++++++++++------ 4 files changed, 377 insertions(+), 38 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e99fc06f..cffbdf46 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: Protocol CI on: push: - branches: [master] + branches: [master, up/release-protocol] pull_request: - branches: [master] + branches: [master, up/release-protocol] permissions: contents: read @@ -20,6 +20,18 @@ jobs: - name: Install protobuf compiler run: sudo apt-get update && sudo apt-get install -y protobuf-compiler + - name: Use Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install JavaScript generator dependencies + run: npm ci + + - name: Generate JavaScript bindings + run: npm run build:js + - name: Compile protocol descriptors run: | protoc --proto_path=. --include_imports \ diff --git a/package-lock.json b/package-lock.json index 462ec2a5..7c35fddc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,30 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "requires": { + "minipass": "^7.0.4" + } + }, + "@mapbox/node-pre-gyp": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.3.tgz", + "integrity": "sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==", + "dev": true, + "requires": { + "consola": "^3.2.3", + "detect-libc": "^2.0.0", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^2.6.7", + "nopt": "^8.0.0", + "semver": "^7.5.3", + "tar": "^7.4.0" + } + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -80,6 +104,18 @@ "integrity": "sha512-Fvm24+u85lGmV4hT5G++aht2C5I4Z4dYlWZIh62FAfFO/TfzXtPpoLI6I7AuBWkIFqZCnhFOoTT7RjjaIL5Fjg==", "dev": true }, + "abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "dev": true + }, + "agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true + }, "bytebuffer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-5.0.1.tgz", @@ -95,6 +131,12 @@ } } }, + "chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true + }, "commander": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", @@ -103,22 +145,101 @@ "graceful-readlink": ">= 1.0.0" } }, + "consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true + }, + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true + }, "google-protobuf": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.7.1.tgz", - "integrity": "sha512-6fvlUey6cNKtWSEn1bt4CT4wc2EID1fVluHS1dOnqIlxyIu3cBid2BvWE8Rwl6wN+hRTgiAKhfyydAGV/weZYQ==" + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz", + "integrity": "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==" }, "graceful-readlink": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" }, + "grpc-tools": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/grpc-tools/-/grpc-tools-1.13.1.tgz", + "integrity": "sha512-0sttMUxThNIkCTJq5qI0xXMz5zWqV2u3yG1kR3Sj9OokGIoyRBFjoInK9NyW7x5fH7knj48Roh1gq5xbl0VoDQ==", + "dev": true, + "requires": { + "@mapbox/node-pre-gyp": "^2.0.0" + } + }, + "https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "4" + } + }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", "dev": true }, + "minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true + }, + "minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "requires": { + "minipass": "^7.1.2" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "dev": true, + "requires": { + "abbrev": "^3.0.0" + } + }, "pbjs": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/pbjs/-/pbjs-0.0.5.tgz", @@ -155,10 +276,60 @@ "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.1.0.tgz", "integrity": "sha1-2KgZVJ6tPmvRievp5Q6WY2u8XMc=" }, + "semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true + }, + "tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "dev": true, + "requires": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, "ts-protoc-gen": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/ts-protoc-gen/-/ts-protoc-gen-0.9.0.tgz", - "integrity": "sha512-cFEUTY9U9o6C4DPPfMHk2ZUdIAKL91hZN1fyx5Stz3g56BDVOC7hk+r5fEMCAGaaIgi2akkT1a2hrxu1wo2Phg==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/ts-protoc-gen/-/ts-protoc-gen-0.10.0.tgz", + "integrity": "sha512-EEbgDWNHK3CvcNhmib94I4HMO23qLddjLRdXW8EUE11VJxbi3n5J0l2DiX/L1pijOaPTkbEoRK+zQinKgKGqsw==", + "dev": true, + "requires": { + "google-protobuf": "^3.6.1" + } + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true } } diff --git a/package.json b/package.json index 9e82e419..369dd38a 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "clean": "rm -rf ./lib/*.js ./lib/*.ts", "build": "npm run build:js && npm run build:json && npm run build:postprocess", - "build:js": "protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --js_out=import_style=commonjs,binary:./lib --ts_out=./lib types.proto messages.proto messages-ethereum.proto messages-eos.proto messages-nano.proto messages-cosmos.proto messages-binance.proto messages-ripple.proto messages-tendermint.proto messages-thorchain.proto messages-osmosis.proto messages-mayachain.proto messages-solana.proto messages-tron.proto messages-ton.proto messages-zcash.proto messages-hive.proto", + "build:js": "mkdir -p ./lib && ./node_modules/.bin/grpc_tools_node_protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --js_out=import_style=commonjs,binary:./lib --ts_out=./lib types.proto messages.proto messages-ethereum.proto messages-eos.proto messages-nano.proto messages-cosmos.proto messages-binance.proto messages-ripple.proto messages-tendermint.proto messages-thorchain.proto messages-osmosis.proto messages-mayachain.proto messages-solana.proto messages-tron.proto messages-ton.proto messages-zcash.proto messages-hive.proto", "build:json": "pbjs --keep-case -t json ./types.proto ./messages.proto ./messages-ethereum.proto ./messages-eos.proto ./messages-nano.proto ./messages-cosmos.proto ./messages-binance.proto ./messages-ripple.proto ./messages-tendermint.proto ./messages-thorchain.proto ./messages-osmosis.proto ./messages-mayachain.proto ./messages-solana.proto ./messages-tron.proto ./messages-ton.proto ./messages-zcash.proto ./messages-hive.proto > ./lib/proto.json", "build:postprocess": "find ./lib -name \"*.js\" -exec sed -i '' -e \"s/var global = Function(\\'return this\\')();/var global = (function(){ return this }).call(null);/g\" {} \\;", "prepublishOnly": "npm run build", @@ -21,11 +21,12 @@ "author": "", "license": "ISC", "devDependencies": { + "grpc-tools": "1.13.1", "protobufjs": "^6.8.8", "ts-protoc-gen": "^0.10.0" }, "dependencies": { - "google-protobuf": "^3.7.0-rc.2", + "google-protobuf": "3.21.4", "pbjs": "^0.0.5" } } diff --git a/yarn.lock b/yarn.lock index c4f17406..ce3c2145 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,92 +2,205 @@ # yarn lockfile v1 +"@isaacs/fs-minipass@^4.0.0": + version "4.0.1" + resolved "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz" + integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== + dependencies: + minipass "^7.0.4" + +"@mapbox/node-pre-gyp@^2.0.0": + version "2.0.3" + resolved "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.3.tgz" + integrity sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg== + dependencies: + consola "^3.2.3" + detect-libc "^2.0.0" + https-proxy-agent "^7.0.5" + node-fetch "^2.6.7" + nopt "^8.0.0" + semver "^7.5.3" + tar "^7.4.0" + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz" + integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= "@protobufjs/base64@^1.1.2": version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + resolved "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== "@protobufjs/codegen@^2.0.4": version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + resolved "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== "@protobufjs/eventemitter@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + resolved "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz" + integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= "@protobufjs/fetch@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + resolved "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz" + integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= dependencies: "@protobufjs/aspromise" "^1.1.1" "@protobufjs/inquire" "^1.1.0" "@protobufjs/float@^1.0.2": version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + resolved "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz" + integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= "@protobufjs/inquire@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + resolved "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz" + integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= "@protobufjs/path@^1.1.2": version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + resolved "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz" + integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= "@protobufjs/pool@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + resolved "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz" + integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= "@protobufjs/utf8@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz" + integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= "@types/long@^4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.0.tgz#719551d2352d301ac8b81db732acb6bdc28dbdef" + resolved "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz" + integrity sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q== "@types/node@^10.1.0": - version "10.12.24" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.24.tgz#b13564af612a22a20b5d95ca40f1bffb3af315cf" + version "10.14.6" + resolved "https://registry.npmjs.org/@types/node/-/node-10.14.6.tgz" + integrity sha512-Fvm24+u85lGmV4hT5G++aht2C5I4Z4dYlWZIh62FAfFO/TfzXtPpoLI6I7AuBWkIFqZCnhFOoTT7RjjaIL5Fjg== + +abbrev@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz" + integrity sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg== + +agent-base@^7.1.2: + version "7.1.4" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz" + integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== bytebuffer@5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-5.0.1.tgz#582eea4b1a873b6d020a48d58df85f0bba6cfddd" + resolved "https://registry.npmjs.org/bytebuffer/-/bytebuffer-5.0.1.tgz" + integrity sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0= dependencies: long "~3" +chownr@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz" + integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== + commander@2.9.0: version "2.9.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" + resolved "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" + integrity sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q= dependencies: graceful-readlink ">= 1.0.0" -google-protobuf@^3.6.1: - version "3.9.0" - resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.9.0.tgz#1f33e51e7993ea51e758a82650ad4347273b9bc6" +consola@^3.2.3: + version "3.4.2" + resolved "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz" + integrity sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA== + +debug@4: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +detect-libc@^2.0.0: + version "2.1.2" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== -google-protobuf@^3.7.0-rc.2: - version "3.7.0-rc.2" - resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.7.0-rc.2.tgz#a65e9216825065099c4ff243eee9e16e764cc2c9" +google-protobuf@^3.6.1, google-protobuf@3.21.4: + version "3.21.4" + resolved "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz" + integrity sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ== "graceful-readlink@>= 1.0.0": version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + resolved "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" + integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= + +grpc-tools@1.13.1: + version "1.13.1" + resolved "https://registry.npmjs.org/grpc-tools/-/grpc-tools-1.13.1.tgz" + integrity sha512-0sttMUxThNIkCTJq5qI0xXMz5zWqV2u3yG1kR3Sj9OokGIoyRBFjoInK9NyW7x5fH7knj48Roh1gq5xbl0VoDQ== + dependencies: + "@mapbox/node-pre-gyp" "^2.0.0" + +https-proxy-agent@^7.0.5: + version "7.0.6" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz" + integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== + dependencies: + agent-base "^7.1.2" + debug "4" long@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== long@~3: version "3.2.0" - resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" + resolved "https://registry.npmjs.org/long/-/long-3.2.0.tgz" + integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s= + +minipass@^7.0.4, minipass@^7.1.2: + version "7.1.3" + resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz" + integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== + +minizlib@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz" + integrity sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw== + dependencies: + minipass "^7.1.2" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +nopt@^8.0.0: + version "8.1.0" + resolved "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz" + integrity sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A== + dependencies: + abbrev "^3.0.0" pbjs@^0.0.5: version "0.0.5" - resolved "https://registry.yarnpkg.com/pbjs/-/pbjs-0.0.5.tgz#b4c88e15aac4552ca0922aa64cd5338efd3447bf" + resolved "https://registry.npmjs.org/pbjs/-/pbjs-0.0.5.tgz" + integrity sha1-tMiOFarEVSygkiqmTNUzjv00R78= dependencies: bytebuffer "5.0.1" commander "2.9.0" @@ -95,7 +208,8 @@ pbjs@^0.0.5: protobufjs@^6.8.8: version "6.8.8" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.8.tgz#c8b4f1282fd7a90e6f5b109ed11c84af82908e7c" + resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz" + integrity sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -113,10 +227,51 @@ protobufjs@^6.8.8: protocol-buffers-schema@3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.1.0.tgz#d8a819549ead3e6bd189ebe9e50e96636bbc5cc7" + resolved "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.1.0.tgz" + integrity sha1-2KgZVJ6tPmvRievp5Q6WY2u8XMc= + +semver@^7.5.3: + version "7.8.5" + resolved "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz" + integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== + +tar@^7.4.0: + version "7.5.22" + resolved "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz" + integrity sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA== + dependencies: + "@isaacs/fs-minipass" "^4.0.0" + chownr "^3.0.0" + minipass "^7.1.2" + minizlib "^3.1.0" + yallist "^5.0.0" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== ts-protoc-gen@^0.10.0: version "0.10.0" - resolved "https://registry.yarnpkg.com/ts-protoc-gen/-/ts-protoc-gen-0.10.0.tgz#f708d99be59ad0be6bdce6f4fe893ec41757d2c9" + resolved "https://registry.npmjs.org/ts-protoc-gen/-/ts-protoc-gen-0.10.0.tgz" + integrity sha512-EEbgDWNHK3CvcNhmib94I4HMO23qLddjLRdXW8EUE11VJxbi3n5J0l2DiX/L1pijOaPTkbEoRK+zQinKgKGqsw== dependencies: google-protobuf "^3.6.1" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +yallist@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz" + integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== From 4b41d1194284781eb5239a8a71315460968e2584 Mon Sep 17 00:00:00 2001 From: highlander Date: Fri, 31 Jul 2026 20:40:49 -0300 Subject: [PATCH 21/23] fix(build): generate bindings for git dependencies --- .github/workflows/ci.yml | 7 +++++-- package.json | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cffbdf46..2e901a1e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,8 +29,11 @@ jobs: - name: Install JavaScript generator dependencies run: npm ci - - name: Generate JavaScript bindings - run: npm run build:js + - name: Verify install generated JavaScript bindings + run: | + test -s lib/messages_pb.js + test -s lib/messages-solana_pb.js + test -s lib/messages-zcash_pb.js - name: Compile protocol descriptors run: | diff --git a/package.json b/package.json index 369dd38a..997305f1 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "build:js": "mkdir -p ./lib && ./node_modules/.bin/grpc_tools_node_protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --js_out=import_style=commonjs,binary:./lib --ts_out=./lib types.proto messages.proto messages-ethereum.proto messages-eos.proto messages-nano.proto messages-cosmos.proto messages-binance.proto messages-ripple.proto messages-tendermint.proto messages-thorchain.proto messages-osmosis.proto messages-mayachain.proto messages-solana.proto messages-tron.proto messages-ton.proto messages-zcash.proto messages-hive.proto", "build:json": "pbjs --keep-case -t json ./types.proto ./messages.proto ./messages-ethereum.proto ./messages-eos.proto ./messages-nano.proto ./messages-cosmos.proto ./messages-binance.proto ./messages-ripple.proto ./messages-tendermint.proto ./messages-thorchain.proto ./messages-osmosis.proto ./messages-mayachain.proto ./messages-solana.proto ./messages-tron.proto ./messages-ton.proto ./messages-zcash.proto ./messages-hive.proto > ./lib/proto.json", "build:postprocess": "find ./lib -name \"*.js\" -exec sed -i '' -e \"s/var global = Function(\\'return this\\')();/var global = (function(){ return this }).call(null);/g\" {} \\;", + "prepare": "npm run build:js", "prepublishOnly": "npm run build", "test": "echo \"Error: no test specified\" && exit 1" }, From be2854903f8795daf42ca7b96d93fc348b886b58 Mon Sep 17 00:00:00 2001 From: highlander Date: Sat, 1 Aug 2026 18:36:22 -0300 Subject: [PATCH 22/23] feat(features): add supports_taproot capability bit Lets a host ask the device whether it can derive and spend P2TR, instead of inferring it from a firmware version. Version inference breaks the moment the feature is retargeted to a different release, and it forces every client to carry a version table. Field 27: 19 and 20 are gaps with no reserved markers, so they are not safe to reuse against historical wire data. Additive and optional -- older hosts ignore it, older firmware simply does not set it. --- messages.proto | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/messages.proto b/messages.proto index 1cf8bbb4..26e7290e 100644 --- a/messages.proto +++ b/messages.proto @@ -319,6 +319,11 @@ message Features { optional bool wipe_code_protection = 25; optional uint32 auto_lock_delay_ms = 26; // Current auto lock delay (in milliseconds) + optional bool supports_taproot = + 27; // Firmware can derive and spend P2TR (BIP-86 / BIP-340 / BIP-341). + // Lets a host detect taproot support directly instead of inferring + // it from a firmware version, which breaks whenever the feature is + // retargeted to a different release. } /** From 635571b00b1318280d71a53ce3d886dd3bc1c11f Mon Sep 17 00:00:00 2001 From: highlander Date: Sun, 2 Aug 2026 18:35:02 -0300 Subject: [PATCH 23/23] fix(ci): request reviews safely for fork PRs --- .github/workflows/copilot-review.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/copilot-review.yml b/.github/workflows/copilot-review.yml index 54db1498..8afdb03e 100644 --- a/.github/workflows/copilot-review.yml +++ b/.github/workflows/copilot-review.yml @@ -1,11 +1,15 @@ name: Request Copilot Review on: - pull_request: + # This workflow never checks out or executes pull-request code. Using the + # base-repository context is therefore safe and is required for cross-fork + # PRs, whose pull_request GITHUB_TOKEN is always downgraded to read-only. + pull_request_target: types: [opened, reopened, ready_for_review, synchronize] jobs: request-copilot-review: + if: github.event.pull_request.draft == false runs-on: ubuntu-latest permissions: pull-requests: write