Skip to content
This repository has been archived by the owner on Nov 23, 2023. It is now read-only.

Expose initial support for Hedera #449

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
94 changes: 94 additions & 0 deletions src/data/messages/messages.json
Expand Up @@ -4237,6 +4237,84 @@
"options": {},
"oneofs": {}
},
{
"name": "HederaGetPublicKey",
"fields": [
{
"rule": "repeated",
"options": {},
"type": "uint32",
"name": "address_n",
"id": 1
},
{
"rule": "optional",
"options": {},
"type": "bool",
"name": "show_display",
"id": 2
}
],
"enums": [],
"messages": [],
"options": {},
"oneofs": {}
},
{
"name": "HederaPublicKey",
"fields": [
{
"rule": "optional",
"options": {},
"type": "bytes",
"name": "public_key",
"id": 1
}
],
"enums": [],
"messages": [],
"options": {},
"oneofs": {}
},
{
"name": "HederaSignTx",
"fields": [
{
"rule": "repeated",
"options": {},
"type": "uint32",
"name": "address_n",
"id": 1
},
{
"rule": "optional",
"options": {},
"type": "bytes",
"name": "transaction",
"id": 2
}
],
"enums": [],
"messages": [],
"options": {},
"oneofs": {}
},
{
"name": "HederaSignedTx",
"fields": [
{
"rule": "optional",
"options": {},
"type": "bytes",
"name": "signature",
"id": 1
}
],
"enums": [],
"messages": [],
"options": {},
"oneofs": {}
},
{
"name": "Features",
"fields": [
Expand Down Expand Up @@ -9364,6 +9442,22 @@
{
"name": "MessageType_BinanceSignedTx",
"id": 709
},
{
"name": "MessageType_HederaSignTx",
"id": 800
},
{
"name": "MessageType_HederaSignedTx",
"id": 801
},
{
"name": "MessageType_HederaGetPublicKey",
"id": 802
},
{
"name": "MessageType_HederaPublicKey",
"id": 803
}
],
"options": {}
Expand Down
119 changes: 119 additions & 0 deletions src/js/core/methods/HederaGetPublicKey.js
@@ -0,0 +1,119 @@
/* @flow */
'use strict';

import AbstractMethod from './AbstractMethod';
import { validateParams, getFirmwareRange } from './helpers/paramsValidator';
import { getMiscNetwork } from '../../data/CoinInfo';
import { validatePath, fromHardened, getSerializedPath } from '../../utils/pathUtils';

import * as UI from '../../constants/ui';
import { UiMessage } from '../../message/builder';

import type { HederaPublicKey } from '../../types/trezor';
import type { CoreMessage, UiPromiseResponse } from '../../types';
import type { HederaPublicKey as HederaPublicKeyResponse } from '../../types/hedera';

type Batch = {
path: Array<number>,
showOnTrezor: boolean,
}

type Params = Array<Batch>;

export default class HederaGetPublicKey extends AbstractMethod {
params: Params;
hasBundle: boolean;
confirmed: boolean = false;

constructor(message: CoreMessage) {
super(message);

this.requiredPermissions = ['read'];
this.firmwareRange = getFirmwareRange(this.name, getMiscNetwork('Hedera'), this.firmwareRange);
this.info = 'Export Hedera public key';

// create a bundle with only one batch if bundle doesn't exists
this.hasBundle = message.payload.hasOwnProperty('bundle');
const payload: Object = !this.hasBundle ? { ...message.payload, bundle: [ ...message.payload ] } : message.payload;

// validate bundle type
validateParams(payload, [
{ name: 'bundle', type: 'array' },
]);

const bundle = [];
payload.bundle.forEach(batch => {
// validate incoming parameters for each batch
validateParams(batch, [
{ name: 'path', obligatory: true },
{ name: 'showOnTrezor', type: 'boolean' },
]);

const path: Array<number> = validatePath(batch.path, 3);
let showOnTrezor: boolean = false;
if (batch.hasOwnProperty('showOnTrezor')) {
showOnTrezor = batch.showOnTrezor;
}

bundle.push({
path,
showOnTrezor,
});
});

this.params = bundle;
}

async confirmation(): Promise<boolean> {
if (this.confirmed) return true;
// wait for popup window
await this.getPopupPromise().promise;
// initialize user response promise
const uiPromise = this.createUiPromise(UI.RECEIVE_CONFIRMATION, this.device);

let label: string;
if (this.params.length > 1) {
label = 'Export multiple Hedera public keys';
} else {
label = `Export Hedera public key for account #${ (fromHardened(this.params[0].path[2]) + 1) }`;
}

// request confirmation view
this.postMessage(new UiMessage(UI.REQUEST_CONFIRMATION, {
view: 'export-xpub',
label,
}));

// wait for user action
const uiResp: UiPromiseResponse = await uiPromise.promise;

this.confirmed = uiResp.payload;
return this.confirmed;
}

async run(): Promise<HederaPublicKeyResponse | Array<HederaPublicKeyResponse>> {
const responses: Array<HederaPublicKeyResponse> = [];

for (let i = 0; i < this.params.length; i++) {
const batch: Batch = this.params[i];
const response: HederaPublicKey = await this.device.getCommands().hederaGetPublicKey(
batch.path,
batch.showOnTrezor
);
responses.push({
publicKey: response.public_key,
path: batch.path,
serializedPath: getSerializedPath(batch.path),
});

if (this.hasBundle) {
// send progress
this.postMessage(new UiMessage(UI.BUNDLE_PROGRESS, {
progress: i,
response,
}));
}
}
return this.hasBundle ? responses : responses[0];
}
}
50 changes: 50 additions & 0 deletions src/js/core/methods/HederaSignTransaction.js
@@ -0,0 +1,50 @@
/* @flow */
'use strict';

import AbstractMethod from './AbstractMethod';
import { validateParams, getFirmwareRange } from './helpers/paramsValidator';
import { getMiscNetwork } from '../../data/CoinInfo';
import { validatePath } from '../../utils/pathUtils';

import type { CoreMessage } from '../../types';
import type { HederaSignedTx } from '../../types/trezor';

type Params = {
path: Array<number>,
transaction: string,
}

export default class HederaSignTransaction extends AbstractMethod {
params: Params;

constructor(message: CoreMessage) {
super(message);

this.requiredPermissions = ['read', 'write'];
this.firmwareRange = getFirmwareRange(this.name, getMiscNetwork('Hedera'), this.firmwareRange);

const payload: Object = message.payload;

// validate incoming parameters
validateParams(payload, [
{ name: 'path', obligatory: true },
{ name: 'transaction', type: 'string', obligatory: true },
]);

const path: Array<number> = validatePath(payload.path, 3);

this.info = 'Sign Hedera Transaction';

this.params = {
path,
transaction: payload.transaction,
};
}

async run(): Promise<HederaSignedTx> {
return await this.device.getCommands().hederaSignTx(
this.params.path,
this.params.transaction,
);
}
}
4 changes: 4 additions & 0 deletions src/js/core/methods/index.js
Expand Up @@ -28,6 +28,8 @@ import GetDeviceState from './GetDeviceState';
import GetFeatures from './GetFeatures';
import GetPublicKey from './GetPublicKey';
import GetSettings from './GetSettings';
import HederaGetPublicKey from './HederaGetPublicKey';
import HederaSignTransaction from './HederaSignTransaction';
import LiskGetAddress from './LiskGetAddress';
import LiskGetPublicKey from './LiskGetPublicKey';
import LiskSignMessage from './LiskSignMessage';
Expand Down Expand Up @@ -85,6 +87,8 @@ const classes: {[k: string]: any} = {
'getFeatures': GetFeatures,
'getPublicKey': GetPublicKey,
'getSettings': GetSettings,
'hederaGetPublicKey': HederaGetPublicKey,
'hederaSignTransaction': HederaSignTransaction,
'liskGetAddress': LiskGetAddress,
'liskGetPublicKey': LiskGetPublicKey,
'liskSignMessage': LiskSignMessage,
Expand Down
18 changes: 18 additions & 0 deletions src/js/device/DeviceCommands.js
Expand Up @@ -391,6 +391,24 @@ export default class DeviceCommands {
// CardanoSignTx message can be found inside ./core/methods/helpers/cardanoSignTx
// Cardano: end

// Hedera: begin
async hederaGetPublicKey(address_n: Array<number>, showOnTrezor: boolean): Promise<trezor.HederaPublicKey> {
const response: MessageResponse<trezor.HederaPublicKey> = await this.typedCall('HederaGetPublicKey', 'HederaPublicKey', {
address_n,
show_display: !!showOnTrezor,
});
return response.message;
}

async hederaSignTx(address_n: Array<number>, transaction: string): Promise<trezor.HederaSignedTx> {
const response: MessageResponse<trezor.HederaSignedTx> = await this.typedCall('HederaSignTx', 'HederaSignedTx', {
address_n,
transaction,
});
return response.message;
}
// Hedera: end

// Lisk: begin
async liskGetAddress(address_n: Array<number>, showOnTrezor: boolean): Promise<trezor.LiskAddress> {
const response: MessageResponse<trezor.LiskAddress> = await this.typedCall('LiskGetAddress', 'LiskAddress', {
Expand Down
8 changes: 8 additions & 0 deletions src/js/index.js
Expand Up @@ -149,6 +149,14 @@ class TrezorConnect {
return await call({ method: 'getPublicKey', ...params });
}

static hederaGetPublicKey: $T.HederaGetPublicKey = async (params) => {
return await call({ method: 'hederaGetPublicKey', ...params });
}

static hederaSignTransaction: $T.HederaSignTx = async (params) => {
return await call({ method: 'hederaSignTransaction', ...params });
}

static liskGetAddress: $T.LiskGetAddress = async (params) => {
const useEventListener = eventEmitter.listenerCount(UI.ADDRESS_VALIDATION) > 0;
return await call({ method: 'liskGetAddress', ...params, useEventListener });
Expand Down
47 changes: 47 additions & 0 deletions src/js/types/hedera.js
@@ -0,0 +1,47 @@
/* @flow */
// Hedera types

import type { $Path, $Common } from './params';
import type { Unsuccessful$ } from './response';
import type { HederaSignedTx } from './trezor';

export type SignatureAsset = {
signature: {
publicKey: string,
},
}

export type $HederaGetPublicKey = $Common & {
path: $Path,
showOnTrezor?: boolean,
}

export type HederaPublicKey = {
path: Array<number>,
serializedPath: string,
publicKey: string,
}

// get public key

export type HederaGetPublicKey$ = {
success: true,
payload: HederaPublicKey,
} | Unsuccessful$;

export type HederaGetPublicKey$$ = {
success: true,
payload: Array<HederaPublicKey>,
} | Unsuccessful$;

// sign transaction

export type $HederaSignTransaction = $Common & {
path: $Path,
transaction: string,
}

export type HederaSignTransaction$ = {
success: true,
payload: HederaSignedTx,
} | Unsuccessful$
6 changes: 6 additions & 0 deletions src/js/types/index.js
Expand Up @@ -246,6 +246,12 @@ declare function F_GetPublicKey(params: (P.$Common & P.$GetPublicKey)): Promise<
declare function F_GetPublicKey(params: (P.$Common & { bundle: Array<P.$GetPublicKey> })): Promise<R.GetPublicKey$$>;
export type GetPublicKey = typeof F_GetPublicKey;

declare function F_HederaGetPublicKey(params: (P.$Common & HEDERA.$HederaGetPublicKey)): Promise<HEDERA.HederaGetPublicKey$>;
declare function F_HederaGetPublicKey(params: (P.$Common & { bundle: Array<HEDERA.$HederaGetPublicKey> })): Promise<HEDERA.HederaGetPublicKey$$>;
export type HederaGetPublicKey = typeof F_HederaGetPublicKey;

export type HederaSignTransaction = (HEDERA.$HederaSignTransaction) => Promise<HEDERA.HederaSignTransaction$>

declare function F_LiskGetAddress(params: (P.$Common & LISK.$LiskGetAddress)): Promise<LISK.LiskGetAddress$>;
declare function F_LiskGetAddress(params: (P.$Common & { bundle: Array<LISK.$LiskGetAddress> })): Promise<LISK.LiskGetAddress$$>;
export type LiskGetAddress = typeof F_LiskGetAddress;
Expand Down