From 6a8689c44090b1dffca26c8fa6e77f1ccf026548 Mon Sep 17 00:00:00 2001 From: highlander Date: Sun, 7 Jun 2026 16:20:48 -0500 Subject: [PATCH] fix(lint): make master lint-clean (zcash, hive, keepkey) `yarn lint` (eslint --cache --max-warnings=0 .) fails on a clean checkout across four files; the CI cache was intermittently masking hive.ts/keepkey.ts. - zcash.ts / zcash.test.ts: prettier formatting; convert 14 `console.log` debug traces to `console.info` (the no-console rule allows info/warn/error and these are intentional [zcash-pczt] diagnostics); remove an unused `hexToBytes` helper. - hive.ts / keepkey.ts: prettier formatting only (alignment/wrapping). No logic changes. zcash unit tests pass (4/4). Supersedes the stale #41. --- packages/hdwallet-keepkey/src/hive.ts | 290 ++++++++++++++------ packages/hdwallet-keepkey/src/keepkey.ts | 2 +- packages/hdwallet-keepkey/src/zcash.test.ts | 173 +++++++----- packages/hdwallet-keepkey/src/zcash.ts | 48 ++-- 4 files changed, 345 insertions(+), 168 deletions(-) diff --git a/packages/hdwallet-keepkey/src/hive.ts b/packages/hdwallet-keepkey/src/hive.ts index 266306bb..9b584579 100644 --- a/packages/hdwallet-keepkey/src/hive.ts +++ b/packages/hdwallet-keepkey/src/hive.ts @@ -9,9 +9,9 @@ const Msg = jspb.Message as any; // ── Hive Message Type IDs (messages-hive.proto, wire IDs 1600–1603) ─── const MESSAGETYPE_HIVEGETPUBLICKEY = 1600; -const MESSAGETYPE_HIVEPUBLICKEY = 1601; -const MESSAGETYPE_HIVESIGNTX = 1602; -const MESSAGETYPE_HIVESIGNEDTX = 1603; +const MESSAGETYPE_HIVEPUBLICKEY = 1601; +const MESSAGETYPE_HIVESIGNTX = 1602; +const MESSAGETYPE_HIVESIGNEDTX = 1603; // ── Protobuf Shims ───────────────────────────────────────────────────── @@ -26,15 +26,23 @@ export class HiveGetPublicKey extends jspb.Message { jspb.Message.initialize(this, opt_data || [], 0, -1, HiveGetPublicKey.repeatedFields_, null); } - getAddressNList(): number[] { return Msg.getRepeatedField(this, 1) as number[]; } - setAddressNList(value: number[]): void { jspb.Message.setField(this, 1, value || []); } - addAddressN(value: number): void { jspb.Message.addToRepeatedField(this, 1, value); } + getAddressNList(): number[] { + return Msg.getRepeatedField(this, 1) as number[]; + } + setAddressNList(value: number[]): void { + jspb.Message.setField(this, 1, value || []); + } + addAddressN(value: number): void { + jspb.Message.addToRepeatedField(this, 1, value); + } getShowDisplay(): boolean | undefined { const f = jspb.Message.getField(this, 2); return f == null ? undefined : !!f; } - setShowDisplay(value: boolean): void { jspb.Message.setField(this, 2, value ? 1 : 0); } + setShowDisplay(value: boolean): void { + jspb.Message.setField(this, 2, value ? 1 : 0); + } serializeBinary(): Uint8Array { const writer = new jspb.BinaryWriter(); @@ -42,8 +50,12 @@ export class HiveGetPublicKey extends jspb.Message { return writer.getResultBuffer(); } - toObject(): object { return { addressNList: this.getAddressNList(), showDisplay: this.getShowDisplay() }; } - static toObject(_: boolean, msg: HiveGetPublicKey): object { return msg.toObject(); } + toObject(): object { + return { addressNList: this.getAddressNList(), showDisplay: this.getShowDisplay() }; + } + static toObject(_: boolean, msg: HiveGetPublicKey): object { + return msg.toObject(); + } static deserializeBinary(bytes: Uint8Array): HiveGetPublicKey { const reader = new jspb.BinaryReader(bytes); @@ -60,8 +72,11 @@ export class HiveGetPublicKey extends jspb.Message { for (const v of values) msg.addAddressN(v); break; } - case 2: msg.setShowDisplay(reader.readBool()); break; - default: reader.skipField(); + case 2: + msg.setShowDisplay(reader.readBool()); + break; + default: + reader.skipField(); } } return msg; @@ -84,16 +99,24 @@ export class HivePublicKey extends jspb.Message { jspb.Message.initialize(this, opt_data || [], 0, -1, null, null); } - getPublicKey(): string | undefined { return jspb.Message.getFieldWithDefault(this, 1, "") as string; } - setPublicKey(value: string): void { jspb.Message.setField(this, 1, value); } + getPublicKey(): string | undefined { + return jspb.Message.getFieldWithDefault(this, 1, "") as string; + } + setPublicKey(value: string): void { + jspb.Message.setField(this, 1, value); + } - getRawPublicKey(): Uint8Array | string { return jspb.Message.getFieldWithDefault(this, 2, "") as Uint8Array | string; } + getRawPublicKey(): Uint8Array | string { + return jspb.Message.getFieldWithDefault(this, 2, "") as Uint8Array | string; + } getRawPublicKey_asU8(): Uint8Array { const val = this.getRawPublicKey(); if (val instanceof Uint8Array) return val; return jspb.Message.bytesAsU8(val as string); } - setRawPublicKey(value: Uint8Array | string): void { jspb.Message.setField(this, 2, value); } + setRawPublicKey(value: Uint8Array | string): void { + jspb.Message.setField(this, 2, value); + } serializeBinary(): Uint8Array { const writer = new jspb.BinaryWriter(); @@ -101,8 +124,12 @@ export class HivePublicKey extends jspb.Message { return writer.getResultBuffer(); } - toObject(): object { return { publicKey: this.getPublicKey(), rawPublicKey: this.getRawPublicKey() }; } - static toObject(_: boolean, msg: HivePublicKey): object { return msg.toObject(); } + toObject(): object { + return { publicKey: this.getPublicKey(), rawPublicKey: this.getRawPublicKey() }; + } + static toObject(_: boolean, msg: HivePublicKey): object { + return msg.toObject(); + } static deserializeBinary(bytes: Uint8Array): HivePublicKey { const reader = new jspb.BinaryReader(bytes); @@ -114,9 +141,14 @@ export class HivePublicKey extends jspb.Message { while (reader.nextField()) { if (reader.isEndGroup()) break; switch (reader.getFieldNumber()) { - case 1: msg.setPublicKey(reader.readString()); break; - case 2: msg.setRawPublicKey(reader.readBytes()); break; - default: reader.skipField(); + case 1: + msg.setPublicKey(reader.readString()); + break; + case 2: + msg.setRawPublicKey(reader.readBytes()); + break; + default: + reader.skipField(); } } return msg; @@ -144,44 +176,90 @@ export class HiveSignTx extends jspb.Message { jspb.Message.initialize(this, opt_data || [], 0, -1, HiveSignTx.repeatedFields_, null); } - getAddressNList(): number[] { return Msg.getRepeatedField(this, 1) as number[]; } - setAddressNList(value: number[]): void { jspb.Message.setField(this, 1, value || []); } - addAddressN(value: number): void { jspb.Message.addToRepeatedField(this, 1, value); } + getAddressNList(): number[] { + return Msg.getRepeatedField(this, 1) as number[]; + } + setAddressNList(value: number[]): void { + jspb.Message.setField(this, 1, value || []); + } + addAddressN(value: number): void { + jspb.Message.addToRepeatedField(this, 1, value); + } - getChainId(): Uint8Array | string { return jspb.Message.getFieldWithDefault(this, 2, "") as Uint8Array | string; } + getChainId(): Uint8Array | string { + return jspb.Message.getFieldWithDefault(this, 2, "") as Uint8Array | string; + } getChainId_asU8(): Uint8Array { const val = this.getChainId(); if (val instanceof Uint8Array) return val; return jspb.Message.bytesAsU8(val as string); } - setChainId(value: Uint8Array | string): void { jspb.Message.setField(this, 2, value); } + setChainId(value: Uint8Array | string): void { + jspb.Message.setField(this, 2, value); + } - getRefBlockNum(): number { return jspb.Message.getFieldWithDefault(this, 3, 0) as number; } - setRefBlockNum(value: number): void { jspb.Message.setField(this, 3, value); } + getRefBlockNum(): number { + return jspb.Message.getFieldWithDefault(this, 3, 0) as number; + } + setRefBlockNum(value: number): void { + jspb.Message.setField(this, 3, value); + } - getRefBlockPrefix(): number { return jspb.Message.getFieldWithDefault(this, 4, 0) as number; } - setRefBlockPrefix(value: number): void { jspb.Message.setField(this, 4, value); } + getRefBlockPrefix(): number { + return jspb.Message.getFieldWithDefault(this, 4, 0) as number; + } + setRefBlockPrefix(value: number): void { + jspb.Message.setField(this, 4, value); + } - getExpiration(): number { return jspb.Message.getFieldWithDefault(this, 5, 0) as number; } - setExpiration(value: number): void { jspb.Message.setField(this, 5, value); } + getExpiration(): number { + return jspb.Message.getFieldWithDefault(this, 5, 0) as number; + } + setExpiration(value: number): void { + jspb.Message.setField(this, 5, value); + } - getFrom(): string { return jspb.Message.getFieldWithDefault(this, 6, "") as string; } - setFrom(value: string): void { jspb.Message.setField(this, 6, value); } + getFrom(): string { + return jspb.Message.getFieldWithDefault(this, 6, "") as string; + } + setFrom(value: string): void { + jspb.Message.setField(this, 6, value); + } - getTo(): string { return jspb.Message.getFieldWithDefault(this, 7, "") as string; } - setTo(value: string): void { jspb.Message.setField(this, 7, value); } + getTo(): string { + return jspb.Message.getFieldWithDefault(this, 7, "") as string; + } + setTo(value: string): void { + jspb.Message.setField(this, 7, value); + } - getAmount(): number { return jspb.Message.getFieldWithDefault(this, 8, 0) as number; } - setAmount(value: number): void { jspb.Message.setField(this, 8, value); } + getAmount(): number { + return jspb.Message.getFieldWithDefault(this, 8, 0) as number; + } + setAmount(value: number): void { + jspb.Message.setField(this, 8, value); + } - getDecimals(): number { return jspb.Message.getFieldWithDefault(this, 9, 3) as number; } - setDecimals(value: number): void { jspb.Message.setField(this, 9, value); } + getDecimals(): number { + return jspb.Message.getFieldWithDefault(this, 9, 3) as number; + } + setDecimals(value: number): void { + jspb.Message.setField(this, 9, value); + } - getAssetSymbol(): string { return jspb.Message.getFieldWithDefault(this, 10, "") as string; } - setAssetSymbol(value: string): void { jspb.Message.setField(this, 10, value); } + getAssetSymbol(): string { + return jspb.Message.getFieldWithDefault(this, 10, "") as string; + } + setAssetSymbol(value: string): void { + jspb.Message.setField(this, 10, value); + } - getMemo(): string | undefined { return jspb.Message.getFieldWithDefault(this, 11, "") as string; } - setMemo(value: string): void { jspb.Message.setField(this, 11, value); } + getMemo(): string | undefined { + return jspb.Message.getFieldWithDefault(this, 11, "") as string; + } + setMemo(value: string): void { + jspb.Message.setField(this, 11, value); + } serializeBinary(): Uint8Array { const writer = new jspb.BinaryWriter(); @@ -191,14 +269,22 @@ export class HiveSignTx extends jspb.Message { toObject(): object { return { - addressNList: this.getAddressNList(), chainId: this.getChainId(), - refBlockNum: this.getRefBlockNum(), refBlockPrefix: this.getRefBlockPrefix(), - expiration: this.getExpiration(), from: this.getFrom(), to: this.getTo(), - amount: this.getAmount(), decimals: this.getDecimals(), - assetSymbol: this.getAssetSymbol(), memo: this.getMemo(), + addressNList: this.getAddressNList(), + chainId: this.getChainId(), + refBlockNum: this.getRefBlockNum(), + refBlockPrefix: this.getRefBlockPrefix(), + expiration: this.getExpiration(), + from: this.getFrom(), + to: this.getTo(), + amount: this.getAmount(), + decimals: this.getDecimals(), + assetSymbol: this.getAssetSymbol(), + memo: this.getMemo(), }; } - static toObject(_: boolean, msg: HiveSignTx): object { return msg.toObject(); } + static toObject(_: boolean, msg: HiveSignTx): object { + return msg.toObject(); + } static deserializeBinary(bytes: Uint8Array): HiveSignTx { const reader = new jspb.BinaryReader(bytes); @@ -215,17 +301,38 @@ export class HiveSignTx extends jspb.Message { for (const v of values) msg.addAddressN(v); break; } - case 2: msg.setChainId(reader.readBytes()); break; - case 3: msg.setRefBlockNum(reader.readUint32()); break; - case 4: msg.setRefBlockPrefix(reader.readUint32()); break; - case 5: msg.setExpiration(reader.readUint32()); break; - case 6: msg.setFrom(reader.readString()); break; - case 7: msg.setTo(reader.readString()); break; - case 8: msg.setAmount(reader.readUint64()); break; - case 9: msg.setDecimals(reader.readUint32()); break; - case 10: msg.setAssetSymbol(reader.readString()); break; - case 11: msg.setMemo(reader.readString()); break; - default: reader.skipField(); + case 2: + msg.setChainId(reader.readBytes()); + break; + case 3: + msg.setRefBlockNum(reader.readUint32()); + break; + case 4: + msg.setRefBlockPrefix(reader.readUint32()); + break; + case 5: + msg.setExpiration(reader.readUint32()); + break; + case 6: + msg.setFrom(reader.readString()); + break; + case 7: + msg.setTo(reader.readString()); + break; + case 8: + msg.setAmount(reader.readUint64()); + break; + case 9: + msg.setDecimals(reader.readUint32()); + break; + case 10: + msg.setAssetSymbol(reader.readString()); + break; + case 11: + msg.setMemo(reader.readString()); + break; + default: + reader.skipField(); } } return msg; @@ -266,21 +373,29 @@ export class HiveSignedTx extends jspb.Message { jspb.Message.initialize(this, opt_data || [], 0, -1, null, null); } - getSignature(): Uint8Array | string { return jspb.Message.getFieldWithDefault(this, 1, "") as Uint8Array | string; } + getSignature(): Uint8Array | string { + return jspb.Message.getFieldWithDefault(this, 1, "") as Uint8Array | string; + } getSignature_asU8(): Uint8Array { const val = this.getSignature(); if (val instanceof Uint8Array) return val; return jspb.Message.bytesAsU8(val as string); } - setSignature(value: Uint8Array | string): void { jspb.Message.setField(this, 1, value); } + setSignature(value: Uint8Array | string): void { + jspb.Message.setField(this, 1, value); + } - getSerializedTx(): Uint8Array | string { return jspb.Message.getFieldWithDefault(this, 2, "") as Uint8Array | string; } + getSerializedTx(): Uint8Array | string { + return jspb.Message.getFieldWithDefault(this, 2, "") as Uint8Array | string; + } getSerializedTx_asU8(): Uint8Array { const val = this.getSerializedTx(); if (val instanceof Uint8Array) return val; return jspb.Message.bytesAsU8(val as string); } - setSerializedTx(value: Uint8Array | string): void { jspb.Message.setField(this, 2, value); } + setSerializedTx(value: Uint8Array | string): void { + jspb.Message.setField(this, 2, value); + } serializeBinary(): Uint8Array { const writer = new jspb.BinaryWriter(); @@ -288,8 +403,12 @@ export class HiveSignedTx extends jspb.Message { return writer.getResultBuffer(); } - toObject(): object { return { signature: this.getSignature(), serializedTx: this.getSerializedTx() }; } - static toObject(_: boolean, msg: HiveSignedTx): object { return msg.toObject(); } + toObject(): object { + return { signature: this.getSignature(), serializedTx: this.getSerializedTx() }; + } + static toObject(_: boolean, msg: HiveSignedTx): object { + return msg.toObject(); + } static deserializeBinary(bytes: Uint8Array): HiveSignedTx { const reader = new jspb.BinaryReader(bytes); @@ -301,9 +420,14 @@ export class HiveSignedTx extends jspb.Message { while (reader.nextField()) { if (reader.isEndGroup()) break; switch (reader.getFieldNumber()) { - case 1: msg.setSignature(reader.readBytes()); break; - case 2: msg.setSerializedTx(reader.readBytes()); break; - default: reader.skipField(); + case 1: + msg.setSignature(reader.readBytes()); + break; + case 2: + msg.setSerializedTx(reader.readBytes()); + break; + default: + reader.skipField(); } } return msg; @@ -322,29 +446,26 @@ export class HiveSignedTx extends jspb.Message { function registerHiveMessages() { const mt = Messages.MessageType as unknown as Record; mt["MESSAGETYPE_HIVEGETPUBLICKEY"] = MESSAGETYPE_HIVEGETPUBLICKEY; - mt["MESSAGETYPE_HIVEPUBLICKEY"] = MESSAGETYPE_HIVEPUBLICKEY; - mt["MESSAGETYPE_HIVESIGNTX"] = MESSAGETYPE_HIVESIGNTX; - mt["MESSAGETYPE_HIVESIGNEDTX"] = MESSAGETYPE_HIVESIGNEDTX; + mt["MESSAGETYPE_HIVEPUBLICKEY"] = MESSAGETYPE_HIVEPUBLICKEY; + mt["MESSAGETYPE_HIVESIGNTX"] = MESSAGETYPE_HIVESIGNTX; + mt["MESSAGETYPE_HIVESIGNEDTX"] = MESSAGETYPE_HIVESIGNEDTX; messageNameRegistry[MESSAGETYPE_HIVEGETPUBLICKEY] = "HiveGetPublicKey"; - messageNameRegistry[MESSAGETYPE_HIVEPUBLICKEY] = "HivePublicKey"; - messageNameRegistry[MESSAGETYPE_HIVESIGNTX] = "HiveSignTx"; - messageNameRegistry[MESSAGETYPE_HIVESIGNEDTX] = "HiveSignedTx"; + messageNameRegistry[MESSAGETYPE_HIVEPUBLICKEY] = "HivePublicKey"; + messageNameRegistry[MESSAGETYPE_HIVESIGNTX] = "HiveSignTx"; + messageNameRegistry[MESSAGETYPE_HIVESIGNEDTX] = "HiveSignedTx"; messageTypeRegistry[MESSAGETYPE_HIVEGETPUBLICKEY] = HiveGetPublicKey as any; - messageTypeRegistry[MESSAGETYPE_HIVEPUBLICKEY] = HivePublicKey as any; - messageTypeRegistry[MESSAGETYPE_HIVESIGNTX] = HiveSignTx as any; - messageTypeRegistry[MESSAGETYPE_HIVESIGNEDTX] = HiveSignedTx as any; + messageTypeRegistry[MESSAGETYPE_HIVEPUBLICKEY] = HivePublicKey as any; + messageTypeRegistry[MESSAGETYPE_HIVESIGNTX] = HiveSignTx as any; + messageTypeRegistry[MESSAGETYPE_HIVESIGNEDTX] = HiveSignedTx as any; } registerHiveMessages(); // ── Wallet Methods ───────────────────────────────────────────────────── -export async function hiveGetPublicKey( - transport: Transport, - msg: core.HiveGetPublicKey, -): Promise { +export async function hiveGetPublicKey(transport: Transport, msg: core.HiveGetPublicKey): Promise { const req = new HiveGetPublicKey(); req.setAddressNList(msg.addressNList); if (msg.showDisplay !== undefined) req.setShowDisplay(msg.showDisplay); @@ -360,10 +481,7 @@ export async function hiveGetPublicKey( }; } -export async function hiveSignTx( - transport: Transport, - msg: core.HiveSignTx, -): Promise { +export async function hiveSignTx(transport: Transport, msg: core.HiveSignTx): Promise { return transport.lockDuring(async () => { const req = new HiveSignTx(); req.setAddressNList(msg.addressNList); diff --git a/packages/hdwallet-keepkey/src/keepkey.ts b/packages/hdwallet-keepkey/src/keepkey.ts index 80b68ea5..774ae456 100644 --- a/packages/hdwallet-keepkey/src/keepkey.ts +++ b/packages/hdwallet-keepkey/src/keepkey.ts @@ -7,10 +7,10 @@ import * as Btc from "./bitcoin"; import * as Cosmos from "./cosmos"; import * as Eos from "./eos"; import * as Eth from "./ethereum"; +import * as Hive from "./hive"; import * as Mayachain from "./mayachain"; import * as Osmosis from "./osmosis"; import * as Ripple from "./ripple"; -import * as Hive from "./hive"; import * as Solana from "./solana"; import * as Thorchain from "./thorchain"; import * as Ton from "./ton"; diff --git a/packages/hdwallet-keepkey/src/zcash.test.ts b/packages/hdwallet-keepkey/src/zcash.test.ts index d848faa4..1ea79bf4 100644 --- a/packages/hdwallet-keepkey/src/zcash.test.ts +++ b/packages/hdwallet-keepkey/src/zcash.test.ts @@ -27,10 +27,10 @@ const SHIELD_REQUEST = { expiry_height: 0, }, digests: { - header: "59bc2475723880114749687687be420e7e3389ce82e0ad6b9ba62e0a28457d3d", + header: "59bc2475723880114749687687be420e7e3389ce82e0ad6b9ba62e0a28457d3d", transparent: "f6424c87af931906154bc15c40fa50b9323fc99271e5c1a98c2d9cc214eb9f94", // sapling intentionally absent — firmware rejects it if set - orchard: "a8554ee3a53af330a6b6cf56112a203d3d028f2e421cb494a3f590161d27414a", + orchard: "a8554ee3a53af330a6b6cf56112a203d3d028f2e421cb494a3f590161d27414a", }, bundle_meta: { flags: 3, @@ -46,27 +46,45 @@ const SHIELD_REQUEST = { index: 0, addressNList: [0x80000000 + 44, 0x80000000 + 133, 0x80000000, 0, 0], amount: 4008918, - prevoutTxid: "ca3a5ef0323760c97406564a0eb51239ca1afa4944e968af78084d70982c13df", + prevoutTxid: "ca3a5ef0323760c97406564a0eb51239ca1afa4944e968af78084d70982c13df", prevoutIndex: 1, - sequence: 0xffffffff, + sequence: 0xffffffff, scriptPubkey: "76a9149ef6ee0267fd387526020c265a470e2dad7f3b5e88ac", }, ], actions: [ { - index: 0, alpha: "aa".repeat(32), cv_net: "bb".repeat(32), - nullifier: "cc".repeat(32), cmx: "dd".repeat(32), epk: "ee".repeat(32), - enc_compact: "ff".repeat(52), enc_memo: "11".repeat(512), enc_noncompact: "22".repeat(16), - rk: "33".repeat(32), out_ciphertext: "44".repeat(80), value: 3983918, is_spend: false, + index: 0, + alpha: "aa".repeat(32), + cv_net: "bb".repeat(32), + nullifier: "cc".repeat(32), + cmx: "dd".repeat(32), + epk: "ee".repeat(32), + enc_compact: "ff".repeat(52), + enc_memo: "11".repeat(512), + enc_noncompact: "22".repeat(16), + rk: "33".repeat(32), + out_ciphertext: "44".repeat(80), + value: 3983918, + is_spend: false, // recipient/rseed required for output actions (firmware clear-signing check) recipient: "ab".repeat(43), rseed: "cd".repeat(32), }, { - index: 1, alpha: "55".repeat(32), cv_net: "66".repeat(32), - nullifier: "77".repeat(32), cmx: "88".repeat(32), epk: "99".repeat(32), - enc_compact: "aa".repeat(52), enc_memo: "bb".repeat(512), enc_noncompact: "cc".repeat(16), - rk: "dd".repeat(32), out_ciphertext: "ee".repeat(80), value: 0, is_spend: true, + index: 1, + alpha: "55".repeat(32), + cv_net: "66".repeat(32), + nullifier: "77".repeat(32), + cmx: "88".repeat(32), + epk: "99".repeat(32), + enc_compact: "aa".repeat(52), + enc_memo: "bb".repeat(512), + enc_noncompact: "cc".repeat(16), + rk: "dd".repeat(32), + out_ciphertext: "ee".repeat(80), + value: 0, + is_spend: true, }, ], }; @@ -78,18 +96,16 @@ function makeMockTransport(callImpl: jest.Mock, readResponseImpl?: jest.Mock) { debugLink: false, call: callImpl, lockDuring: (fn: () => Promise) => fn(), - readResponse: readResponseImpl ?? jest.fn().mockResolvedValue({ - message_enum: -1, message_type: "Unknown", proto: {}, - }), + readResponse: + readResponseImpl ?? + jest.fn().mockResolvedValue({ + message_enum: -1, + message_type: "Unknown", + proto: {}, + }), } as any; } -function hexToBytes(hex: string): Uint8Array { - const bytes = new Uint8Array(hex.length / 2); - for (let i = 0; i < hex.length; i += 2) bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16); - return bytes; -} - describe("zcashSignPczt — shield tx (1 output, 1 input, 2 actions)", () => { it("sends ZcashSignPCZT with all required header and digest fields", async () => { const capturedMsg: ZcashMessages.ZcashSignPCZT[] = []; @@ -117,13 +133,16 @@ describe("zcashSignPczt — shield tx (1 output, 1 input, 2 actions)", () => { expect(msg.getBranchId()).toBe(0x4dec4df0); // Sub-digests - expect(Buffer.from(msg.getHeaderDigest_asU8()).toString("hex")) - .toBe("59bc2475723880114749687687be420e7e3389ce82e0ad6b9ba62e0a28457d3d"); - expect(Buffer.from(msg.getTransparentDigest_asU8()).toString("hex")) - .toBe("f6424c87af931906154bc15c40fa50b9323fc99271e5c1a98c2d9cc214eb9f94"); + expect(Buffer.from(msg.getHeaderDigest_asU8()).toString("hex")).toBe( + "59bc2475723880114749687687be420e7e3389ce82e0ad6b9ba62e0a28457d3d" + ); + expect(Buffer.from(msg.getTransparentDigest_asU8()).toString("hex")).toBe( + "f6424c87af931906154bc15c40fa50b9323fc99271e5c1a98c2d9cc214eb9f94" + ); expect(msg.hasSaplingDigest()).toBe(false); // sapling must NOT be set - expect(Buffer.from(msg.getOrchardDigest_asU8()).toString("hex")) - .toBe("a8554ee3a53af330a6b6cf56112a203d3d028f2e421cb494a3f590161d27414a"); + expect(Buffer.from(msg.getOrchardDigest_asU8()).toString("hex")).toBe( + "a8554ee3a53af330a6b6cf56112a203d3d028f2e421cb494a3f590161d27414a" + ); // Transparent counts expect(msg.getNTransparentOutputs()).toBe(1); @@ -195,7 +214,7 @@ describe("zcashSignPczt — shield tx (1 output, 1 input, 2 actions)", () => { } // Step 4a: ZcashPCZTAction (action 0) → ZcashPCZTActionAck(nextIndex=1) - if (mtype === Messages.MessageType.MESSAGETYPE_ZCASHPCZTACTION && calls.filter(c => c === mtype).length === 1) { + if (mtype === Messages.MessageType.MESSAGETYPE_ZCASHPCZTACTION && calls.filter((c) => c === mtype).length === 1) { capturedActionMsg.push(msg); const ack = new ZcashMessages.ZcashPCZTActionAck(); ack.setNextIndex(1); @@ -220,7 +239,7 @@ describe("zcashSignPczt — shield tx (1 output, 1 input, 2 actions)", () => { throw new Error(`unexpected call: ${mtype}`); }); - const result = await zcashSignPczt(makeMockTransport(call, readResponse), SHIELD_REQUEST, SIGHASH) as any; + const result = (await zcashSignPczt(makeMockTransport(call, readResponse), SHIELD_REQUEST, SIGHASH)) as any; // Protocol sequence: SignPCZT → Output → Input → Action × 2 (via call) // + readResponse() called once to drain ZcashSignedPCZT after ZcashTransparentSigned @@ -237,19 +256,22 @@ describe("zcashSignPczt — shield tx (1 output, 1 input, 2 actions)", () => { expect(capturedOutputMsg).toHaveLength(1); expect(capturedOutputMsg[0].getIndex()).toBe(0); expect(capturedOutputMsg[0].getAmount()).toBe(10000); - expect(Buffer.from(capturedOutputMsg[0].getScriptPubkey_asU8()).toString("hex")) - .toBe("76a9149ef6ee0267fd387526020c265a470e2dad7f3b5e88ac"); + expect(Buffer.from(capturedOutputMsg[0].getScriptPubkey_asU8()).toString("hex")).toBe( + "76a9149ef6ee0267fd387526020c265a470e2dad7f3b5e88ac" + ); // ZcashTransparentInput fields expect(capturedInputMsg).toHaveLength(1); expect(capturedInputMsg[0].getIndex()).toBe(0); expect(capturedInputMsg[0].getAmount()).toBe(4008918); - expect(Buffer.from(capturedInputMsg[0].getPrevoutTxid_asU8()).toString("hex")) - .toBe("ca3a5ef0323760c97406564a0eb51239ca1afa4944e968af78084d70982c13df"); + expect(Buffer.from(capturedInputMsg[0].getPrevoutTxid_asU8()).toString("hex")).toBe( + "ca3a5ef0323760c97406564a0eb51239ca1afa4944e968af78084d70982c13df" + ); expect(capturedInputMsg[0].getPrevoutIndex()).toBe(1); expect(capturedInputMsg[0].getSequence()).toBe(0xffffffff); - expect(Buffer.from(capturedInputMsg[0].getScriptPubkey_asU8()).toString("hex")) - .toBe("76a9149ef6ee0267fd387526020c265a470e2dad7f3b5e88ac"); + expect(Buffer.from(capturedInputMsg[0].getScriptPubkey_asU8()).toString("hex")).toBe( + "76a9149ef6ee0267fd387526020c265a470e2dad7f3b5e88ac" + ); // ZcashPCZTAction fields — output action must carry value/recipient/rseed for clear-signing expect(capturedActionMsg).toHaveLength(2); @@ -258,11 +280,9 @@ describe("zcashSignPczt — shield tx (1 output, 1 input, 2 actions)", () => { // value must be the OUTPUT note value, not 0 — firmware recomputes cmx from value+recipient+rseed+nullifier expect(capturedActionMsg[0].getValue()).toBe(3983918); expect(capturedActionMsg[0].getRecipient_asU8()).toHaveLength(43); - expect(Buffer.from(capturedActionMsg[0].getRecipient_asU8()).toString("hex")) - .toBe("ab".repeat(43)); + expect(Buffer.from(capturedActionMsg[0].getRecipient_asU8()).toString("hex")).toBe("ab".repeat(43)); expect(capturedActionMsg[0].getRseed_asU8()).toHaveLength(32); - expect(Buffer.from(capturedActionMsg[0].getRseed_asU8()).toString("hex")) - .toBe("cd".repeat(32)); + expect(Buffer.from(capturedActionMsg[0].getRseed_asU8()).toString("hex")).toBe("cd".repeat(32)); // action[1] is the dummy spend (is_spend=true) — value 0, no recipient/rseed expect(capturedActionMsg[1].getIsSpend()).toBe(true); expect(capturedActionMsg[1].getValue()).toBe(0); @@ -277,7 +297,7 @@ describe("zcashSignPczt — shield tx (1 output, 1 input, 2 actions)", () => { const calls: number[] = []; const call = jest.fn().mockImplementation((mtype: number) => { calls.push(mtype); - const actionCount = calls.filter(c => c === Messages.MessageType.MESSAGETYPE_ZCASHPCZTACTION).length; + const actionCount = calls.filter((c) => c === Messages.MessageType.MESSAGETYPE_ZCASHPCZTACTION).length; if (mtype === Messages.MessageType.MESSAGETYPE_ZCASHSIGNPCZT) { const ack = new ZcashMessages.ZcashPCZTActionAck(); @@ -341,11 +361,15 @@ const DESHIELD_REQUEST = { branch_id: 0x4dec4df0, header_fields: { tx_version: 5, version_group_id: 0x26a7270a, lock_time: 0, expiry_height: 0 }, digests: { - header: "59bc2475723880114749687687be420e7e3389ce82e0ad6b9ba62e0a28457d3d", + header: "59bc2475723880114749687687be420e7e3389ce82e0ad6b9ba62e0a28457d3d", transparent: "0a259ca3000000000000000000000000000000000000000000000000000000ff", - orchard: "d0f62785000000000000000000000000000000000000000000000000000000ff", + orchard: "d0f62785000000000000000000000000000000000000000000000000000000ff", + }, + bundle_meta: { + flags: 3, + value_balance: 2515000, + anchor: "419a28788f9fbfe0a01807e50c00e938f7b9b8381584021efeecb3d18a3c0b28", }, - bundle_meta: { flags: 3, value_balance: 2515000, anchor: "419a28788f9fbfe0a01807e50c00e938f7b9b8381584021efeecb3d18a3c0b28" }, display: { amount: "0.02500000 ZEC", fee: "0.00015000 ZEC" }, transparent_outputs: [ { index: 0, value: 2500000, script_pubkey: "76a9149ef6ee0267fd387526020c265a470e2dad7f3b5e88ac" }, @@ -354,22 +378,40 @@ const DESHIELD_REQUEST = { actions: [ { // Change output back to Orchard — is_spend=false, value is the OUTPUT note value (change amount) - index: 0, alpha: "aa".repeat(32), cv_net: "bb".repeat(32), - nullifier: "cc".repeat(32), cmx: "dd".repeat(32), epk: "ee".repeat(32), - enc_compact: "ff".repeat(52), enc_memo: "11".repeat(512), enc_noncompact: "22".repeat(16), - rk: "33".repeat(32), out_ciphertext: "44".repeat(80), - value: 1358918, is_spend: false, - recipient: "ab".repeat(43), rseed: "cd".repeat(32), + index: 0, + alpha: "aa".repeat(32), + cv_net: "bb".repeat(32), + nullifier: "cc".repeat(32), + cmx: "dd".repeat(32), + epk: "ee".repeat(32), + enc_compact: "ff".repeat(52), + enc_memo: "11".repeat(512), + enc_noncompact: "22".repeat(16), + rk: "33".repeat(32), + out_ciphertext: "44".repeat(80), + value: 1358918, + is_spend: false, + recipient: "ab".repeat(43), + rseed: "cd".repeat(32), }, { // Real spend + dummy output — is_spend=true, dummy output value is 0 // recipient/rseed still required: firmware verifies cmx for the dummy output too - index: 1, alpha: "55".repeat(32), cv_net: "66".repeat(32), - nullifier: "77".repeat(32), cmx: "88".repeat(32), epk: "99".repeat(32), - enc_compact: "aa".repeat(52), enc_memo: "bb".repeat(512), enc_noncompact: "cc".repeat(16), - rk: "dd".repeat(32), out_ciphertext: "ee".repeat(80), - value: 0, is_spend: true, - recipient: "ef".repeat(43), rseed: "12".repeat(32), + index: 1, + alpha: "55".repeat(32), + cv_net: "66".repeat(32), + nullifier: "77".repeat(32), + cmx: "88".repeat(32), + epk: "99".repeat(32), + enc_compact: "aa".repeat(52), + enc_memo: "bb".repeat(512), + enc_noncompact: "cc".repeat(16), + rk: "dd".repeat(32), + out_ciphertext: "ee".repeat(80), + value: 0, + is_spend: true, + recipient: "ef".repeat(43), + rseed: "12".repeat(32), }, ], }; @@ -386,7 +428,7 @@ describe("zcashSignPczt — deshield tx (1 output, 0 inputs, 2 actions)", () => const call = jest.fn().mockImplementation((mtype: number, msg: any) => { calls.push(mtype); - const actionsSent = calls.filter(c => c === Messages.MessageType.MESSAGETYPE_ZCASHPCZTACTION).length; + const actionsSent = calls.filter((c) => c === Messages.MessageType.MESSAGETYPE_ZCASHPCZTACTION).length; // Step 1: ZcashSignPCZT → TransparentAck(nextOutputIndex=0) if (mtype === Messages.MessageType.MESSAGETYPE_ZCASHSIGNPCZT) { @@ -394,7 +436,8 @@ describe("zcashSignPczt — deshield tx (1 output, 0 inputs, 2 actions)", () => ack.setNextOutputIndex(0); return Promise.resolve({ message_enum: Messages.MessageType.MESSAGETYPE_ZCASHTRANSPARENTACK, - message_type: "ZcashTransparentAck", proto: ack, + message_type: "ZcashTransparentAck", + proto: ack, }); } @@ -407,7 +450,8 @@ describe("zcashSignPczt — deshield tx (1 output, 0 inputs, 2 actions)", () => ack.setNextIndex(0); return Promise.resolve({ message_enum: Messages.MessageType.MESSAGETYPE_ZCASHPCZTACTIONACK, - message_type: "ZcashPCZTActionAck", proto: ack, + message_type: "ZcashPCZTActionAck", + proto: ack, }); } @@ -418,7 +462,8 @@ describe("zcashSignPczt — deshield tx (1 output, 0 inputs, 2 actions)", () => ack.setNextIndex(1); return Promise.resolve({ message_enum: Messages.MessageType.MESSAGETYPE_ZCASHPCZTACTIONACK, - message_type: "ZcashPCZTActionAck", proto: ack, + message_type: "ZcashPCZTActionAck", + proto: ack, }); } @@ -427,14 +472,15 @@ describe("zcashSignPczt — deshield tx (1 output, 0 inputs, 2 actions)", () => capturedActionMsg.push(msg); return Promise.resolve({ message_enum: Messages.MessageType.MESSAGETYPE_ZCASHSIGNEDPCZT, - message_type: "ZcashSignedPCZT", proto: signedPczt, + message_type: "ZcashSignedPCZT", + proto: signedPczt, }); } throw new Error(`unexpected call: ${mtype}`); }); - const result = await zcashSignPczt(makeMockTransport(call), DESHIELD_REQUEST, SIGHASH) as any; + const result = (await zcashSignPczt(makeMockTransport(call), DESHIELD_REQUEST, SIGHASH)) as any; // Protocol must be: SignPCZT → Output → Action × 2 (no Input step) expect(calls).toEqual([ @@ -453,8 +499,9 @@ describe("zcashSignPczt — deshield tx (1 output, 0 inputs, 2 actions)", () => // Output fields expect(capturedOutputMsg[0].getIndex()).toBe(0); expect(capturedOutputMsg[0].getAmount()).toBe(2500000); - expect(Buffer.from(capturedOutputMsg[0].getScriptPubkey_asU8()).toString("hex")) - .toBe("76a9149ef6ee0267fd387526020c265a470e2dad7f3b5e88ac"); + expect(Buffer.from(capturedOutputMsg[0].getScriptPubkey_asU8()).toString("hex")).toBe( + "76a9149ef6ee0267fd387526020c265a470e2dad7f3b5e88ac" + ); // Action[0] — change output: value must be OUTPUT note value (not 0 or spend value) expect(capturedActionMsg[0].getValue()).toBe(1358918); diff --git a/packages/hdwallet-keepkey/src/zcash.ts b/packages/hdwallet-keepkey/src/zcash.ts index 04b4c7a9..d10db5d4 100644 --- a/packages/hdwallet-keepkey/src/zcash.ts +++ b/packages/hdwallet-keepkey/src/zcash.ts @@ -192,7 +192,7 @@ export async function zcashSignPczt( if (nTransparentOutputs > 0) signMsg.setNTransparentOutputs(nTransparentOutputs); if (nTransparentInputs > 0) signMsg.setNTransparentInputs(nTransparentInputs); - console.log("[zcash-pczt] → ZcashSignPCZT", { + console.info("[zcash-pczt] → ZcashSignPCZT", { n_actions: signingRequest.n_actions, branch_id: (signingRequest.branch_id ?? 0x37519621).toString(16), account, @@ -217,7 +217,7 @@ export async function zcashSignPczt( omitLock: true, }); - console.log("[zcash-pczt] ← response:", response.message_type, response.message_enum); + console.info("[zcash-pczt] ← response:", response.message_type, response.message_enum); // Step 2: Transparent phase (outputs first, then inputs) const transparentSignatures: string[] = []; @@ -231,19 +231,27 @@ export async function zcashSignPczt( // Step 2a: Output phase — firmware requests outputs before inputs if (nTransparentOutputs > 0) { let ack = response.proto as ZcashMessages.ZcashTransparentAck; - console.log("[zcash-pczt] TransparentAck hasNextOutputIndex:", ack.hasNextOutputIndex(), - "nextOutputIndex:", ack.getNextOutputIndex(), - "hasNextInputIndex:", ack.hasNextInputIndex(), - "nextInputIndex:", ack.getNextInputIndex()); + console.info( + "[zcash-pczt] TransparentAck hasNextOutputIndex:", + ack.hasNextOutputIndex(), + "nextOutputIndex:", + ack.getNextOutputIndex(), + "hasNextInputIndex:", + ack.hasNextInputIndex(), + "nextInputIndex:", + ack.getNextInputIndex() + ); if (!ack.hasNextOutputIndex()) { - throw new Error(`zcash: expected nextOutputIndex in TransparentAck, got nextInputIndex=${ack.getNextInputIndex()}`); + throw new Error( + `zcash: expected nextOutputIndex in TransparentAck, got nextInputIndex=${ack.getNextInputIndex()}` + ); } let outputIndex = ack.getNextOutputIndex() ?? 0; for (let i = 0; i < nTransparentOutputs; i++) { const output = transparentOutputs[outputIndex]; - console.log(`[zcash-pczt] → ZcashTransparentOutput [${i}]`, { + console.info(`[zcash-pczt] → ZcashTransparentOutput [${i}]`, { raw_output: output, index: output?.index, value: output?.value, @@ -261,7 +269,7 @@ export async function zcashSignPczt( omitLock: true, }); - console.log(`[zcash-pczt] ← output[${i}] response:`, response.message_type, response.message_enum); + console.info(`[zcash-pczt] ← output[${i}] response:`, response.message_type, response.message_enum); // After the last output with no transparent inputs, firmware skips straight // to Orchard and sends ZcashPCZTActionAck(0) instead of TransparentAck. @@ -274,7 +282,7 @@ export async function zcashSignPczt( } ack = response.proto as ZcashMessages.ZcashTransparentAck; - console.log("[zcash-pczt] TransparentAck after output:", { + console.info("[zcash-pczt] TransparentAck after output:", { hasNextOutputIndex: ack.hasNextOutputIndex(), nextOutputIndex: ack.getNextOutputIndex(), hasNextInputIndex: ack.hasNextInputIndex(), @@ -297,7 +305,7 @@ export async function zcashSignPczt( for (let i = 0; i < nTransparentInputs; i++) { const input = transparentInputs[inputIndex]; - console.log(`[zcash-pczt] → ZcashTransparentInput [${i}]`, { + console.info(`[zcash-pczt] → ZcashTransparentInput [${i}]`, { raw_input: input, index: input?.index, amount: input?.amount, @@ -322,7 +330,7 @@ export async function zcashSignPczt( omitLock: true, }); - console.log(`[zcash-pczt] ← input[${i}] response:`, response.message_type, response.message_enum); + console.info(`[zcash-pczt] ← input[${i}] response:`, response.message_type, response.message_enum); // Firmware 7.15+: after last input, sends ZcashPCZTActionAck(0) and buffers // transparent ECDSA sigs internally — they come out with ZcashTransparentSigned @@ -337,7 +345,7 @@ export async function zcashSignPczt( for (const sig of sigResp.getSignaturesList_asU8()) { transparentSignatures.push(bytesToHex(sig)); } - console.log(`[zcash-pczt] ZcashTransparentSigned (legacy): ${transparentSignatures.length} sig(s)`); + console.info(`[zcash-pczt] ZcashTransparentSigned (legacy): ${transparentSignatures.length} sig(s)`); break; } @@ -363,7 +371,7 @@ export async function zcashSignPczt( } const action = signingRequest.actions[i]; - console.log(`[zcash-pczt] → ZcashPCZTAction [${i}]`, { + console.info(`[zcash-pczt] → ZcashPCZTAction [${i}]`, { index: action?.index, value: action?.value, is_spend: action?.is_spend, @@ -405,7 +413,7 @@ export async function zcashSignPczt( omitLock: true, }); - console.log(`[zcash-pczt] ← action[${i}] response:`, response.message_type, response.message_enum); + console.info(`[zcash-pczt] ← action[${i}] response:`, response.message_type, response.message_enum); } // Step 4: Firmware 7.15+ sends ZcashTransparentSigned immediately before ZcashSignedPCZT @@ -415,9 +423,11 @@ export async function zcashSignPczt( for (const sig of sigResp.getSignaturesList_asU8()) { transparentSignatures.push(bytesToHex(sig)); } - console.log(`[zcash-pczt] ZcashTransparentSigned: ${transparentSignatures.length} sig(s), reading ZcashSignedPCZT...`); + console.info( + `[zcash-pczt] ZcashTransparentSigned: ${transparentSignatures.length} sig(s), reading ZcashSignedPCZT...` + ); response = await (transport as any).readResponse(false); - console.log(`[zcash-pczt] ← readResponse:`, response.message_type, response.message_enum); + console.info(`[zcash-pczt] ← readResponse:`, response.message_type, response.message_enum); } // Step 5: Collect Orchard signatures @@ -430,7 +440,9 @@ export async function zcashSignPczt( orchardSignatures.push(bytesToHex(sig)); } - console.log(`[zcash-pczt] DONE: ${orchardSignatures.length} Orchard sig(s), ${transparentSignatures.length} transparent sig(s)`); + console.info( + `[zcash-pczt] DONE: ${orchardSignatures.length} Orchard sig(s), ${transparentSignatures.length} transparent sig(s)` + ); if (!hasTransparentPhase) { return orchardSignatures;