UNPKG

@avalabs/hw-app-avalanche

Version:

Node API for Avalanche App (Ledger Nano S/X/S+)

997 lines (858 loc) 28.3 kB
/** ****************************************************************************** * (c) 2019-2020 Zondax GmbH * (c) 2016-2017 Ledger * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************* */ import Transport from "@ledgerhq/hw-transport"; import { CHAIN_ID_SIZE, CHUNK_SIZE, CLA, COLLECTION_NAME_MAX_LEN, ADDRESS_LENGTH, ALGORITHM_ID_1, ALGORITHM_ID_SIZE, TYPE_SIZE, VERSION_SIZE, SIGNATURE_LENGTH_SIZE, errorCodeToString, FIRST_MESSAGE, getVersion, HASH_LEN, INS, LAST_MESSAGE, LedgerError, NEXT_MESSAGE, P1_VALUES, PAYLOAD_TYPE, processErrorResponse, TYPE_1, VERSION_1, P2_VALUES, ED25519_PK_SIZE, } from "./common"; import { pathCoinType, serializeChainID, serializeHrp, serializePath, serializePathSuffix, } from "./helper"; import { ResponseAddress, ResponseAppInfo, ResponseSign, ResponseVersion, ResponseWalletId, ResponseXPub, } from "./types"; import Eth from "@ledgerhq/hw-app-eth"; import { LedgerEthTransactionResolution, LoadConfig, ResolutionConfig, } from "@ledgerhq/hw-app-eth/lib/services/types"; import { EIP712Message } from "@ledgerhq/types-live"; export * from "./types"; export { LedgerError }; function processGetAddrResponse(response: Buffer) { let partialResponse = response; const errorCodeData = partialResponse.slice(-2); if (errorCodeData.length < 2) { throw new Error("Invalid response: missing error code data"); } const returnCode = errorCodeData[0]! * 256 + errorCodeData[1]!; //get public key len (variable) const PKLEN = partialResponse[0]; if (PKLEN === undefined) { throw new Error("Invalid response: missing public key length"); } const publicKey = Buffer.from(partialResponse.slice(1, 1 + PKLEN)); //"advance" buffer partialResponse = partialResponse.slice(1 + PKLEN); let hash: Buffer | undefined; let address: string; if (PKLEN != ED25519_PK_SIZE) { hash = Buffer.from(partialResponse.slice(0, 20)); //"advance" buffer partialResponse = partialResponse.slice(20); address = Buffer.from(partialResponse.subarray(0, -2)).toString(); } else { // ED25519: Convert raw bytes to hex string address = partialResponse.subarray(0, -2).toString("hex"); } return { publicKey, hash, address, returnCode, errorMessage: errorCodeToString(returnCode), }; } function processGetXPubResponse(response: Buffer) { let partialResponse = response; const errorCodeData = partialResponse.slice(-2); if (errorCodeData.length < 2) { throw new Error("Invalid response: missing error code data"); } const returnCode = errorCodeData[0]! * 256 + errorCodeData[1]!; //get public key len (variable) const PKLEN = partialResponse[0]; if (PKLEN === undefined) { throw new Error("Invalid response: missing public key length"); } const publicKey = Buffer.from(partialResponse.slice(1, 1 + PKLEN)); //"advance" buffer partialResponse = partialResponse.slice(1 + PKLEN); const chain_code = Buffer.from(partialResponse.slice(0, -2)); return { publicKey, chain_code, returnCode, errorMessage: errorCodeToString(returnCode), }; } export default class AvalancheApp { transport; private eth; constructor( transport: Transport, ethScrambleKey = "w0w", ethLoadConfig: LoadConfig = {}, ) { this.transport = transport; if (!transport) { throw new Error("Transport has not been defined"); } this.eth = new Eth(transport, ethScrambleKey, ethLoadConfig); } private static prepareChunks(message: Buffer, serializedPathBuffer?: Buffer) { const chunks = []; // First chunk (only path) if (serializedPathBuffer !== undefined) { // First chunk (only path) chunks.push(serializedPathBuffer); } const buffer = Buffer.from(message); for (let i = 0; i < buffer.length; i += CHUNK_SIZE) { let end = i + CHUNK_SIZE; if (i > buffer.length) { end = buffer.length; } chunks.push(buffer.slice(i, end)); } return chunks; } private async signGetChunks(message: Buffer, path?: string) { if (path === undefined) { return await AvalancheApp.prepareChunks(message, Buffer.alloc(0)); } return await AvalancheApp.prepareChunks(message, serializePath(path)); } private concatMessageAndChangePath( message: Buffer, path?: Array<string>, ): Buffer { // data const msg = message; // no change_path if (path === undefined) { const buffer = Buffer.alloc(1); buffer.writeUInt8(0); return Buffer.concat([new Uint8Array(buffer), new Uint8Array(msg)]); } let buffer = Buffer.alloc(1); buffer.writeUInt8(path.length); path.forEach((element) => { buffer = Buffer.concat([ new Uint8Array(buffer), new Uint8Array(serializePathSuffix(element)), ]); }); return Buffer.concat([new Uint8Array(buffer), new Uint8Array(msg)]); } private async signSendChunk( chunkIdx: number, chunkNum: number, chunk: Buffer, param?: number, ins: number = INS.SIGN, ): Promise<ResponseSign> { let payloadType = PAYLOAD_TYPE.ADD; let p2 = 0; if (chunkIdx === 1) { payloadType = PAYLOAD_TYPE.INIT; if (param === undefined) { throw Error("number type not given"); } p2 = param; } if (chunkIdx === chunkNum) { payloadType = PAYLOAD_TYPE.LAST; } return await this.transport .send(CLA, ins, payloadType, p2, chunk, [ LedgerError.NoErrors, LedgerError.DataIsInvalid, LedgerError.BadKeyHandle, LedgerError.SignVerifyError, ]) .then((response: Buffer) => { const errorCodeData = response.slice(-2); if (errorCodeData.length < 2) { throw new Error("Invalid response: missing error code data"); } const returnCode = errorCodeData[0]! * 256 + errorCodeData[1]!; let errorMessage = errorCodeToString(returnCode); if ( returnCode === LedgerError.BadKeyHandle || returnCode === LedgerError.DataIsInvalid || returnCode === LedgerError.SignVerifyError ) { errorMessage = `${errorMessage} : ${response.slice(0, response.length - 2).toString("ascii")}`; } if (returnCode === LedgerError.NoErrors && response.length > 2) { return { hash: null, signature: null, returnCode: returnCode, errorMessage: errorMessage, }; } return { returnCode: returnCode, errorMessage: errorMessage, }; }, processErrorResponse); } async signHash( path_prefix: string, signing_paths: Array<string>, hash: Buffer, curve_type: number = P2_VALUES.SECP256K1, ): Promise<ResponseSign> { if (hash.length !== HASH_LEN) { throw new Error("Invalid hash length"); } //send hash and path const first_response = await this.transport .send( CLA, INS.SIGN_HASH, FIRST_MESSAGE, 0x00, Buffer.concat([ new Uint8Array(serializePath(path_prefix)), new Uint8Array(hash), ]), [LedgerError.NoErrors], ) .then((response: Buffer) => { const errorCodeData = response.slice(-2); if (errorCodeData.length < 2) { throw new Error("Invalid response: missing error code data"); } const returnCode = errorCodeData[0]! * 256 + errorCodeData[1]!; let errorMessage = errorCodeToString(returnCode); if ( returnCode === LedgerError.BadKeyHandle || returnCode === LedgerError.DataIsInvalid ) { errorMessage = `${errorMessage} : ${response.slice(0, response.length - 2).toString("ascii")}`; } return { returnCode: returnCode, errorMessage: errorMessage, }; }, processErrorResponse); if (first_response.returnCode !== LedgerError.NoErrors) { return first_response; } return this._signAndCollect(signing_paths, curve_type); } private async _signAndCollect( signing_paths: Array<string>, curve_type: number, ): Promise<ResponseSign> { // base response object to output on each iteration const result = { returnCode: LedgerError.NoErrors, errorMessage: "", hash: null, signatures: null as null | Map<string, Buffer>, }; // where each pair path_suffix, signature are stored const signatures = new Map(); for (let idx = 0; idx < signing_paths.length; idx++) { const suffix = signing_paths[idx]; if (!suffix) { throw new Error(`Invalid signing path at index ${idx}`); } const path_buf = serializePathSuffix(suffix); const p1 = idx >= signing_paths.length - 1 ? LAST_MESSAGE : NEXT_MESSAGE; // send path to sign hash that should be in device's ram memory await this.transport .send(CLA, INS.SIGN_HASH, p1, curve_type, path_buf, [ LedgerError.NoErrors, LedgerError.DataIsInvalid, LedgerError.BadKeyHandle, LedgerError.SignVerifyError, ]) .then((response: Buffer) => { const errorCodeData = response.slice(-2); if (errorCodeData.length < 2) { throw new Error("Invalid response: missing error code data"); } const returnCode = errorCodeData[0]! * 256 + errorCodeData[1]!; const errorMessage = errorCodeToString(returnCode); if ( returnCode === LedgerError.BadKeyHandle || returnCode === LedgerError.DataIsInvalid || returnCode === LedgerError.SignVerifyError ) { result.errorMessage = `${errorMessage} : ${response.slice(0, response.length - 2).toString("ascii")}`; } if (returnCode === LedgerError.NoErrors && response.length > 2) { signatures.set(suffix, response.slice(0, -2)); } result.returnCode = returnCode; result.errorMessage = errorMessage; return; }, processErrorResponse); if (result.returnCode !== LedgerError.NoErrors) { break; } } result.signatures = signatures; return result; } async sign( path_prefix: string, signing_paths: Array<string>, message: Buffer, change_paths?: Array<string>, curve_type: number = P2_VALUES.SECP256K1, ): Promise<ResponseSign> { // Do not show outputs that go to the signers let paths = signing_paths; if (change_paths !== undefined) { // remove duplication just is case paths = [...new Set([...paths, ...change_paths])]; } // Prepend change_paths to the message as the device do set which outputs should be // shown at parsing const msg = this.concatMessageAndChangePath(message, paths); // Send transaction for review const response = await this.signGetChunks(msg, path_prefix).then( (chunks) => { if (!chunks || chunks.length === 0 || !chunks[0]) { throw new Error("Invalid chunks array"); } return this.signSendChunk( 1, chunks.length, chunks[0], FIRST_MESSAGE, INS.SIGN, ).then(async (response) => { // initialize response let result = { returnCode: response.returnCode, errorMessage: response.errorMessage, signatures: null as null | Map<string, Buffer>, }; // send chunks for (let i = 1; i < chunks.length; i += 1) { const chunk = chunks[i]; if (!chunk) { throw new Error(`Invalid chunk at index ${i}`); } result = await this.signSendChunk( 1 + i, chunks.length, chunk, NEXT_MESSAGE, INS.SIGN, ); if (result.returnCode !== LedgerError.NoErrors) { break; } } return result; }, processErrorResponse); }, processErrorResponse, ); if (response.returnCode !== LedgerError.NoErrors) { return response; } // Transaction was approved so start iterating over signing_paths to sign // and collect each signature return this._signAndCollect(signing_paths, curve_type); } // Sign an arbitrary message. // This function takes in an avax path prefix like: m/44'/9000'/0'/0' // signing_paths: ["0/1", "5/8"] // message: The message to be signed async signMsg( path_prefix: string, signing_paths: Array<string>, message: string, curve_type: number = P2_VALUES.SECP256K1, ): Promise<ResponseSign> { const coinType = pathCoinType(path_prefix); if (coinType !== "9000'") { throw new Error("Only avax path is supported"); } const header = Buffer.from("\x1AAvalanche Signed Message:\n", "utf8"); const content = Buffer.from(message, "utf8"); const msgSize = Buffer.alloc(4); msgSize.writeUInt32BE(content.length, 0); const avax_msg = Buffer.from(`${header}${msgSize}${content}`, "utf8"); // Send msg for review const response = await this.signGetChunks(avax_msg, path_prefix).then( (chunks) => { if (!chunks || chunks.length === 0 || !chunks[0]) { throw new Error("Invalid chunks array"); } return this.signSendChunk( 1, chunks.length, chunks[0], FIRST_MESSAGE, INS.SIGN_MSG, ).then(async (response) => { // initialize response let result = { returnCode: response.returnCode, errorMessage: response.errorMessage, signatures: null as null | Map<string, Buffer>, }; // send chunks for (let i = 1; i < chunks.length; i += 1) { const chunk = chunks[i]; if (!chunk) { throw new Error(`Invalid chunk at index ${i}`); } result = await this.signSendChunk( 1 + i, chunks.length, chunk, NEXT_MESSAGE, INS.SIGN_MSG, ); if (result.returnCode !== LedgerError.NoErrors) { break; } } return result; }, processErrorResponse); }, processErrorResponse, ); if (response.returnCode !== LedgerError.NoErrors) { return response; } // Message was approved so start iterating over signing_paths to sign // and collect each signature return this._signAndCollect(signing_paths, curve_type); } async getVersion(): Promise<ResponseVersion> { return await getVersion(this.transport).catch((err) => processErrorResponse(err), ); } async getAppInfo(): Promise<ResponseAppInfo> { return await this.transport.send(0xb0, 0x01, 0, 0).then((response) => { const errorCodeData = response.slice(-2); if (errorCodeData.length < 2) { throw new Error("Invalid response: missing error code data"); } const returnCode = errorCodeData[0]! * 256 + errorCodeData[1]!; const result: { errorMessage?: string; returnCode?: LedgerError } = {}; let appName = "err"; let appVersion = "err"; let flagLen = 0; let flagsValue = 0; if (response[0] !== 1) { // Ledger responds with format ID 1. There is no spec for any format != 1 result.errorMessage = "response format ID not recognized"; result.returnCode = LedgerError.DeviceIsBusy; } else { const appNameLen = response[1]; if (appNameLen === undefined) { throw new Error("Invalid response: missing app name length"); } appName = response.slice(2, 2 + appNameLen).toString("ascii"); let idx = 2 + appNameLen; const appVersionLen = response[idx]; if (appVersionLen === undefined) { throw new Error("Invalid response: missing app version length"); } idx += 1; appVersion = response.slice(idx, idx + appVersionLen).toString("ascii"); idx += appVersionLen; const appFlagsLen = response[idx]; if (appFlagsLen === undefined) { throw new Error("Invalid response: missing app flags length"); } idx += 1; flagLen = appFlagsLen; const flagsVal = response[idx]; if (flagsVal === undefined) { throw new Error("Invalid response: missing flags value"); } flagsValue = flagsVal; } return { returnCode, errorMessage: errorCodeToString(returnCode), // appName, appVersion, flagLen, flagsValue, flagRecovery: (flagsValue & 1) !== 0, flagSignedMcuCode: (flagsValue & 2) !== 0, flagOnboarded: (flagsValue & 4) !== 0, flagPINValidated: (flagsValue & 128) !== 0, }; }, processErrorResponse); } private async _pubkey( path: string, show: boolean, hrp?: string, chainid?: string, curve_type: number = P2_VALUES.SECP256K1, ): Promise<ResponseAddress> { const p1 = show ? P1_VALUES.SHOW_ADDRESS_IN_DEVICE : P1_VALUES.ONLY_RETRIEVE; const serializedPath = serializePath(path); // Validate curve type if ( curve_type !== P2_VALUES.SECP256K1 && curve_type !== P2_VALUES.ED25519 ) { throw new Error( "Invalid curve type. Must be 0 for secp256k1 or 1 for ed25519", ); } const payload = curve_type === P2_VALUES.SECP256K1 ? Buffer.concat([ new Uint8Array(serializeHrp(hrp)), new Uint8Array(serializeChainID(chainid)), new Uint8Array(serializedPath), ]) : serializedPath; return await this.transport .send(CLA, INS.GET_ADDR, p1, curve_type, payload, [LedgerError.NoErrors]) .then(processGetAddrResponse, processErrorResponse); } async getAddressAndPubKey( path: string, show: boolean, hrp?: string, chainid?: string, curve_type: number = P2_VALUES.SECP256K1, ) { return await this._pubkey(path, show, hrp, chainid, curve_type); } private async _xpub( path: string, show: boolean, hrp?: string, chainid?: string, ): Promise<ResponseXPub> { const p1 = show ? P1_VALUES.SHOW_ADDRESS_IN_DEVICE : P1_VALUES.ONLY_RETRIEVE; const serializedPath = serializePath(path); const serializedHrp = serializeHrp(hrp); const serializedChainID = serializeChainID(chainid); return await this.transport .send( CLA, INS.GET_EXTENDED_PUBLIC_KEY, p1, 0, Buffer.concat([ new Uint8Array(serializedHrp), new Uint8Array(serializedChainID), new Uint8Array(serializedPath), ]), [LedgerError.NoErrors], ) .then(processGetXPubResponse, processErrorResponse); } async getExtendedPubKey( path: string, show: boolean, hrp?: string, chainid?: string, ) { return await this._xpub(path, show, hrp, chainid); } private async _walletId(show: boolean): Promise<ResponseWalletId> { const p1 = show ? P1_VALUES.SHOW_ADDRESS_IN_DEVICE : P1_VALUES.ONLY_RETRIEVE; return await this.transport .send(CLA, INS.WALLET_ID, p1, 0) .then((response) => { const errorCodeData = response.slice(-2); if (errorCodeData.length < 2) { throw new Error("Invalid response: missing error code data"); } const returnCode = (errorCodeData[0]! * 256 + errorCodeData[1]!) as LedgerError; return { returnCode, errorMessage: errorCodeToString(returnCode), id: response.slice(0, 6), }; }, processErrorResponse); } async getWalletId() { return await this._walletId(false); } async showWalletId() { return await this._walletId(true); } signEVMTransaction( path: string, rawTxHex: string, resolution?: LedgerEthTransactionResolution | null, ): Promise<{ s: string; v: string; r: string; }> { return this.eth.signTransaction(path, rawTxHex, resolution); } getETHAddress( path: string, boolDisplay?: boolean, boolChaincode?: boolean, ): Promise<{ publicKey: string; address: string; chainCode?: string; }> { return this.eth.getAddress(path, boolDisplay, boolChaincode); } getAppConfiguration(): Promise<{ arbitraryDataEnabled: number; erc20ProvisioningNecessary: number; starkEnabled: number; starkv2Supported: number; version: string; }> { return this.eth.getAppConfiguration(); } async provideERC20TokenInformation( ticker: string, contractName: string, address: string, decimals: number, chainId: number, ): Promise<boolean> { // Calculate lengths const tickerLength = Buffer.byteLength(ticker); const contractNameLength = Buffer.byteLength(contractName); // Create a buffer with the exact size needed const buffer = Buffer.alloc( 1 + tickerLength + 1 + contractNameLength + 20 + 4 + 4, ); let offset = 0; // Ticker length and ticker buffer.writeUInt8(tickerLength, offset); offset += 1; buffer.write(ticker, offset); offset += tickerLength; // Contract name length and contract name buffer.writeUInt8(contractNameLength, offset); offset += 1; buffer.write(contractName, offset); offset += contractNameLength; // Address (20 bytes, hex string needs to be parsed) var addr_offset = 0; if (address.startsWith("0x")) { addr_offset = 2; } // Slice to remove '0x' const addressBuffer = Buffer.from(address.slice(addr_offset), "hex"); addressBuffer.copy(new Uint8Array(buffer), offset); offset += 20; // Decimals (4 bytes, big endian) buffer.writeUInt32BE(decimals, offset); offset += 4; // Chain ID (4 bytes, big endian) buffer.writeUInt32BE(chainId, offset); offset += 4; return await this.eth.provideERC20TokenInformation(buffer.toString("hex")); } async provideNFTInformation( collectionName: string, contractAddress: string, chainId: bigint, ): Promise<boolean> { const NAME_LENGTH_SIZE = 1; const HEADER_SIZE = TYPE_SIZE + VERSION_SIZE + NAME_LENGTH_SIZE; const KEY_ID_SIZE = 1; const PROD_NFT_METADATA_KEY = 1; const collectionNameLength = Buffer.byteLength(collectionName, "utf8"); if (collectionNameLength > COLLECTION_NAME_MAX_LEN) { throw new Error( `Collection name exceeds maximum allowed length of ${COLLECTION_NAME_MAX_LEN}`, ); } // We generate a fake signature, because verification is disabled // in the app. const fakeDerSignature = this._generateFakeDerSignature(); const buffer = Buffer.alloc( HEADER_SIZE + collectionNameLength + ADDRESS_LENGTH + CHAIN_ID_SIZE + KEY_ID_SIZE + ALGORITHM_ID_SIZE + SIGNATURE_LENGTH_SIZE + fakeDerSignature.length, ); let offset = 0; buffer.writeUInt8(TYPE_1, offset); offset += TYPE_SIZE; buffer.writeUInt8(VERSION_1, offset); offset += VERSION_SIZE; buffer.writeUInt8(collectionNameLength, offset); offset += NAME_LENGTH_SIZE; buffer.write(collectionName, offset, "utf8"); offset += collectionNameLength; Buffer.from(contractAddress.slice(2), "hex").copy( new Uint8Array(buffer), offset, ); // Remove '0x' from address offset += ADDRESS_LENGTH; buffer.writeBigUInt64BE(chainId, offset); offset += CHAIN_ID_SIZE; buffer.writeUInt8(PROD_NFT_METADATA_KEY, offset); // Assume production key for simplicity offset += KEY_ID_SIZE; buffer.writeUInt8(ALGORITHM_ID_1, offset); // Assume a specific algorithm for signature or hash offset += ALGORITHM_ID_SIZE; buffer.writeUInt8(fakeDerSignature.length, offset); offset += SIGNATURE_LENGTH_SIZE; fakeDerSignature.copy(new Uint8Array(buffer), offset); return await this.eth.provideNFTInformation(buffer.toString("hex")); } _generateFakeDerSignature(): Buffer { const fakeSignatureLength = 70; const fakeDerSignature = Buffer.alloc(fakeSignatureLength); // Fill the buffer with random bytes for (let i = 0; i < fakeSignatureLength; i++) { fakeDerSignature[i] = Math.floor(Math.random() * 256); } return fakeDerSignature; } // We assume pluginName is ERC721 for Nft tokens async setPlugin( contractAddress: string, methodSelector: string, chainId: bigint, ): Promise<boolean> { const KEY_ID = 2; const PLUGIN_NAME_LENGTH_SIZE = 1; const KEY_ID_SIZE = 1; const PLUGIN_NAME = "ERC721"; const pluginNameBuffer = Buffer.from(PLUGIN_NAME, "utf8"); const pluginNameLength = pluginNameBuffer.length; const contractAddressBuffer = Buffer.from(contractAddress.slice(2), "hex"); const methodSelectorBuffer = Buffer.from(methodSelector.slice(2), "hex"); // We generate a fake signature, because verification is disabled // in the app. const signatureBuffer = this._generateFakeDerSignature(); const signatureLength = signatureBuffer.length; const buffer = Buffer.alloc( TYPE_SIZE + VERSION_SIZE + PLUGIN_NAME_LENGTH_SIZE + pluginNameLength + contractAddressBuffer.length + methodSelectorBuffer.length + CHAIN_ID_SIZE + KEY_ID_SIZE + ALGORITHM_ID_SIZE + SIGNATURE_LENGTH_SIZE + signatureLength, ); let offset = 0; buffer.writeUInt8(TYPE_1, offset); offset += TYPE_SIZE; buffer.writeUInt8(VERSION_1, offset); offset += VERSION_SIZE; buffer.writeUInt8(pluginNameLength, offset); offset += PLUGIN_NAME_LENGTH_SIZE; pluginNameBuffer.copy(new Uint8Array(buffer), offset); offset += pluginNameLength; contractAddressBuffer.copy(new Uint8Array(buffer), offset); offset += contractAddressBuffer.length; methodSelectorBuffer.copy(new Uint8Array(buffer), offset); offset += methodSelectorBuffer.length; buffer.writeBigUInt64BE(BigInt(chainId), offset); offset += CHAIN_ID_SIZE; // use default key_id buffer.writeUInt8(KEY_ID, offset); offset += KEY_ID_SIZE; // use default algorithm buffer.writeUInt8(ALGORITHM_ID_1, offset); offset += ALGORITHM_ID_SIZE; buffer.writeUInt8(signatureLength, offset); offset += SIGNATURE_LENGTH_SIZE; signatureBuffer.copy(new Uint8Array(buffer), offset); return await this.eth.setPlugin(buffer.toString("hex")); } async clearSignTransaction( path: string, rawTxHex: string, resolutionConfig: ResolutionConfig, throwOnError = false, ): Promise<{ r: string; s: string; v: string }> { return await this.eth.clearSignTransaction( path, rawTxHex, resolutionConfig, throwOnError, ); } async signEIP712Message( path: string, jsonMessage: EIP712Message, fullImplem = false, ): Promise<{ v: number; s: string; r: string }> { return await this.eth.signEIP712Message(path, jsonMessage, fullImplem); } async signEIP712HashedMessage( path: string, domainSeparatorHex: string, hashStructMessageHex: string, ): Promise<{ v: number; s: string; r: string }> { return await this.eth.signEIP712HashedMessage( path, domainSeparatorHex, hashStructMessageHex, ); } }