@avalabs/hw-app-avalanche
Version:
Node API for Avalanche App (Ledger Nano S/X/S+)
997 lines (858 loc) • 28.3 kB
text/typescript
/** ******************************************************************************
* (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,
);
}
}