From 83f2f3f9ac8fe77a230d6cfaae05a1ab99dc083d Mon Sep 17 00:00:00 2001 From: arobsn <87387688+arobsn@users.noreply.github.com> Date: Tue, 2 Jul 2024 09:27:27 -0300 Subject: [PATCH 01/11] reorganize file structure --- src/{validations/parse.ts => assertions.ts} | 29 +++- src/device.spec.ts | 17 +++ src/{errors/returnCodes.ts => device.ts} | 112 +++++++++++++- src/erg.ts | 36 ++--- src/errors/deviceError.ts | 15 -- src/errors/index.ts | 2 - src/interactions/attestInput.ts | 89 +++++++---- src/interactions/common/device.ts | 82 ---------- src/interactions/deriveAddress.ts | 36 +++-- src/interactions/getAppName.ts | 15 +- src/interactions/getExtendedPublicKey.ts | 16 +- src/interactions/getVersion.ts | 4 +- src/interactions/signTx.ts | 157 ++++++++++++++------ src/serialization/deserialize.ts | 34 ++--- src/serialization/serialize.spec.ts | 6 +- src/serialization/serialize.ts | 49 +++--- src/serialization/utils.ts | 2 +- src/{models => types}/attestedBox.ts | 6 +- src/types/internal.ts | 10 +- src/validations/assert.ts | 5 - src/validations/index.ts | 2 - 21 files changed, 442 insertions(+), 282 deletions(-) rename src/{validations/parse.ts => assertions.ts} (71%) create mode 100644 src/device.spec.ts rename src/{errors/returnCodes.ts => device.ts} (57%) delete mode 100644 src/errors/deviceError.ts delete mode 100644 src/errors/index.ts delete mode 100644 src/interactions/common/device.ts rename src/{models => types}/attestedBox.ts (87%) delete mode 100644 src/validations/assert.ts delete mode 100644 src/validations/index.ts diff --git a/src/validations/parse.ts b/src/assertions.ts similarity index 71% rename from src/validations/parse.ts rename to src/assertions.ts index 5792fea..f3667af 100644 --- a/src/validations/parse.ts +++ b/src/assertions.ts @@ -7,6 +7,12 @@ const MAX_UINT32_VALUE = 4294967295; const MAX_UINT16_VALUE = 65535; const MAX_UNIT8_VALUE = 255; +export function assert(cond: boolean, errMsg: string): asserts cond { + if (!cond) { + throw new Error(`Assertion failed${errMsg ? `: ${errMsg}` : "."}`); + } +} + export function isValidBip32Path(path: number[] | string): boolean { if (typeof path == "string") { return bip32Path.validateString(path, true); @@ -21,7 +27,9 @@ export function isValidErgoPath(path: number[]): boolean { } const [pathPurpose, pathCoinType] = path; - const [ergoPurpose, ergoCoinType] = bip32Path.fromString("m/44'/429'").toPathArray(); + const [ergoPurpose, ergoCoinType] = bip32Path + .fromString("m/44'/429'") + .toPathArray(); return pathPurpose === ergoPurpose && pathCoinType === ergoCoinType; } @@ -39,19 +47,28 @@ export function isBuffer(data: unknown): boolean { export function isUint32(data: unknown): boolean { return ( - typeof data == "number" && isInteger(data) && data >= MIN_UINT_VALUE && data <= MAX_UINT32_VALUE + typeof data == "number" && + isInteger(data) && + data >= MIN_UINT_VALUE && + data <= MAX_UINT32_VALUE ); } export function isUint16(data: number): boolean { return ( - typeof data == "number" && isInteger(data) && data >= MIN_UINT_VALUE && data <= MAX_UINT16_VALUE + typeof data == "number" && + isInteger(data) && + data >= MIN_UINT_VALUE && + data <= MAX_UINT16_VALUE ); } export function isUint8(data: unknown): boolean { return ( - typeof data == "number" && isInteger(data) && data >= MIN_UINT_VALUE && data <= MAX_UNIT8_VALUE + typeof data == "number" && + isInteger(data) && + data >= MIN_UINT_VALUE && + data <= MAX_UNIT8_VALUE ); } @@ -73,4 +90,6 @@ export function isUint64String(data: string): boolean { } export const isHexString = (data: unknown) => - typeof data === "string" && data.length % 2 === 0 && /^[0-9a-fA-F]*$/.test(data); + typeof data === "string" && + data.length % 2 === 0 && + /^[0-9a-fA-F]*$/.test(data); diff --git a/src/device.spec.ts b/src/device.spec.ts new file mode 100644 index 0000000..3243bb8 --- /dev/null +++ b/src/device.spec.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from "vitest"; +import { DeviceError, RETURN_CODE } from "./device"; + +describe("DeviceError construction", () => { + it("should create a new DeviceError instance from a know RETURN_CODE", () => { + const error = new DeviceError(RETURN_CODE.TOO_MUCH_DATA); + expect(error.code).toBe(RETURN_CODE.TOO_MUCH_DATA); + expect(error.message).toBe("Too much data"); + expect(error.name).to.be.equal("DeviceError"); + }); + + it("should create a new DeviceError instance from a unknown RETURN_CODE", () => { + const error = new DeviceError(0 as RETURN_CODE); + expect(error.code).toBe(0); + expect(error.message).toBe("Unknown error"); + }); +}); diff --git a/src/errors/returnCodes.ts b/src/device.ts similarity index 57% rename from src/errors/returnCodes.ts rename to src/device.ts index 27e538c..73313d5 100644 --- a/src/errors/returnCodes.ts +++ b/src/device.ts @@ -1,3 +1,111 @@ +import type Transport from "@ledgerhq/hw-transport"; +import type { DeviceResponse } from "./types/internal"; +import { serialize } from "./serialization/serialize"; + +export const enum COMMAND { + GET_APP_VERSION = 0x01, + GET_APP_NAME = 0x02, + + GET_EXTENTED_PUB_KEY = 0x10, + DERIVE_ADDRESS = 0x11, + ATTEST_INPUT = 0x20, + SIGN_TX = 0x21 +} + +const MAX_DATA_LENGTH = 255; +const MIN_RESPONSE_LENGTH = 2; + +export class Device { + private _transport: Transport; + private _cla: number; + + public get transport(): Transport { + return this._transport; + } + + constructor(transport: Transport, cla: number) { + this._transport = transport; + this._cla = cla; + } + + public async sendData( + ins: COMMAND, + p1: number, + p2: number, + data: Buffer + ): Promise { + let responses: DeviceResponse[] = []; + for (let i = 0; i < Math.ceil(data.length / MAX_DATA_LENGTH); i++) { + const chunk = data.slice( + i * MAX_DATA_LENGTH, + Math.min((i + 1) * MAX_DATA_LENGTH, data.length) + ); + + responses.push(await this.send(ins, p1, p2, chunk)); + } + + return responses; + } + + public async send( + ins: COMMAND, + p1: number, + p2: number, + data: Buffer + ): Promise { + if (data.length > MAX_DATA_LENGTH) { + throw new DeviceError(RETURN_CODE.TOO_MUCH_DATA); + } + + const apdu = this.mountApdu(this._cla, ins, p1, p2, data); + const response = await this.transport.exchange(apdu); + + if (response.length < MIN_RESPONSE_LENGTH) { + throw new DeviceError(RETURN_CODE.WRONG_RESPONSE_LENGTH); + } + const returnCode = response.readUInt16BE(response.length - 2); + if (returnCode != RETURN_CODE.OK) { + throw new DeviceError(returnCode); + } + + const responseData = response.slice(0, response.length - 2); + return { returnCode, data: responseData }; + } + + private mountApdu( + cla: number, + ins: COMMAND, + p1: number, + p2: number, + data: Buffer + ): Buffer { + return Buffer.concat([ + serialize.uint8(cla), + serialize.uint8(ins), + serialize.uint8(p1), + serialize.uint8(p2), + serialize.uint8(data.length), + data + ]); + } +} + +export class DeviceError extends Error { + #code; + + public get code() { + return this.#code; + } + + constructor(code: RETURN_CODE, options?: ErrorOptions) { + super(RETURN_MESSAGES[code] || "Unknown error", options); + this.#code = code; + + Object.setPrototypeOf(this, new.target.prototype); + this.name = new.target.name; + } +} + export enum RETURN_CODE { DENIED = 0x6985, WRONG_P1P2 = 0x6a86, @@ -83,7 +191,3 @@ export const RETURN_MESSAGES = { [RETURN_CODE.STACK_OVERFLOW]: "Stack overflow", [RETURN_CODE.OK]: "Ok" }; - -export function getReturnMessage(code: RETURN_CODE): string { - return RETURN_MESSAGES[code] || "Unknown error"; -} diff --git a/src/erg.ts b/src/erg.ts index e75a4bb..39b1807 100644 --- a/src/erg.ts +++ b/src/erg.ts @@ -1,16 +1,16 @@ import type Transport from "@ledgerhq/hw-transport"; -import Device from "./interactions/common/device"; +import { Device, DeviceError, RETURN_CODE } from "./device"; import { - AppName, - UnsignedBox, - DerivedAddress, - ExtendedPublicKey, - Version, - UnsignedTx, + type AppName, + type UnsignedBox, + type DerivedAddress, + type ExtendedPublicKey, + type Version, + type UnsignedTx, Network } from "./types/public"; -import { assert, isValidErgoPath } from "./validations"; -import AttestedBox from "./models/attestedBox"; +import { assert, isValidErgoPath } from "./assertions"; +import type { AttestedBox } from "./types/attestedBox"; import { getAppName, getExtendedPublicKey, @@ -20,12 +20,14 @@ import { attestInput, signTx } from "./interactions"; -import Serialize from "./serialization/serialize"; -import { AttestedTx, SignTxResponse } from "./types/internal"; +import { serialize } from "./serialization/serialize"; +import type { + AttestedTransaction, + SignTransactionResponse +} from "./types/internal"; import { uniq } from "./serialization/utils"; -import { DeviceError, RETURN_CODE } from "./errors"; -export * from "./errors"; +export { DeviceError, RETURN_CODE }; export * from "./types/public"; export const CLA = 0xe0; @@ -127,7 +129,7 @@ export class ErgoLedgerApp { public async getExtendedPublicKey(path: string): Promise { this._debug("getExtendedPublicKey", path); - const pathArray = Serialize.bip32PathAsArray(path); + const pathArray = serialize.bip32PathAsArray(path); assert(isValidErgoPath(pathArray), "Invalid Ergo path."); return getExtendedPublicKey(this._device, pathArray, this.authToken); } @@ -163,7 +165,7 @@ export class ErgoLedgerApp { } private getDerivationPathArray(path: string) { - const pathArray = Serialize.bip32PathAsArray(path); + const pathArray = serialize.bip32PathAsArray(path); assert(isValidErgoPath(pathArray), "Invalid Ergo path."); assert(pathArray.length >= 5, "Invalid path length."); assert( @@ -195,7 +197,7 @@ export class ErgoLedgerApp { const attestedInputs = await this._attestInputs(tx.inputs); const signPaths = uniq(tx.inputs.map((i) => i.signPath)); - const attestedTx: AttestedTx = { + const attestedTx: AttestedTransaction = { inputs: attestedInputs, dataInputs: tx.dataInputs, outputs: tx.outputs, @@ -203,7 +205,7 @@ export class ErgoLedgerApp { changeMap: tx.changeMap }; - const signatures: SignTxResponse = {}; + const signatures: SignTransactionResponse = {}; for (let path of signPaths) { signatures[path] = await signTx( this._device, diff --git a/src/errors/deviceError.ts b/src/errors/deviceError.ts deleted file mode 100644 index c349b1f..0000000 --- a/src/errors/deviceError.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { getReturnMessage } from "./returnCodes"; - -export class DeviceError extends Error { - private _code; - - public get code() { - return this._code; - } - - constructor(code: number) { - super(getReturnMessage(code)); - this._code = code; - this.name = this.constructor.name; - } -} diff --git a/src/errors/index.ts b/src/errors/index.ts deleted file mode 100644 index 422fc89..0000000 --- a/src/errors/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { DeviceError } from "./deviceError"; -export { getReturnMessage, RETURN_CODE, RETURN_MESSAGES } from "./returnCodes"; diff --git a/src/interactions/attestInput.ts b/src/interactions/attestInput.ts index c99214b..991e683 100644 --- a/src/interactions/attestInput.ts +++ b/src/interactions/attestInput.ts @@ -1,9 +1,9 @@ -import Device, { COMMAND } from "./common/device"; -import { AttestedBoxFrame, UnsignedBox, Token } from "../types/public"; +import { COMMAND, type Device } from "../device"; +import type { AttestedBoxFrame, UnsignedBox, Token } from "../types/public"; import type { DeviceResponse } from "../types/internal"; -import AttestedBox from "../models/attestedBox"; -import Serialize from "../serialization/serialize"; -import Deserialize from "../serialization/deserialize"; +import { serialize } from "../serialization/serialize"; +import { deserialize } from "../serialization/deserialize"; +import { AttestedBox } from "../types/attestedBox"; const enum P1 { BOX_START = 0x01, @@ -29,22 +29,33 @@ export async function attestInput( frameCount = await sendTokens(device, box.tokens, sessionId); } if (box.additionalRegisters.length > 0) { - frameCount = await sendRegisters(device, box.additionalRegisters, sessionId); + frameCount = await sendRegisters( + device, + box.additionalRegisters, + sessionId + ); } - return new AttestedBox(box, await getAttestedFrames(device, frameCount, sessionId)); + return new AttestedBox( + box, + await getAttestedFrames(device, frameCount, sessionId) + ); } -async function sendHeader(device: Device, box: UnsignedBox, authToken?: number): Promise { +async function sendHeader( + device: Device, + box: UnsignedBox, + authToken?: number +): Promise { const header = Buffer.concat([ - Serialize.hex(box.txId), - Serialize.uint16(box.index), - Serialize.uint64(box.value), - Serialize.uint32(box.ergoTree.length), - Serialize.uint32(box.creationHeight), - Serialize.uint8(box.tokens.length), - Serialize.uint32(box.additionalRegisters.length), - authToken ? Serialize.uint32(authToken) : Buffer.alloc(0) + serialize.hex(box.txId), + serialize.uint16(box.index), + serialize.uint64(box.value), + serialize.uint32(box.ergoTree.length), + serialize.uint32(box.creationHeight), + serialize.uint8(box.tokens.length), + serialize.uint32(box.additionalRegisters.length), + authToken ? serialize.uint32(authToken) : Buffer.alloc(0) ]); const response = await device.send( @@ -56,7 +67,11 @@ async function sendHeader(device: Device, box: UnsignedBox, authToken?: number): return response.data[0]; } -async function sendErgoTree(device: Device, data: Buffer, sessionId: number): Promise { +async function sendErgoTree( + device: Device, + data: Buffer, + sessionId: number +): Promise { const results = await device.sendData( COMMAND.ATTEST_INPUT, P1.ADD_ERGO_TREE_CHUNK, @@ -67,22 +82,32 @@ async function sendErgoTree(device: Device, data: Buffer, sessionId: number): Pr return results.pop()?.data[0] || 0; } -async function sendTokens(device: Device, tokens: Token[], sessionId: number): Promise { +async function sendTokens( + device: Device, + tokens: Token[], + sessionId: number +): Promise { const MAX_PACKET_SIZE = 6; - const packets = Serialize.arrayAndChunk(tokens, MAX_PACKET_SIZE, (t) => - Buffer.concat([Serialize.hex(t.id), Serialize.uint64(t.amount)]) + const packets = serialize.arrayAndChunk(tokens, MAX_PACKET_SIZE, (t) => + Buffer.concat([serialize.hex(t.id), serialize.uint64(t.amount)]) ); const results: DeviceResponse[] = []; for (let p of packets) { - results.push(await device.send(COMMAND.ATTEST_INPUT, P1.ADD_TOKENS, sessionId, p)); + results.push( + await device.send(COMMAND.ATTEST_INPUT, P1.ADD_TOKENS, sessionId, p) + ); } /* v8 ignore next */ return results.pop()?.data[0] || 0; } -async function sendRegisters(device: Device, data: Buffer, sessionId: number): Promise { +async function sendRegisters( + device: Device, + data: Buffer, + sessionId: number +): Promise { const results = await device.sendData( COMMAND.ATTEST_INPUT, P1.ADD_REGISTERS_CHUNK, @@ -114,23 +139,25 @@ async function getAttestedFrames( return responses; } -export function parseAttestedFrameResponse(frameBuff: Buffer): AttestedBoxFrame { +export function parseAttestedFrameResponse( + frameBuff: Buffer +): AttestedBoxFrame { let offset = 0; - const boxId = Deserialize.hex(frameBuff.slice(offset, (offset += 32))); - const count = Deserialize.uint8(frameBuff.slice(offset, (offset += 1))); - const index = Deserialize.uint8(frameBuff.slice(offset, (offset += 1))); - const amount = Deserialize.uint64(frameBuff.slice(offset, (offset += 8))); - const tokenCount = Deserialize.uint8(frameBuff.slice(offset, (offset += 1))); + const boxId = deserialize.hex(frameBuff.slice(offset, (offset += 32))); + const count = deserialize.uint8(frameBuff.slice(offset, (offset += 1))); + const index = deserialize.uint8(frameBuff.slice(offset, (offset += 1))); + const amount = deserialize.uint64(frameBuff.slice(offset, (offset += 8))); + const tokenCount = deserialize.uint8(frameBuff.slice(offset, (offset += 1))); const tokens: Token[] = []; for (let i = 0; i < tokenCount; i++) { tokens.push({ - id: Deserialize.hex(frameBuff.slice(offset, (offset += 32))), - amount: Deserialize.uint64(frameBuff.slice(offset, (offset += 8))) + id: deserialize.hex(frameBuff.slice(offset, (offset += 32))), + amount: deserialize.uint64(frameBuff.slice(offset, (offset += 8))) }); } - const attestation = Deserialize.hex(frameBuff.slice(offset, (offset += 16))); + const attestation = deserialize.hex(frameBuff.slice(offset, (offset += 16))); return { boxId, diff --git a/src/interactions/common/device.ts b/src/interactions/common/device.ts deleted file mode 100644 index d096610..0000000 --- a/src/interactions/common/device.ts +++ /dev/null @@ -1,82 +0,0 @@ -import type Transport from "@ledgerhq/hw-transport"; -import { DeviceError } from "../../errors/deviceError"; -import { RETURN_CODE } from "../../errors"; -import { DeviceResponse } from "../../types/internal"; -import Serialize from "../../serialization/serialize"; - -export const enum COMMAND { - GET_APP_VERSION = 0x01, - GET_APP_NAME = 0x02, - - GET_EXTENTED_PUB_KEY = 0x10, - DERIVE_ADDRESS = 0x11, - ATTEST_INPUT = 0x20, - SIGN_TX = 0x21 -} - -const MAX_DATA_LENGTH = 255; -const MIN_RESPONSE_LENGTH = 2; - -export default class Device { - private _transport: Transport; - private _cla: number; - - public get transport(): Transport { - return this._transport; - } - - constructor(transport: Transport, cla: number) { - this._transport = transport; - this._cla = cla; - } - - public async sendData( - ins: COMMAND, - p1: number, - p2: number, - data: Buffer - ): Promise { - let responses: DeviceResponse[] = []; - for (let i = 0; i < Math.ceil(data.length / MAX_DATA_LENGTH); i++) { - const chunk = data.slice( - i * MAX_DATA_LENGTH, - Math.min((i + 1) * MAX_DATA_LENGTH, data.length) - ); - - responses.push(await this.send(ins, p1, p2, chunk)); - } - - return responses; - } - - public async send(ins: COMMAND, p1: number, p2: number, data: Buffer): Promise { - if (data.length > MAX_DATA_LENGTH) { - throw new DeviceError(RETURN_CODE.TOO_MUCH_DATA); - } - - const apdu = this.mountApdu(this._cla, ins, p1, p2, data); - const response = await this.transport.exchange(apdu); - - if (response.length < MIN_RESPONSE_LENGTH) { - throw new DeviceError(RETURN_CODE.WRONG_RESPONSE_LENGTH); - } - const returnCode = response.readUInt16BE(response.length - 2); - if (returnCode != RETURN_CODE.OK) { - throw new DeviceError(returnCode); - } - - const responseData = response.slice(0, response.length - 2); - return { returnCode, data: responseData }; - } - - private mountApdu(cla: number, ins: COMMAND, p1: number, p2: number, data: Buffer): Buffer { - return Buffer.concat([ - Serialize.uint8(cla), - Serialize.uint8(ins), - Serialize.uint8(p1), - Serialize.uint8(p2), - Serialize.uint8(data.length), - data - ]); - } -} diff --git a/src/interactions/deriveAddress.ts b/src/interactions/deriveAddress.ts index 0f770d0..33d2abe 100644 --- a/src/interactions/deriveAddress.ts +++ b/src/interactions/deriveAddress.ts @@ -1,9 +1,8 @@ -import Device, { COMMAND } from "./common/device"; -import { DerivedAddress, Network } from "../types/public"; -import { DeviceResponse } from "../types/internal"; -import { RETURN_CODE } from "../errors"; -import Serialize from "../serialization/serialize"; -import Deserialize from "../serialization/deserialize"; +import { COMMAND, RETURN_CODE, type Device } from "../device"; +import type { DerivedAddress, Network } from "../types/public"; +import type { DeviceResponse } from "../types/internal"; +import { serialize } from "../serialization/serialize"; +import { deserialize } from "../serialization/deserialize"; const enum ReturnType { Return, @@ -27,12 +26,15 @@ function sendDeriveAddress( returnType: ReturnType, authToken?: number ): Promise { - const data = Buffer.concat([Buffer.alloc(1, network), Serialize.bip32Path(path)]); + const data = Buffer.concat([ + Buffer.alloc(1, network), + serialize.bip32Path(path) + ]); return device.send( COMMAND.DERIVE_ADDRESS, returnType == ReturnType.Return ? P1.RETURN : P1.DISPLAY, authToken ? P2.WITH_TOKEN : P2.WITHOUT_TOKEN, - authToken ? Buffer.concat([data, Serialize.uint32(authToken)]) : data + authToken ? Buffer.concat([data, serialize.uint32(authToken)]) : data ); } @@ -42,8 +44,14 @@ export async function deriveAddress( path: number[], authToken?: number ): Promise { - const response = await sendDeriveAddress(device, network, path, ReturnType.Return, authToken); - return { addressHex: Deserialize.hex(response.data) }; + const response = await sendDeriveAddress( + device, + network, + path, + ReturnType.Return, + authToken + ); + return { addressHex: deserialize.hex(response.data) }; } export async function showAddress( @@ -52,6 +60,12 @@ export async function showAddress( path: number[], authToken?: number ): Promise { - const response = await sendDeriveAddress(device, network, path, ReturnType.Display, authToken); + const response = await sendDeriveAddress( + device, + network, + path, + ReturnType.Display, + authToken + ); return response.returnCode === RETURN_CODE.OK; } diff --git a/src/interactions/getAppName.ts b/src/interactions/getAppName.ts index d4108de..a8ba27e 100644 --- a/src/interactions/getAppName.ts +++ b/src/interactions/getAppName.ts @@ -1,6 +1,6 @@ -import Device, { COMMAND } from "./common/device"; -import { AppName } from "../types/public"; -import Deserialize from "../serialization/deserialize"; +import { COMMAND, type Device } from "../device"; +import type { AppName } from "../types/public"; +import { deserialize } from "../serialization/deserialize"; const enum P1 { UNUSED = 0x00 @@ -11,6 +11,11 @@ const enum P2 { } export async function getAppName(device: Device): Promise { - const response = await device.send(COMMAND.GET_APP_NAME, P1.UNUSED, P2.UNUSED, Buffer.from([])); - return { name: Deserialize.ascii(response.data) }; + const response = await device.send( + COMMAND.GET_APP_NAME, + P1.UNUSED, + P2.UNUSED, + Buffer.from([]) + ); + return { name: deserialize.ascii(response.data) }; } diff --git a/src/interactions/getExtendedPublicKey.ts b/src/interactions/getExtendedPublicKey.ts index a456bcf..1fce170 100644 --- a/src/interactions/getExtendedPublicKey.ts +++ b/src/interactions/getExtendedPublicKey.ts @@ -1,8 +1,8 @@ -import Device, { COMMAND } from "./common/device"; -import { ExtendedPublicKey } from "../types/public"; +import { COMMAND, type Device } from "../device"; +import type { ExtendedPublicKey } from "../types/public"; import { chunkBy } from "../serialization/utils"; -import Serialize from "../serialization/serialize"; -import Deserialize from "../serialization/deserialize"; +import { serialize } from "../serialization/serialize"; +import { deserialize } from "../serialization/deserialize"; const enum P1 { WITHOUT_TOKEN = 0x01, @@ -18,17 +18,17 @@ export async function getExtendedPublicKey( path: number[], authToken?: number ): Promise { - const data = Serialize.bip32Path(path); + const data = serialize.bip32Path(path); const response = await device.send( COMMAND.GET_EXTENTED_PUB_KEY, authToken ? P1.WITH_TOKEN : P1.WITHOUT_TOKEN, P2.UNUSED, - authToken ? Buffer.concat([data, Serialize.uint32(authToken)]) : data + authToken ? Buffer.concat([data, serialize.uint32(authToken)]) : data ); const [publicKey, chainCode] = chunkBy(response.data, [33, 32]); return { - publicKey: Deserialize.hex(publicKey), - chainCode: Deserialize.hex(chainCode) + publicKey: deserialize.hex(publicKey), + chainCode: deserialize.hex(chainCode) }; } diff --git a/src/interactions/getVersion.ts b/src/interactions/getVersion.ts index 2fa0f5b..8cb14d7 100644 --- a/src/interactions/getVersion.ts +++ b/src/interactions/getVersion.ts @@ -1,5 +1,5 @@ -import { Version } from "../types/public"; -import Device, { COMMAND } from "./common/device"; +import type { Version } from "../types/public"; +import { COMMAND, type Device } from "../device"; const FLAG_IS_DEBUG = 0x01; diff --git a/src/interactions/signTx.ts b/src/interactions/signTx.ts index 9965d54..24662ee 100644 --- a/src/interactions/signTx.ts +++ b/src/interactions/signTx.ts @@ -1,10 +1,10 @@ -import AttestedBox from "../models/attestedBox"; -import Deserialize from "../serialization/deserialize"; -import Serialize from "../serialization/serialize"; -import { ChangeMap, BoxCandidate, Token, Network } from "../types/public"; -import Device, { COMMAND } from "./common/device"; +import { deserialize } from "../serialization/deserialize"; +import { serialize } from "../serialization/serialize"; +import type { ChangeMap, BoxCandidate, Token, Network } from "../types/public"; +import { COMMAND, type Device } from "../device"; import { ErgoAddress } from "@fleet-sdk/core"; -import { AttestedTx } from "../types/internal"; +import type { AttestedTransaction } from "../types/internal"; +import type { AttestedBox } from "../types/attestedBox"; const MINER_FEE_TREE = "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304"; @@ -32,7 +32,7 @@ const enum P2 { export async function signTx( device: Device, - tx: AttestedTx, + tx: AttestedTransaction, signPath: string, network: Network, authToken?: number @@ -42,7 +42,13 @@ export async function signTx( await sendDistinctTokensIds(device, sessionId, tx.distinctTokenIds); await sendInputs(device, sessionId, tx.inputs); await sendDataInputs(device, sessionId, tx.dataInputs); - await sendOutputs(device, sessionId, tx.outputs, tx.changeMap, tx.distinctTokenIds); + await sendOutputs( + device, + sessionId, + tx.outputs, + tx.changeMap, + tx.distinctTokenIds + ); const signBytes = await sendConfirmAndSign(device, sessionId); return new Uint8Array(signBytes); @@ -59,9 +65,9 @@ async function sendHeader( P1.START_SIGNING, authToken ? P2.WITH_TOKEN : P2.WITHOUT_TOKEN, Buffer.concat([ - Serialize.uint8(network), - Serialize.bip32Path(path), - authToken ? Serialize.uint32(authToken) : Buffer.alloc(0) + serialize.uint8(network), + serialize.bip32Path(path), + authToken ? serialize.uint32(authToken) : Buffer.alloc(0) ]) ); @@ -71,7 +77,7 @@ async function sendHeader( async function sendStartTx( device: Device, sessionId: number, - tx: AttestedTx, + tx: AttestedTransaction, uniqueTokenIdsCount: number ): Promise { const response = await device.send( @@ -79,33 +85,48 @@ async function sendStartTx( P1.START_TRANSACTION, sessionId, Buffer.concat([ - Serialize.uint16(tx.inputs.length), - Serialize.uint16(tx.dataInputs.length), - Serialize.uint8(uniqueTokenIdsCount), - Serialize.uint16(tx.outputs.length) + serialize.uint16(tx.inputs.length), + serialize.uint16(tx.dataInputs.length), + serialize.uint8(uniqueTokenIdsCount), + serialize.uint16(tx.outputs.length) ]) ); return response.data[0]; } -async function sendDistinctTokensIds(device: Device, sessionId: number, ids: Uint8Array[]) { +async function sendDistinctTokensIds( + device: Device, + sessionId: number, + ids: Uint8Array[] +) { if (ids.length === 0) { return; } const MAX_PACKET_SIZE = 7; - const packets = Serialize.arrayAndChunk(ids, MAX_PACKET_SIZE, (id) => Buffer.from(id)); + const packets = serialize.arrayAndChunk(ids, MAX_PACKET_SIZE, (id) => + Buffer.from(id) + ); for (let p of packets) { await device.send(COMMAND.SIGN_TX, P1.ADD_TOKEN_IDS, sessionId, p); } } -async function sendInputs(device: Device, sessionId: number, inputBoxes: AttestedBox[]) { +async function sendInputs( + device: Device, + sessionId: number, + inputBoxes: AttestedBox[] +) { for (let box of inputBoxes) { for (let frame of box.frames) { - await device.send(COMMAND.SIGN_TX, P1.ADD_INPUT_BOX_FRAME, sessionId, frame.buffer); + await device.send( + COMMAND.SIGN_TX, + P1.ADD_INPUT_BOX_FRAME, + sessionId, + frame.buffer + ); } if (box.extension !== undefined && box.extension.length > 0) { @@ -114,7 +135,11 @@ async function sendInputs(device: Device, sessionId: number, inputBoxes: Atteste } } -async function sendBoxContextExtension(device: Device, sessionId: number, extension: Buffer) { +async function sendBoxContextExtension( + device: Device, + sessionId: number, + extension: Buffer +) { await device.sendData( COMMAND.SIGN_TX, P1.ADD_INPUT_BOX_CONTEXT_EXTENSION_CHUNK, @@ -123,9 +148,15 @@ async function sendBoxContextExtension(device: Device, sessionId: number, extens ); } -async function sendDataInputs(device: Device, sessionId: number, boxIds: string[]) { +async function sendDataInputs( + device: Device, + sessionId: number, + boxIds: string[] +) { const MAX_PACKET_SIZE = 7; - const packets = Serialize.arrayAndChunk(boxIds, MAX_PACKET_SIZE, (id) => Serialize.hex(id)); + const packets = serialize.arrayAndChunk(boxIds, MAX_PACKET_SIZE, (id) => + serialize.hex(id) + ); for (let p of packets) { await device.send(COMMAND.SIGN_TX, P1.ADD_DATA_INPUTS, sessionId, p); @@ -139,7 +170,9 @@ async function sendOutputs( changeMap: ChangeMap, distinctTokenIds: Uint8Array[] ) { - const distinctTokenIdsStr = distinctTokenIds.map((t) => Buffer.from(t).toString("hex")); + const distinctTokenIdsStr = distinctTokenIds.map((t) => + Buffer.from(t).toString("hex") + ); for (let box of boxes) { await device.send( @@ -147,25 +180,32 @@ async function sendOutputs( P1.ADD_OUTPUT_BOX_START, sessionId, Buffer.concat([ - Serialize.uint64(box.value), - Serialize.uint32(box.ergoTree.length), - Serialize.uint32(box.creationHeight), - Serialize.uint8(box.tokens.length), - Serialize.uint32(box.registers.length) + serialize.uint64(box.value), + serialize.uint32(box.ergoTree.length), + serialize.uint32(box.creationHeight), + serialize.uint8(box.tokens.length), + serialize.uint32(box.registers.length) ]) ); - const tree = Deserialize.hex(box.ergoTree); + const tree = deserialize.hex(box.ergoTree); if (tree === MINER_FEE_TREE) { await addOutputBoxMinersFeeTree(device, sessionId); - } else if (ErgoAddress.fromErgoTree(tree).toString() === changeMap.address) { + } else if ( + ErgoAddress.fromErgoTree(tree).toString() === changeMap.address + ) { await addOutputBoxChangeTree(device, sessionId, changeMap.path); } else { await addOutputBoxErgoTree(device, sessionId, box.ergoTree); } if (box.tokens && box.tokens.length > 0) { - await addOutputBoxTokens(device, sessionId, box.tokens, distinctTokenIdsStr); + await addOutputBoxTokens( + device, + sessionId, + box.tokens, + distinctTokenIdsStr + ); } if (box.registers.length > 0) { @@ -174,20 +214,38 @@ async function sendOutputs( } } -async function addOutputBoxErgoTree(device: Device, sessionId: number, ergoTree: Buffer) { - await device.sendData(COMMAND.SIGN_TX, P1.ADD_OUTPUT_BOX_ERGO_TREE_CHUNK, sessionId, ergoTree); +async function addOutputBoxErgoTree( + device: Device, + sessionId: number, + ergoTree: Buffer +) { + await device.sendData( + COMMAND.SIGN_TX, + P1.ADD_OUTPUT_BOX_ERGO_TREE_CHUNK, + sessionId, + ergoTree + ); } async function addOutputBoxMinersFeeTree(device: Device, sessionId: number) { - await device.send(COMMAND.SIGN_TX, P1.ADD_OUTPUT_BOX_MINERS_FEE_TREE, sessionId, Buffer.from([])); + await device.send( + COMMAND.SIGN_TX, + P1.ADD_OUTPUT_BOX_MINERS_FEE_TREE, + sessionId, + Buffer.from([]) + ); } -async function addOutputBoxChangeTree(device: Device, sessionId: number, path: string) { +async function addOutputBoxChangeTree( + device: Device, + sessionId: number, + path: string +) { await device.send( COMMAND.SIGN_TX, P1.ADD_OUTPUT_BOX_CHANGE_TREE, sessionId, - Serialize.bip32Path(path) + serialize.bip32Path(path) ); } @@ -201,17 +259,32 @@ async function addOutputBoxTokens( COMMAND.SIGN_TX, P1.ADD_OUTPUT_BOX_TOKENS, sessionId, - Serialize.array(tokens, (t) => - Buffer.concat([Serialize.uint32(distinctTokenIds.indexOf(t.id)), Serialize.uint64(t.amount)]) + serialize.array(tokens, (t) => + Buffer.concat([ + serialize.uint32(distinctTokenIds.indexOf(t.id)), + serialize.uint64(t.amount) + ]) ) ); } -async function addOutputBoxRegisters(device: Device, sessionId: number, registers: Buffer) { - await device.sendData(COMMAND.SIGN_TX, P1.ADD_OUTPUT_BOX_REGISTERS_CHUNK, sessionId, registers); +async function addOutputBoxRegisters( + device: Device, + sessionId: number, + registers: Buffer +) { + await device.sendData( + COMMAND.SIGN_TX, + P1.ADD_OUTPUT_BOX_REGISTERS_CHUNK, + sessionId, + registers + ); } -async function sendConfirmAndSign(device: Device, sessionId: number): Promise { +async function sendConfirmAndSign( + device: Device, + sessionId: number +): Promise { const response = await device.send( COMMAND.SIGN_TX, P1.CONFIRM_AND_SIGN, diff --git a/src/serialization/deserialize.ts b/src/serialization/deserialize.ts index 6b2c8f3..69e3752 100644 --- a/src/serialization/deserialize.ts +++ b/src/serialization/deserialize.ts @@ -1,38 +1,38 @@ import basex from "base-x"; -import { assert } from "../validations"; +import { assert } from "../assertions"; const bs10 = basex("0123456789"); -export default class Deserialize { - public static hex(buffer: Buffer): string { +export const deserialize = { + hex(buffer: Buffer): string { return buffer.toString("hex"); - } + }, - public static ascii(buffer: Buffer): string { + ascii(buffer: Buffer): string { return buffer.toString("ascii"); - } + }, - public static uint8(data: Buffer): number { + uint8(data: Buffer): number { assert(data.length === 1, "invalid uint8 buffer"); return data.readUIntBE(0, 1); - } + }, - public static uint16(data: Buffer): number { + uint16(data: Buffer): number { assert(data.length === 2, "invalid uint16 buffer"); return data.readUIntBE(0, 2); - } + }, - public static uint32(data: Buffer): number { + uint32(data: Buffer): number { assert(data.length === 4, "invalid uint32 buffer"); return data.readUIntBE(0, 4); - } + }, - public static uint64(buffer: Buffer): string { + uint64(buffer: Buffer): string { assert(buffer.length === 8, "invalid uint64 buffer"); - return this.trimLeadingZeros(bs10.encode(buffer)); + return trimLeadingZeros(bs10.encode(buffer)); } +}; - private static trimLeadingZeros(text: string): string { - return text.replace(/^0+/, ""); - } +function trimLeadingZeros(text: string): string { + return text.replace(/^0+/, ""); } diff --git a/src/serialization/serialize.spec.ts b/src/serialization/serialize.spec.ts index 5e0e49e..3d91c3b 100644 --- a/src/serialization/serialize.spec.ts +++ b/src/serialization/serialize.spec.ts @@ -1,13 +1,13 @@ import { describe, expect, it } from "vitest"; -import Serialize from "./serialize"; +import { serialize } from "./serialize"; describe("serializations", () => { describe("serialize class", () => { it("should serialize and split", () => { const MAX_CHUNK_LENGTH = 3; const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - const chunks = Serialize.arrayAndChunk(arr, MAX_CHUNK_LENGTH, (n) => - Serialize.uint8(n) + const chunks = serialize.arrayAndChunk(arr, MAX_CHUNK_LENGTH, (n) => + serialize.uint8(n) ); expect(chunks).toHaveLength(4); diff --git a/src/serialization/serialize.ts b/src/serialization/serialize.ts index 06ec167..434d92e 100644 --- a/src/serialization/serialize.ts +++ b/src/serialization/serialize.ts @@ -6,15 +6,16 @@ import { isUint64String, isUint8, isValidBip32Path -} from "../validations"; +} from "../assertions"; import basex from "base-x"; import bip32Path from "bip32-path"; const bs10 = basex("0123456789"); -export default class Serialize { - public static bip32Path(path: number[] | string): Buffer { - var pathArray = typeof path === "string" ? this.bip32PathAsArray(path) : path; +export const serialize = { + bip32Path(path: number[] | string): Buffer { + var pathArray = + typeof path === "string" ? this.bip32PathAsArray(path) : path; const buffer = Buffer.alloc(1 + pathArray.length * 4); buffer[0] = pathArray.length; @@ -23,61 +24,61 @@ export default class Serialize { }); return buffer; - } + }, - public static bip32PathAsArray(path: string): number[] { + bip32PathAsArray(path: string): number[] { assert(isValidBip32Path(path), "Invalid Bip32 path."); return bip32Path.fromString(path).toPathArray(); - } + }, - public static uint8(value: number): Buffer { + uint8(value: number): Buffer { assert(isUint8(value), "invalid uint8 value"); const data = Buffer.alloc(1); data.writeUInt8(value, 0); return data; - } + }, - public static uint16(value: number): Buffer { + uint16(value: number): Buffer { assert(isUint16(value), "invalid uint16 value"); const data = Buffer.alloc(2); data.writeUInt16BE(value, 0); return data; - } + }, - public static uint32(value: number): Buffer { + uint32(value: number): Buffer { assert(isUint32(value), "invalid uint32 value"); const buffer = Buffer.alloc(4); buffer.writeUInt32BE(value, 0); return buffer; - } + }, - public static uint64(value: string): Buffer { + uint64(value: string): Buffer { assert(isUint64String(value), "invalid uint64 string"); const data = bs10.decode(value); assert(data.length <= 8, "excessive data"); const padding = Buffer.alloc(8 - data.length); return Buffer.concat([padding, Buffer.from(data)]); - } + }, - public static hex(data: string): Buffer { + hex(data: string): Buffer { assert(isHexString(data), "invalid hex string"); return Buffer.from(data, "hex"); - } + }, - public static array(data: T[], serializeCallback: (value: T) => Buffer): Buffer { + array(data: T[], serializeCallback: (value: T) => Buffer): Buffer { const chucks: Buffer[] = []; for (let i = 0; i < data.length; i++) { chucks.push(serializeCallback(data[i])); } return Buffer.concat(chucks); - } + }, - public static arrayAndChunk( + arrayAndChunk( data: T[], maxPacketSize: number, serializeCallback: (value: T) => Buffer @@ -85,7 +86,11 @@ export default class Serialize { const packets = []; for (let i = 0; i < Math.ceil(data.length / maxPacketSize); i++) { const chunks = []; - for (let j = i * maxPacketSize; j < Math.min((i + 1) * maxPacketSize, data.length); j++) { + for ( + let j = i * maxPacketSize; + j < Math.min((i + 1) * maxPacketSize, data.length); + j++ + ) { chunks.push(serializeCallback(data[j])); } packets.push(Buffer.concat(chunks)); @@ -93,4 +98,4 @@ export default class Serialize { return packets; } -} +}; diff --git a/src/serialization/utils.ts b/src/serialization/utils.ts index 6f07bf9..2f7dcf5 100644 --- a/src/serialization/utils.ts +++ b/src/serialization/utils.ts @@ -1,4 +1,4 @@ -import { assert, isArray, isBuffer, isInteger } from "../validations"; +import { assert, isArray, isBuffer, isInteger } from "../assertions"; const sum = (arr: Array) => arr.reduce((x, y) => x + y, 0); diff --git a/src/models/attestedBox.ts b/src/types/attestedBox.ts similarity index 87% rename from src/models/attestedBox.ts rename to src/types/attestedBox.ts index e783b42..5c8e5ec 100644 --- a/src/models/attestedBox.ts +++ b/src/types/attestedBox.ts @@ -1,7 +1,7 @@ -import { AttestedBoxFrame, UnsignedBox } from "../types/public"; -import { assert } from "../validations"; +import { assert } from "../assertions"; +import type { UnsignedBox, AttestedBoxFrame } from "./public"; -export default class AttestedBox { +export class AttestedBox { private _box: UnsignedBox; private _frames: AttestedBoxFrame[]; private _extension?: Buffer; diff --git a/src/types/internal.ts b/src/types/internal.ts index 0d83c40..d1a1a1a 100644 --- a/src/types/internal.ts +++ b/src/types/internal.ts @@ -1,13 +1,13 @@ -import { RETURN_CODE } from "../errors"; -import AttestedBox from "../models/attestedBox"; -import { BoxCandidate, ChangeMap } from "./public"; +import type { RETURN_CODE } from "../device"; +import type { BoxCandidate, ChangeMap } from "./public"; +import type { AttestedBox } from "./attestedBox"; export type DeviceResponse = { data: Buffer; returnCode: RETURN_CODE; }; -export type AttestedTx = { +export type AttestedTransaction = { inputs: AttestedBox[]; dataInputs: string[]; outputs: BoxCandidate[]; @@ -15,6 +15,6 @@ export type AttestedTx = { changeMap: ChangeMap; }; -export type SignTxResponse = { +export type SignTransactionResponse = { [path: string]: Uint8Array; }; diff --git a/src/validations/assert.ts b/src/validations/assert.ts deleted file mode 100644 index 492e8e2..0000000 --- a/src/validations/assert.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function assert(cond: boolean, errMsg: string): asserts cond { - if (!cond) { - throw new Error(`Assertion failed${errMsg ? `: ${errMsg}` : "."}`); - } -} diff --git a/src/validations/index.ts b/src/validations/index.ts deleted file mode 100644 index 66fe245..0000000 --- a/src/validations/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./assert"; -export * from "./parse"; From 3f213523c595244761913f76a64d011b41e12d56 Mon Sep 17 00:00:00 2001 From: arobsn <87387688+arobsn@users.noreply.github.com> Date: Tue, 2 Jul 2024 09:29:44 -0300 Subject: [PATCH 02/11] bump dependencies --- package.json | 9 +++--- pnpm-lock.yaml | 81 +++++++++++++++++++++++++++++++------------------- 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 90538ee..806a573 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,10 @@ "access": "public" }, "dependencies": { - "@fleet-sdk/core": "0.1.0-alpha.12", - "base-x": "^4.0.0", + "@fleet-sdk/common": "^0.4.1", + "@fleet-sdk/core": "0.5.0", + "@fleet-sdk/crypto": "^0.5.0", + "base-x": "^5.0.0", "bip32-path": "^0.4.2" }, "devDependencies": { @@ -66,11 +68,10 @@ "@ledgerhq/hw-transport": "^6.31.0", "@ledgerhq/hw-transport-mocker": "^6.29.0", "@types/node": "^20.14.9", - "@types/typescript": "^2.0.0", "@vitest/coverage-v8": "^1.6.0", "open-cli": "^8.0.0", "tsup": "^8.1.0", - "typescript": "^5.5.2", + "typescript": "^5.5.3", "vitest": "^1.6.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 81a12f7..e579a8f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,12 +8,18 @@ importers: .: dependencies: + '@fleet-sdk/common': + specifier: ^0.4.1 + version: 0.4.1 '@fleet-sdk/core': - specifier: 0.1.0-alpha.12 - version: 0.1.0-alpha.12 + specifier: 0.5.0 + version: 0.5.0 + '@fleet-sdk/crypto': + specifier: ^0.5.0 + version: 0.5.0 base-x: - specifier: ^4.0.0 - version: 4.0.0 + specifier: ^5.0.0 + version: 5.0.0 bip32-path: specifier: ^0.4.2 version: 0.4.2 @@ -30,9 +36,6 @@ importers: '@types/node': specifier: ^20.14.9 version: 20.14.9 - '@types/typescript': - specifier: ^2.0.0 - version: 2.0.0 '@vitest/coverage-v8': specifier: ^1.6.0 version: 1.6.0(vitest@1.6.0(@types/node@20.14.9)) @@ -41,10 +44,10 @@ importers: version: 8.0.0 tsup: specifier: ^8.1.0 - version: 8.1.0(postcss@8.4.39)(typescript@5.5.2) + version: 8.1.0(postcss@8.4.39)(typescript@5.5.3) typescript: - specifier: ^5.5.2 - version: 5.5.2 + specifier: ^5.5.3 + version: 5.5.3 vitest: specifier: ^1.6.0 version: 1.6.0(@types/node@20.14.9) @@ -266,9 +269,21 @@ packages: cpu: [x64] os: [win32] - '@fleet-sdk/core@0.1.0-alpha.12': - resolution: {integrity: sha512-pBrMcygKe2WNcEDR/ucJU/0Gb2VVmuHdBXnScdX4mnf8fZwdqRfO/YsXew2Z/fR8eGwB+KhPpteOhzamx8ZzoA==} - engines: {node: '>=10'} + '@fleet-sdk/common@0.4.1': + resolution: {integrity: sha512-jivyBz7kAye1FrS2gDlZkVpu7rrWAw1aySX+OkIWJA703P9BrrAEmBGU0jz4tKL+3LY2CsJhulOlpSAuc3/ivQ==} + engines: {node: '>=14'} + + '@fleet-sdk/core@0.5.0': + resolution: {integrity: sha512-uPWd3p4E+Feh1qXYe56WwSd98uB/bDzjziKOJWZBD1NJ2gd0F5TN+w9p31tp65xALAjZNRrztuslULCGbZOAZA==} + engines: {node: '>=18'} + + '@fleet-sdk/crypto@0.5.0': + resolution: {integrity: sha512-ktfhO8r/SFYQfz3QcPWvRkvm5qZHLMT/zXXs4jVIbt7jQUSXvEX5f90YzO1n6eqs3G3xeb/fg0wMI5dYb4QXMA==} + engines: {node: '>=18'} + + '@fleet-sdk/serializer@0.5.0': + resolution: {integrity: sha512-pQsebhoJKhdmj9jZSh5pqwiVn/S+FkYdltdYDoWLTTR8x4zAxNyB4OORybnMojJTpjoUyEO+kK+LiA+7OrbpCg==} + engines: {node: '>=18'} '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} @@ -430,10 +445,6 @@ packages: '@types/node@20.14.9': resolution: {integrity: sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==} - '@types/typescript@2.0.0': - resolution: {integrity: sha512-WMEWfMISiJ2QKyk5/dSdgL0ZwP//PZj0jmDU0hMh51FmLq4WIYzjlngsUQZXejQL+QtkXJUOGjb3G3UCvgZuSQ==} - deprecated: This is a stub types definition for TypeScript (https://github.com/Microsoft/TypeScript). TypeScript provides its own type definitions, so you don't need @types/typescript installed! - '@vitest/coverage-v8@1.6.0': resolution: {integrity: sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==} peerDependencies: @@ -500,8 +511,8 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base-x@4.0.0: - resolution: {integrity: sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==} + base-x@5.0.0: + resolution: {integrity: sha512-sMW3VGSX1QWVFA6l8U62MLKz29rRfpTlYdCqLdpLo1/Yd4zZwSbnUaDfciIAowAqvq7YFnWq9hrhdg1KYgc1lQ==} binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} @@ -1200,8 +1211,8 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - typescript@5.5.2: - resolution: {integrity: sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==} + typescript@5.5.3: + resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} engines: {node: '>=14.17'} hasBin: true @@ -1439,11 +1450,25 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true - '@fleet-sdk/core@0.1.0-alpha.12': + '@fleet-sdk/common@0.4.1': {} + + '@fleet-sdk/core@0.5.0': + dependencies: + '@fleet-sdk/common': 0.4.1 + '@fleet-sdk/crypto': 0.5.0 + '@fleet-sdk/serializer': 0.5.0 + + '@fleet-sdk/crypto@0.5.0': dependencies: + '@fleet-sdk/common': 0.4.1 '@noble/hashes': 1.4.0 '@scure/base': 1.1.7 + '@fleet-sdk/serializer@0.5.0': + dependencies: + '@fleet-sdk/common': 0.4.1 + '@fleet-sdk/crypto': 0.5.0 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -1577,10 +1602,6 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/typescript@2.0.0': - dependencies: - typescript: 5.5.2 - '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.9))': dependencies: '@ampproject/remapping': 2.3.0 @@ -1660,7 +1681,7 @@ snapshots: balanced-match@1.0.2: {} - base-x@4.0.0: {} + base-x@5.0.0: {} binary-extensions@2.3.0: {} @@ -2329,7 +2350,7 @@ snapshots: tslib@2.6.3: {} - tsup@8.1.0(postcss@8.4.39)(typescript@5.5.2): + tsup@8.1.0(postcss@8.4.39)(typescript@5.5.3): dependencies: bundle-require: 4.2.1(esbuild@0.21.5) cac: 6.7.14 @@ -2347,7 +2368,7 @@ snapshots: tree-kill: 1.2.2 optionalDependencies: postcss: 8.4.39 - typescript: 5.5.2 + typescript: 5.5.3 transitivePeerDependencies: - supports-color - ts-node @@ -2358,7 +2379,7 @@ snapshots: type-fest@2.19.0: {} - typescript@5.5.2: {} + typescript@5.5.3: {} ufo@1.5.3: {} From 074bc60edf1e497f6f7a97e901aab292baf3a2ef Mon Sep 17 00:00:00 2001 From: arobsn <87387688+arobsn@users.noreply.github.com> Date: Tue, 2 Jul 2024 10:19:39 -0300 Subject: [PATCH 03/11] first serializer refactor round --- src/assertions.ts | 21 ++++------- src/device.ts | 2 +- src/erg.ts | 35 ++++-------------- src/interactions/attestInput.ts | 4 +-- src/interactions/deriveAddress.ts | 26 +++++++++----- src/interactions/getExtendedPublicKey.ts | 6 ++-- src/interactions/signTx.ts | 8 ++--- src/serialization/serialize.spec.ts | 6 ++-- src/serialization/serialize.ts | 45 +++++++++++++----------- src/serialization/utils.spec.ts | 7 +--- src/serialization/utils.ts | 26 +++++--------- src/{ => types}/bip32-path.d.ts | 0 12 files changed, 79 insertions(+), 107 deletions(-) rename src/{ => types}/bip32-path.d.ts (100%) diff --git a/src/assertions.ts b/src/assertions.ts index f3667af..ef15474 100644 --- a/src/assertions.ts +++ b/src/assertions.ts @@ -7,6 +7,10 @@ const MAX_UINT32_VALUE = 4294967295; const MAX_UINT16_VALUE = 65535; const MAX_UNIT8_VALUE = 255; +const [ERGO_PURPOSE, ERGO_COIN_TYPE] = bip32Path + .fromString("m/44'/429'") + .toPathArray(); + export function assert(cond: boolean, errMsg: string): asserts cond { if (!cond) { throw new Error(`Assertion failed${errMsg ? `: ${errMsg}` : "."}`); @@ -14,7 +18,7 @@ export function assert(cond: boolean, errMsg: string): asserts cond { } export function isValidBip32Path(path: number[] | string): boolean { - if (typeof path == "string") { + if (typeof path === "string") { return bip32Path.validateString(path, true); } @@ -22,15 +26,9 @@ export function isValidBip32Path(path: number[] | string): boolean { } export function isValidErgoPath(path: number[]): boolean { - if (path.length < 2) { - return false; - } - + if (path.length < 2) return false; const [pathPurpose, pathCoinType] = path; - const [ergoPurpose, ergoCoinType] = bip32Path - .fromString("m/44'/429'") - .toPathArray(); - return pathPurpose === ergoPurpose && pathCoinType === ergoCoinType; + return pathPurpose === ERGO_PURPOSE && pathCoinType === ERGO_COIN_TYPE; } export function isInteger(data: unknown): boolean { @@ -88,8 +86,3 @@ export function isUint64String(data: string): boolean { (data.length > MIN_UINT_64_STR.length || data >= MIN_UINT_64_STR) ); } - -export const isHexString = (data: unknown) => - typeof data === "string" && - data.length % 2 === 0 && - /^[0-9a-fA-F]*$/.test(data); diff --git a/src/device.ts b/src/device.ts index 73313d5..e466c6f 100644 --- a/src/device.ts +++ b/src/device.ts @@ -6,7 +6,7 @@ export const enum COMMAND { GET_APP_VERSION = 0x01, GET_APP_NAME = 0x02, - GET_EXTENTED_PUB_KEY = 0x10, + GET_EXTENDED_PUB_KEY = 0x10, DERIVE_ADDRESS = 0x11, ATTEST_INPUT = 0x20, SIGN_TX = 0x21 diff --git a/src/erg.ts b/src/erg.ts index 39b1807..5e7752c 100644 --- a/src/erg.ts +++ b/src/erg.ts @@ -21,18 +21,16 @@ import { signTx } from "./interactions"; import { serialize } from "./serialization/serialize"; +import { uniq } from "@fleet-sdk/common"; import type { AttestedTransaction, SignTransactionResponse } from "./types/internal"; -import { uniq } from "./serialization/utils"; export { DeviceError, RETURN_CODE }; export * from "./types/public"; export const CLA = 0xe0; -const CHANGE_PATH_INDEX = 3; - /** * Ergo's Ledger hardware wallet API */ @@ -128,10 +126,7 @@ export class ErgoLedgerApp { */ public async getExtendedPublicKey(path: string): Promise { this._debug("getExtendedPublicKey", path); - - const pathArray = serialize.bip32PathAsArray(path); - assert(isValidErgoPath(pathArray), "Invalid Ergo path."); - return getExtendedPublicKey(this._device, pathArray, this.authToken); + return getExtendedPublicKey(this._device, path, this.authToken); } /** @@ -144,9 +139,7 @@ export class ErgoLedgerApp { network = Network.Mainnet ): Promise { this._debug("deriveAddress", path); - - const pathArray = this.getDerivationPathArray(path); - return deriveAddress(this._device, network, pathArray, this.authToken); + return deriveAddress(this._device, network, path, this.authToken); } /** @@ -159,21 +152,7 @@ export class ErgoLedgerApp { network = Network.Mainnet ): Promise { this._debug("showAddress", path); - - const pathArray = this.getDerivationPathArray(path); - return showAddress(this._device, network, pathArray, this.authToken); - } - - private getDerivationPathArray(path: string) { - const pathArray = serialize.bip32PathAsArray(path); - assert(isValidErgoPath(pathArray), "Invalid Ergo path."); - assert(pathArray.length >= 5, "Invalid path length."); - assert( - pathArray[CHANGE_PATH_INDEX] in [0, 1], - "Invalid change path value." - ); - - return pathArray; + return showAddress(this._device, network, path, this.authToken); } public async attestInput(box: UnsignedBox): Promise { @@ -206,7 +185,7 @@ export class ErgoLedgerApp { }; const signatures: SignTransactionResponse = {}; - for (let path of signPaths) { + for (const path of signPaths) { signatures[path] = await signTx( this._device, attestedTx, @@ -217,7 +196,7 @@ export class ErgoLedgerApp { } const signBytes: Uint8Array[] = []; - for (let input of tx.inputs) { + for (const input of tx.inputs) { signBytes.push(signatures[input.signPath]); } @@ -235,7 +214,7 @@ export class ErgoLedgerApp { return attestedBoxes; } - private _debug(caller: string, message: any = "") { + private _debug(caller: string, message: unknown = "") { if (!this._logging) { return; } diff --git a/src/interactions/attestInput.ts b/src/interactions/attestInput.ts index 991e683..14d20ba 100644 --- a/src/interactions/attestInput.ts +++ b/src/interactions/attestInput.ts @@ -88,12 +88,12 @@ async function sendTokens( sessionId: number ): Promise { const MAX_PACKET_SIZE = 6; - const packets = serialize.arrayAndChunk(tokens, MAX_PACKET_SIZE, (t) => + const packets = serialize.arrayAsMappedChunks(tokens, MAX_PACKET_SIZE, (t) => Buffer.concat([serialize.hex(t.id), serialize.uint64(t.amount)]) ); const results: DeviceResponse[] = []; - for (let p of packets) { + for (const p of packets) { results.push( await device.send(COMMAND.ATTEST_INPUT, P1.ADD_TOKENS, sessionId, p) ); diff --git a/src/interactions/deriveAddress.ts b/src/interactions/deriveAddress.ts index 33d2abe..92ca49b 100644 --- a/src/interactions/deriveAddress.ts +++ b/src/interactions/deriveAddress.ts @@ -1,12 +1,12 @@ import { COMMAND, RETURN_CODE, type Device } from "../device"; import type { DerivedAddress, Network } from "../types/public"; import type { DeviceResponse } from "../types/internal"; -import { serialize } from "../serialization/serialize"; +import { pathToArray, serialize } from "../serialization/serialize"; import { deserialize } from "../serialization/deserialize"; const enum ReturnType { - Return, - Display + Return = 0x01, + Display = 0x02 } const enum P1 { @@ -19,20 +19,30 @@ const enum P2 { WITH_TOKEN = 0x02 } +const CHANGE_PATH_INDEX = 3; +const ALLOWED_CHANGE_PATHS = [0, 1]; + function sendDeriveAddress( device: Device, network: Network, - path: number[], + path: string, returnType: ReturnType, authToken?: number ): Promise { + const pathArray = pathToArray(path); + if (pathArray.length < 5) throw new Error("Invalid path length."); + if (!ALLOWED_CHANGE_PATHS.includes(pathArray[CHANGE_PATH_INDEX])) { + throw new Error("Invalid change path value."); + } + const data = Buffer.concat([ Buffer.alloc(1, network), - serialize.bip32Path(path) + serialize.path(pathArray) ]); + return device.send( COMMAND.DERIVE_ADDRESS, - returnType == ReturnType.Return ? P1.RETURN : P1.DISPLAY, + returnType === ReturnType.Return ? P1.RETURN : P1.DISPLAY, authToken ? P2.WITH_TOKEN : P2.WITHOUT_TOKEN, authToken ? Buffer.concat([data, serialize.uint32(authToken)]) : data ); @@ -41,7 +51,7 @@ function sendDeriveAddress( export async function deriveAddress( device: Device, network: Network, - path: number[], + path: string, authToken?: number ): Promise { const response = await sendDeriveAddress( @@ -57,7 +67,7 @@ export async function deriveAddress( export async function showAddress( device: Device, network: Network, - path: number[], + path: string, authToken?: number ): Promise { const response = await sendDeriveAddress( diff --git a/src/interactions/getExtendedPublicKey.ts b/src/interactions/getExtendedPublicKey.ts index 1fce170..36f82fe 100644 --- a/src/interactions/getExtendedPublicKey.ts +++ b/src/interactions/getExtendedPublicKey.ts @@ -15,12 +15,12 @@ const enum P2 { export async function getExtendedPublicKey( device: Device, - path: number[], + path: string, authToken?: number ): Promise { - const data = serialize.bip32Path(path); + const data = serialize.path(path); const response = await device.send( - COMMAND.GET_EXTENTED_PUB_KEY, + COMMAND.GET_EXTENDED_PUB_KEY, authToken ? P1.WITH_TOKEN : P1.WITHOUT_TOKEN, P2.UNUSED, authToken ? Buffer.concat([data, serialize.uint32(authToken)]) : data diff --git a/src/interactions/signTx.ts b/src/interactions/signTx.ts index 24662ee..79016fb 100644 --- a/src/interactions/signTx.ts +++ b/src/interactions/signTx.ts @@ -66,7 +66,7 @@ async function sendHeader( authToken ? P2.WITH_TOKEN : P2.WITHOUT_TOKEN, Buffer.concat([ serialize.uint8(network), - serialize.bip32Path(path), + serialize.path(path), authToken ? serialize.uint32(authToken) : Buffer.alloc(0) ]) ); @@ -105,7 +105,7 @@ async function sendDistinctTokensIds( } const MAX_PACKET_SIZE = 7; - const packets = serialize.arrayAndChunk(ids, MAX_PACKET_SIZE, (id) => + const packets = serialize.arrayAsMappedChunks(ids, MAX_PACKET_SIZE, (id) => Buffer.from(id) ); @@ -154,7 +154,7 @@ async function sendDataInputs( boxIds: string[] ) { const MAX_PACKET_SIZE = 7; - const packets = serialize.arrayAndChunk(boxIds, MAX_PACKET_SIZE, (id) => + const packets = serialize.arrayAsMappedChunks(boxIds, MAX_PACKET_SIZE, (id) => serialize.hex(id) ); @@ -245,7 +245,7 @@ async function addOutputBoxChangeTree( COMMAND.SIGN_TX, P1.ADD_OUTPUT_BOX_CHANGE_TREE, sessionId, - serialize.bip32Path(path) + serialize.path(path) ); } diff --git a/src/serialization/serialize.spec.ts b/src/serialization/serialize.spec.ts index 3d91c3b..b36a32f 100644 --- a/src/serialization/serialize.spec.ts +++ b/src/serialization/serialize.spec.ts @@ -6,8 +6,10 @@ describe("serializations", () => { it("should serialize and split", () => { const MAX_CHUNK_LENGTH = 3; const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - const chunks = serialize.arrayAndChunk(arr, MAX_CHUNK_LENGTH, (n) => - serialize.uint8(n) + const chunks = serialize.arrayAsMappedChunks( + arr, + MAX_CHUNK_LENGTH, + serialize.uint8 ); expect(chunks).toHaveLength(4); diff --git a/src/serialization/serialize.ts b/src/serialization/serialize.ts index 434d92e..8fd5fb7 100644 --- a/src/serialization/serialize.ts +++ b/src/serialization/serialize.ts @@ -1,11 +1,12 @@ +import { isHex } from "@fleet-sdk/common"; import { assert, - isHexString, isUint16, isUint32, isUint64String, isUint8, - isValidBip32Path + isValidBip32Path, + isValidErgoPath } from "../assertions"; import basex from "base-x"; import bip32Path from "bip32-path"; @@ -13,22 +14,18 @@ import bip32Path from "bip32-path"; const bs10 = basex("0123456789"); export const serialize = { - bip32Path(path: number[] | string): Buffer { - var pathArray = - typeof path === "string" ? this.bip32PathAsArray(path) : path; + path(path: number[] | string): Buffer { + const pathArray = typeof path === "string" ? pathToArray(path) : path; + assert(isValidErgoPath(pathArray), "Invalid Ergo path"); const buffer = Buffer.alloc(1 + pathArray.length * 4); buffer[0] = pathArray.length; - pathArray.forEach((element: any, index: number) => { - buffer.writeUInt32BE(element, 1 + 4 * index); - }); - return buffer; - }, + for (let i = 0; i < pathArray.length; i++) { + buffer.writeUInt32BE(pathArray[i], 1 + 4 * i); + } - bip32PathAsArray(path: string): number[] { - assert(isValidBip32Path(path), "Invalid Bip32 path."); - return bip32Path.fromString(path).toPathArray(); + return buffer; }, uint8(value: number): Buffer { @@ -65,7 +62,7 @@ export const serialize = { }, hex(data: string): Buffer { - assert(isHexString(data), "invalid hex string"); + assert(isHex(data), "invalid hex string"); return Buffer.from(data, "hex"); }, @@ -78,24 +75,30 @@ export const serialize = { return Buffer.concat(chucks); }, - arrayAndChunk( + arrayAsMappedChunks( data: T[], - maxPacketSize: number, - serializeCallback: (value: T) => Buffer + maxSize: number, + encode: (value: T) => Buffer ): Buffer[] { const packets = []; - for (let i = 0; i < Math.ceil(data.length / maxPacketSize); i++) { + for (let i = 0; i < Math.ceil(data.length / maxSize); i++) { const chunks = []; for ( - let j = i * maxPacketSize; - j < Math.min((i + 1) * maxPacketSize, data.length); + let j = i * maxSize; + j < Math.min((i + 1) * maxSize, data.length); j++ ) { - chunks.push(serializeCallback(data[j])); + chunks.push(encode(data[j])); } + packets.push(Buffer.concat(chunks)); } return packets; } }; + +export function pathToArray(path: string): number[] { + assert(isValidBip32Path(path), "Invalid Bip32 path."); + return bip32Path.fromString(path).toPathArray(); +} diff --git a/src/serialization/utils.spec.ts b/src/serialization/utils.spec.ts index 0b73f73..212acb1 100644 --- a/src/serialization/utils.spec.ts +++ b/src/serialization/utils.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { chunkBy, uniq } from "./utils"; +import { chunkBy } from "./utils"; describe("Utils test", () => { it("should chunk buffers", () => { @@ -9,9 +9,4 @@ describe("Utils test", () => { expect(first.length).toEqual(5); expect(last.length).toEqual(6); }); - - it("should return a duplicate free array", () => { - const array = ["a", "b", "a", "c", "c", "a", "d", "a"]; - expect(uniq(array)).toEqual(["a", "b", "c", "d"]); - }); }); diff --git a/src/serialization/utils.ts b/src/serialization/utils.ts index 2f7dcf5..310c82e 100644 --- a/src/serialization/utils.ts +++ b/src/serialization/utils.ts @@ -1,28 +1,18 @@ -import { assert, isArray, isBuffer, isInteger } from "../assertions"; +import { assert } from "../assertions"; -const sum = (arr: Array) => arr.reduce((x, y) => x + y, 0); +const sum = (arr: number[]) => arr.reduce((x, y) => x + y, 0); -export function uniq(array: T[]): T[] { - return [...new Set(array)]; -} - -export function chunkBy(data: Buffer, chunkLengths: Array) { - assert(isBuffer(data), "invalid buffer"); - assert(isArray(chunkLengths), "invalid chunks"); - for (const len of chunkLengths) { - assert(isInteger(len), "bad chunk length"); - assert(len > 0, "bad chunk length"); - } - assert(data.length <= sum(chunkLengths), "data too short"); +export function chunkBy(data: Buffer, chunkLengths: number[]) { + assert(data.length <= sum(chunkLengths), "data is too big"); let offset = 0; const result = []; const restLength = data.length - sum(chunkLengths); - for (let c of [...chunkLengths, restLength]) { - result.push(data.slice(offset, offset + c)); - - offset += c; + for (const length of [...chunkLengths, restLength]) { + assert(length >= 0, `bad chunk length: ${length}`); + result.push(data.subarray(offset, offset + length)); + offset += length; } return result; diff --git a/src/bip32-path.d.ts b/src/types/bip32-path.d.ts similarity index 100% rename from src/bip32-path.d.ts rename to src/types/bip32-path.d.ts From 363a58ec62372fd252a5a1ffe54369834edb0953 Mon Sep 17 00:00:00 2001 From: arobsn <87387688+arobsn@users.noreply.github.com> Date: Tue, 2 Jul 2024 10:51:16 -0300 Subject: [PATCH 04/11] refactor interactions --- biome.json | 3 +- src/assertions.ts | 6 +- src/erg.spec.ts | 30 +++++++++- src/erg.ts | 2 - src/interactions/attestInput.spec.ts | 8 +-- src/interactions/attestInput.ts | 28 +++++---- src/interactions/deriveAddress.ts | 10 +++- src/interactions/getVersion.ts | 4 +- src/interactions/signTx.ts | 85 +++++++--------------------- src/serialization/utils.ts | 2 +- src/types/attestedBox.ts | 2 +- src/types/public.ts | 6 +- 12 files changed, 84 insertions(+), 102 deletions(-) diff --git a/biome.json b/biome.json index 2374fc0..7aec70e 100644 --- a/biome.json +++ b/biome.json @@ -13,7 +13,8 @@ "enabled": true }, "formatter": { - "indentStyle": "space" + "indentStyle": "space", + "lineWidth": 90 }, "javascript": { "formatter": { diff --git a/src/assertions.ts b/src/assertions.ts index ef15474..a3b73df 100644 --- a/src/assertions.ts +++ b/src/assertions.ts @@ -45,7 +45,7 @@ export function isBuffer(data: unknown): boolean { export function isUint32(data: unknown): boolean { return ( - typeof data == "number" && + typeof data === "number" && isInteger(data) && data >= MIN_UINT_VALUE && data <= MAX_UINT32_VALUE @@ -54,7 +54,7 @@ export function isUint32(data: unknown): boolean { export function isUint16(data: number): boolean { return ( - typeof data == "number" && + typeof data === "number" && isInteger(data) && data >= MIN_UINT_VALUE && data <= MAX_UINT16_VALUE @@ -63,7 +63,7 @@ export function isUint16(data: number): boolean { export function isUint8(data: unknown): boolean { return ( - typeof data == "number" && + typeof data === "number" && isInteger(data) && data >= MIN_UINT_VALUE && data <= MAX_UNIT8_VALUE diff --git a/src/erg.spec.ts b/src/erg.spec.ts index d305aaf..2781aed 100644 --- a/src/erg.spec.ts +++ b/src/erg.spec.ts @@ -198,6 +198,30 @@ describe("public key management without auth token", () => { "Operation denied by user" ); }); + + it("should fail when deriving address with invalid path", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e01102011600058000002c800001ad800000000000000000000000 + <= 6985 + `) + ); + + const app = new ErgoLedgerApp(transport).useAuthToken(false); + await expect(() => app.showAddress("m/44'/429'/0'/3/0")).rejects.toThrow( + "Invalid change path: 3" + ); + await expect(() => app.showAddress("m/44'/429'")).rejects.toThrow( + "Invalid path length. 2" + ); + + await expect(() => app.deriveAddress("m/44'/429'/0'/10/0")).rejects.toThrow( + "Invalid change path: 10" + ); + await expect(() => app.deriveAddress("m/44'/429'/0'/1")).rejects.toThrow( + "Invalid path length. 4" + ); + }); }); describe("transaction signing", () => { @@ -274,14 +298,14 @@ describe("transaction signing", () => { attestation: "8c2d9e1dcd467155df32bf1ded800710", boxId: "2bb2f3111e33ad9d9ab1fa3ae184fc1f06a6ac5a71d40a825997f6637b44784f", - buffer: Buffer.from([ + bytes: Buffer.from([ 43, 178, 243, 17, 30, 51, 173, 157, 154, 177, 250, 58, 225, 132, 252, 31, 6, 166, 172, 90, 113, 212, 10, 130, 89, 151, 246, 99, 123, 68, 120, 79, 1, 0, 0, 0, 0, 0, 5, 245, 225, 0, 0, 140, 45, 158, 29, 205, 70, 113, 85, 223, 50, 191, 29, 237, 128, 7, 16 ]), - frameIndex: 0, - framesCount: 1, + index: 0, + count: 1, tokens: [] } ]); diff --git a/src/erg.ts b/src/erg.ts index 5e7752c..0f62988 100644 --- a/src/erg.ts +++ b/src/erg.ts @@ -9,7 +9,6 @@ import { type UnsignedTx, Network } from "./types/public"; -import { assert, isValidErgoPath } from "./assertions"; import type { AttestedBox } from "./types/attestedBox"; import { getAppName, @@ -20,7 +19,6 @@ import { attestInput, signTx } from "./interactions"; -import { serialize } from "./serialization/serialize"; import { uniq } from "@fleet-sdk/common"; import type { AttestedTransaction, diff --git a/src/interactions/attestInput.spec.ts b/src/interactions/attestInput.spec.ts index e8a827f..a89b2a2 100644 --- a/src/interactions/attestInput.spec.ts +++ b/src/interactions/attestInput.spec.ts @@ -1,19 +1,19 @@ import { describe, expect, it } from "vitest"; -import { parseAttestedFrameResponse } from "./attestInput"; +import { decodeAttestedFrameResponse } from "./attestInput"; describe("attestInput test", () => { const frameHex = "7cbe85a5f2d2154538eb883bbbee10dd414ec24b6e52b43495da906bce2c5e8a010000000000ab6a1fde032d554219a80c011cc51509e34fa4950965bb8e01de4d012536e766c9ca08bc2c000000174876e7febcd5db3a2872f279ef89edaa51a9344a6095ea1f03396874b695b5ba95ff602e00000017483412969f90c012e03bf99397e363fb1571b7999941e0862a217307e3467ee80cf53af700000000000000012f5151af1796a5827de6df5339ddca7a"; it("should parse frame response", () => { - const parsedFrame = parseAttestedFrameResponse( + const parsedFrame = decodeAttestedFrameResponse( Buffer.from(frameHex, "hex") ); expect(parsedFrame).toMatchObject({ boxId: "7cbe85a5f2d2154538eb883bbbee10dd414ec24b6e52b43495da906bce2c5e8a", - framesCount: 1, - frameIndex: 0, + count: 1, + index: 0, amount: "2875858910", tokens: [ { diff --git a/src/interactions/attestInput.ts b/src/interactions/attestInput.ts index 14d20ba..4219b46 100644 --- a/src/interactions/attestInput.ts +++ b/src/interactions/attestInput.ts @@ -133,39 +133,37 @@ async function getAttestedFrames( Buffer.from([i]) ); - responses.push(parseAttestedFrameResponse(response.data)); + responses.push(decodeAttestedFrameResponse(response.data)); } return responses; } -export function parseAttestedFrameResponse( - frameBuff: Buffer -): AttestedBoxFrame { +export function decodeAttestedFrameResponse(bytes: Buffer): AttestedBoxFrame { let offset = 0; - const boxId = deserialize.hex(frameBuff.slice(offset, (offset += 32))); - const count = deserialize.uint8(frameBuff.slice(offset, (offset += 1))); - const index = deserialize.uint8(frameBuff.slice(offset, (offset += 1))); - const amount = deserialize.uint64(frameBuff.slice(offset, (offset += 8))); - const tokenCount = deserialize.uint8(frameBuff.slice(offset, (offset += 1))); + const boxId = deserialize.hex(bytes.subarray(offset, (offset += 32))); + const count = deserialize.uint8(bytes.subarray(offset, (offset += 1))); + const index = deserialize.uint8(bytes.subarray(offset, (offset += 1))); + const amount = deserialize.uint64(bytes.subarray(offset, (offset += 8))); + const tokenCount = deserialize.uint8(bytes.subarray(offset, (offset += 1))); const tokens: Token[] = []; for (let i = 0; i < tokenCount; i++) { tokens.push({ - id: deserialize.hex(frameBuff.slice(offset, (offset += 32))), - amount: deserialize.uint64(frameBuff.slice(offset, (offset += 8))) + id: deserialize.hex(bytes.subarray(offset, (offset += 32))), + amount: deserialize.uint64(bytes.subarray(offset, (offset += 8))) }); } - const attestation = deserialize.hex(frameBuff.slice(offset, (offset += 16))); + const attestation = deserialize.hex(bytes.subarray(offset, (offset += 16))); return { boxId, - framesCount: count, - frameIndex: index, + count, + index, amount, tokens, attestation, - buffer: frameBuff + bytes }; } diff --git a/src/interactions/deriveAddress.ts b/src/interactions/deriveAddress.ts index 92ca49b..06ab249 100644 --- a/src/interactions/deriveAddress.ts +++ b/src/interactions/deriveAddress.ts @@ -30,9 +30,13 @@ function sendDeriveAddress( authToken?: number ): Promise { const pathArray = pathToArray(path); - if (pathArray.length < 5) throw new Error("Invalid path length."); - if (!ALLOWED_CHANGE_PATHS.includes(pathArray[CHANGE_PATH_INDEX])) { - throw new Error("Invalid change path value."); + if (pathArray.length < 5) { + throw new Error(`Invalid path length. ${pathArray.length}`); + } + + const change = pathArray[CHANGE_PATH_INDEX]; + if (!ALLOWED_CHANGE_PATHS.includes(change)) { + throw new Error(`Invalid change path: ${change}`); } const data = Buffer.concat([ diff --git a/src/interactions/getVersion.ts b/src/interactions/getVersion.ts index 8cb14d7..d3bebc1 100644 --- a/src/interactions/getVersion.ts +++ b/src/interactions/getVersion.ts @@ -1,7 +1,7 @@ import type { Version } from "../types/public"; import { COMMAND, type Device } from "../device"; -const FLAG_IS_DEBUG = 0x01; +const IS_DEBUG_FLAG = 0x01; const enum P1 { UNUSED = 0x00 @@ -23,6 +23,6 @@ export async function getAppVersion(device: Device): Promise { major: response.data[0], minor: response.data[1], patch: response.data[2], - flags: { isDebug: response.data[3] == FLAG_IS_DEBUG } + flags: { isDebug: response.data[3] === IS_DEBUG_FLAG } }; } diff --git a/src/interactions/signTx.ts b/src/interactions/signTx.ts index 79016fb..cd2212c 100644 --- a/src/interactions/signTx.ts +++ b/src/interactions/signTx.ts @@ -42,16 +42,10 @@ export async function signTx( await sendDistinctTokensIds(device, sessionId, tx.distinctTokenIds); await sendInputs(device, sessionId, tx.inputs); await sendDataInputs(device, sessionId, tx.dataInputs); - await sendOutputs( - device, - sessionId, - tx.outputs, - tx.changeMap, - tx.distinctTokenIds - ); - const signBytes = await sendConfirmAndSign(device, sessionId); + await sendOutputs(device, sessionId, tx.outputs, tx.changeMap, tx.distinctTokenIds); + const proof = await sendConfirmAndSign(device, sessionId); - return new Uint8Array(signBytes); + return new Uint8Array(proof); } async function sendHeader( @@ -100,37 +94,26 @@ async function sendDistinctTokensIds( sessionId: number, ids: Uint8Array[] ) { - if (ids.length === 0) { - return; - } + if (ids.length === 0) return; const MAX_PACKET_SIZE = 7; const packets = serialize.arrayAsMappedChunks(ids, MAX_PACKET_SIZE, (id) => Buffer.from(id) ); - for (let p of packets) { + for (const p of packets) { await device.send(COMMAND.SIGN_TX, P1.ADD_TOKEN_IDS, sessionId, p); } } -async function sendInputs( - device: Device, - sessionId: number, - inputBoxes: AttestedBox[] -) { - for (let box of inputBoxes) { - for (let frame of box.frames) { - await device.send( - COMMAND.SIGN_TX, - P1.ADD_INPUT_BOX_FRAME, - sessionId, - frame.buffer - ); +async function sendInputs(device: Device, sessionId: number, inputs: AttestedBox[]) { + for (const input of inputs) { + for (const frame of input.frames) { + await device.send(COMMAND.SIGN_TX, P1.ADD_INPUT_BOX_FRAME, sessionId, frame.bytes); } - if (box.extension !== undefined && box.extension.length > 0) { - await sendBoxContextExtension(device, sessionId, box.extension); + if (input.extension !== undefined && input.extension.length > 0) { + await sendBoxContextExtension(device, sessionId, input.extension); } } } @@ -148,17 +131,11 @@ async function sendBoxContextExtension( ); } -async function sendDataInputs( - device: Device, - sessionId: number, - boxIds: string[] -) { +async function sendDataInputs(device: Device, sessionId: number, boxIds: string[]) { const MAX_PACKET_SIZE = 7; - const packets = serialize.arrayAsMappedChunks(boxIds, MAX_PACKET_SIZE, (id) => - serialize.hex(id) - ); + const packets = serialize.arrayAsMappedChunks(boxIds, MAX_PACKET_SIZE, serialize.hex); - for (let p of packets) { + for (const p of packets) { await device.send(COMMAND.SIGN_TX, P1.ADD_DATA_INPUTS, sessionId, p); } } @@ -170,11 +147,9 @@ async function sendOutputs( changeMap: ChangeMap, distinctTokenIds: Uint8Array[] ) { - const distinctTokenIdsStr = distinctTokenIds.map((t) => - Buffer.from(t).toString("hex") - ); + const distinctTokenIdsStr = distinctTokenIds.map((t) => Buffer.from(t).toString("hex")); - for (let box of boxes) { + for (const box of boxes) { await device.send( COMMAND.SIGN_TX, P1.ADD_OUTPUT_BOX_START, @@ -191,21 +166,14 @@ async function sendOutputs( const tree = deserialize.hex(box.ergoTree); if (tree === MINER_FEE_TREE) { await addOutputBoxMinersFeeTree(device, sessionId); - } else if ( - ErgoAddress.fromErgoTree(tree).toString() === changeMap.address - ) { + } else if (ErgoAddress.fromErgoTree(tree).toString() === changeMap.address) { await addOutputBoxChangeTree(device, sessionId, changeMap.path); } else { await addOutputBoxErgoTree(device, sessionId, box.ergoTree); } if (box.tokens && box.tokens.length > 0) { - await addOutputBoxTokens( - device, - sessionId, - box.tokens, - distinctTokenIdsStr - ); + await addOutputBoxTokens(device, sessionId, box.tokens, distinctTokenIdsStr); } if (box.registers.length > 0) { @@ -214,11 +182,7 @@ async function sendOutputs( } } -async function addOutputBoxErgoTree( - device: Device, - sessionId: number, - ergoTree: Buffer -) { +async function addOutputBoxErgoTree(device: Device, sessionId: number, ergoTree: Buffer) { await device.sendData( COMMAND.SIGN_TX, P1.ADD_OUTPUT_BOX_ERGO_TREE_CHUNK, @@ -236,11 +200,7 @@ async function addOutputBoxMinersFeeTree(device: Device, sessionId: number) { ); } -async function addOutputBoxChangeTree( - device: Device, - sessionId: number, - path: string -) { +async function addOutputBoxChangeTree(device: Device, sessionId: number, path: string) { await device.send( COMMAND.SIGN_TX, P1.ADD_OUTPUT_BOX_CHANGE_TREE, @@ -281,10 +241,7 @@ async function addOutputBoxRegisters( ); } -async function sendConfirmAndSign( - device: Device, - sessionId: number -): Promise { +async function sendConfirmAndSign(device: Device, sessionId: number): Promise { const response = await device.send( COMMAND.SIGN_TX, P1.CONFIRM_AND_SIGN, diff --git a/src/serialization/utils.ts b/src/serialization/utils.ts index 310c82e..a831cee 100644 --- a/src/serialization/utils.ts +++ b/src/serialization/utils.ts @@ -3,7 +3,7 @@ import { assert } from "../assertions"; const sum = (arr: number[]) => arr.reduce((x, y) => x + y, 0); export function chunkBy(data: Buffer, chunkLengths: number[]) { - assert(data.length <= sum(chunkLengths), "data is too big"); + assert(data.length >= sum(chunkLengths), "data is too small"); let offset = 0; const result = []; diff --git a/src/types/attestedBox.ts b/src/types/attestedBox.ts index 5c8e5ec..6bb1f9b 100644 --- a/src/types/attestedBox.ts +++ b/src/types/attestedBox.ts @@ -36,7 +36,7 @@ export class AttestedBox { lengthBuffer.writeUInt32BE(extension.length, 0); } - firstFrame.buffer = Buffer.concat([firstFrame.buffer, lengthBuffer]); + firstFrame.bytes = Buffer.concat([firstFrame.bytes, lengthBuffer]); return this; } diff --git a/src/types/public.ts b/src/types/public.ts index 1aed31c..badf479 100644 --- a/src/types/public.ts +++ b/src/types/public.ts @@ -64,13 +64,13 @@ export type BoxCandidate = { export type AttestedBoxFrame = { boxId: string; - framesCount: number; - frameIndex: number; + count: number; + index: number; amount: string; tokens: Token[]; attestation: string; extensionLength?: number; - buffer: Buffer; + bytes: Buffer; }; export type UnsignedTx = { From 995f3c68c7f3ea651816e7ee089803f14573c928 Mon Sep 17 00:00:00 2001 From: arobsn <87387688+arobsn@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:02:17 -0300 Subject: [PATCH 05/11] refactor device --- src/device.spec.ts | 36 +++++++++++++++++++++++++- src/device.ts | 64 ++++++++++++++++++++++------------------------ 2 files changed, 66 insertions(+), 34 deletions(-) diff --git a/src/device.spec.ts b/src/device.spec.ts index 3243bb8..da3c130 100644 --- a/src/device.spec.ts +++ b/src/device.spec.ts @@ -1,5 +1,7 @@ import { describe, expect, it } from "vitest"; -import { DeviceError, RETURN_CODE } from "./device"; +import { Device, DeviceError, RETURN_CODE } from "./device"; +import { RecordStore, openTransportReplayer } from "@ledgerhq/hw-transport-mocker"; +import { CLA } from "./erg"; describe("DeviceError construction", () => { it("should create a new DeviceError instance from a know RETURN_CODE", () => { @@ -15,3 +17,35 @@ describe("DeviceError construction", () => { expect(error.message).toBe("Unknown error"); }); }); + +describe("Negative tests", () => { + it("Should throw an error if the response length is less than the minimum", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e00102030104 + <= 69 + `) + ); + + const device = new Device(transport, CLA); + + await expect(() => device.send(0x1, 0x2, 0x3, Buffer.from([0x4]))).rejects.toThrow( + "Wrong response length" + ); + }); + + it("Should throw if data is too long", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e00102030104 + <= 9000 + `) + ); + + const device = new Device(transport, CLA); + + await expect(() => device.send(0x1, 0x2, 0x3, Buffer.alloc(260))).rejects.toThrow( + "Too much data" + ); + }); +}); diff --git a/src/device.ts b/src/device.ts index e466c6f..720f807 100644 --- a/src/device.ts +++ b/src/device.ts @@ -16,27 +16,27 @@ const MAX_DATA_LENGTH = 255; const MIN_RESPONSE_LENGTH = 2; export class Device { - private _transport: Transport; - private _cla: number; + #transport: Transport; + #cla: number; - public get transport(): Transport { - return this._transport; + get transport(): Transport { + return this.#transport; } constructor(transport: Transport, cla: number) { - this._transport = transport; - this._cla = cla; + this.#transport = transport; + this.#cla = cla; } - public async sendData( + async sendData( ins: COMMAND, p1: number, p2: number, data: Buffer ): Promise { - let responses: DeviceResponse[] = []; + const responses: DeviceResponse[] = []; for (let i = 0; i < Math.ceil(data.length / MAX_DATA_LENGTH); i++) { - const chunk = data.slice( + const chunk = data.subarray( i * MAX_DATA_LENGTH, Math.min((i + 1) * MAX_DATA_LENGTH, data.length) ); @@ -47,7 +47,7 @@ export class Device { return responses; } - public async send( + async send( ins: COMMAND, p1: number, p2: number, @@ -57,43 +57,41 @@ export class Device { throw new DeviceError(RETURN_CODE.TOO_MUCH_DATA); } - const apdu = this.mountApdu(this._cla, ins, p1, p2, data); - const response = await this.transport.exchange(apdu); + const adpu = mountApdu(this.#cla, ins, p1, p2, data); + const response = await this.transport.exchange(adpu); if (response.length < MIN_RESPONSE_LENGTH) { throw new DeviceError(RETURN_CODE.WRONG_RESPONSE_LENGTH); } const returnCode = response.readUInt16BE(response.length - 2); - if (returnCode != RETURN_CODE.OK) { - throw new DeviceError(returnCode); - } + if (returnCode !== RETURN_CODE.OK) throw new DeviceError(returnCode); - const responseData = response.slice(0, response.length - 2); + const responseData = response.subarray(0, response.length - 2); return { returnCode, data: responseData }; } +} - private mountApdu( - cla: number, - ins: COMMAND, - p1: number, - p2: number, - data: Buffer - ): Buffer { - return Buffer.concat([ - serialize.uint8(cla), - serialize.uint8(ins), - serialize.uint8(p1), - serialize.uint8(p2), - serialize.uint8(data.length), - data - ]); - } +function mountApdu( + cla: number, + ins: COMMAND, + p1: number, + p2: number, + data: Buffer +): Buffer { + return Buffer.concat([ + serialize.uint8(cla), + serialize.uint8(ins), + serialize.uint8(p1), + serialize.uint8(p2), + serialize.uint8(data.length), + data + ]); } export class DeviceError extends Error { #code; - public get code() { + get code() { return this.#code; } From 6e29d52db71761c03ed5f3e4ef62cac476920fd9 Mon Sep 17 00:00:00 2001 From: arobsn <87387688+arobsn@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:03:45 -0300 Subject: [PATCH 06/11] fix formatting --- rollup.config.ts | 62 ------ src/assertions.ts | 4 +- src/erg.spec.ts | 305 +++++++++++++-------------- src/erg.ts | 15 +- src/interactions/attestInput.spec.ts | 4 +- src/interactions/attestInput.ts | 15 +- src/interactions/deriveAddress.ts | 5 +- src/serialization/serialize.ts | 6 +- 8 files changed, 151 insertions(+), 265 deletions(-) delete mode 100644 rollup.config.ts diff --git a/rollup.config.ts b/rollup.config.ts deleted file mode 100644 index 6786ba3..0000000 --- a/rollup.config.ts +++ /dev/null @@ -1,62 +0,0 @@ -import json from "rollup-plugin-json"; -import typescript from "rollup-plugin-typescript2"; -import commonjs from "rollup-plugin-commonjs"; -import resolve from "rollup-plugin-node-resolve"; -import uglify from "@lopatnov/rollup-plugin-uglify"; - -import pkg from "./package.json"; -const globals = { - "bip32-path": "bip32Path", - "base-x": "basex", - "@fleet-sdk/core": "core" -}; - -export default [ - { - input: `src/${pkg.libraryFile}.ts`, - output: [ - { - file: pkg.main, - format: "umd", - name: pkg.umdName, - sourcemap: true, - globals - }, - { - file: pkg.module, - format: "es", - sourcemap: true, - globals - } - ], - external: [...Object.keys(pkg.devDependencies || {}), ...Object.keys(pkg.dependencies || {})], - plugins: [ - json(), - typescript({ - typescript: require("typescript") - }), - resolve(), - commonjs() - ] - }, - { - input: `src/${pkg.libraryFile}.ts`, - output: { - file: `dist/${pkg.libraryFile}.min.js`, - name: pkg.umdName, - format: "umd", - sourcemap: true, - globals - }, - external: [...Object.keys(pkg.devDependencies || {}), ...Object.keys(pkg.dependencies || {})], - plugins: [ - json(), - typescript({ - typescript: require("typescript") - }), - resolve(), - commonjs(), - uglify() - ] - } -]; diff --git a/src/assertions.ts b/src/assertions.ts index a3b73df..279b740 100644 --- a/src/assertions.ts +++ b/src/assertions.ts @@ -7,9 +7,7 @@ const MAX_UINT32_VALUE = 4294967295; const MAX_UINT16_VALUE = 65535; const MAX_UNIT8_VALUE = 255; -const [ERGO_PURPOSE, ERGO_COIN_TYPE] = bip32Path - .fromString("m/44'/429'") - .toPathArray(); +const [ERGO_PURPOSE, ERGO_COIN_TYPE] = bip32Path.fromString("m/44'/429'").toPathArray(); export function assert(cond: boolean, errMsg: string): asserts cond { if (!cond) { diff --git a/src/erg.spec.ts b/src/erg.spec.ts index 2781aed..6a0cf1e 100644 --- a/src/erg.spec.ts +++ b/src/erg.spec.ts @@ -1,9 +1,6 @@ import { describe, it, expect, test, vi } from "vitest"; import { ErgoLedgerApp } from "./erg"; -import { - openTransportReplayer, - RecordStore -} from "@ledgerhq/hw-transport-mocker"; +import { openTransportReplayer, RecordStore } from "@ledgerhq/hw-transport-mocker"; describe("construction", () => { it("should construct app with transport", async () => { @@ -96,10 +93,8 @@ describe("public key management with auth token", () => { expect(app.authToken).toEqual(authToken); expect(extendedPublicKey).toEqual({ - publicKey: - "025381e95e132a4b7a6fc66844a81657a07da1ef5041eaefb7fce03f71c06a11a9", - chainCode: - "9cc4eb9abc8d3f55afeff7bcb8fe2d0a8d100fa35f6fcbac74deded867633eba" + publicKey: "025381e95e132a4b7a6fc66844a81657a07da1ef5041eaefb7fce03f71c06a11a9", + chainCode: "9cc4eb9abc8d3f55afeff7bcb8fe2d0a8d100fa35f6fcbac74deded867633eba" }); }); @@ -150,10 +145,8 @@ describe("public key management without auth token", () => { const extendedPublicKey = await app.getExtendedPublicKey("m/44'/429'/0'"); expect(extendedPublicKey).toEqual({ - publicKey: - "025381e95e132a4b7a6fc66844a81657a07da1ef5041eaefb7fce03f71c06a11a9", - chainCode: - "9cc4eb9abc8d3f55afeff7bcb8fe2d0a8d100fa35f6fcbac74deded867633eba" + publicKey: "025381e95e132a4b7a6fc66844a81657a07da1ef5041eaefb7fce03f71c06a11a9", + chainCode: "9cc4eb9abc8d3f55afeff7bcb8fe2d0a8d100fa35f6fcbac74deded867633eba" }); }); @@ -166,9 +159,9 @@ describe("public key management without auth token", () => { ); const app = new ErgoLedgerApp(transport).useAuthToken(false); - await expect(() => - app.getExtendedPublicKey("m/44'/429'/0'") - ).rejects.toThrow("Operation denied by user"); + await expect(() => app.getExtendedPublicKey("m/44'/429'/0'")).rejects.toThrow( + "Operation denied by user" + ); }); it("should show address", async () => { @@ -228,13 +221,9 @@ describe("transaction signing", () => { test.each(txTestVectors)( "should sign $name", async ({ adpuQueue, authToken, proofs, tx }) => { - const transport = await openTransportReplayer( - RecordStore.fromString(adpuQueue) - ); + const transport = await openTransportReplayer(RecordStore.fromString(adpuQueue)); - const app = new ErgoLedgerApp(transport, authToken).useAuthToken( - !!authToken - ); + const app = new ErgoLedgerApp(transport, authToken).useAuthToken(!!authToken); const result = await app.signTx(tx); expect(result).to.deep.equal(proofs); @@ -243,9 +232,7 @@ describe("transaction signing", () => { it("Should throw with empty inputs", async () => { const { adpuQueue, tx } = txTestVectors[0]; - const transport = await openTransportReplayer( - RecordStore.fromString(adpuQueue) - ); + const transport = await openTransportReplayer(RecordStore.fromString(adpuQueue)); const app = new ErgoLedgerApp(transport); await expect(() => app.signTx({ ...tx, inputs: [] })).rejects.toThrow( @@ -278,9 +265,8 @@ describe("transaction signing", () => { index: 0, value: "100000000", ergoTree: Buffer.from([ - 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, - 2, 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, - 196, 33, 45 + 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, 245, + 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, 45 ]), creationHeight: 1298664, tokens: [], @@ -296,13 +282,12 @@ describe("transaction signing", () => { { amount: "100000000", attestation: "8c2d9e1dcd467155df32bf1ded800710", - boxId: - "2bb2f3111e33ad9d9ab1fa3ae184fc1f06a6ac5a71d40a825997f6637b44784f", + boxId: "2bb2f3111e33ad9d9ab1fa3ae184fc1f06a6ac5a71d40a825997f6637b44784f", bytes: Buffer.from([ - 43, 178, 243, 17, 30, 51, 173, 157, 154, 177, 250, 58, 225, 132, 252, - 31, 6, 166, 172, 90, 113, 212, 10, 130, 89, 151, 246, 99, 123, 68, - 120, 79, 1, 0, 0, 0, 0, 0, 5, 245, 225, 0, 0, 140, 45, 158, 29, 205, - 70, 113, 85, 223, 50, 191, 29, 237, 128, 7, 16 + 43, 178, 243, 17, 30, 51, 173, 157, 154, 177, 250, 58, 225, 132, 252, 31, 6, + 166, 172, 90, 113, 212, 10, 130, 89, 151, 246, 99, 123, 68, 120, 79, 1, 0, 0, 0, + 0, 0, 5, 245, 225, 0, 0, 140, 45, 158, 29, 205, 70, 113, 85, 223, 50, 191, 29, + 237, 128, 7, 16 ]), index: 0, count: 1, @@ -375,9 +360,9 @@ const txTestVectors = [ index: 1, value: "108997360", ergoTree: Buffer.from([ - 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, - 220, 2, 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, - 228, 25, 196, 33, 45 + 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, + 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, + 45 ]), creationHeight: 1298593, tokens: [], @@ -391,9 +376,9 @@ const txTestVectors = [ { value: "100000000", ergoTree: Buffer.from([ - 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, - 220, 2, 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, - 228, 25, 196, 33, 45 + 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, + 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, + 45 ]), creationHeight: 1298664, tokens: [], @@ -402,13 +387,12 @@ const txTestVectors = [ { value: "1100000", ergoTree: Buffer.from([ - 16, 5, 4, 0, 4, 0, 14, 54, 16, 2, 4, 160, 11, 8, 205, 2, 121, 190, - 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, - 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, - 234, 2, 209, 146, 163, 154, 140, 199, 167, 1, 115, 0, 115, 1, 16, 1, - 2, 4, 2, 209, 150, 131, 3, 1, 147, 163, 140, 199, 178, 165, 115, 0, - 0, 1, 147, 194, 178, 165, 115, 1, 0, 116, 115, 2, 115, 3, 131, 1, 8, - 205, 238, 172, 147, 177, 165, 115, 4 + 16, 5, 4, 0, 4, 0, 14, 54, 16, 2, 4, 160, 11, 8, 205, 2, 121, 190, 102, 126, + 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, + 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, 234, 2, 209, 146, 163, 154, + 140, 199, 167, 1, 115, 0, 115, 1, 16, 1, 2, 4, 2, 209, 150, 131, 3, 1, 147, + 163, 140, 199, 178, 165, 115, 0, 0, 1, 147, 194, 178, 165, 115, 1, 0, 116, + 115, 2, 115, 3, 131, 1, 8, 205, 238, 172, 147, 177, 165, 115, 4 ]), creationHeight: 1298664, tokens: [], @@ -417,9 +401,9 @@ const txTestVectors = [ { value: "7897360", ergoTree: Buffer.from([ - 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, - 220, 2, 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, - 228, 25, 196, 33, 45 + 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, + 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, + 45 ]), creationHeight: 1298664, tokens: [], @@ -434,10 +418,10 @@ const txTestVectors = [ }, proofs: [ Uint8Array.from([ - 68, 90, 41, 103, 107, 77, 70, 59, 154, 66, 102, 174, 66, 213, 208, 70, - 230, 97, 14, 147, 99, 71, 214, 174, 10, 34, 200, 245, 57, 246, 237, 96, - 119, 119, 126, 148, 120, 245, 1, 230, 87, 234, 188, 107, 140, 239, 212, - 6, 192, 10, 80, 184, 8, 64, 165, 254 + 68, 90, 41, 103, 107, 77, 70, 59, 154, 66, 102, 174, 66, 213, 208, 70, 230, 97, + 14, 147, 99, 71, 214, 174, 10, 34, 200, 245, 57, 246, 237, 96, 119, 119, 126, 148, + 120, 245, 1, 230, 87, 234, 188, 107, 140, 239, 212, 6, 192, 10, 80, 184, 8, 64, + 165, 254 ]) ] }, @@ -527,9 +511,9 @@ const txTestVectors = [ index: 11, value: "40680", ergoTree: Buffer.from([ - 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, - 220, 2, 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, - 228, 25, 196, 33, 45 + 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, + 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, + 45 ]), creationHeight: 1298572, tokens: [ @@ -547,9 +531,9 @@ const txTestVectors = [ index: 1, value: "111156680", ergoTree: Buffer.from([ - 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, - 220, 2, 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, - 228, 25, 196, 33, 45 + 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, + 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, + 45 ]), creationHeight: 1298572, tokens: [], @@ -563,9 +547,9 @@ const txTestVectors = [ { value: "1000000", ergoTree: Buffer.from([ - 0, 8, 205, 3, 83, 218, 249, 90, 145, 149, 98, 41, 151, 37, 20, 226, - 244, 145, 253, 241, 249, 226, 237, 71, 147, 249, 22, 130, 158, 228, - 39, 208, 35, 181, 27, 1 + 0, 8, 205, 3, 83, 218, 249, 90, 145, 149, 98, 41, 151, 37, 20, 226, 244, 145, + 253, 241, 249, 226, 237, 71, 147, 249, 22, 130, 158, 228, 39, 208, 35, 181, + 27, 1 ]), creationHeight: 1298578, tokens: [ @@ -579,13 +563,12 @@ const txTestVectors = [ { value: "1100000", ergoTree: Buffer.from([ - 16, 5, 4, 0, 4, 0, 14, 54, 16, 2, 4, 160, 11, 8, 205, 2, 121, 190, - 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, - 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, - 234, 2, 209, 146, 163, 154, 140, 199, 167, 1, 115, 0, 115, 1, 16, 1, - 2, 4, 2, 209, 150, 131, 3, 1, 147, 163, 140, 199, 178, 165, 115, 0, - 0, 1, 147, 194, 178, 165, 115, 1, 0, 116, 115, 2, 115, 3, 131, 1, 8, - 205, 238, 172, 147, 177, 165, 115, 4 + 16, 5, 4, 0, 4, 0, 14, 54, 16, 2, 4, 160, 11, 8, 205, 2, 121, 190, 102, 126, + 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, + 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, 234, 2, 209, 146, 163, 154, + 140, 199, 167, 1, 115, 0, 115, 1, 16, 1, 2, 4, 2, 209, 150, 131, 3, 1, 147, + 163, 140, 199, 178, 165, 115, 0, 0, 1, 147, 194, 178, 165, 115, 1, 0, 116, + 115, 2, 115, 3, 131, 1, 8, 205, 238, 172, 147, 177, 165, 115, 4 ]), creationHeight: 1298578, tokens: [], @@ -594,9 +577,9 @@ const txTestVectors = [ { value: "109097360", ergoTree: Buffer.from([ - 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, - 220, 2, 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, - 228, 25, 196, 33, 45 + 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, + 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, + 45 ]), creationHeight: 1298578, tokens: [], @@ -605,9 +588,8 @@ const txTestVectors = [ ], distinctTokenIds: [ Uint8Array.from([ - 0, 177, 226, 54, 182, 11, 149, 194, 198, 248, 0, 122, 157, 137, 188, - 70, 15, 201, 231, 143, 152, 176, 159, 174, 201, 68, 144, 7, 180, 11, - 204, 243 + 0, 177, 226, 54, 182, 11, 149, 194, 198, 248, 0, 122, 157, 137, 188, 70, 15, + 201, 231, 143, 152, 176, 159, 174, 201, 68, 144, 7, 180, 11, 204, 243 ]) ], changeMap: { @@ -617,16 +599,16 @@ const txTestVectors = [ }, proofs: [ Uint8Array.from([ - 179, 67, 83, 188, 164, 121, 254, 90, 237, 4, 2, 94, 189, 71, 222, 179, - 210, 252, 37, 43, 22, 111, 112, 18, 7, 65, 100, 32, 251, 26, 136, 0, 5, - 102, 21, 145, 11, 28, 172, 27, 86, 234, 167, 11, 111, 55, 33, 165, 91, - 143, 156, 157, 109, 106, 90, 176 + 179, 67, 83, 188, 164, 121, 254, 90, 237, 4, 2, 94, 189, 71, 222, 179, 210, 252, + 37, 43, 22, 111, 112, 18, 7, 65, 100, 32, 251, 26, 136, 0, 5, 102, 21, 145, 11, + 28, 172, 27, 86, 234, 167, 11, 111, 55, 33, 165, 91, 143, 156, 157, 109, 106, 90, + 176 ]), Uint8Array.from([ - 179, 67, 83, 188, 164, 121, 254, 90, 237, 4, 2, 94, 189, 71, 222, 179, - 210, 252, 37, 43, 22, 111, 112, 18, 7, 65, 100, 32, 251, 26, 136, 0, 5, - 102, 21, 145, 11, 28, 172, 27, 86, 234, 167, 11, 111, 55, 33, 165, 91, - 143, 156, 157, 109, 106, 90, 176 + 179, 67, 83, 188, 164, 121, 254, 90, 237, 4, 2, 94, 189, 71, 222, 179, 210, 252, + 37, 43, 22, 111, 112, 18, 7, 65, 100, 32, 251, 26, 136, 0, 5, 102, 21, 145, 11, + 28, 172, 27, 86, 234, 167, 11, 111, 55, 33, 165, 91, 143, 156, 157, 109, 106, 90, + 176 ]) ] }, @@ -782,18 +764,17 @@ const txTestVectors = [ index: 1, value: "7553272000", ergoTree: Buffer.from([ - 16, 6, 4, 0, 14, 32, 215, 22, 147, 196, 154, 132, 251, 190, 205, 73, - 8, 201, 72, 19, 180, 101, 20, 177, 139, 103, 169, 153, 82, 220, 30, - 110, 71, 145, 85, 109, 228, 19, 4, 0, 4, 0, 5, 0, 5, 0, 216, 3, 214, - 1, 227, 0, 4, 214, 2, 228, 198, 167, 4, 8, 214, 3, 228, 198, 167, 5, - 5, 149, 230, 114, 1, 216, 4, 214, 4, 178, 165, 228, 114, 1, 0, 214, - 5, 178, 219, 99, 8, 114, 4, 115, 0, 0, 214, 6, 219, 99, 8, 167, 214, - 7, 153, 193, 167, 193, 114, 4, 209, 150, 131, 2, 1, 150, 131, 5, 1, - 147, 194, 114, 4, 194, 167, 147, 140, 114, 5, 1, 115, 1, 147, 228, - 198, 114, 4, 4, 8, 114, 2, 147, 228, 198, 114, 4, 5, 5, 114, 3, 147, - 228, 198, 114, 4, 6, 14, 197, 167, 150, 131, 2, 1, 146, 156, 153, - 140, 114, 5, 2, 149, 145, 177, 114, 6, 115, 2, 140, 178, 114, 6, - 115, 3, 0, 2, 115, 4, 114, 3, 114, 7, 146, 114, 7, 115, 5, 114, 2 + 16, 6, 4, 0, 14, 32, 215, 22, 147, 196, 154, 132, 251, 190, 205, 73, 8, 201, + 72, 19, 180, 101, 20, 177, 139, 103, 169, 153, 82, 220, 30, 110, 71, 145, 85, + 109, 228, 19, 4, 0, 4, 0, 5, 0, 5, 0, 216, 3, 214, 1, 227, 0, 4, 214, 2, 228, + 198, 167, 4, 8, 214, 3, 228, 198, 167, 5, 5, 149, 230, 114, 1, 216, 4, 214, 4, + 178, 165, 228, 114, 1, 0, 214, 5, 178, 219, 99, 8, 114, 4, 115, 0, 0, 214, 6, + 219, 99, 8, 167, 214, 7, 153, 193, 167, 193, 114, 4, 209, 150, 131, 2, 1, 150, + 131, 5, 1, 147, 194, 114, 4, 194, 167, 147, 140, 114, 5, 1, 115, 1, 147, 228, + 198, 114, 4, 4, 8, 114, 2, 147, 228, 198, 114, 4, 5, 5, 114, 3, 147, 228, 198, + 114, 4, 6, 14, 197, 167, 150, 131, 2, 1, 146, 156, 153, 140, 114, 5, 2, 149, + 145, 177, 114, 6, 115, 2, 140, 178, 114, 6, 115, 3, 0, 2, 115, 4, 114, 3, 114, + 7, 146, 114, 7, 115, 5, 114, 2 ]), creationHeight: 1215896, tokens: [ @@ -803,11 +784,11 @@ const txTestVectors = [ } ], additionalRegisters: Buffer.from([ - 3, 8, 205, 2, 15, 69, 105, 90, 11, 147, 166, 49, 136, 74, 44, 208, - 113, 106, 114, 82, 35, 35, 103, 133, 40, 82, 43, 49, 92, 84, 214, - 207, 156, 96, 76, 215, 5, 224, 209, 4, 14, 32, 219, 170, 253, 50, - 105, 221, 13, 51, 44, 153, 13, 165, 25, 164, 138, 117, 2, 78, 12, - 139, 200, 129, 160, 25, 177, 193, 63, 250, 87, 8, 230, 31 + 3, 8, 205, 2, 15, 69, 105, 90, 11, 147, 166, 49, 136, 74, 44, 208, 113, 106, + 114, 82, 35, 35, 103, 133, 40, 82, 43, 49, 92, 84, 214, 207, 156, 96, 76, 215, + 5, 224, 209, 4, 14, 32, 219, 170, 253, 50, 105, 221, 13, 51, 44, 153, 13, 165, + 25, 164, 138, 117, 2, 78, 12, 139, 200, 129, 160, 25, 177, 193, 63, 250, 87, + 8, 230, 31 ]), extension: Buffer.from([1, 0, 4, 2]), signPath: "m/44'/429'/0'/0/0" @@ -817,9 +798,9 @@ const txTestVectors = [ index: 18, value: "41760", ergoTree: Buffer.from([ - 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, - 220, 2, 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, - 228, 25, 196, 33, 45 + 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, + 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, + 45 ]), creationHeight: 1298593, tokens: [ @@ -837,9 +818,9 @@ const txTestVectors = [ index: 5, value: "40320", ergoTree: Buffer.from([ - 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, - 220, 2, 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, - 228, 25, 196, 33, 45 + 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, + 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, + 45 ]), creationHeight: 1298593, tokens: [ @@ -857,9 +838,9 @@ const txTestVectors = [ index: 0, value: "100000000", ergoTree: Buffer.from([ - 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, - 220, 2, 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, - 228, 25, 196, 33, 45 + 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, + 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, + 45 ]), creationHeight: 1298664, tokens: [], @@ -878,9 +859,9 @@ const txTestVectors = [ { value: "10000000", ergoTree: Buffer.from([ - 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, - 220, 2, 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, - 228, 25, 196, 33, 45 + 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, + 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, + 45 ]), creationHeight: 1298679, tokens: [ @@ -894,18 +875,17 @@ const txTestVectors = [ { value: "7551068000", ergoTree: Buffer.from([ - 16, 6, 4, 0, 14, 32, 215, 22, 147, 196, 154, 132, 251, 190, 205, 73, - 8, 201, 72, 19, 180, 101, 20, 177, 139, 103, 169, 153, 82, 220, 30, - 110, 71, 145, 85, 109, 228, 19, 4, 0, 4, 0, 5, 0, 5, 0, 216, 3, 214, - 1, 227, 0, 4, 214, 2, 228, 198, 167, 4, 8, 214, 3, 228, 198, 167, 5, - 5, 149, 230, 114, 1, 216, 4, 214, 4, 178, 165, 228, 114, 1, 0, 214, - 5, 178, 219, 99, 8, 114, 4, 115, 0, 0, 214, 6, 219, 99, 8, 167, 214, - 7, 153, 193, 167, 193, 114, 4, 209, 150, 131, 2, 1, 150, 131, 5, 1, - 147, 194, 114, 4, 194, 167, 147, 140, 114, 5, 1, 115, 1, 147, 228, - 198, 114, 4, 4, 8, 114, 2, 147, 228, 198, 114, 4, 5, 5, 114, 3, 147, - 228, 198, 114, 4, 6, 14, 197, 167, 150, 131, 2, 1, 146, 156, 153, - 140, 114, 5, 2, 149, 145, 177, 114, 6, 115, 2, 140, 178, 114, 6, - 115, 3, 0, 2, 115, 4, 114, 3, 114, 7, 146, 114, 7, 115, 5, 114, 2 + 16, 6, 4, 0, 14, 32, 215, 22, 147, 196, 154, 132, 251, 190, 205, 73, 8, 201, + 72, 19, 180, 101, 20, 177, 139, 103, 169, 153, 82, 220, 30, 110, 71, 145, 85, + 109, 228, 19, 4, 0, 4, 0, 5, 0, 5, 0, 216, 3, 214, 1, 227, 0, 4, 214, 2, 228, + 198, 167, 4, 8, 214, 3, 228, 198, 167, 5, 5, 149, 230, 114, 1, 216, 4, 214, 4, + 178, 165, 228, 114, 1, 0, 214, 5, 178, 219, 99, 8, 114, 4, 115, 0, 0, 214, 6, + 219, 99, 8, 167, 214, 7, 153, 193, 167, 193, 114, 4, 209, 150, 131, 2, 1, 150, + 131, 5, 1, 147, 194, 114, 4, 194, 167, 147, 140, 114, 5, 1, 115, 1, 147, 228, + 198, 114, 4, 4, 8, 114, 2, 147, 228, 198, 114, 4, 5, 5, 114, 3, 147, 228, 198, + 114, 4, 6, 14, 197, 167, 150, 131, 2, 1, 146, 156, 153, 140, 114, 5, 2, 149, + 145, 177, 114, 6, 115, 2, 140, 178, 114, 6, 115, 3, 0, 2, 115, 4, 114, 3, 114, + 7, 146, 114, 7, 115, 5, 114, 2 ]), creationHeight: 1298679, tokens: [ @@ -915,23 +895,22 @@ const txTestVectors = [ } ], registers: Buffer.from([ - 3, 8, 205, 2, 15, 69, 105, 90, 11, 147, 166, 49, 136, 74, 44, 208, - 113, 106, 114, 82, 35, 35, 103, 133, 40, 82, 43, 49, 92, 84, 214, - 207, 156, 96, 76, 215, 5, 224, 209, 4, 14, 32, 136, 100, 48, 96, 84, - 152, 13, 65, 112, 159, 76, 237, 216, 134, 240, 108, 158, 221, 45, - 15, 89, 161, 74, 250, 145, 226, 101, 96, 119, 216, 3, 242 + 3, 8, 205, 2, 15, 69, 105, 90, 11, 147, 166, 49, 136, 74, 44, 208, 113, 106, + 114, 82, 35, 35, 103, 133, 40, 82, 43, 49, 92, 84, 214, 207, 156, 96, 76, 215, + 5, 224, 209, 4, 14, 32, 136, 100, 48, 96, 84, 152, 13, 65, 112, 159, 76, 237, + 216, 134, 240, 108, 158, 221, 45, 15, 89, 161, 74, 250, 145, 226, 101, 96, + 119, 216, 3, 242 ]) }, { value: "2204000", ergoTree: Buffer.from([ - 16, 5, 4, 0, 4, 0, 14, 54, 16, 2, 4, 160, 11, 8, 205, 2, 121, 190, - 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, - 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, - 234, 2, 209, 146, 163, 154, 140, 199, 167, 1, 115, 0, 115, 1, 16, 1, - 2, 4, 2, 209, 150, 131, 3, 1, 147, 163, 140, 199, 178, 165, 115, 0, - 0, 1, 147, 194, 178, 165, 115, 1, 0, 116, 115, 2, 115, 3, 131, 1, 8, - 205, 238, 172, 147, 177, 165, 115, 4 + 16, 5, 4, 0, 4, 0, 14, 54, 16, 2, 4, 160, 11, 8, 205, 2, 121, 190, 102, 126, + 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, + 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, 234, 2, 209, 146, 163, 154, + 140, 199, 167, 1, 115, 0, 115, 1, 16, 1, 2, 4, 2, 209, 150, 131, 3, 1, 147, + 163, 140, 199, 178, 165, 115, 0, 0, 1, 147, 194, 178, 165, 115, 1, 0, 116, + 115, 2, 115, 3, 131, 1, 8, 205, 238, 172, 147, 177, 165, 115, 4 ]), creationHeight: 1298679, tokens: [], @@ -940,9 +919,9 @@ const txTestVectors = [ { value: "90041760", ergoTree: Buffer.from([ - 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, - 220, 2, 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, - 228, 25, 196, 33, 45 + 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, + 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, + 45 ]), creationHeight: 1298679, tokens: [], @@ -951,9 +930,9 @@ const txTestVectors = [ { value: "40320", ergoTree: Buffer.from([ - 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, - 220, 2, 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, - 228, 25, 196, 33, 45 + 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, + 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, + 45 ]), creationHeight: 1298679, tokens: [ @@ -967,14 +946,12 @@ const txTestVectors = [ ], distinctTokenIds: [ Uint8Array.from([ - 239, 128, 43, 71, 92, 6, 24, 159, 219, 248, 68, 21, 60, 220, 29, 68, - 154, 91, 168, 124, 206, 19, 209, 27, 180, 123, 90, 83, 159, 39, 241, - 43 + 239, 128, 43, 71, 92, 6, 24, 159, 219, 248, 68, 21, 60, 220, 29, 68, 154, 91, + 168, 124, 206, 19, 209, 27, 180, 123, 90, 83, 159, 39, 241, 43 ]), Uint8Array.from([ - 215, 22, 147, 196, 154, 132, 251, 190, 205, 73, 8, 201, 72, 19, 180, - 101, 20, 177, 139, 103, 169, 153, 82, 220, 30, 110, 71, 145, 85, 109, - 228, 19 + 215, 22, 147, 196, 154, 132, 251, 190, 205, 73, 8, 201, 72, 19, 180, 101, 20, + 177, 139, 103, 169, 153, 82, 220, 30, 110, 71, 145, 85, 109, 228, 19 ]) ], changeMap: { @@ -984,28 +961,28 @@ const txTestVectors = [ }, proofs: [ Uint8Array.from([ - 228, 0, 151, 4, 137, 84, 132, 119, 5, 78, 45, 157, 172, 174, 204, 178, - 193, 69, 249, 227, 25, 235, 202, 62, 90, 187, 180, 190, 115, 250, 4, 2, - 197, 134, 141, 125, 250, 104, 173, 255, 233, 180, 192, 134, 61, 74, 206, - 60, 60, 161, 247, 144, 129, 90, 237, 51 + 228, 0, 151, 4, 137, 84, 132, 119, 5, 78, 45, 157, 172, 174, 204, 178, 193, 69, + 249, 227, 25, 235, 202, 62, 90, 187, 180, 190, 115, 250, 4, 2, 197, 134, 141, 125, + 250, 104, 173, 255, 233, 180, 192, 134, 61, 74, 206, 60, 60, 161, 247, 144, 129, + 90, 237, 51 ]), Uint8Array.from([ - 228, 0, 151, 4, 137, 84, 132, 119, 5, 78, 45, 157, 172, 174, 204, 178, - 193, 69, 249, 227, 25, 235, 202, 62, 90, 187, 180, 190, 115, 250, 4, 2, - 197, 134, 141, 125, 250, 104, 173, 255, 233, 180, 192, 134, 61, 74, 206, - 60, 60, 161, 247, 144, 129, 90, 237, 51 + 228, 0, 151, 4, 137, 84, 132, 119, 5, 78, 45, 157, 172, 174, 204, 178, 193, 69, + 249, 227, 25, 235, 202, 62, 90, 187, 180, 190, 115, 250, 4, 2, 197, 134, 141, 125, + 250, 104, 173, 255, 233, 180, 192, 134, 61, 74, 206, 60, 60, 161, 247, 144, 129, + 90, 237, 51 ]), Uint8Array.from([ - 228, 0, 151, 4, 137, 84, 132, 119, 5, 78, 45, 157, 172, 174, 204, 178, - 193, 69, 249, 227, 25, 235, 202, 62, 90, 187, 180, 190, 115, 250, 4, 2, - 197, 134, 141, 125, 250, 104, 173, 255, 233, 180, 192, 134, 61, 74, 206, - 60, 60, 161, 247, 144, 129, 90, 237, 51 + 228, 0, 151, 4, 137, 84, 132, 119, 5, 78, 45, 157, 172, 174, 204, 178, 193, 69, + 249, 227, 25, 235, 202, 62, 90, 187, 180, 190, 115, 250, 4, 2, 197, 134, 141, 125, + 250, 104, 173, 255, 233, 180, 192, 134, 61, 74, 206, 60, 60, 161, 247, 144, 129, + 90, 237, 51 ]), Uint8Array.from([ - 228, 0, 151, 4, 137, 84, 132, 119, 5, 78, 45, 157, 172, 174, 204, 178, - 193, 69, 249, 227, 25, 235, 202, 62, 90, 187, 180, 190, 115, 250, 4, 2, - 197, 134, 141, 125, 250, 104, 173, 255, 233, 180, 192, 134, 61, 74, 206, - 60, 60, 161, 247, 144, 129, 90, 237, 51 + 228, 0, 151, 4, 137, 84, 132, 119, 5, 78, 45, 157, 172, 174, 204, 178, 193, 69, + 249, 227, 25, 235, 202, 62, 90, 187, 180, 190, 115, 250, 4, 2, 197, 134, 141, 125, + 250, 104, 173, 255, 233, 180, 192, 134, 61, 74, 206, 60, 60, 161, 247, 144, 129, + 90, 237, 51 ]) ] } diff --git a/src/erg.ts b/src/erg.ts index 0f62988..ba69a6f 100644 --- a/src/erg.ts +++ b/src/erg.ts @@ -20,10 +20,7 @@ import { signTx } from "./interactions"; import { uniq } from "@fleet-sdk/common"; -import type { - AttestedTransaction, - SignTransactionResponse -} from "./types/internal"; +import type { AttestedTransaction, SignTransactionResponse } from "./types/internal"; export { DeviceError, RETURN_CODE }; export * from "./types/public"; @@ -145,10 +142,7 @@ export class ErgoLedgerApp { * @param path Bip44 path. * @returns a Promise with true if the user accepts or throws an exception if it get rejected. */ - public async showAddress( - path: string, - network = Network.Mainnet - ): Promise { + public async showAddress(path: string, network = Network.Mainnet): Promise { this._debug("showAddress", path); return showAddress(this._device, network, path, this.authToken); } @@ -162,10 +156,7 @@ export class ErgoLedgerApp { return attestInput(this._device, box, this.authToken); } - public async signTx( - tx: UnsignedTx, - network = Network.Mainnet - ): Promise { + public async signTx(tx: UnsignedTx, network = Network.Mainnet): Promise { this._debug("signTx", { tx, network }); if (!tx.inputs || tx.inputs.length === 0) { diff --git a/src/interactions/attestInput.spec.ts b/src/interactions/attestInput.spec.ts index a89b2a2..917a2f3 100644 --- a/src/interactions/attestInput.spec.ts +++ b/src/interactions/attestInput.spec.ts @@ -6,9 +6,7 @@ describe("attestInput test", () => { "7cbe85a5f2d2154538eb883bbbee10dd414ec24b6e52b43495da906bce2c5e8a010000000000ab6a1fde032d554219a80c011cc51509e34fa4950965bb8e01de4d012536e766c9ca08bc2c000000174876e7febcd5db3a2872f279ef89edaa51a9344a6095ea1f03396874b695b5ba95ff602e00000017483412969f90c012e03bf99397e363fb1571b7999941e0862a217307e3467ee80cf53af700000000000000012f5151af1796a5827de6df5339ddca7a"; it("should parse frame response", () => { - const parsedFrame = decodeAttestedFrameResponse( - Buffer.from(frameHex, "hex") - ); + const parsedFrame = decodeAttestedFrameResponse(Buffer.from(frameHex, "hex")); expect(parsedFrame).toMatchObject({ boxId: "7cbe85a5f2d2154538eb883bbbee10dd414ec24b6e52b43495da906bce2c5e8a", diff --git a/src/interactions/attestInput.ts b/src/interactions/attestInput.ts index 4219b46..7ea6902 100644 --- a/src/interactions/attestInput.ts +++ b/src/interactions/attestInput.ts @@ -29,17 +29,10 @@ export async function attestInput( frameCount = await sendTokens(device, box.tokens, sessionId); } if (box.additionalRegisters.length > 0) { - frameCount = await sendRegisters( - device, - box.additionalRegisters, - sessionId - ); + frameCount = await sendRegisters(device, box.additionalRegisters, sessionId); } - return new AttestedBox( - box, - await getAttestedFrames(device, frameCount, sessionId) - ); + return new AttestedBox(box, await getAttestedFrames(device, frameCount, sessionId)); } async function sendHeader( @@ -94,9 +87,7 @@ async function sendTokens( const results: DeviceResponse[] = []; for (const p of packets) { - results.push( - await device.send(COMMAND.ATTEST_INPUT, P1.ADD_TOKENS, sessionId, p) - ); + results.push(await device.send(COMMAND.ATTEST_INPUT, P1.ADD_TOKENS, sessionId, p)); } /* v8 ignore next */ diff --git a/src/interactions/deriveAddress.ts b/src/interactions/deriveAddress.ts index 06ab249..c651cf0 100644 --- a/src/interactions/deriveAddress.ts +++ b/src/interactions/deriveAddress.ts @@ -39,10 +39,7 @@ function sendDeriveAddress( throw new Error(`Invalid change path: ${change}`); } - const data = Buffer.concat([ - Buffer.alloc(1, network), - serialize.path(pathArray) - ]); + const data = Buffer.concat([Buffer.alloc(1, network), serialize.path(pathArray)]); return device.send( COMMAND.DERIVE_ADDRESS, diff --git a/src/serialization/serialize.ts b/src/serialization/serialize.ts index 8fd5fb7..7d0b804 100644 --- a/src/serialization/serialize.ts +++ b/src/serialization/serialize.ts @@ -83,11 +83,7 @@ export const serialize = { const packets = []; for (let i = 0; i < Math.ceil(data.length / maxSize); i++) { const chunks = []; - for ( - let j = i * maxSize; - j < Math.min((i + 1) * maxSize, data.length); - j++ - ) { + for (let j = i * maxSize; j < Math.min((i + 1) * maxSize, data.length); j++) { chunks.push(encode(data[j])); } From c9609bf77a140f9e1c3b546e083544c9f0be20b1 Mon Sep 17 00:00:00 2001 From: arobsn <87387688+arobsn@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:05:32 -0300 Subject: [PATCH 07/11] refactor `attestedInput` --- src/types/attestedBox.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/types/attestedBox.ts b/src/types/attestedBox.ts index 6bb1f9b..4ce2ed2 100644 --- a/src/types/attestedBox.ts +++ b/src/types/attestedBox.ts @@ -2,36 +2,36 @@ import { assert } from "../assertions"; import type { UnsignedBox, AttestedBoxFrame } from "./public"; export class AttestedBox { - private _box: UnsignedBox; - private _frames: AttestedBoxFrame[]; - private _extension?: Buffer; + #box: UnsignedBox; + #frames: AttestedBoxFrame[]; + #extension?: Buffer; constructor(box: UnsignedBox, frames: AttestedBoxFrame[]) { - this._box = box; - this._frames = frames; + this.#box = box; + this.#frames = frames; } public get box(): UnsignedBox { - return this._box; + return this.#box; } public get frames(): AttestedBoxFrame[] { - return this._frames; + return this.#frames; } public get extension(): Buffer | undefined { - return this._extension; + return this.#extension; } public setExtension(extension: Buffer): AttestedBox { - assert(!this._extension, "extension already present"); + assert(!this.#extension, "The extension is already inserted"); const lengthBuffer = Buffer.alloc(4); - const firstFrame = this._frames[0]; + const firstFrame = this.#frames[0]; if (extension.length === 1 && extension[0] === 0) { lengthBuffer.writeUInt32BE(0, 0); } else { - this._extension = extension; + this.#extension = extension; firstFrame.extensionLength = extension.length; lengthBuffer.writeUInt32BE(extension.length, 0); } From 4d5e785631b90d498befc4bbdbd39742cbc572f1 Mon Sep 17 00:00:00 2001 From: arobsn <87387688+arobsn@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:36:03 -0300 Subject: [PATCH 08/11] reach 100% of coverage --- src/assertions.spec.ts | 20 ++++++++ src/assertions.ts | 76 +++++-------------------------- src/erg.ts | 34 ++++++++------ src/interactions/deriveAddress.ts | 8 +++- src/interactions/signTx.ts | 69 ++++++++++++++++++++++------ src/serialization/deserialize.ts | 12 +---- src/serialization/serialize.ts | 16 ++----- src/serialization/utils.ts | 2 +- src/types/attestedBox.ts | 2 +- src/types/public.ts | 7 +-- 10 files changed, 122 insertions(+), 124 deletions(-) create mode 100644 src/assertions.spec.ts diff --git a/src/assertions.spec.ts b/src/assertions.spec.ts new file mode 100644 index 0000000..d2e43f1 --- /dev/null +++ b/src/assertions.spec.ts @@ -0,0 +1,20 @@ +import { describe, it, expect } from "vitest"; +import { isErgoPath, isUint64String } from "./assertions"; +import { pathToArray } from "./serialization/serialize"; + +describe("assertions", () => { + it("Ergo path", () => { + expect(isErgoPath(pathToArray("m/44'/429'"))).to.be.true; + expect(isErgoPath(pathToArray("m/44'/2'"))).to.be.false; + expect(isErgoPath(pathToArray("m/44'"))).to.be.false; + }); + + it("UInt64", () => { + expect(isUint64String("0")).to.be.true; + expect(isUint64String("18446744073709551615")).to.be.true; + + expect(isUint64String("18446744073709551616")).to.be.false; + expect(isUint64String("1.2")).to.be.false; + expect(isUint64String("11a")).to.be.false; + }); +}); diff --git a/src/assertions.ts b/src/assertions.ts index 279b740..3d75cbe 100644 --- a/src/assertions.ts +++ b/src/assertions.ts @@ -1,7 +1,7 @@ import bip32Path from "bip32-path"; -const MIN_UINT_64_STR = "0"; -const MAX_UINT_64_STR = "18446744073709551615"; +const MIN_UINT_64 = 0n; +const MAX_UINT_64 = 18446744073709551615n; const MIN_UINT_VALUE = 0; const MAX_UINT32_VALUE = 4294967295; const MAX_UINT16_VALUE = 65535; @@ -9,78 +9,26 @@ const MAX_UNIT8_VALUE = 255; const [ERGO_PURPOSE, ERGO_COIN_TYPE] = bip32Path.fromString("m/44'/429'").toPathArray(); -export function assert(cond: boolean, errMsg: string): asserts cond { - if (!cond) { - throw new Error(`Assertion failed${errMsg ? `: ${errMsg}` : "."}`); - } -} - -export function isValidBip32Path(path: number[] | string): boolean { - if (typeof path === "string") { - return bip32Path.validateString(path, true); - } - - return bip32Path.validatePathArray(path, true); -} - -export function isValidErgoPath(path: number[]): boolean { +export function isErgoPath(path: number[]): boolean { if (path.length < 2) return false; const [pathPurpose, pathCoinType] = path; return pathPurpose === ERGO_PURPOSE && pathCoinType === ERGO_COIN_TYPE; } -export function isInteger(data: unknown): boolean { - return Number.isInteger(data); -} - -export function isArray(data: unknown): boolean { - return Array.isArray(data); -} - -export function isBuffer(data: unknown): boolean { - return Buffer.isBuffer(data); -} - -export function isUint32(data: unknown): boolean { - return ( - typeof data === "number" && - isInteger(data) && - data >= MIN_UINT_VALUE && - data <= MAX_UINT32_VALUE - ); +export function isUint32(data: number): boolean { + return Number.isInteger(data) && data >= MIN_UINT_VALUE && data <= MAX_UINT32_VALUE; } export function isUint16(data: number): boolean { - return ( - typeof data === "number" && - isInteger(data) && - data >= MIN_UINT_VALUE && - data <= MAX_UINT16_VALUE - ); + return Number.isInteger(data) && data >= MIN_UINT_VALUE && data <= MAX_UINT16_VALUE; } -export function isUint8(data: unknown): boolean { - return ( - typeof data === "number" && - isInteger(data) && - data >= MIN_UINT_VALUE && - data <= MAX_UNIT8_VALUE - ); +export function isUint8(data: number): boolean { + return Number.isInteger(data) && data >= MIN_UINT_VALUE && data <= MAX_UNIT8_VALUE; } -export function isUint64String(data: string): boolean { - return ( - typeof data === "string" && - /^[0-9]*$/.test(data) && - // Length checks - data.length > 0 && - data.length <= MAX_UINT_64_STR.length && - // Leading zeros - (data.length === 1 || data[0] !== "0") && - // less or equal than max value - // Note: this is string comparison! - (data.length < MAX_UINT_64_STR.length || data <= MAX_UINT_64_STR) && - // Note: this is string comparison! - (data.length > MIN_UINT_64_STR.length || data >= MIN_UINT_64_STR) - ); +export function isUint64String(value: string): boolean { + if (!/^[0-9]*$/.test(value)) return false; + const parsed = BigInt(value); + return parsed >= MIN_UINT_64 && parsed <= MAX_UINT_64; } diff --git a/src/erg.ts b/src/erg.ts index ba69a6f..64479e8 100644 --- a/src/erg.ts +++ b/src/erg.ts @@ -1,13 +1,12 @@ import type Transport from "@ledgerhq/hw-transport"; import { Device, DeviceError, RETURN_CODE } from "./device"; -import { - type AppName, - type UnsignedBox, - type DerivedAddress, - type ExtendedPublicKey, - type Version, - type UnsignedTx, - Network +import type { + AppName, + UnsignedBox, + DerivedAddress, + ExtendedPublicKey, + Version, + UnsignedTransaction } from "./types/public"; import type { AttestedBox } from "./types/attestedBox"; import { @@ -19,10 +18,13 @@ import { attestInput, signTx } from "./interactions"; -import { uniq } from "@fleet-sdk/common"; -import type { AttestedTransaction, SignTransactionResponse } from "./types/internal"; +import { uniq, Network } from "@fleet-sdk/common"; +import type { + AttestedTransaction, + SignTransactionResponse +} from "./types/internal"; -export { DeviceError, RETURN_CODE }; +export { DeviceError, RETURN_CODE, Network }; export * from "./types/public"; export const CLA = 0xe0; @@ -142,7 +144,10 @@ export class ErgoLedgerApp { * @param path Bip44 path. * @returns a Promise with true if the user accepts or throws an exception if it get rejected. */ - public async showAddress(path: string, network = Network.Mainnet): Promise { + public async showAddress( + path: string, + network = Network.Mainnet + ): Promise { this._debug("showAddress", path); return showAddress(this._device, network, path, this.authToken); } @@ -156,7 +161,10 @@ export class ErgoLedgerApp { return attestInput(this._device, box, this.authToken); } - public async signTx(tx: UnsignedTx, network = Network.Mainnet): Promise { + public async signTx( + tx: UnsignedTransaction, + network = Network.Mainnet + ): Promise { this._debug("signTx", { tx, network }); if (!tx.inputs || tx.inputs.length === 0) { diff --git a/src/interactions/deriveAddress.ts b/src/interactions/deriveAddress.ts index c651cf0..a852851 100644 --- a/src/interactions/deriveAddress.ts +++ b/src/interactions/deriveAddress.ts @@ -1,8 +1,9 @@ import { COMMAND, RETURN_CODE, type Device } from "../device"; -import type { DerivedAddress, Network } from "../types/public"; +import type { DerivedAddress } from "../types/public"; import type { DeviceResponse } from "../types/internal"; import { pathToArray, serialize } from "../serialization/serialize"; import { deserialize } from "../serialization/deserialize"; +import type { Network } from "@fleet-sdk/common"; const enum ReturnType { Return = 0x01, @@ -39,7 +40,10 @@ function sendDeriveAddress( throw new Error(`Invalid change path: ${change}`); } - const data = Buffer.concat([Buffer.alloc(1, network), serialize.path(pathArray)]); + const data = Buffer.concat([ + Buffer.alloc(1, network), + serialize.path(pathArray) + ]); return device.send( COMMAND.DERIVE_ADDRESS, diff --git a/src/interactions/signTx.ts b/src/interactions/signTx.ts index cd2212c..3e90927 100644 --- a/src/interactions/signTx.ts +++ b/src/interactions/signTx.ts @@ -1,8 +1,8 @@ import { deserialize } from "../serialization/deserialize"; import { serialize } from "../serialization/serialize"; -import type { ChangeMap, BoxCandidate, Token, Network } from "../types/public"; +import type { ChangeMap, BoxCandidate, Token } from "../types/public"; import { COMMAND, type Device } from "../device"; -import { ErgoAddress } from "@fleet-sdk/core"; +import { ErgoAddress, type Network } from "@fleet-sdk/core"; import type { AttestedTransaction } from "../types/internal"; import type { AttestedBox } from "../types/attestedBox"; @@ -42,7 +42,13 @@ export async function signTx( await sendDistinctTokensIds(device, sessionId, tx.distinctTokenIds); await sendInputs(device, sessionId, tx.inputs); await sendDataInputs(device, sessionId, tx.dataInputs); - await sendOutputs(device, sessionId, tx.outputs, tx.changeMap, tx.distinctTokenIds); + await sendOutputs( + device, + sessionId, + tx.outputs, + tx.changeMap, + tx.distinctTokenIds + ); const proof = await sendConfirmAndSign(device, sessionId); return new Uint8Array(proof); @@ -106,10 +112,19 @@ async function sendDistinctTokensIds( } } -async function sendInputs(device: Device, sessionId: number, inputs: AttestedBox[]) { +async function sendInputs( + device: Device, + sessionId: number, + inputs: AttestedBox[] +) { for (const input of inputs) { for (const frame of input.frames) { - await device.send(COMMAND.SIGN_TX, P1.ADD_INPUT_BOX_FRAME, sessionId, frame.bytes); + await device.send( + COMMAND.SIGN_TX, + P1.ADD_INPUT_BOX_FRAME, + sessionId, + frame.bytes + ); } if (input.extension !== undefined && input.extension.length > 0) { @@ -131,9 +146,17 @@ async function sendBoxContextExtension( ); } -async function sendDataInputs(device: Device, sessionId: number, boxIds: string[]) { +async function sendDataInputs( + device: Device, + sessionId: number, + boxIds: string[] +) { const MAX_PACKET_SIZE = 7; - const packets = serialize.arrayAsMappedChunks(boxIds, MAX_PACKET_SIZE, serialize.hex); + const packets = serialize.arrayAsMappedChunks( + boxIds, + MAX_PACKET_SIZE, + serialize.hex + ); for (const p of packets) { await device.send(COMMAND.SIGN_TX, P1.ADD_DATA_INPUTS, sessionId, p); @@ -147,7 +170,9 @@ async function sendOutputs( changeMap: ChangeMap, distinctTokenIds: Uint8Array[] ) { - const distinctTokenIdsStr = distinctTokenIds.map((t) => Buffer.from(t).toString("hex")); + const distinctTokenIdsStr = distinctTokenIds.map((t) => + Buffer.from(t).toString("hex") + ); for (const box of boxes) { await device.send( @@ -166,14 +191,21 @@ async function sendOutputs( const tree = deserialize.hex(box.ergoTree); if (tree === MINER_FEE_TREE) { await addOutputBoxMinersFeeTree(device, sessionId); - } else if (ErgoAddress.fromErgoTree(tree).toString() === changeMap.address) { + } else if ( + ErgoAddress.fromErgoTree(tree).toString() === changeMap.address + ) { await addOutputBoxChangeTree(device, sessionId, changeMap.path); } else { await addOutputBoxErgoTree(device, sessionId, box.ergoTree); } if (box.tokens && box.tokens.length > 0) { - await addOutputBoxTokens(device, sessionId, box.tokens, distinctTokenIdsStr); + await addOutputBoxTokens( + device, + sessionId, + box.tokens, + distinctTokenIdsStr + ); } if (box.registers.length > 0) { @@ -182,7 +214,11 @@ async function sendOutputs( } } -async function addOutputBoxErgoTree(device: Device, sessionId: number, ergoTree: Buffer) { +async function addOutputBoxErgoTree( + device: Device, + sessionId: number, + ergoTree: Buffer +) { await device.sendData( COMMAND.SIGN_TX, P1.ADD_OUTPUT_BOX_ERGO_TREE_CHUNK, @@ -200,7 +236,11 @@ async function addOutputBoxMinersFeeTree(device: Device, sessionId: number) { ); } -async function addOutputBoxChangeTree(device: Device, sessionId: number, path: string) { +async function addOutputBoxChangeTree( + device: Device, + sessionId: number, + path: string +) { await device.send( COMMAND.SIGN_TX, P1.ADD_OUTPUT_BOX_CHANGE_TREE, @@ -241,7 +281,10 @@ async function addOutputBoxRegisters( ); } -async function sendConfirmAndSign(device: Device, sessionId: number): Promise { +async function sendConfirmAndSign( + device: Device, + sessionId: number +): Promise { const response = await device.send( COMMAND.SIGN_TX, P1.CONFIRM_AND_SIGN, diff --git a/src/serialization/deserialize.ts b/src/serialization/deserialize.ts index 69e3752..ce91a9d 100644 --- a/src/serialization/deserialize.ts +++ b/src/serialization/deserialize.ts @@ -1,5 +1,5 @@ import basex from "base-x"; -import { assert } from "../assertions"; +import { assert } from "@fleet-sdk/common"; const bs10 = basex("0123456789"); @@ -17,16 +17,6 @@ export const deserialize = { return data.readUIntBE(0, 1); }, - uint16(data: Buffer): number { - assert(data.length === 2, "invalid uint16 buffer"); - return data.readUIntBE(0, 2); - }, - - uint32(data: Buffer): number { - assert(data.length === 4, "invalid uint32 buffer"); - return data.readUIntBE(0, 4); - }, - uint64(buffer: Buffer): string { assert(buffer.length === 8, "invalid uint64 buffer"); return trimLeadingZeros(bs10.encode(buffer)); diff --git a/src/serialization/serialize.ts b/src/serialization/serialize.ts index 7d0b804..0885832 100644 --- a/src/serialization/serialize.ts +++ b/src/serialization/serialize.ts @@ -1,13 +1,5 @@ -import { isHex } from "@fleet-sdk/common"; -import { - assert, - isUint16, - isUint32, - isUint64String, - isUint8, - isValidBip32Path, - isValidErgoPath -} from "../assertions"; +import { isHex, assert } from "@fleet-sdk/common"; +import { isUint16, isUint32, isUint64String, isUint8, isErgoPath } from "../assertions"; import basex from "base-x"; import bip32Path from "bip32-path"; @@ -16,7 +8,7 @@ const bs10 = basex("0123456789"); export const serialize = { path(path: number[] | string): Buffer { const pathArray = typeof path === "string" ? pathToArray(path) : path; - assert(isValidErgoPath(pathArray), "Invalid Ergo path"); + assert(isErgoPath(pathArray), "Invalid Ergo path"); const buffer = Buffer.alloc(1 + pathArray.length * 4); buffer[0] = pathArray.length; @@ -55,7 +47,6 @@ export const serialize = { uint64(value: string): Buffer { assert(isUint64String(value), "invalid uint64 string"); const data = bs10.decode(value); - assert(data.length <= 8, "excessive data"); const padding = Buffer.alloc(8 - data.length); return Buffer.concat([padding, Buffer.from(data)]); @@ -95,6 +86,5 @@ export const serialize = { }; export function pathToArray(path: string): number[] { - assert(isValidBip32Path(path), "Invalid Bip32 path."); return bip32Path.fromString(path).toPathArray(); } diff --git a/src/serialization/utils.ts b/src/serialization/utils.ts index a831cee..d475118 100644 --- a/src/serialization/utils.ts +++ b/src/serialization/utils.ts @@ -1,4 +1,4 @@ -import { assert } from "../assertions"; +import { assert } from "@fleet-sdk/common"; const sum = (arr: number[]) => arr.reduce((x, y) => x + y, 0); diff --git a/src/types/attestedBox.ts b/src/types/attestedBox.ts index 4ce2ed2..d633473 100644 --- a/src/types/attestedBox.ts +++ b/src/types/attestedBox.ts @@ -1,4 +1,4 @@ -import { assert } from "../assertions"; +import { assert } from "@fleet-sdk/common"; import type { UnsignedBox, AttestedBoxFrame } from "./public"; export class AttestedBox { diff --git a/src/types/public.ts b/src/types/public.ts index badf479..6508fb2 100644 --- a/src/types/public.ts +++ b/src/types/public.ts @@ -73,7 +73,7 @@ export type AttestedBoxFrame = { bytes: Buffer; }; -export type UnsignedTx = { +export type UnsignedTransaction = { inputs: UnsignedBox[]; dataInputs: string[]; outputs: BoxCandidate[]; @@ -85,8 +85,3 @@ export type ChangeMap = { address: string; path: string; }; - -export enum Network { - Mainnet = 0 << 4, - Testnet = 1 << 4 -} From 5e94bf28c0fdd90d2a22ebd6da57fcde97a0ce23 Mon Sep 17 00:00:00 2001 From: arobsn <87387688+arobsn@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:37:02 -0300 Subject: [PATCH 09/11] add ci checks --- .github/workflows/build-and-test-package.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-and-test-package.yml b/.github/workflows/build-and-test-package.yml index f13de10..20b7174 100644 --- a/.github/workflows/build-and-test-package.yml +++ b/.github/workflows/build-and-test-package.yml @@ -28,3 +28,6 @@ jobs: - run: pnpm install --frozen-lockfile - run: pnpm run build - run: pnpm test:unit + - run: pnpm test:cov + - run: pnpm test:format + - run: pnpm test:lint From f830979c97d01a4e1c13b6d20019b74e64d88ecf Mon Sep 17 00:00:00 2001 From: arobsn <87387688+arobsn@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:38:31 -0300 Subject: [PATCH 10/11] fix ci --- .github/workflows/build-and-test-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test-package.yml b/.github/workflows/build-and-test-package.yml index 20b7174..c7701f4 100644 --- a/.github/workflows/build-and-test-package.yml +++ b/.github/workflows/build-and-test-package.yml @@ -28,6 +28,6 @@ jobs: - run: pnpm install --frozen-lockfile - run: pnpm run build - run: pnpm test:unit - - run: pnpm test:cov - run: pnpm test:format - run: pnpm test:lint + - run: pnpm cov:check From 83043b3622396ca1ab850490a430eb5b85c0dc13 Mon Sep 17 00:00:00 2001 From: arobsn <87387688+arobsn@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:40:39 -0300 Subject: [PATCH 11/11] fix formatting --- src/erg.ts | 10 +---- src/interactions/deriveAddress.ts | 5 +-- src/interactions/signTx.ts | 65 ++++++------------------------- 3 files changed, 14 insertions(+), 66 deletions(-) diff --git a/src/erg.ts b/src/erg.ts index 64479e8..1607a43 100644 --- a/src/erg.ts +++ b/src/erg.ts @@ -18,11 +18,8 @@ import { attestInput, signTx } from "./interactions"; +import type { AttestedTransaction, SignTransactionResponse } from "./types/internal"; import { uniq, Network } from "@fleet-sdk/common"; -import type { - AttestedTransaction, - SignTransactionResponse -} from "./types/internal"; export { DeviceError, RETURN_CODE, Network }; export * from "./types/public"; @@ -144,10 +141,7 @@ export class ErgoLedgerApp { * @param path Bip44 path. * @returns a Promise with true if the user accepts or throws an exception if it get rejected. */ - public async showAddress( - path: string, - network = Network.Mainnet - ): Promise { + public async showAddress(path: string, network = Network.Mainnet): Promise { this._debug("showAddress", path); return showAddress(this._device, network, path, this.authToken); } diff --git a/src/interactions/deriveAddress.ts b/src/interactions/deriveAddress.ts index a852851..f43e738 100644 --- a/src/interactions/deriveAddress.ts +++ b/src/interactions/deriveAddress.ts @@ -40,10 +40,7 @@ function sendDeriveAddress( throw new Error(`Invalid change path: ${change}`); } - const data = Buffer.concat([ - Buffer.alloc(1, network), - serialize.path(pathArray) - ]); + const data = Buffer.concat([Buffer.alloc(1, network), serialize.path(pathArray)]); return device.send( COMMAND.DERIVE_ADDRESS, diff --git a/src/interactions/signTx.ts b/src/interactions/signTx.ts index 3e90927..eed172c 100644 --- a/src/interactions/signTx.ts +++ b/src/interactions/signTx.ts @@ -42,13 +42,7 @@ export async function signTx( await sendDistinctTokensIds(device, sessionId, tx.distinctTokenIds); await sendInputs(device, sessionId, tx.inputs); await sendDataInputs(device, sessionId, tx.dataInputs); - await sendOutputs( - device, - sessionId, - tx.outputs, - tx.changeMap, - tx.distinctTokenIds - ); + await sendOutputs(device, sessionId, tx.outputs, tx.changeMap, tx.distinctTokenIds); const proof = await sendConfirmAndSign(device, sessionId); return new Uint8Array(proof); @@ -112,19 +106,10 @@ async function sendDistinctTokensIds( } } -async function sendInputs( - device: Device, - sessionId: number, - inputs: AttestedBox[] -) { +async function sendInputs(device: Device, sessionId: number, inputs: AttestedBox[]) { for (const input of inputs) { for (const frame of input.frames) { - await device.send( - COMMAND.SIGN_TX, - P1.ADD_INPUT_BOX_FRAME, - sessionId, - frame.bytes - ); + await device.send(COMMAND.SIGN_TX, P1.ADD_INPUT_BOX_FRAME, sessionId, frame.bytes); } if (input.extension !== undefined && input.extension.length > 0) { @@ -146,17 +131,9 @@ async function sendBoxContextExtension( ); } -async function sendDataInputs( - device: Device, - sessionId: number, - boxIds: string[] -) { +async function sendDataInputs(device: Device, sessionId: number, boxIds: string[]) { const MAX_PACKET_SIZE = 7; - const packets = serialize.arrayAsMappedChunks( - boxIds, - MAX_PACKET_SIZE, - serialize.hex - ); + const packets = serialize.arrayAsMappedChunks(boxIds, MAX_PACKET_SIZE, serialize.hex); for (const p of packets) { await device.send(COMMAND.SIGN_TX, P1.ADD_DATA_INPUTS, sessionId, p); @@ -170,9 +147,7 @@ async function sendOutputs( changeMap: ChangeMap, distinctTokenIds: Uint8Array[] ) { - const distinctTokenIdsStr = distinctTokenIds.map((t) => - Buffer.from(t).toString("hex") - ); + const distinctTokenIdsStr = distinctTokenIds.map((t) => Buffer.from(t).toString("hex")); for (const box of boxes) { await device.send( @@ -191,21 +166,14 @@ async function sendOutputs( const tree = deserialize.hex(box.ergoTree); if (tree === MINER_FEE_TREE) { await addOutputBoxMinersFeeTree(device, sessionId); - } else if ( - ErgoAddress.fromErgoTree(tree).toString() === changeMap.address - ) { + } else if (ErgoAddress.fromErgoTree(tree).toString() === changeMap.address) { await addOutputBoxChangeTree(device, sessionId, changeMap.path); } else { await addOutputBoxErgoTree(device, sessionId, box.ergoTree); } if (box.tokens && box.tokens.length > 0) { - await addOutputBoxTokens( - device, - sessionId, - box.tokens, - distinctTokenIdsStr - ); + await addOutputBoxTokens(device, sessionId, box.tokens, distinctTokenIdsStr); } if (box.registers.length > 0) { @@ -214,11 +182,7 @@ async function sendOutputs( } } -async function addOutputBoxErgoTree( - device: Device, - sessionId: number, - ergoTree: Buffer -) { +async function addOutputBoxErgoTree(device: Device, sessionId: number, ergoTree: Buffer) { await device.sendData( COMMAND.SIGN_TX, P1.ADD_OUTPUT_BOX_ERGO_TREE_CHUNK, @@ -236,11 +200,7 @@ async function addOutputBoxMinersFeeTree(device: Device, sessionId: number) { ); } -async function addOutputBoxChangeTree( - device: Device, - sessionId: number, - path: string -) { +async function addOutputBoxChangeTree(device: Device, sessionId: number, path: string) { await device.send( COMMAND.SIGN_TX, P1.ADD_OUTPUT_BOX_CHANGE_TREE, @@ -281,10 +241,7 @@ async function addOutputBoxRegisters( ); } -async function sendConfirmAndSign( - device: Device, - sessionId: number -): Promise { +async function sendConfirmAndSign(device: Device, sessionId: number): Promise { const response = await device.send( COMMAND.SIGN_TX, P1.CONFIRM_AND_SIGN,