From f1df66d63979a5c9d7cb9c4e7bc665c70ef7d0d6 Mon Sep 17 00:00:00 2001 From: highlander Date: Fri, 26 Jun 2026 16:51:00 -0500 Subject: [PATCH 1/2] feat(hive): multi-key fetch + account create/update wallet methods (Phase 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plumbs the 3 proto messages that firmware 7.15.0 + device-protocol already support but the JS wallet couldn't call: - hiveGetPublicKeys (1604/1605) — fetch all 4 role keys in one interaction - hiveSignAccountCreate (1606/1607) — account_create (op 9), sponsor-funded - hiveSignAccountUpdate (1608/1609) — account_update (op 10), secure existing acct Adds hdwallet-core interfaces + hiveSlip48Path(role, accountIndex) helper, the hand-written jspb shims + runtime registration in hdwallet-keepkey, and the KeepKeyHDWallet methods. Unblocks the vault onboarding wizard (Phase 3). Tests: jspb round-trip for every new message (validates wire field numbers/ types), hiveSlip48Path derivation, registry 1604-1609, wrapper success + unexpected-response guard. 15 passing. --- packages/hdwallet-core/src/hive.ts | 66 ++ packages/hdwallet-keepkey/src/hive.test.ts | 222 ++++++ packages/hdwallet-keepkey/src/hive.ts | 780 ++++++++++++++++++++- packages/hdwallet-keepkey/src/keepkey.ts | 12 + 4 files changed, 1079 insertions(+), 1 deletion(-) create mode 100644 packages/hdwallet-keepkey/src/hive.test.ts diff --git a/packages/hdwallet-core/src/hive.ts b/packages/hdwallet-core/src/hive.ts index b4a9f187..b2eecd0a 100644 --- a/packages/hdwallet-core/src/hive.ts +++ b/packages/hdwallet-core/src/hive.ts @@ -29,8 +29,74 @@ export interface HiveSignedTx { serializedTx: Uint8Array; } +// ── SLIP-0048 path helper ────────────────────────────────────────────── +// Hive uses SLIP-0048: m/48'/13'/role'/account'/0' (all 5 hardened). Must match +// firmware (hive.h HIVE_SLIP48_*), Ledger, and Hive Keychain. +export const HIVE_ROLE = { owner: 0, active: 1, memo: 3, posting: 4 } as const; +export type HiveRole = keyof typeof HIVE_ROLE; + +export function hiveSlip48Path(role: HiveRole, accountIndex = 0): BIP32Path { + return [0x80000030, 0x8000000d, 0x80000000 + HIVE_ROLE[role], 0x80000000 + accountIndex, 0x80000000]; +} + +// ── Multi-key fetch (all four role keys in one device interaction) ────── +export interface HiveGetPublicKeys { + accountIndex?: number; // default 0 + showDisplay?: boolean; +} + +export interface HivePublicKeys { + ownerKey: string; + activeKey: string; + memoKey: string; + postingKey: string; +} + +// ── account_create (op 9) — sponsor-funded new account ────────────────── +export interface HiveSignAccountCreate { + addressNList: BIP32Path; // owner key path: m/48'/13'/0'/account'/0' + chainId?: Uint8Array | string; + refBlockNum: number; + refBlockPrefix: number; + expiration: number; + creator: string; // sponsor account name + newAccountName: string; + ownerKey: string; + activeKey: string; + postingKey: string; + memoKey: string; + feeAmount: number; // milliHIVE (3000 = 3.000 HIVE) +} + +export interface HiveSignedAccountCreate { + signature: Uint8Array; + serializedTx: Uint8Array; +} + +// ── account_update (op 10) — secure an existing account (Flow B) ──────── +export interface HiveSignAccountUpdate { + addressNList: BIP32Path; // owner key path + chainId?: Uint8Array | string; + refBlockNum: number; + refBlockPrefix: number; + expiration: number; + account: string; + newOwnerKey: string; + newActiveKey: string; + newPostingKey: string; + newMemoKey: string; +} + +export interface HiveSignedAccountUpdate { + signature: Uint8Array; + serializedTx: Uint8Array; +} + export interface HiveWallet extends HDWallet { readonly _supportsHive: boolean; hiveGetPublicKey(msg: HiveGetPublicKey): Promise; + hiveGetPublicKeys(msg: HiveGetPublicKeys): Promise; hiveSignTx(msg: HiveSignTx): Promise; + hiveSignAccountCreate(msg: HiveSignAccountCreate): Promise; + hiveSignAccountUpdate(msg: HiveSignAccountUpdate): Promise; } diff --git a/packages/hdwallet-keepkey/src/hive.test.ts b/packages/hdwallet-keepkey/src/hive.test.ts new file mode 100644 index 00000000..adc8a087 --- /dev/null +++ b/packages/hdwallet-keepkey/src/hive.test.ts @@ -0,0 +1,222 @@ +/** + * Unit tests for the Hive multi-key + account-operation protobuf shims (Phase 1). + * + * Covers: + * - Type registry registration (1604-1609) + * - jspb round-trip (serializeBinary → deserializeBinaryFromReader) for + * HiveGetPublicKeys / HivePublicKeys / HiveSignAccountCreate / + * HiveSignAccountUpdate / HiveSignedAccountCreate — the hand-written wire + * format is the thing most likely to be wrong, so pin every field. + * - hiveSlip48Path: SLIP-0048 derivation (the bug that motivated all of this) + * - hiveGetPublicKeys / hiveSignAccountCreate wrappers: success path + + * unexpected-response guard. + */ +import * as core from "@keepkey/hdwallet-core"; +import { hiveSlip48Path } from "@keepkey/hdwallet-core"; +import * as jspb from "google-protobuf"; + +import { + HiveGetPublicKeys, + hiveGetPublicKeys, + HivePublicKeys, + HiveSignAccountCreate, + hiveSignAccountCreate, + HiveSignAccountUpdate, + HiveSignedAccountCreate, +} from "./hive"; +import { messageNameRegistry, messageTypeRegistry } from "./typeRegistry"; + +const MESSAGETYPE_HIVEGETPUBLICKEYS = 1604; +const MESSAGETYPE_HIVEPUBLICKEYS = 1605; +const MESSAGETYPE_HIVESIGNACCOUNTCREATE = 1606; +const MESSAGETYPE_HIVESIGNEDACCOUNTCREATE = 1607; +const MESSAGETYPE_HIVESIGNACCOUNTUPDATE = 1608; +const MESSAGETYPE_HIVESIGNEDACCOUNTUPDATE = 1609; + +const OWNER_PATH = [0x80000030, 0x8000000d, 0x80000000, 0x80000000, 0x80000000]; // m/48'/13'/0'/0'/0' + +function makeMockTransport(callImpl: jest.Mock) { + return { + debugLink: false, + call: callImpl, + lockDuring: (fn: () => Promise) => fn(), + } as any; +} + +describe("Hive account-op protobuf registration", () => { + const cases: Array<[number, string]> = [ + [MESSAGETYPE_HIVEGETPUBLICKEYS, "HiveGetPublicKeys"], + [MESSAGETYPE_HIVEPUBLICKEYS, "HivePublicKeys"], + [MESSAGETYPE_HIVESIGNACCOUNTCREATE, "HiveSignAccountCreate"], + [MESSAGETYPE_HIVESIGNEDACCOUNTCREATE, "HiveSignedAccountCreate"], + [MESSAGETYPE_HIVESIGNACCOUNTUPDATE, "HiveSignAccountUpdate"], + [MESSAGETYPE_HIVESIGNEDACCOUNTUPDATE, "HiveSignedAccountUpdate"], + ]; + it.each(cases)("registers %i as %s", (id, name) => { + expect(messageTypeRegistry[id]).toBeDefined(); + expect(messageNameRegistry[id]).toBe(name); + }); +}); + +describe("hiveSlip48Path", () => { + it("derives m/48'/13'/role'/account'/0' for each role", () => { + expect(hiveSlip48Path("owner")).toEqual([0x80000030, 0x8000000d, 0x80000000, 0x80000000, 0x80000000]); + expect(hiveSlip48Path("active")).toEqual([0x80000030, 0x8000000d, 0x80000001, 0x80000000, 0x80000000]); + expect(hiveSlip48Path("memo")).toEqual([0x80000030, 0x8000000d, 0x80000003, 0x80000000, 0x80000000]); + expect(hiveSlip48Path("posting")).toEqual([0x80000030, 0x8000000d, 0x80000004, 0x80000000, 0x80000000]); + }); + it("honors account index (all hardened)", () => { + expect(hiveSlip48Path("active", 2)).toEqual([0x80000030, 0x8000000d, 0x80000001, 0x80000002, 0x80000000]); + }); +}); + +describe("Hive jspb round-trip", () => { + it("HiveGetPublicKeys: account_index + show_display", () => { + const m = new HiveGetPublicKeys(); + m.setAccountIndex(3); + m.setShowDisplay(true); + const decoded = HiveGetPublicKeys.deserializeBinaryFromReader( + new HiveGetPublicKeys(), + new jspb.BinaryReader(m.serializeBinary()), + ); + expect(decoded.getAccountIndex()).toBe(3); + expect(decoded.getShowDisplay()).toBe(true); + }); + + it("HivePublicKeys: all four role keys survive", () => { + const m = new HivePublicKeys(); + m.setOwnerKey("STM_owner"); + m.setActiveKey("STM_active"); + m.setMemoKey("STM_memo"); + m.setPostingKey("STM_posting"); + const decoded = HivePublicKeys.deserializeBinaryFromReader( + new HivePublicKeys(), + new jspb.BinaryReader(m.serializeBinary()), + ); + expect(decoded.getOwnerKey()).toBe("STM_owner"); + expect(decoded.getActiveKey()).toBe("STM_active"); + expect(decoded.getMemoKey()).toBe("STM_memo"); + expect(decoded.getPostingKey()).toBe("STM_posting"); + }); + + it("HiveSignAccountCreate: every field including uint64 fee", () => { + const m = new HiveSignAccountCreate(); + m.setAddressNList(OWNER_PATH); + m.setChainId(new Uint8Array([0xbe, 0xea, 0xb0, 0xde])); + m.setRefBlockNum(12345); + m.setRefBlockPrefix(67890); + m.setExpiration(1700000000); + m.setCreator("sponsor"); + m.setNewAccountName("alice"); + m.setOwnerKey("STM_o"); + m.setActiveKey("STM_a"); + m.setPostingKey("STM_p"); + m.setMemoKey("STM_m"); + m.setFeeAmount(3000); + + const decoded = HiveSignAccountCreate.deserializeBinaryFromReader( + new HiveSignAccountCreate(), + new jspb.BinaryReader(m.serializeBinary()), + ); + expect(decoded.getAddressNList()).toEqual(OWNER_PATH); + expect(Array.from(decoded.getChainId_asU8())).toEqual([0xbe, 0xea, 0xb0, 0xde]); + expect(decoded.getRefBlockNum()).toBe(12345); + expect(decoded.getRefBlockPrefix()).toBe(67890); + expect(decoded.getExpiration()).toBe(1700000000); + expect(decoded.getCreator()).toBe("sponsor"); + expect(decoded.getNewAccountName()).toBe("alice"); + expect(decoded.getOwnerKey()).toBe("STM_o"); + expect(decoded.getActiveKey()).toBe("STM_a"); + expect(decoded.getPostingKey()).toBe("STM_p"); + expect(decoded.getMemoKey()).toBe("STM_m"); + expect(decoded.getFeeAmount()).toBe(3000); + }); + + it("HiveSignAccountUpdate: account + 4 new keys", () => { + const m = new HiveSignAccountUpdate(); + m.setAddressNList(OWNER_PATH); + m.setRefBlockNum(1); + m.setRefBlockPrefix(2); + m.setExpiration(3); + m.setAccount("alice"); + m.setNewOwnerKey("STM_no"); + m.setNewActiveKey("STM_na"); + m.setNewPostingKey("STM_np"); + m.setNewMemoKey("STM_nm"); + const decoded = HiveSignAccountUpdate.deserializeBinaryFromReader( + new HiveSignAccountUpdate(), + new jspb.BinaryReader(m.serializeBinary()), + ); + expect(decoded.getAccount()).toBe("alice"); + expect(decoded.getNewOwnerKey()).toBe("STM_no"); + expect(decoded.getNewActiveKey()).toBe("STM_na"); + expect(decoded.getNewPostingKey()).toBe("STM_np"); + expect(decoded.getNewMemoKey()).toBe("STM_nm"); + }); +}); + +describe("Hive wrappers", () => { + it("hiveGetPublicKeys returns the four keys on success", async () => { + const resp = new HivePublicKeys(); + resp.setOwnerKey("STM_o"); + resp.setActiveKey("STM_a"); + resp.setMemoKey("STM_m"); + resp.setPostingKey("STM_p"); + const call = jest.fn().mockResolvedValue({ + message_enum: MESSAGETYPE_HIVEPUBLICKEYS, + message_type: "HivePublicKeys", + proto: resp, + }); + const out = await hiveGetPublicKeys(makeMockTransport(call), { accountIndex: 0 }); + expect(out).toEqual({ ownerKey: "STM_o", activeKey: "STM_a", memoKey: "STM_m", postingKey: "STM_p" }); + expect(call).toHaveBeenCalledWith(MESSAGETYPE_HIVEGETPUBLICKEYS, expect.anything(), expect.anything()); + }); + + it("hiveSignAccountCreate returns signature + serializedTx on success", async () => { + const signed = new HiveSignedAccountCreate(); + signed.setSignature(new Uint8Array([1, 2, 3])); + signed.setSerializedTx(new Uint8Array([4, 5, 6])); + const call = jest.fn().mockResolvedValue({ + message_enum: MESSAGETYPE_HIVESIGNEDACCOUNTCREATE, + message_type: "HiveSignedAccountCreate", + proto: signed, + }); + const out = await hiveSignAccountCreate(makeMockTransport(call), { + addressNList: OWNER_PATH, + refBlockNum: 1, + refBlockPrefix: 2, + expiration: 3, + creator: "sponsor", + newAccountName: "alice", + ownerKey: "STM_o", + activeKey: "STM_a", + postingKey: "STM_p", + memoKey: "STM_m", + feeAmount: 3000, + }); + expect(Array.from(out.signature)).toEqual([1, 2, 3]); + expect(Array.from(out.serializedTx)).toEqual([4, 5, 6]); + }); + + it("hiveSignAccountCreate throws on unexpected response", async () => { + const call = jest.fn().mockResolvedValue({ message_enum: 9999, message_type: "Other", proto: {} }); + await expect( + hiveSignAccountCreate(makeMockTransport(call), { + addressNList: OWNER_PATH, + refBlockNum: 1, + refBlockPrefix: 2, + expiration: 3, + creator: "sponsor", + newAccountName: "alice", + ownerKey: "STM_o", + activeKey: "STM_a", + postingKey: "STM_p", + memoKey: "STM_m", + feeAmount: 3000, + }), + ).rejects.toThrow(/unexpected response/); + }); +}); + +// satisfies the unused-import linter if core is not otherwise referenced +void core; diff --git a/packages/hdwallet-keepkey/src/hive.ts b/packages/hdwallet-keepkey/src/hive.ts index 9b584579..f81d7412 100644 --- a/packages/hdwallet-keepkey/src/hive.ts +++ b/packages/hdwallet-keepkey/src/hive.ts @@ -7,11 +7,17 @@ import { messageNameRegistry, messageTypeRegistry } from "./typeRegistry"; const Msg = jspb.Message as any; -// ── Hive Message Type IDs (messages-hive.proto, wire IDs 1600–1603) ─── +// ── Hive Message Type IDs (messages-hive.proto, wire IDs 1600–1609) ─── const MESSAGETYPE_HIVEGETPUBLICKEY = 1600; const MESSAGETYPE_HIVEPUBLICKEY = 1601; const MESSAGETYPE_HIVESIGNTX = 1602; const MESSAGETYPE_HIVESIGNEDTX = 1603; +const MESSAGETYPE_HIVEGETPUBLICKEYS = 1604; +const MESSAGETYPE_HIVEPUBLICKEYS = 1605; +const MESSAGETYPE_HIVESIGNACCOUNTCREATE = 1606; +const MESSAGETYPE_HIVESIGNEDACCOUNTCREATE = 1607; +const MESSAGETYPE_HIVESIGNACCOUNTUPDATE = 1608; +const MESSAGETYPE_HIVESIGNEDACCOUNTUPDATE = 1609; // ── Protobuf Shims ───────────────────────────────────────────────────── @@ -441,6 +447,662 @@ export class HiveSignedTx extends jspb.Message { } } +/** + * HiveGetPublicKeys: account_index(1, uint32, default 0), show_display(2, bool) + */ +export class HiveGetPublicKeys extends jspb.Message { + constructor(opt_data?: any) { + super(); + jspb.Message.initialize(this, opt_data || [], 0, -1, null, null); + } + + getAccountIndex(): number { + return jspb.Message.getFieldWithDefault(this, 1, 0) as number; + } + setAccountIndex(value: number): void { + jspb.Message.setField(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); + } + + serializeBinary(): Uint8Array { + const writer = new jspb.BinaryWriter(); + HiveGetPublicKeys.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); + } + + toObject(): object { + return { accountIndex: this.getAccountIndex(), showDisplay: this.getShowDisplay() }; + } + static toObject(_: boolean, msg: HiveGetPublicKeys): object { + return msg.toObject(); + } + + static deserializeBinary(bytes: Uint8Array): HiveGetPublicKeys { + const reader = new jspb.BinaryReader(bytes); + const msg = new HiveGetPublicKeys(); + return HiveGetPublicKeys.deserializeBinaryFromReader(msg, reader); + } + + static deserializeBinaryFromReader(msg: HiveGetPublicKeys, reader: jspb.BinaryReader): HiveGetPublicKeys { + while (reader.nextField()) { + if (reader.isEndGroup()) break; + switch (reader.getFieldNumber()) { + case 1: + msg.setAccountIndex(reader.readUint32()); + break; + case 2: + msg.setShowDisplay(reader.readBool()); + break; + default: + reader.skipField(); + } + } + return msg; + } + + static serializeBinaryToWriter(message: HiveGetPublicKeys, writer: jspb.BinaryWriter): void { + const accountIndex = jspb.Message.getField(message, 1) as number | null; + if (accountIndex != null) writer.writeUint32(1, accountIndex); + const showDisplay = jspb.Message.getField(message, 2); + if (showDisplay != null) writer.writeBool(2, !!showDisplay); + } +} + +/** + * HivePublicKeys: owner_key(1), active_key(2), memo_key(3), posting_key(4) — all string + */ +export class HivePublicKeys extends jspb.Message { + constructor(opt_data?: any) { + super(); + jspb.Message.initialize(this, opt_data || [], 0, -1, null, null); + } + + getOwnerKey(): string { + return jspb.Message.getFieldWithDefault(this, 1, "") as string; + } + setOwnerKey(value: string): void { + jspb.Message.setField(this, 1, value); + } + getActiveKey(): string { + return jspb.Message.getFieldWithDefault(this, 2, "") as string; + } + setActiveKey(value: string): void { + jspb.Message.setField(this, 2, value); + } + getMemoKey(): string { + return jspb.Message.getFieldWithDefault(this, 3, "") as string; + } + setMemoKey(value: string): void { + jspb.Message.setField(this, 3, value); + } + getPostingKey(): string { + return jspb.Message.getFieldWithDefault(this, 4, "") as string; + } + setPostingKey(value: string): void { + jspb.Message.setField(this, 4, value); + } + + serializeBinary(): Uint8Array { + const writer = new jspb.BinaryWriter(); + HivePublicKeys.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); + } + + toObject(): object { + return { + ownerKey: this.getOwnerKey(), + activeKey: this.getActiveKey(), + memoKey: this.getMemoKey(), + postingKey: this.getPostingKey(), + }; + } + static toObject(_: boolean, msg: HivePublicKeys): object { + return msg.toObject(); + } + + static deserializeBinary(bytes: Uint8Array): HivePublicKeys { + const reader = new jspb.BinaryReader(bytes); + const msg = new HivePublicKeys(); + return HivePublicKeys.deserializeBinaryFromReader(msg, reader); + } + + static deserializeBinaryFromReader(msg: HivePublicKeys, reader: jspb.BinaryReader): HivePublicKeys { + while (reader.nextField()) { + if (reader.isEndGroup()) break; + switch (reader.getFieldNumber()) { + case 1: + msg.setOwnerKey(reader.readString()); + break; + case 2: + msg.setActiveKey(reader.readString()); + break; + case 3: + msg.setMemoKey(reader.readString()); + break; + case 4: + msg.setPostingKey(reader.readString()); + break; + default: + reader.skipField(); + } + } + return msg; + } + + static serializeBinaryToWriter(message: HivePublicKeys, writer: jspb.BinaryWriter): void { + for (const [field, _getter] of [ + [1, "getOwnerKey"], + [2, "getActiveKey"], + [3, "getMemoKey"], + [4, "getPostingKey"], + ] as const) { + const v = jspb.Message.getField(message, field as number) as string | null; + if (v != null) writer.writeString(field as number, v); + } + } +} + +/** + * HiveSignAccountCreate: address_n(1,repeated), chain_id(2,bytes), ref_block_num(3), + * ref_block_prefix(4), expiration(5), creator(6), new_account_name(7), owner_key(8), + * active_key(9), posting_key(10), memo_key(11), fee_amount(12,uint64) + */ +export class HiveSignAccountCreate extends jspb.Message { + static repeatedFields_ = [1]; + + constructor(opt_data?: any) { + super(); + jspb.Message.initialize(this, opt_data || [], 0, -1, HiveSignAccountCreate.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); + } + + 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); + } + + 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); + } + getExpiration(): number { + return jspb.Message.getFieldWithDefault(this, 5, 0) as number; + } + setExpiration(value: number): void { + jspb.Message.setField(this, 5, value); + } + getCreator(): string { + return jspb.Message.getFieldWithDefault(this, 6, "") as string; + } + setCreator(value: string): void { + jspb.Message.setField(this, 6, value); + } + getNewAccountName(): string { + return jspb.Message.getFieldWithDefault(this, 7, "") as string; + } + setNewAccountName(value: string): void { + jspb.Message.setField(this, 7, value); + } + getOwnerKey(): string { + return jspb.Message.getFieldWithDefault(this, 8, "") as string; + } + setOwnerKey(value: string): void { + jspb.Message.setField(this, 8, value); + } + getActiveKey(): string { + return jspb.Message.getFieldWithDefault(this, 9, "") as string; + } + setActiveKey(value: string): void { + jspb.Message.setField(this, 9, value); + } + getPostingKey(): string { + return jspb.Message.getFieldWithDefault(this, 10, "") as string; + } + setPostingKey(value: string): void { + jspb.Message.setField(this, 10, value); + } + getMemoKey(): string { + return jspb.Message.getFieldWithDefault(this, 11, "") as string; + } + setMemoKey(value: string): void { + jspb.Message.setField(this, 11, value); + } + getFeeAmount(): number { + return jspb.Message.getFieldWithDefault(this, 12, 0) as number; + } + setFeeAmount(value: number): void { + jspb.Message.setField(this, 12, value); + } + + serializeBinary(): Uint8Array { + const writer = new jspb.BinaryWriter(); + HiveSignAccountCreate.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); + } + + toObject(): object { + return { + addressNList: this.getAddressNList(), + chainId: this.getChainId(), + refBlockNum: this.getRefBlockNum(), + refBlockPrefix: this.getRefBlockPrefix(), + expiration: this.getExpiration(), + creator: this.getCreator(), + newAccountName: this.getNewAccountName(), + ownerKey: this.getOwnerKey(), + activeKey: this.getActiveKey(), + postingKey: this.getPostingKey(), + memoKey: this.getMemoKey(), + feeAmount: this.getFeeAmount(), + }; + } + static toObject(_: boolean, msg: HiveSignAccountCreate): object { + return msg.toObject(); + } + + static deserializeBinary(bytes: Uint8Array): HiveSignAccountCreate { + const reader = new jspb.BinaryReader(bytes); + const msg = new HiveSignAccountCreate(); + return HiveSignAccountCreate.deserializeBinaryFromReader(msg, reader); + } + + static deserializeBinaryFromReader(msg: HiveSignAccountCreate, reader: jspb.BinaryReader): HiveSignAccountCreate { + while (reader.nextField()) { + if (reader.isEndGroup()) break; + switch (reader.getFieldNumber()) { + case 1: { + const values = reader.isDelimited() ? reader.readPackedUint32() : [reader.readUint32()]; + 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.setCreator(reader.readString()); + break; + case 7: + msg.setNewAccountName(reader.readString()); + break; + case 8: + msg.setOwnerKey(reader.readString()); + break; + case 9: + msg.setActiveKey(reader.readString()); + break; + case 10: + msg.setPostingKey(reader.readString()); + break; + case 11: + msg.setMemoKey(reader.readString()); + break; + case 12: + msg.setFeeAmount(reader.readUint64()); + break; + default: + reader.skipField(); + } + } + return msg; + } + + static serializeBinaryToWriter(message: HiveSignAccountCreate, writer: jspb.BinaryWriter): void { + const addressN = message.getAddressNList(); + if (addressN.length > 0) writer.writeRepeatedUint32(1, addressN); + const chainId = message.getChainId_asU8(); + if (chainId.length > 0) writer.writeBytes(2, chainId); + const u32 = (f: number) => jspb.Message.getField(message, f) as number | null; + const str = (f: number) => jspb.Message.getField(message, f) as string | null; + if (u32(3) != null) writer.writeUint32(3, u32(3)!); + if (u32(4) != null) writer.writeUint32(4, u32(4)!); + if (u32(5) != null) writer.writeUint32(5, u32(5)!); + if (str(6) != null) writer.writeString(6, str(6)!); + if (str(7) != null) writer.writeString(7, str(7)!); + if (str(8) != null) writer.writeString(8, str(8)!); + if (str(9) != null) writer.writeString(9, str(9)!); + if (str(10) != null) writer.writeString(10, str(10)!); + if (str(11) != null) writer.writeString(11, str(11)!); + if (u32(12) != null) writer.writeUint64(12, u32(12)!); + } +} + +/** + * HiveSignAccountUpdate: address_n(1,repeated), chain_id(2,bytes), ref_block_num(3), + * ref_block_prefix(4), expiration(5), account(6), new_owner_key(7), new_active_key(8), + * new_posting_key(9), new_memo_key(10) + */ +export class HiveSignAccountUpdate extends jspb.Message { + static repeatedFields_ = [1]; + + constructor(opt_data?: any) { + super(); + jspb.Message.initialize(this, opt_data || [], 0, -1, HiveSignAccountUpdate.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); + } + + 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); + } + + 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); + } + getExpiration(): number { + return jspb.Message.getFieldWithDefault(this, 5, 0) as number; + } + setExpiration(value: number): void { + jspb.Message.setField(this, 5, value); + } + getAccount(): string { + return jspb.Message.getFieldWithDefault(this, 6, "") as string; + } + setAccount(value: string): void { + jspb.Message.setField(this, 6, value); + } + getNewOwnerKey(): string { + return jspb.Message.getFieldWithDefault(this, 7, "") as string; + } + setNewOwnerKey(value: string): void { + jspb.Message.setField(this, 7, value); + } + getNewActiveKey(): string { + return jspb.Message.getFieldWithDefault(this, 8, "") as string; + } + setNewActiveKey(value: string): void { + jspb.Message.setField(this, 8, value); + } + getNewPostingKey(): string { + return jspb.Message.getFieldWithDefault(this, 9, "") as string; + } + setNewPostingKey(value: string): void { + jspb.Message.setField(this, 9, value); + } + getNewMemoKey(): string { + return jspb.Message.getFieldWithDefault(this, 10, "") as string; + } + setNewMemoKey(value: string): void { + jspb.Message.setField(this, 10, value); + } + + serializeBinary(): Uint8Array { + const writer = new jspb.BinaryWriter(); + HiveSignAccountUpdate.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); + } + + toObject(): object { + return { + addressNList: this.getAddressNList(), + chainId: this.getChainId(), + refBlockNum: this.getRefBlockNum(), + refBlockPrefix: this.getRefBlockPrefix(), + expiration: this.getExpiration(), + account: this.getAccount(), + newOwnerKey: this.getNewOwnerKey(), + newActiveKey: this.getNewActiveKey(), + newPostingKey: this.getNewPostingKey(), + newMemoKey: this.getNewMemoKey(), + }; + } + static toObject(_: boolean, msg: HiveSignAccountUpdate): object { + return msg.toObject(); + } + + static deserializeBinary(bytes: Uint8Array): HiveSignAccountUpdate { + const reader = new jspb.BinaryReader(bytes); + const msg = new HiveSignAccountUpdate(); + return HiveSignAccountUpdate.deserializeBinaryFromReader(msg, reader); + } + + static deserializeBinaryFromReader(msg: HiveSignAccountUpdate, reader: jspb.BinaryReader): HiveSignAccountUpdate { + while (reader.nextField()) { + if (reader.isEndGroup()) break; + switch (reader.getFieldNumber()) { + case 1: { + const values = reader.isDelimited() ? reader.readPackedUint32() : [reader.readUint32()]; + 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.setAccount(reader.readString()); + break; + case 7: + msg.setNewOwnerKey(reader.readString()); + break; + case 8: + msg.setNewActiveKey(reader.readString()); + break; + case 9: + msg.setNewPostingKey(reader.readString()); + break; + case 10: + msg.setNewMemoKey(reader.readString()); + break; + default: + reader.skipField(); + } + } + return msg; + } + + static serializeBinaryToWriter(message: HiveSignAccountUpdate, writer: jspb.BinaryWriter): void { + const addressN = message.getAddressNList(); + if (addressN.length > 0) writer.writeRepeatedUint32(1, addressN); + const chainId = message.getChainId_asU8(); + if (chainId.length > 0) writer.writeBytes(2, chainId); + const u32 = (f: number) => jspb.Message.getField(message, f) as number | null; + const str = (f: number) => jspb.Message.getField(message, f) as string | null; + if (u32(3) != null) writer.writeUint32(3, u32(3)!); + if (u32(4) != null) writer.writeUint32(4, u32(4)!); + if (u32(5) != null) writer.writeUint32(5, u32(5)!); + if (str(6) != null) writer.writeString(6, str(6)!); + if (str(7) != null) writer.writeString(7, str(7)!); + if (str(8) != null) writer.writeString(8, str(8)!); + if (str(9) != null) writer.writeString(9, str(9)!); + if (str(10) != null) writer.writeString(10, str(10)!); + } +} + +/** + * HiveSignedAccountCreate / HiveSignedAccountUpdate: signature(1,bytes), serialized_tx(2,bytes). + * Identical shape to HiveSignedTx. + */ +export class HiveSignedAccountCreate extends jspb.Message { + constructor(opt_data?: any) { + super(); + jspb.Message.initialize(this, opt_data || [], 0, -1, null, null); + } + getSignature_asU8(): Uint8Array { + const val = jspb.Message.getFieldWithDefault(this, 1, "") as Uint8Array | string; + return val instanceof Uint8Array ? val : jspb.Message.bytesAsU8(val as string); + } + setSignature(value: Uint8Array | string): void { + jspb.Message.setField(this, 1, value); + } + getSerializedTx_asU8(): Uint8Array { + const val = jspb.Message.getFieldWithDefault(this, 2, "") as Uint8Array | string; + return val instanceof Uint8Array ? val : jspb.Message.bytesAsU8(val as string); + } + setSerializedTx(value: Uint8Array | string): void { + jspb.Message.setField(this, 2, value); + } + + serializeBinary(): Uint8Array { + const writer = new jspb.BinaryWriter(); + HiveSignedAccountCreate.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); + } + toObject(): object { + return { signature: this.getSignature_asU8(), serializedTx: this.getSerializedTx_asU8() }; + } + static toObject(_: boolean, msg: HiveSignedAccountCreate): object { + return msg.toObject(); + } + static deserializeBinary(bytes: Uint8Array): HiveSignedAccountCreate { + const reader = new jspb.BinaryReader(bytes); + return HiveSignedAccountCreate.deserializeBinaryFromReader(new HiveSignedAccountCreate(), reader); + } + static deserializeBinaryFromReader(msg: HiveSignedAccountCreate, reader: jspb.BinaryReader): HiveSignedAccountCreate { + 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(); + } + } + return msg; + } + static serializeBinaryToWriter(message: HiveSignedAccountCreate, writer: jspb.BinaryWriter): void { + const sig = message.getSignature_asU8(); + if (sig.length > 0) writer.writeBytes(1, sig); + const tx = message.getSerializedTx_asU8(); + if (tx.length > 0) writer.writeBytes(2, tx); + } +} + +export class HiveSignedAccountUpdate extends jspb.Message { + constructor(opt_data?: any) { + super(); + jspb.Message.initialize(this, opt_data || [], 0, -1, null, null); + } + getSignature_asU8(): Uint8Array { + const val = jspb.Message.getFieldWithDefault(this, 1, "") as Uint8Array | string; + return val instanceof Uint8Array ? val : jspb.Message.bytesAsU8(val as string); + } + setSignature(value: Uint8Array | string): void { + jspb.Message.setField(this, 1, value); + } + getSerializedTx_asU8(): Uint8Array { + const val = jspb.Message.getFieldWithDefault(this, 2, "") as Uint8Array | string; + return val instanceof Uint8Array ? val : jspb.Message.bytesAsU8(val as string); + } + setSerializedTx(value: Uint8Array | string): void { + jspb.Message.setField(this, 2, value); + } + + serializeBinary(): Uint8Array { + const writer = new jspb.BinaryWriter(); + HiveSignedAccountUpdate.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); + } + toObject(): object { + return { signature: this.getSignature_asU8(), serializedTx: this.getSerializedTx_asU8() }; + } + static toObject(_: boolean, msg: HiveSignedAccountUpdate): object { + return msg.toObject(); + } + static deserializeBinary(bytes: Uint8Array): HiveSignedAccountUpdate { + const reader = new jspb.BinaryReader(bytes); + return HiveSignedAccountUpdate.deserializeBinaryFromReader(new HiveSignedAccountUpdate(), reader); + } + static deserializeBinaryFromReader(msg: HiveSignedAccountUpdate, reader: jspb.BinaryReader): HiveSignedAccountUpdate { + 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(); + } + } + return msg; + } + static serializeBinaryToWriter(message: HiveSignedAccountUpdate, writer: jspb.BinaryWriter): void { + const sig = message.getSignature_asU8(); + if (sig.length > 0) writer.writeBytes(1, sig); + const tx = message.getSerializedTx_asU8(); + if (tx.length > 0) writer.writeBytes(2, tx); + } +} + // ── Runtime Registration ─────────────────────────────────────────────── function registerHiveMessages() { @@ -449,16 +1111,34 @@ function registerHiveMessages() { mt["MESSAGETYPE_HIVEPUBLICKEY"] = MESSAGETYPE_HIVEPUBLICKEY; mt["MESSAGETYPE_HIVESIGNTX"] = MESSAGETYPE_HIVESIGNTX; mt["MESSAGETYPE_HIVESIGNEDTX"] = MESSAGETYPE_HIVESIGNEDTX; + mt["MESSAGETYPE_HIVEGETPUBLICKEYS"] = MESSAGETYPE_HIVEGETPUBLICKEYS; + mt["MESSAGETYPE_HIVEPUBLICKEYS"] = MESSAGETYPE_HIVEPUBLICKEYS; + mt["MESSAGETYPE_HIVESIGNACCOUNTCREATE"] = MESSAGETYPE_HIVESIGNACCOUNTCREATE; + mt["MESSAGETYPE_HIVESIGNEDACCOUNTCREATE"] = MESSAGETYPE_HIVESIGNEDACCOUNTCREATE; + mt["MESSAGETYPE_HIVESIGNACCOUNTUPDATE"] = MESSAGETYPE_HIVESIGNACCOUNTUPDATE; + mt["MESSAGETYPE_HIVESIGNEDACCOUNTUPDATE"] = MESSAGETYPE_HIVESIGNEDACCOUNTUPDATE; messageNameRegistry[MESSAGETYPE_HIVEGETPUBLICKEY] = "HiveGetPublicKey"; messageNameRegistry[MESSAGETYPE_HIVEPUBLICKEY] = "HivePublicKey"; messageNameRegistry[MESSAGETYPE_HIVESIGNTX] = "HiveSignTx"; messageNameRegistry[MESSAGETYPE_HIVESIGNEDTX] = "HiveSignedTx"; + messageNameRegistry[MESSAGETYPE_HIVEGETPUBLICKEYS] = "HiveGetPublicKeys"; + messageNameRegistry[MESSAGETYPE_HIVEPUBLICKEYS] = "HivePublicKeys"; + messageNameRegistry[MESSAGETYPE_HIVESIGNACCOUNTCREATE] = "HiveSignAccountCreate"; + messageNameRegistry[MESSAGETYPE_HIVESIGNEDACCOUNTCREATE] = "HiveSignedAccountCreate"; + messageNameRegistry[MESSAGETYPE_HIVESIGNACCOUNTUPDATE] = "HiveSignAccountUpdate"; + messageNameRegistry[MESSAGETYPE_HIVESIGNEDACCOUNTUPDATE] = "HiveSignedAccountUpdate"; 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_HIVEGETPUBLICKEYS] = HiveGetPublicKeys as any; + messageTypeRegistry[MESSAGETYPE_HIVEPUBLICKEYS] = HivePublicKeys as any; + messageTypeRegistry[MESSAGETYPE_HIVESIGNACCOUNTCREATE] = HiveSignAccountCreate as any; + messageTypeRegistry[MESSAGETYPE_HIVESIGNEDACCOUNTCREATE] = HiveSignedAccountCreate as any; + messageTypeRegistry[MESSAGETYPE_HIVESIGNACCOUNTUPDATE] = HiveSignAccountUpdate as any; + messageTypeRegistry[MESSAGETYPE_HIVESIGNEDACCOUNTUPDATE] = HiveSignedAccountUpdate as any; } registerHiveMessages(); @@ -524,3 +1204,101 @@ export async function hiveSignTx(transport: Transport, msg: core.HiveSignTx): Pr }; }); } + +function toChainIdBytes(chainId: Uint8Array | string): Uint8Array { + if (chainId instanceof Uint8Array) return chainId; + if (typeof chainId === "string") return core.fromHexString(chainId); + return new Uint8Array(chainId as any); +} + +export async function hiveGetPublicKeys( + transport: Transport, + msg: core.HiveGetPublicKeys +): Promise { + const req = new HiveGetPublicKeys(); + if (msg.accountIndex !== undefined) req.setAccountIndex(msg.accountIndex); + if (msg.showDisplay !== undefined) req.setShowDisplay(msg.showDisplay); + + const response = await transport.call(MESSAGETYPE_HIVEGETPUBLICKEYS, req, { + msgTimeout: core.LONG_TIMEOUT, + }); + + if (response.message_enum !== MESSAGETYPE_HIVEPUBLICKEYS) { + throw new Error(`hive: unexpected response ${response.message_type}`); + } + const resp = response.proto as HivePublicKeys; + return { + ownerKey: core.mustBeDefined(resp.getOwnerKey()), + activeKey: core.mustBeDefined(resp.getActiveKey()), + memoKey: core.mustBeDefined(resp.getMemoKey()), + postingKey: core.mustBeDefined(resp.getPostingKey()), + }; +} + +export async function hiveSignAccountCreate( + transport: Transport, + msg: core.HiveSignAccountCreate +): Promise { + return transport.lockDuring(async () => { + const req = new HiveSignAccountCreate(); + req.setAddressNList(msg.addressNList); + if (msg.chainId !== undefined) req.setChainId(toChainIdBytes(msg.chainId)); + req.setRefBlockNum(msg.refBlockNum); + req.setRefBlockPrefix(msg.refBlockPrefix); + req.setExpiration(msg.expiration); + req.setCreator(msg.creator); + req.setNewAccountName(msg.newAccountName); + req.setOwnerKey(msg.ownerKey); + req.setActiveKey(msg.activeKey); + req.setPostingKey(msg.postingKey); + req.setMemoKey(msg.memoKey); + req.setFeeAmount(msg.feeAmount); + + const resp = await transport.call(MESSAGETYPE_HIVESIGNACCOUNTCREATE, req, { + msgTimeout: core.LONG_TIMEOUT, + omitLock: true, + }); + + if (resp.message_enum !== MESSAGETYPE_HIVESIGNEDACCOUNTCREATE) { + throw new Error(`hive: unexpected response ${resp.message_type}`); + } + const signed = resp.proto as HiveSignedAccountCreate; + return { + signature: signed.getSignature_asU8(), + serializedTx: signed.getSerializedTx_asU8(), + }; + }); +} + +export async function hiveSignAccountUpdate( + transport: Transport, + msg: core.HiveSignAccountUpdate +): Promise { + return transport.lockDuring(async () => { + const req = new HiveSignAccountUpdate(); + req.setAddressNList(msg.addressNList); + if (msg.chainId !== undefined) req.setChainId(toChainIdBytes(msg.chainId)); + req.setRefBlockNum(msg.refBlockNum); + req.setRefBlockPrefix(msg.refBlockPrefix); + req.setExpiration(msg.expiration); + req.setAccount(msg.account); + req.setNewOwnerKey(msg.newOwnerKey); + req.setNewActiveKey(msg.newActiveKey); + req.setNewPostingKey(msg.newPostingKey); + req.setNewMemoKey(msg.newMemoKey); + + const resp = await transport.call(MESSAGETYPE_HIVESIGNACCOUNTUPDATE, req, { + msgTimeout: core.LONG_TIMEOUT, + omitLock: true, + }); + + if (resp.message_enum !== MESSAGETYPE_HIVESIGNEDACCOUNTUPDATE) { + throw new Error(`hive: unexpected response ${resp.message_type}`); + } + const signed = resp.proto as HiveSignedAccountUpdate; + return { + signature: signed.getSignature_asU8(), + serializedTx: signed.getSerializedTx_asU8(), + }; + }); +} diff --git a/packages/hdwallet-keepkey/src/keepkey.ts b/packages/hdwallet-keepkey/src/keepkey.ts index 774ae456..430434ac 100644 --- a/packages/hdwallet-keepkey/src/keepkey.ts +++ b/packages/hdwallet-keepkey/src/keepkey.ts @@ -1574,10 +1574,22 @@ export class KeepKeyHDWallet implements core.HDWallet, core.BTCWallet, core.ETHW return Hive.hiveGetPublicKey(this.transport, msg); } + public hiveGetPublicKeys(msg: core.HiveGetPublicKeys): Promise { + return Hive.hiveGetPublicKeys(this.transport, msg); + } + public hiveSignTx(msg: core.HiveSignTx): Promise { return Hive.hiveSignTx(this.transport, msg); } + public hiveSignAccountCreate(msg: core.HiveSignAccountCreate): Promise { + return Hive.hiveSignAccountCreate(this.transport, msg); + } + + public hiveSignAccountUpdate(msg: core.HiveSignAccountUpdate): Promise { + return Hive.hiveSignAccountUpdate(this.transport, msg); + } + public describePath(msg: core.DescribePath): core.PathDescription { return this.info.describePath(msg); } From c6231572135cfcbce0f94d33b6602d10dfe01f65 Mon Sep 17 00:00:00 2001 From: highlander Date: Sun, 28 Jun 2026 18:38:39 -0500 Subject: [PATCH 2/2] test(hive): prettier-fix hive.test.ts + cover account_update wrapper - Run prettier on hive.test.ts (was missed; CI lint failed on trailing commas at lines 80/94/119/148/216) - Add hiveSignAccountUpdate wrapper tests (success + unexpected-response), guarding the 1608/1609 enum/response-class wiring that was untested --- packages/hdwallet-keepkey/src/hive.test.ts | 55 ++++++++++++++++++++-- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/packages/hdwallet-keepkey/src/hive.test.ts b/packages/hdwallet-keepkey/src/hive.test.ts index adc8a087..7d2137aa 100644 --- a/packages/hdwallet-keepkey/src/hive.test.ts +++ b/packages/hdwallet-keepkey/src/hive.test.ts @@ -22,7 +22,9 @@ import { HiveSignAccountCreate, hiveSignAccountCreate, HiveSignAccountUpdate, + hiveSignAccountUpdate, HiveSignedAccountCreate, + HiveSignedAccountUpdate, } from "./hive"; import { messageNameRegistry, messageTypeRegistry } from "./typeRegistry"; @@ -77,7 +79,7 @@ describe("Hive jspb round-trip", () => { m.setShowDisplay(true); const decoded = HiveGetPublicKeys.deserializeBinaryFromReader( new HiveGetPublicKeys(), - new jspb.BinaryReader(m.serializeBinary()), + new jspb.BinaryReader(m.serializeBinary()) ); expect(decoded.getAccountIndex()).toBe(3); expect(decoded.getShowDisplay()).toBe(true); @@ -91,7 +93,7 @@ describe("Hive jspb round-trip", () => { m.setPostingKey("STM_posting"); const decoded = HivePublicKeys.deserializeBinaryFromReader( new HivePublicKeys(), - new jspb.BinaryReader(m.serializeBinary()), + new jspb.BinaryReader(m.serializeBinary()) ); expect(decoded.getOwnerKey()).toBe("STM_owner"); expect(decoded.getActiveKey()).toBe("STM_active"); @@ -116,7 +118,7 @@ describe("Hive jspb round-trip", () => { const decoded = HiveSignAccountCreate.deserializeBinaryFromReader( new HiveSignAccountCreate(), - new jspb.BinaryReader(m.serializeBinary()), + new jspb.BinaryReader(m.serializeBinary()) ); expect(decoded.getAddressNList()).toEqual(OWNER_PATH); expect(Array.from(decoded.getChainId_asU8())).toEqual([0xbe, 0xea, 0xb0, 0xde]); @@ -145,7 +147,7 @@ describe("Hive jspb round-trip", () => { m.setNewMemoKey("STM_nm"); const decoded = HiveSignAccountUpdate.deserializeBinaryFromReader( new HiveSignAccountUpdate(), - new jspb.BinaryReader(m.serializeBinary()), + new jspb.BinaryReader(m.serializeBinary()) ); expect(decoded.getAccount()).toBe("alice"); expect(decoded.getNewOwnerKey()).toBe("STM_no"); @@ -213,7 +215,50 @@ describe("Hive wrappers", () => { postingKey: "STM_p", memoKey: "STM_m", feeAmount: 3000, - }), + }) + ).rejects.toThrow(/unexpected response/); + }); + + it("hiveSignAccountUpdate returns signature + serializedTx on success", async () => { + const signed = new HiveSignedAccountUpdate(); + signed.setSignature(new Uint8Array([7, 8, 9])); + signed.setSerializedTx(new Uint8Array([10, 11, 12])); + const call = jest.fn().mockResolvedValue({ + message_enum: MESSAGETYPE_HIVESIGNEDACCOUNTUPDATE, + message_type: "HiveSignedAccountUpdate", + proto: signed, + }); + const out = await hiveSignAccountUpdate(makeMockTransport(call), { + addressNList: OWNER_PATH, + refBlockNum: 1, + refBlockPrefix: 2, + expiration: 3, + account: "alice", + newOwnerKey: "STM_no", + newActiveKey: "STM_na", + newPostingKey: "STM_np", + newMemoKey: "STM_nm", + }); + expect(Array.from(out.signature)).toEqual([7, 8, 9]); + expect(Array.from(out.serializedTx)).toEqual([10, 11, 12]); + // Guards the request enum / response-class wiring for 1608/1609. + expect(call).toHaveBeenCalledWith(MESSAGETYPE_HIVESIGNACCOUNTUPDATE, expect.anything(), expect.anything()); + }); + + it("hiveSignAccountUpdate throws on unexpected response", async () => { + const call = jest.fn().mockResolvedValue({ message_enum: 9999, message_type: "Other", proto: {} }); + await expect( + hiveSignAccountUpdate(makeMockTransport(call), { + addressNList: OWNER_PATH, + refBlockNum: 1, + refBlockPrefix: 2, + expiration: 3, + account: "alice", + newOwnerKey: "STM_no", + newActiveKey: "STM_na", + newPostingKey: "STM_np", + newMemoKey: "STM_nm", + }) ).rejects.toThrow(/unexpected response/); }); });