From 46c14edd258dc2deeb8f130114d6da5892a7ca7b Mon Sep 17 00:00:00 2001 From: Emmanuel Date: Wed, 14 Aug 2024 11:25:24 -0300 Subject: [PATCH 1/8] feat: add getIdentity method for DKG process (#4) * DKG getIdentity * feat: refactor getIdentity method --------- Co-authored-by: ftheirs --- src/consts.ts | 3 +++ src/helper.ts | 23 +++++++++++++++++++++-- src/index.ts | 13 ++++++++++--- src/types.ts | 5 +++++ 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/consts.ts b/src/consts.ts index e95e855..713291e 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -10,5 +10,8 @@ export const KEY_TYPES = { PROOF_GEN_KEY: 0x02, } +export const VERSION = 1 export const KEY_LENGTH = 32 export const REDJUBJUB_SIGNATURE_LEN = 64 +export const ED25519_SIGNATURE_LEN = 64 +export const IDENTITY_LEN = VERSION + KEY_LENGTH + KEY_LENGTH + ED25519_SIGNATURE_LEN diff --git a/src/helper.ts b/src/helper.ts index c19b6b1..b2ab34a 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -1,7 +1,7 @@ import { errorCodeToString } from '@zondax/ledger-js' -import { KEY_LENGTH } from './consts' -import { IronfishKeys, KeyResponse } from './types' +import {ED25519_SIGNATURE_LEN, KEY_LENGTH, VERSION, REDJUBJUB_SIGNATURE_LEN, IDENTITY_LEN} from './consts' +import { IronfishKeys, KeyResponse, ResponseIdentity } from './types' export function processGetKeysResponse(response: Buffer, keyType: IronfishKeys): KeyResponse { const errorCodeData = response.subarray(-2) @@ -59,3 +59,22 @@ export function processGetKeysResponse(response: Buffer, keyType: IronfishKeys): return requestedKey } + +export function processGetIdentityResponse(response: Buffer): ResponseIdentity { + const errorCodeData = response.subarray(-2) + const returnCode = errorCodeData[0] * 256 + errorCodeData[1] + + let getIdentityResponse: ResponseIdentity = { + returnCode: returnCode, + errorMessage: errorCodeToString(returnCode), + } + + const identity = Buffer.from(response.subarray(0, IDENTITY_LEN)) + + getIdentityResponse = { + ...getIdentityResponse, + identity + } + + return getIdentityResponse +} diff --git a/src/index.ts b/src/index.ts index 987837f..1e21738 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,8 +24,8 @@ import GenericApp, { } from '@zondax/ledger-js' import { P2_VALUES, REDJUBJUB_SIGNATURE_LEN } from './consts' -import { processGetKeysResponse } from './helper' -import { IronfishIns, IronfishKeys, KeyResponse, ResponseSign } from './types' +import { processGetIdentityResponse, processGetKeysResponse } from './helper' +import { IronfishIns, IronfishKeys, KeyResponse, ResponseIdentity, ResponseSign } from './types' export * from './types' @@ -40,6 +40,8 @@ export default class IronfishApp extends GenericApp { GET_VERSION: 0x00, GET_KEYS: 0x01, SIGN: 0x02, + //DKG Instructions + GET_IDENTITY: 0x10, }, p1Values: { ONLY_RETRIEVE: 0x00, @@ -60,7 +62,6 @@ export default class IronfishApp extends GenericApp { .then(response => processGetKeysResponse(response, keyType), processErrorResponse) } - // #{TODO} --> Create sign methods, this are example ones! async signSendChunk(chunkIdx: number, chunkNum: number, chunk: Buffer): Promise { let payloadType = PAYLOAD_TYPE.ADD if (chunkIdx === 1) { @@ -125,4 +126,10 @@ export default class IronfishApp extends GenericApp { return result }, processErrorResponse) } + + async dkgGetIdentity(): Promise { + return await this.transport + .send(this.CLA, this.INS.GET_IDENTITY, 0, 0, undefined, [LedgerError.NoErrors]) + .then(response => processGetIdentityResponse(response), processErrorResponse) + } } diff --git a/src/types.ts b/src/types.ts index 617dfc6..c8786e0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,6 +4,7 @@ export interface IronfishIns extends INSGeneric { GET_VERSION: 0x00 GET_KEYS: 0x01 SIGN: 0x02 + GET_IDENTITY: 0x10 } export type KeyResponse = ResponseAddress | ResponseViewKey | ResponseProofGenKey @@ -32,3 +33,7 @@ export enum IronfishKeys { ViewKey = 0x01, ProofGenerationKey = 0x02, } + +export interface ResponseIdentity extends ResponseBase { + identity?: Buffer +} From dc3ed13add84cbc9087af5dba306fd9ac6abc20c Mon Sep 17 00:00:00 2001 From: Emmanuel Date: Fri, 13 Sep 2024 15:50:05 -0300 Subject: [PATCH 2/8] feat: add support to all DKG commands (#6) * feat: add command for dkg round1 * rework dkg round 1 method * feat : add round2 method * feat: pass index to allow to generate more than on identity * feat: remove console logs * feat: parse round 2 result * feat: implement round 3 * feat: add dkg get commitment method * feat: parse get commitment response * feat: add dkg sign method * feat: add command to get dkg keys * feat: allow more key types for dkg get keys * feat: add round3 min support (#5) * feat: fix dkgGetCommitment method * feat: add getNonces command * feat: add get public package command * point to local package * feat: add dkg backup keys command * feat: add restore key command * feat: set different CLA for DKG app * feat: extract deserialize and serialize functions for dkg commands * feat: apply linter * feat: apply linter and formatter --- README-npm.md | 14 +- README.md | 14 +- package.json | 1 + src/deserialize.ts | 33 +++ src/helper.ts | 4 +- src/index.ts | 646 ++++++++++++++++++++++++++++++++++++++++++++- src/ironfish.ts | 140 ++++++++++ src/serialize.ts | 117 ++++++++ src/types.ts | 38 ++- 9 files changed, 983 insertions(+), 24 deletions(-) create mode 100644 src/deserialize.ts create mode 100644 src/ironfish.ts create mode 100644 src/serialize.ts diff --git a/README-npm.md b/README-npm.md index f586d7b..6a1aa0d 100644 --- a/README-npm.md +++ b/README-npm.md @@ -11,13 +11,13 @@ Use `yarn install` to avoid issues. # Available commands -| Operation | Response | Command | -|--------------|------------------------------------------|-------------------------------| -| getVersion | app version | --------------- | -| appInfo | name, version, flags, etc | --------------- | -| deviceInfo | fw and mcu version, id, etc | Only available in dashboard | -| sign | signed message | path + message | -| retrieveKeys | address or view key or proof gen key | path + key type + verify flag | +| Operation | Response | Command | +| ------------ | ------------------------------------ | ----------------------------- | +| getVersion | app version | --------------- | +| appInfo | name, version, flags, etc | --------------- | +| deviceInfo | fw and mcu version, id, etc | Only available in dashboard | +| sign | signed message | path + message | +| retrieveKeys | address or view key or proof gen key | path + key type + verify flag | # Who we are? diff --git a/README.md b/README.md index f586d7b..6a1aa0d 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,13 @@ Use `yarn install` to avoid issues. # Available commands -| Operation | Response | Command | -|--------------|------------------------------------------|-------------------------------| -| getVersion | app version | --------------- | -| appInfo | name, version, flags, etc | --------------- | -| deviceInfo | fw and mcu version, id, etc | Only available in dashboard | -| sign | signed message | path + message | -| retrieveKeys | address or view key or proof gen key | path + key type + verify flag | +| Operation | Response | Command | +| ------------ | ------------------------------------ | ----------------------------- | +| getVersion | app version | --------------- | +| appInfo | name, version, flags, etc | --------------- | +| deviceInfo | fw and mcu version, id, etc | Only available in dashboard | +| sign | signed message | path + message | +| retrieveKeys | address or view key or proof gen key | path + key type + verify flag | # Who we are? diff --git a/package.json b/package.json index e9656fd..b4f006c 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "url": "https://github.com/zondax/ledger-ironfish-js/issues" }, "dependencies": { + "@ironfish/rust-nodejs": "../ironfish/ironfish-rust-nodejs", "@zondax/ledger-js": "^0.2.1" }, "devDependencies": { diff --git a/src/deserialize.ts b/src/deserialize.ts new file mode 100644 index 0000000..bc421ed --- /dev/null +++ b/src/deserialize.ts @@ -0,0 +1,33 @@ +export const deserializeDkgRound1 = (data: Buffer) => { + let pos = 0 + const secretPackageLen = data.readUint16BE(pos) + pos += 2 + const secretPackage = data.subarray(pos, pos + secretPackageLen) + pos += secretPackageLen + const publicPackageLen = data.readUint16BE(pos) + pos += 2 + const publicPackage = data.subarray(pos, pos + publicPackageLen) + pos += publicPackageLen + + return { + secretPackage, + publicPackage, + } +} + +export const deserializeDkgRound2 = (data: Buffer) => { + let pos = 0 + const secretPackageLen = data.readUint16BE(pos) + pos += 2 + const secretPackage = data.subarray(pos, pos + secretPackageLen) + pos += secretPackageLen + const publicPackageLen = data.readUint16BE(pos) + pos += 2 + const publicPackage = data.subarray(pos, pos + publicPackageLen) + pos += publicPackageLen + + return { + secretPackage, + publicPackage, + } +} diff --git a/src/helper.ts b/src/helper.ts index b2ab34a..a8b8948 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -1,6 +1,6 @@ import { errorCodeToString } from '@zondax/ledger-js' -import {ED25519_SIGNATURE_LEN, KEY_LENGTH, VERSION, REDJUBJUB_SIGNATURE_LEN, IDENTITY_LEN} from './consts' +import { ED25519_SIGNATURE_LEN, IDENTITY_LEN, KEY_LENGTH, REDJUBJUB_SIGNATURE_LEN, VERSION } from './consts' import { IronfishKeys, KeyResponse, ResponseIdentity } from './types' export function processGetKeysResponse(response: Buffer, keyType: IronfishKeys): KeyResponse { @@ -73,7 +73,7 @@ export function processGetIdentityResponse(response: Buffer): ResponseIdentity { getIdentityResponse = { ...getIdentityResponse, - identity + identity, } return getIdentityResponse diff --git a/src/index.ts b/src/index.ts index 1e21738..bcbcc97 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,18 +18,45 @@ import GenericApp, { ConstructorParams, LedgerError, PAYLOAD_TYPE, + ResponseBase, Transport, errorCodeToString, processErrorResponse, } from '@zondax/ledger-js' import { P2_VALUES, REDJUBJUB_SIGNATURE_LEN } from './consts' +import { deserializeDkgRound1, deserializeDkgRound2 } from './deserialize' import { processGetIdentityResponse, processGetKeysResponse } from './helper' -import { IronfishIns, IronfishKeys, KeyResponse, ResponseIdentity, ResponseSign } from './types' +import { + serializeDkgGetCommitments, + serializeDkgGetNonces, + serializeDkgRound1, + serializeDkgRound2, + serializeDkgRound3, + serializeDkgSign, +} from './serialize' +import { + IronfishIns, + IronfishKeys, + KeyResponse, + ResponseDkgBackupKeys, + ResponseDkgGetCommitments, + ResponseDkgGetNonces, + ResponseDkgGetPublicPackage, + ResponseDkgRound1, + ResponseDkgRound2, + ResponseDkgRound3, + ResponseDkgSign, + ResponseIdentity, + ResponseSign, +} from './types' export * from './types' +const DUMMY_PATH = "m/44'/1338'/0" + export default class IronfishApp extends GenericApp { + readonly CLA_DKG: number = 0x63 readonly INS!: IronfishIns constructor(transport: Transport) { if (transport == null) throw new Error('Transport has not been defined') @@ -41,7 +68,17 @@ export default class IronfishApp extends GenericApp { GET_KEYS: 0x01, SIGN: 0x02, //DKG Instructions - GET_IDENTITY: 0x10, + DKG_IDENTITY: 0x10, + DKG_ROUND_1: 0x11, + DKG_ROUND_2: 0x12, + DKG_ROUND_3: 0x13, + DKG_GET_COMMITMENTS: 0x14, + DKG_SIGN: 0x15, + DKG_GET_KEYS: 0x16, + DKG_GET_NONCES: 0x17, + DKG_GET_PUBLIC_PACKAGE: 0x18, + DKG_BACKUP_KEYS: 0x19, + DKG_RESTORE_KEYS: 0x1a, }, p1Values: { ONLY_RETRIEVE: 0x00, @@ -62,7 +99,7 @@ export default class IronfishApp extends GenericApp { .then(response => processGetKeysResponse(response, keyType), processErrorResponse) } - async signSendChunk(chunkIdx: number, chunkNum: number, chunk: Buffer): Promise { + async signChunk(ins: number, chunkIdx: number, chunkNum: number, chunk: Buffer): Promise { let payloadType = PAYLOAD_TYPE.ADD if (chunkIdx === 1) { payloadType = PAYLOAD_TYPE.INIT @@ -72,7 +109,7 @@ export default class IronfishApp extends GenericApp { } return await this.transport - .send(this.CLA, this.INS.SIGN, payloadType, P2_VALUES.DEFAULT, chunk, [ + .send(this.CLA, ins, payloadType, P2_VALUES.DEFAULT, chunk, [ LedgerError.NoErrors, LedgerError.DataIsInvalid, LedgerError.BadKeyHandle, @@ -91,7 +128,7 @@ export default class IronfishApp extends GenericApp { errorMessage = `${errorMessage} : ${response.subarray(0, response.length - 2).toString('ascii')}` } - if (returnCode === LedgerError.NoErrors && response.length == (2 + REDJUBJUB_SIGNATURE_LEN)) { + if (returnCode === LedgerError.NoErrors && response.length == 2 + REDJUBJUB_SIGNATURE_LEN) { const signature = response.subarray(0, REDJUBJUB_SIGNATURE_LEN) return { @@ -108,6 +145,10 @@ export default class IronfishApp extends GenericApp { }, processErrorResponse) } + signSendChunk(chunkIdx: number, chunkNum: number, chunk: Buffer): Promise { + return this.signChunk(this.INS.SIGN, chunkIdx, chunkNum, chunk) + } + async sign(path: string, blob: Buffer): Promise { const chunks = this.prepareChunks(path, blob) return await this.signSendChunk(1, chunks.length, chunks[0]).then(async response => { @@ -127,9 +168,600 @@ export default class IronfishApp extends GenericApp { }, processErrorResponse) } - async dkgGetIdentity(): Promise { + async dkgGetIdentity(index: number): Promise { + let data = Buffer.alloc(1) + data.writeUint8(index) + return await this.transport - .send(this.CLA, this.INS.GET_IDENTITY, 0, 0, undefined, [LedgerError.NoErrors]) + .send(this.CLA_DKG, this.INS.DKG_IDENTITY, 0, 0, data, [LedgerError.NoErrors]) .then(response => processGetIdentityResponse(response), processErrorResponse) } + + async sendDkgChunk(ins: number, chunkIdx: number, chunkNum: number, chunk: Buffer): Promise { + let payloadType = PAYLOAD_TYPE.ADD + if (chunkIdx === 1) { + payloadType = PAYLOAD_TYPE.INIT + } + if (chunkIdx === chunkNum) { + payloadType = PAYLOAD_TYPE.LAST + } + + return await this.transport.send(this.CLA_DKG, ins, payloadType, P2_VALUES.DEFAULT, chunk, [ + LedgerError.NoErrors, + LedgerError.DataIsInvalid, + LedgerError.BadKeyHandle, + LedgerError.SignVerifyError, + ]) + } + + async dkgRound1(index: number, identities: string[], minSigners: number): Promise { + const blob = serializeDkgRound1(index, identities, minSigners) + const chunks = this.prepareChunks(DUMMY_PATH, blob) + + try { + let response = Buffer.alloc(0) + let returnCode = 0 + let errorCodeData = Buffer.alloc(0) + let errorMessage = '' + try { + response = await this.sendDkgChunk(this.INS.DKG_ROUND_1, 1, chunks.length, chunks[0]) + // console.log("resp 0 " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + } catch (e) { + // console.log(e) + } + + for (let i = 1; i < chunks.length; i += 1) { + // eslint-disable-next-line no-await-in-loop + response = await this.sendDkgChunk(this.INS.DKG_ROUND_1, 1 + i, chunks.length, chunks[i]) + // console.log("resp " + i + " " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + + // console.log("returnCode " + returnCode) + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + } + + let data = Buffer.alloc(0) + while (true) { + let newData = response.subarray(0, response.length - 2) + data = Buffer.concat([data, newData]) + + if (response.length == 255) { + response = await this.transport.send(this.CLA_DKG, this.INS.DKG_ROUND_1, 0, 0, Buffer.alloc(0)) + // console.log("resp " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + } else { + return { + returnCode, + errorMessage, + ...deserializeDkgRound1(data), + } + } + } + } catch (e) { + return processErrorResponse(e) + } + } + + async dkgRound2(index: number, round1PublicPackages: string[], round1SecretPackage: string): Promise { + const blob = serializeDkgRound2(index, round1PublicPackages, round1SecretPackage) + const chunks = this.prepareChunks(DUMMY_PATH, blob) + + try { + let response = Buffer.alloc(0) + let returnCode = 0 + let errorCodeData = Buffer.alloc(0) + let errorMessage = '' + try { + response = await this.sendDkgChunk(this.INS.DKG_ROUND_2, 1, chunks.length, chunks[0]) + // console.log("resp 0 " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + } catch (e) { + // console.log(e) + } + + for (let i = 1; i < chunks.length; i += 1) { + // eslint-disable-next-line no-await-in-loop + response = await this.sendDkgChunk(this.INS.DKG_ROUND_2, 1 + i, chunks.length, chunks[i]) + // console.log("resp " + i + " " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + + // console.log("returnCode " + returnCode) + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + } + + let data = Buffer.alloc(0) + while (true) { + let newData = response.subarray(0, response.length - 2) + data = Buffer.concat([data, newData]) + + if (response.length == 255) { + response = await this.transport.send(this.CLA_DKG, this.INS.DKG_ROUND_2, 0, 0, Buffer.alloc(0)) + // console.log("resp " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + } else { + return { + returnCode, + errorMessage, + ...deserializeDkgRound2(data), + } + } + } + } catch (e) { + return processErrorResponse(e) + } + } + + async dkgRound3( + index: number, + round1PublicPackages: string[], + round2PublicPackages: string[], + round2SecretPackage: string + ): Promise { + const blob = serializeDkgRound3(index, round1PublicPackages, round2PublicPackages, round2SecretPackage) + const chunks = this.prepareChunks(DUMMY_PATH, blob) + + try { + let response = Buffer.alloc(0) + let returnCode = 0 + let errorCodeData = Buffer.alloc(0) + let errorMessage = '' + try { + response = await this.sendDkgChunk(this.INS.DKG_ROUND_3, 1, chunks.length, chunks[0]) + // console.log("resp 0 " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + } catch (e) { + // console.log(e) + } + + for (let i = 1; i < chunks.length; i += 1) { + // eslint-disable-next-line no-await-in-loop + response = await this.sendDkgChunk(this.INS.DKG_ROUND_3, 1 + i, chunks.length, chunks[i]) + // console.log("resp " + i + " " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + + // console.log("returnCode " + returnCode) + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + } + + let data = Buffer.alloc(0) + while (true) { + let newData = response.subarray(0, response.length - 2) + data = Buffer.concat([data, newData]) + + if (response.length == 255) { + response = await this.transport.send(this.CLA_DKG, this.INS.DKG_ROUND_3, 0, 0, Buffer.alloc(0)) + // console.log("resp " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + } else { + return { + returnCode, + errorMessage, + } + } + } + } catch (e) { + return processErrorResponse(e) + } + } + + async dkgGetCommitments(identities: string[], tx_hash: string): Promise { + const blob = serializeDkgGetCommitments(identities, tx_hash) + const chunks = this.prepareChunks(DUMMY_PATH, blob) + + try { + let response = Buffer.alloc(0) + let returnCode = 0 + let errorCodeData = Buffer.alloc(0) + let errorMessage = '' + try { + response = await this.sendDkgChunk(this.INS.DKG_GET_COMMITMENTS, 1, chunks.length, chunks[0]) + // console.log("resp 0 " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + } catch (e) { + // console.log(e) + } + + for (let i = 1; i < chunks.length; i += 1) { + // eslint-disable-next-line no-await-in-loop + response = await this.sendDkgChunk(this.INS.DKG_GET_COMMITMENTS, 1 + i, chunks.length, chunks[i]) + // console.log("resp " + i + " " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + + // console.log("returnCode " + returnCode) + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + } + + let data = Buffer.alloc(0) + while (true) { + let newData = response.subarray(0, response.length - 2) + data = Buffer.concat([data, newData]) + + if (response.length == 255) { + response = await this.transport.send(this.CLA_DKG, this.INS.DKG_GET_COMMITMENTS, 0, 0, Buffer.alloc(0)) + // console.log("resp " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + } else { + return { + returnCode, + errorMessage, + commitments: data, + } + } + } + } catch (e) { + return processErrorResponse(e) + } + } + + async dkgGetNonces(identities: string[], tx_hash: string): Promise { + const blob = serializeDkgGetNonces(identities, tx_hash) + const chunks = this.prepareChunks(DUMMY_PATH, blob) + + try { + let response = Buffer.alloc(0) + let returnCode = 0 + let errorCodeData = Buffer.alloc(0) + let errorMessage = '' + try { + response = await this.sendDkgChunk(this.INS.DKG_GET_NONCES, 1, chunks.length, chunks[0]) + // console.log("resp 0 " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + } catch (e) { + // console.log(e) + } + + for (let i = 1; i < chunks.length; i += 1) { + // eslint-disable-next-line no-await-in-loop + response = await this.sendDkgChunk(this.INS.DKG_GET_NONCES, 1 + i, chunks.length, chunks[i]) + // console.log("resp " + i + " " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + + // console.log("returnCode " + returnCode) + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + } + + let data = Buffer.alloc(0) + while (true) { + let newData = response.subarray(0, response.length - 2) + data = Buffer.concat([data, newData]) + + if (response.length == 255) { + response = await this.transport.send(this.CLA_DKG, this.INS.DKG_GET_NONCES, 0, 0, Buffer.alloc(0)) + // console.log("resp " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + } else { + return { + returnCode, + errorMessage, + nonces: data, + } + } + } + } catch (e) { + return processErrorResponse(e) + } + } + + async dkgSign(pkRandomness: string, frostSigningPackage: string, nonces: string): Promise { + const blob = serializeDkgSign(pkRandomness, frostSigningPackage, nonces) + const chunks = this.prepareChunks(DUMMY_PATH, blob) + + try { + let response = Buffer.alloc(0) + let returnCode = 0 + let errorCodeData = Buffer.alloc(0) + let errorMessage = '' + try { + response = await this.sendDkgChunk(this.INS.DKG_SIGN, 1, chunks.length, chunks[0]) + // console.log("resp 0 " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + } catch (e) { + // console.log(e) + } + + for (let i = 1; i < chunks.length; i += 1) { + // eslint-disable-next-line no-await-in-loop + response = await this.sendDkgChunk(this.INS.DKG_SIGN, 1 + i, chunks.length, chunks[i]) + // console.log("resp " + i + " " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + + // console.log("returnCode " + returnCode) + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + } + + let data = Buffer.alloc(0) + while (true) { + let newData = response.subarray(0, response.length - 2) + data = Buffer.concat([data, newData]) + + if (response.length == 255) { + response = await this.transport.send(this.CLA_DKG, this.INS.DKG_SIGN, 0, 0, Buffer.alloc(0)) + // console.log("resp " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + } else { + return { + returnCode, + errorMessage, + signature: data, + } + } + } + } catch (e) { + return processErrorResponse(e) + } + } + + async dkgGetPublicPackage(): Promise { + try { + let response = await this.transport.send(this.CLA_DKG, this.INS.DKG_GET_PUBLIC_PACKAGE, 0, 0, Buffer.alloc(0), [LedgerError.NoErrors]) + // console.log("resp 0 " + response.toString("hex")) + let errorCodeData = response.subarray(-2) + let returnCode = errorCodeData[0] * 256 + errorCodeData[1] + let errorMessage = errorCodeToString(returnCode) + + // console.log("returnCode " + returnCode) + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + + let data = Buffer.alloc(0) + while (true) { + let newData = response.subarray(0, response.length - 2) + data = Buffer.concat([data, newData]) + + if (response.length == 255) { + response = await this.sendDkgChunk(this.INS.DKG_GET_PUBLIC_PACKAGE, 0, 0, Buffer.alloc(0)) + // console.log("resp " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + } else { + return { + returnCode, + errorMessage, + publicPackage: data, + } + } + } + } catch (e) { + return processErrorResponse(e) + } + } + + async dkgBackupKeys(): Promise { + try { + let response = await this.transport.send(this.CLA_DKG, this.INS.DKG_BACKUP_KEYS, 0, 0, Buffer.alloc(0), [LedgerError.NoErrors]) + // console.log("resp 0 " + response.toString("hex")) + let errorCodeData = response.subarray(-2) + let returnCode = errorCodeData[0] * 256 + errorCodeData[1] + let errorMessage = errorCodeToString(returnCode) + + // console.log("returnCode " + returnCode) + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + + let data = Buffer.alloc(0) + while (true) { + let newData = response.subarray(0, response.length - 2) + data = Buffer.concat([data, newData]) + + if (response.length == 255) { + response = await this.transport.send(this.CLA_DKG, this.INS.DKG_BACKUP_KEYS, 0, 0, Buffer.alloc(0)) + // console.log("resp " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + } else { + return { + returnCode, + errorMessage, + encryptedKeys: data, + } + } + } + } catch (e) { + return processErrorResponse(e) + } + } + async dkgRetrieveKeys(keyType: IronfishKeys): Promise { + return await this.transport + .send(this.CLA_DKG, this.INS.DKG_GET_KEYS, 0, keyType, Buffer.alloc(0), [LedgerError.NoErrors]) + .then(response => processGetKeysResponse(response, keyType), processErrorResponse) + } + + async dkgRestoreKeys(encryptedKeys: string): Promise { + const chunks = this.prepareChunks(DUMMY_PATH, Buffer.from(encryptedKeys, 'hex')) + + try { + let response = Buffer.alloc(0) + let returnCode = 0 + let errorCodeData = Buffer.alloc(0) + let errorMessage = '' + try { + response = await this.sendDkgChunk(this.INS.DKG_RESTORE_KEYS, 1, chunks.length, chunks[0]) + // console.log("resp 0 " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + } catch (e) { + // console.log(e) + } + + for (let i = 1; i < chunks.length; i += 1) { + // eslint-disable-next-line no-await-in-loop + response = await this.sendDkgChunk(this.INS.DKG_RESTORE_KEYS, 1 + i, chunks.length, chunks[i]) + // console.log("resp " + i + " " + response.toString("hex")) + + errorCodeData = response.subarray(-2) + returnCode = errorCodeData[0] * 256 + errorCodeData[1] + errorMessage = errorCodeToString(returnCode) + + // console.log("returnCode " + returnCode) + if (returnCode !== LedgerError.NoErrors) { + return { + returnCode, + errorMessage, + } + } + } + + return { + returnCode, + errorMessage, + } + } catch (e) { + return processErrorResponse(e) + } + } } diff --git a/src/ironfish.ts b/src/ironfish.ts new file mode 100644 index 0000000..54c428e --- /dev/null +++ b/src/ironfish.ts @@ -0,0 +1,140 @@ +import { deserializePublicPackage, deserializeRound2CombinedPublicPackage } from '@ironfish/rust-nodejs' + +export const minimizeRound3Inputs = (index: number, round1PublicPackages: string[], round2PublicPackages: string[]) => { + let round1Pkgs = round1PublicPackages.map(p => deserializePublicPackage(p)) + let round2Pkgs = round2PublicPackages.map(p => deserializeRound2CombinedPublicPackage(p)) + + let identity: string = '' + + const participants: string[] = [] + const round1PublicPkgs: string[] = [] + const round2PublicPkgs: string[] = [] + const gskBytes: string[] = [] + + for (let i = 0; i < round1Pkgs.length; i++) { + gskBytes.push(round1Pkgs[i].groupSecretKeyShardEncrypted) + + // TODO: is the index 0-indexed? + if (i == index) { + identity = round1Pkgs[i].identity + } else { + participants.push(round1Pkgs[i].identity) + round1PublicPkgs.push(round1Pkgs[i].frostPackage) + } + } + + for (let i = 0; i < round2Pkgs.length; i++) { + for (let j = 0; j < round2Pkgs[i].packages.length; j++) { + if (round2Pkgs[i].packages[j].recipientIdentity == identity) { + round2PublicPkgs.push(round2Pkgs[i].packages[j].frostPackage) + } + } + } + + return { + participants, + round1PublicPkgs, + round2PublicPkgs, + gskBytes, + } +} + +export const encodeRound3Inputs = ( + index: number, + participants: string[], + round1PublicPkgs: string[], + round2PublicPkgs: string[], + round2SecretPackage: string, + gskBytes: string[] +) => { + let round1PublicPkgsLen = round1PublicPkgs[0].length / 2 + let round2PublicPkgsLen = round2PublicPkgs[0].length / 2 + let round2SecretPkgLen = round2SecretPackage.length / 2 // staying the same + let participantsLen = participants[0].length / 2 + let gskLen = gskBytes[0].length / 2 + + let blob = Buffer.alloc( + 1 + + 1 + + 2 + + round1PublicPkgs.length * round1PublicPkgsLen + + 1 + + 2 + + round2PublicPkgs.length * round2PublicPkgsLen + + 2 + + round2SecretPkgLen + + 1 + + 2 + + participants.length * participantsLen + + 1 + + 2 + + gskBytes.length * gskLen + ) + let pos = 0 + + // identity index + blob.writeUint8(index, pos) + pos += 1 + + // number of round 1 packages + blob.writeUint8(round1PublicPkgs.length, pos) + pos += 1 + + // round 1 package length + blob.writeUint16BE(round1PublicPkgsLen, pos) + pos += 2 + + // round 1 packages + for (let i = 0; i < round1PublicPkgs.length; i++) { + blob.fill(Buffer.from(round1PublicPkgs[i], 'hex'), pos) + pos += round1PublicPkgsLen + } + + // number of round 2 packages + blob.writeUint8(round2PublicPkgs.length, pos) + pos += 1 + // round 2 package length + blob.writeUint16BE(round2PublicPkgsLen, pos) + pos += 2 + + // round 2 packages + for (let i = 0; i < round2PublicPkgs.length; i++) { + blob.fill(Buffer.from(round2PublicPkgs[i], 'hex'), pos) + pos += round2PublicPkgsLen + } + + // round 2 secret + blob.writeUint16BE(round2SecretPkgLen, pos) + pos += 2 + + blob.fill(Buffer.from(round2SecretPackage, 'hex'), pos) + pos += round2SecretPkgLen + + // participants + // number of participants + blob.writeUint8(participants.length, pos) + pos += 1 + + // participant payload length + blob.writeUint16BE(participantsLen, pos) + pos += 2 + + for (let i = 0; i < participants.length; i++) { + blob.fill(Buffer.from(participants[i], 'hex'), pos) + pos += participantsLen + } + + // gsk_bytes + blob.writeUint8(gskBytes.length, pos) + pos += 1 + + blob.writeUint16BE(gskLen, pos) + pos += 2 + + for (let i = 0; i < gskBytes.length; i++) { + blob.fill(Buffer.from(gskBytes[i], 'hex'), pos) + pos += gskLen + } + + return blob +} diff --git a/src/serialize.ts b/src/serialize.ts new file mode 100644 index 0000000..92e812e --- /dev/null +++ b/src/serialize.ts @@ -0,0 +1,117 @@ +import { encodeRound3Inputs, minimizeRound3Inputs } from './ironfish' + +export const serializeDkgRound1 = (index: number, identities: string[], minSigners: number): Buffer => { + let blob = Buffer.alloc(1 + 1 + identities.length * 129 + 1) + console.log(`dkgRound1 msg size: ${blob.byteLength}`) + + blob.writeUint8(index) + blob.writeUint8(identities.length, 1) + for (let i = 0; i < identities.length; i++) { + blob.fill(Buffer.from(identities[i], 'hex'), 1 + 1 + i * 129) + } + blob.writeUint8(minSigners, 1 + 1 + identities.length * 129) + + return blob +} + +export const serializeDkgRound2 = (index: number, round1PublicPackages: string[], round1SecretPackage: string): Buffer => { + let round1PublicPackagesLen = round1PublicPackages[0].length / 2 + let round1SecretPackageLen = round1SecretPackage.length / 2 + + let blob = Buffer.alloc(1 + 1 + 2 + round1PublicPackages.length * round1PublicPackagesLen + 2 + round1SecretPackageLen) + console.log(`dkgRound2 msg size: ${blob.byteLength}`) + + let pos = 0 + + blob.writeUint8(index, pos) + pos += 1 + blob.writeUint8(round1PublicPackages.length, pos) + pos += 1 + blob.writeUint16BE(round1PublicPackagesLen, pos) + pos += 2 + + for (let i = 0; i < round1PublicPackages.length; i++) { + blob.fill(Buffer.from(round1PublicPackages[i], 'hex'), pos) + pos += round1PublicPackagesLen + } + + blob.writeUint16BE(round1SecretPackageLen, pos) + pos += 2 + + blob.fill(Buffer.from(round1SecretPackage, 'hex'), pos) + pos += round1SecretPackageLen + + return blob +} + +export const serializeDkgRound3 = ( + index: number, + round1PublicPackages: string[], + round2PublicPackages: string[], + round2SecretPackage: string +): Buffer => { + const { participants, round2PublicPkgs, round1PublicPkgs, gskBytes } = minimizeRound3Inputs( + index, + round1PublicPackages, + round2PublicPackages + ) + const blob = encodeRound3Inputs(index, participants, round1PublicPkgs, round2PublicPkgs, round2SecretPackage, gskBytes) + + return blob +} + +export const serializeDkgGetCommitments = (identities: string[], tx_hash: string): Buffer => { + let blob = Buffer.alloc(1 + identities.length * 129 + 32) + console.log(`dkgGetCommitment msg size: ${blob.byteLength}`) + + blob.writeUint8(identities.length, 0) + + for (let i = 0; i < identities.length; i++) { + blob.fill(Buffer.from(identities[i], 'hex'), 1 + i * 129) + } + + blob.fill(Buffer.from(tx_hash, 'hex'), 1 + identities.length * 129) + + return blob +} + +export const serializeDkgGetNonces = (identities: string[], tx_hash: string): Buffer => { + let blob = Buffer.alloc(1 + identities.length * 129 + 32) + console.log(`dkgGetNonces msg size: ${blob.byteLength}`) + + blob.writeUint8(identities.length, 0) + + for (let i = 0; i < identities.length; i++) { + blob.fill(Buffer.from(identities[i], 'hex'), 1 + i * 129) + } + + blob.fill(Buffer.from(tx_hash, 'hex'), 1 + identities.length * 129) + return blob +} + +export const serializeDkgSign = (pkRandomness: string, frostSigningPackage: string, nonces: string): Buffer => { + let pkRandomnessLen = pkRandomness.length / 2 + let frostSigningPackageLen = frostSigningPackage.length / 2 + let noncesLen = nonces.length / 2 + + let blob = Buffer.alloc(2 + pkRandomnessLen + 2 + frostSigningPackageLen + 2 + noncesLen) + console.log(`dkgSign msg size: ${blob.byteLength}`) + + let pos = 0 + + blob.writeUint16BE(pkRandomnessLen, pos) + pos += 2 + blob.fill(Buffer.from(pkRandomness, 'hex'), pos) + pos += pkRandomnessLen + + blob.writeUint16BE(frostSigningPackageLen, pos) + pos += 2 + blob.fill(Buffer.from(frostSigningPackage, 'hex'), pos) + pos += frostSigningPackageLen + + blob.writeUint16BE(noncesLen, pos) + pos += 2 + blob.fill(Buffer.from(nonces, 'hex'), pos) + + return blob +} diff --git a/src/types.ts b/src/types.ts index c8786e0..06ca410 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,7 +4,17 @@ export interface IronfishIns extends INSGeneric { GET_VERSION: 0x00 GET_KEYS: 0x01 SIGN: 0x02 - GET_IDENTITY: 0x10 + DKG_IDENTITY: 0x10 + DKG_ROUND_1: 0x11 + DKG_ROUND_2: 0x12 + DKG_ROUND_3: 0x13 + DKG_GET_COMMITMENTS: 0x14 + DKG_SIGN: 0x15 + DKG_GET_KEYS: 0x16 + DKG_GET_NONCES: 0x17 + DKG_GET_PUBLIC_PACKAGE: 0x18 + DKG_BACKUP_KEYS: 0x19 + DKG_RESTORE_KEYS: 0x1a } export type KeyResponse = ResponseAddress | ResponseViewKey | ResponseProofGenKey @@ -37,3 +47,29 @@ export enum IronfishKeys { export interface ResponseIdentity extends ResponseBase { identity?: Buffer } + +export interface ResponseDkgRound1 extends ResponseBase { + publicPackage?: Buffer + secretPackage?: Buffer +} + +export interface ResponseDkgRound2 extends ResponseBase { + publicPackage?: Buffer + secretPackage?: Buffer +} +export interface ResponseDkgRound3 extends ResponseBase {} +export interface ResponseDkgGetCommitments extends ResponseBase { + commitments?: Buffer +} +export interface ResponseDkgGetNonces extends ResponseBase { + nonces?: Buffer +} +export interface ResponseDkgSign extends ResponseBase { + signature?: Buffer +} +export interface ResponseDkgGetPublicPackage extends ResponseBase { + publicPackage?: Buffer +} +export interface ResponseDkgBackupKeys extends ResponseBase { + encryptedKeys?: Buffer +} From d32e96d04b2e458dec073f4b94d06a801683479d Mon Sep 17 00:00:00 2001 From: Emmanuel Date: Sat, 14 Sep 2024 10:11:17 -0300 Subject: [PATCH 3/8] feat: add getResult command to retrieve command results async (#8) --- src/deserialize.ts | 8 +- src/index.ts | 314 ++++++++++++++------------------------------- src/types.ts | 1 + 3 files changed, 102 insertions(+), 221 deletions(-) diff --git a/src/deserialize.ts b/src/deserialize.ts index bc421ed..76d1046 100644 --- a/src/deserialize.ts +++ b/src/deserialize.ts @@ -1,4 +1,6 @@ -export const deserializeDkgRound1 = (data: Buffer) => { +export const deserializeDkgRound1 = (data?: Buffer) => { + if (!data) return {} + let pos = 0 const secretPackageLen = data.readUint16BE(pos) pos += 2 @@ -15,7 +17,9 @@ export const deserializeDkgRound1 = (data: Buffer) => { } } -export const deserializeDkgRound2 = (data: Buffer) => { +export const deserializeDkgRound2 = (data?: Buffer) => { + if (!data) return {} + let pos = 0 const secretPackageLen = data.readUint16BE(pos) pos += 2 diff --git a/src/index.ts b/src/index.ts index bcbcc97..772a1b5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -79,6 +79,7 @@ export default class IronfishApp extends GenericApp { DKG_GET_PUBLIC_PACKAGE: 0x18, DKG_BACKUP_KEYS: 0x19, DKG_RESTORE_KEYS: 0x1a, + GET_RESULT: 0x1b, }, p1Values: { ONLY_RETRIEVE: 0x00, @@ -232,32 +233,15 @@ export default class IronfishApp extends GenericApp { } } - let data = Buffer.alloc(0) - while (true) { - let newData = response.subarray(0, response.length - 2) - data = Buffer.concat([data, newData]) + let { isError, responseResult, rawResponse } = this.checkResponseCode(response) + if (isError) return responseResult - if (response.length == 255) { - response = await this.transport.send(this.CLA_DKG, this.INS.DKG_ROUND_1, 0, 0, Buffer.alloc(0)) - // console.log("resp " + response.toString("hex")) + let result = await this.getResult(rawResponse) - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } - } else { - return { - returnCode, - errorMessage, - ...deserializeDkgRound1(data), - } - } + return { + returnCode: result.returnCode, + errorMessage: result.errorMessage, + ...deserializeDkgRound1(result.data), } } catch (e) { return processErrorResponse(e) @@ -302,32 +286,15 @@ export default class IronfishApp extends GenericApp { } } - let data = Buffer.alloc(0) - while (true) { - let newData = response.subarray(0, response.length - 2) - data = Buffer.concat([data, newData]) - - if (response.length == 255) { - response = await this.transport.send(this.CLA_DKG, this.INS.DKG_ROUND_2, 0, 0, Buffer.alloc(0)) - // console.log("resp " + response.toString("hex")) + let { isError, responseResult, rawResponse } = this.checkResponseCode(response) + if (isError) return responseResult - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) + let result = await this.getResult(rawResponse) - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } - } else { - return { - returnCode, - errorMessage, - ...deserializeDkgRound2(data), - } - } + return { + returnCode: result.returnCode, + errorMessage: result.errorMessage, + ...deserializeDkgRound2(result.data), } } catch (e) { return processErrorResponse(e) @@ -377,31 +344,9 @@ export default class IronfishApp extends GenericApp { } } - let data = Buffer.alloc(0) - while (true) { - let newData = response.subarray(0, response.length - 2) - data = Buffer.concat([data, newData]) - - if (response.length == 255) { - response = await this.transport.send(this.CLA_DKG, this.INS.DKG_ROUND_3, 0, 0, Buffer.alloc(0)) - // console.log("resp " + response.toString("hex")) - - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } - } else { - return { - returnCode, - errorMessage, - } - } + return { + returnCode, + errorMessage, } } catch (e) { return processErrorResponse(e) @@ -446,32 +391,15 @@ export default class IronfishApp extends GenericApp { } } - let data = Buffer.alloc(0) - while (true) { - let newData = response.subarray(0, response.length - 2) - data = Buffer.concat([data, newData]) - - if (response.length == 255) { - response = await this.transport.send(this.CLA_DKG, this.INS.DKG_GET_COMMITMENTS, 0, 0, Buffer.alloc(0)) - // console.log("resp " + response.toString("hex")) + let { isError, responseResult, rawResponse } = this.checkResponseCode(response) + if (isError) return responseResult - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) + let result = await this.getResult(rawResponse) - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } - } else { - return { - returnCode, - errorMessage, - commitments: data, - } - } + return { + returnCode: result.returnCode, + errorMessage: result.errorMessage, + commitments: result.data, } } catch (e) { return processErrorResponse(e) @@ -516,32 +444,15 @@ export default class IronfishApp extends GenericApp { } } - let data = Buffer.alloc(0) - while (true) { - let newData = response.subarray(0, response.length - 2) - data = Buffer.concat([data, newData]) - - if (response.length == 255) { - response = await this.transport.send(this.CLA_DKG, this.INS.DKG_GET_NONCES, 0, 0, Buffer.alloc(0)) - // console.log("resp " + response.toString("hex")) + let { isError, responseResult, rawResponse } = this.checkResponseCode(response) + if (isError) return responseResult - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) + let result = await this.getResult(rawResponse) - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } - } else { - return { - returnCode, - errorMessage, - nonces: data, - } - } + return { + returnCode: result.returnCode, + errorMessage: result.errorMessage, + nonces: result.data, } } catch (e) { return processErrorResponse(e) @@ -586,32 +497,15 @@ export default class IronfishApp extends GenericApp { } } - let data = Buffer.alloc(0) - while (true) { - let newData = response.subarray(0, response.length - 2) - data = Buffer.concat([data, newData]) + let { isError, responseResult, rawResponse } = this.checkResponseCode(response) + if (isError) return responseResult - if (response.length == 255) { - response = await this.transport.send(this.CLA_DKG, this.INS.DKG_SIGN, 0, 0, Buffer.alloc(0)) - // console.log("resp " + response.toString("hex")) + let result = await this.getResult(rawResponse) - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } - } else { - return { - returnCode, - errorMessage, - signature: data, - } - } + return { + returnCode: result.returnCode, + errorMessage: result.errorMessage, + signature: result.data, } } catch (e) { return processErrorResponse(e) @@ -621,45 +515,15 @@ export default class IronfishApp extends GenericApp { async dkgGetPublicPackage(): Promise { try { let response = await this.transport.send(this.CLA_DKG, this.INS.DKG_GET_PUBLIC_PACKAGE, 0, 0, Buffer.alloc(0), [LedgerError.NoErrors]) - // console.log("resp 0 " + response.toString("hex")) - let errorCodeData = response.subarray(-2) - let returnCode = errorCodeData[0] * 256 + errorCodeData[1] - let errorMessage = errorCodeToString(returnCode) + let { isError, responseResult, rawResponse } = this.checkResponseCode(response) + if (isError) return responseResult - // console.log("returnCode " + returnCode) - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } + let result = await this.getResult(rawResponse) - let data = Buffer.alloc(0) - while (true) { - let newData = response.subarray(0, response.length - 2) - data = Buffer.concat([data, newData]) - - if (response.length == 255) { - response = await this.sendDkgChunk(this.INS.DKG_GET_PUBLIC_PACKAGE, 0, 0, Buffer.alloc(0)) - // console.log("resp " + response.toString("hex")) - - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } - } else { - return { - returnCode, - errorMessage, - publicPackage: data, - } - } + return { + returnCode: result.returnCode, + errorMessage: result.errorMessage, + publicPackage: result.data, } } catch (e) { return processErrorResponse(e) @@ -669,45 +533,15 @@ export default class IronfishApp extends GenericApp { async dkgBackupKeys(): Promise { try { let response = await this.transport.send(this.CLA_DKG, this.INS.DKG_BACKUP_KEYS, 0, 0, Buffer.alloc(0), [LedgerError.NoErrors]) - // console.log("resp 0 " + response.toString("hex")) - let errorCodeData = response.subarray(-2) - let returnCode = errorCodeData[0] * 256 + errorCodeData[1] - let errorMessage = errorCodeToString(returnCode) - - // console.log("returnCode " + returnCode) - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } - - let data = Buffer.alloc(0) - while (true) { - let newData = response.subarray(0, response.length - 2) - data = Buffer.concat([data, newData]) - - if (response.length == 255) { - response = await this.transport.send(this.CLA_DKG, this.INS.DKG_BACKUP_KEYS, 0, 0, Buffer.alloc(0)) - // console.log("resp " + response.toString("hex")) + let { isError, responseResult, rawResponse } = this.checkResponseCode(response) + if (isError) return responseResult - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) + let result = await this.getResult(rawResponse) - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } - } else { - return { - returnCode, - errorMessage, - encryptedKeys: data, - } - } + return { + returnCode: result.returnCode, + errorMessage: result.errorMessage, + encryptedKeys: result.data, } } catch (e) { return processErrorResponse(e) @@ -764,4 +598,46 @@ export default class IronfishApp extends GenericApp { return processErrorResponse(e) } } + + async getResult(rawResponse: Buffer): Promise<{ data?: Buffer; returnCode: number; errorMessage: string }> { + let data = Buffer.alloc(0) + + let chunks = rawResponse.readUint8(0) + for (let i = 0; i < chunks; i++) { + let response = await this.transport.send(this.CLA_DKG, this.INS.GET_RESULT, i, 0, Buffer.alloc(0)) + let { isError, responseResult, rawResponse } = this.checkResponseCode(response) + if (isError) return responseResult + + data = Buffer.concat([data, rawResponse]) + } + + return { data, returnCode: LedgerError.NoErrors, errorMessage: errorCodeToString(LedgerError.NoErrors) } + } + + checkResponseCode(response: Buffer) { + let errorCodeData = response.subarray(-2) + let returnCode = errorCodeData[0] * 256 + errorCodeData[1] + let errorMessage = errorCodeToString(returnCode) + + // console.log("returnCode " + returnCode) + if (returnCode !== LedgerError.NoErrors) { + return { + isError: true, + rawResponse: response.subarray(0, response.length - 2), + responseResult: { + returnCode, + errorMessage, + }, + } + } else { + return { + isError: false, + rawResponse: response.subarray(0, response.length - 2), + responseResult: { + returnCode, + errorMessage, + }, + } + } + } } diff --git a/src/types.ts b/src/types.ts index 06ca410..9f707fa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -15,6 +15,7 @@ export interface IronfishIns extends INSGeneric { DKG_GET_PUBLIC_PACKAGE: 0x18 DKG_BACKUP_KEYS: 0x19 DKG_RESTORE_KEYS: 0x1a + GET_RESULT: 0x1b } export type KeyResponse = ResponseAddress | ResponseViewKey | ResponseProofGenKey From db06728eae03ff5142645c755c1018a3649896d9 Mon Sep 17 00:00:00 2001 From: emmanuelm41 Date: Sat, 14 Sep 2024 15:44:55 -0300 Subject: [PATCH 4/8] feat: remove getnonces command, and adapt dkg sign --- src/index.ts | 66 +++--------------------------------------------- src/serialize.ts | 24 +++--------------- 2 files changed, 7 insertions(+), 83 deletions(-) diff --git a/src/index.ts b/src/index.ts index 772a1b5..d456536 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,14 +27,7 @@ import GenericApp, { import { P2_VALUES, REDJUBJUB_SIGNATURE_LEN } from './consts' import { deserializeDkgRound1, deserializeDkgRound2 } from './deserialize' import { processGetIdentityResponse, processGetKeysResponse } from './helper' -import { - serializeDkgGetCommitments, - serializeDkgGetNonces, - serializeDkgRound1, - serializeDkgRound2, - serializeDkgRound3, - serializeDkgSign, -} from './serialize' +import { serializeDkgGetCommitments, serializeDkgRound1, serializeDkgRound2, serializeDkgRound3, serializeDkgSign } from './serialize' import { IronfishIns, IronfishKeys, @@ -406,61 +399,8 @@ export default class IronfishApp extends GenericApp { } } - async dkgGetNonces(identities: string[], tx_hash: string): Promise { - const blob = serializeDkgGetNonces(identities, tx_hash) - const chunks = this.prepareChunks(DUMMY_PATH, blob) - - try { - let response = Buffer.alloc(0) - let returnCode = 0 - let errorCodeData = Buffer.alloc(0) - let errorMessage = '' - try { - response = await this.sendDkgChunk(this.INS.DKG_GET_NONCES, 1, chunks.length, chunks[0]) - // console.log("resp 0 " + response.toString("hex")) - - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - } catch (e) { - // console.log(e) - } - - for (let i = 1; i < chunks.length; i += 1) { - // eslint-disable-next-line no-await-in-loop - response = await this.sendDkgChunk(this.INS.DKG_GET_NONCES, 1 + i, chunks.length, chunks[i]) - // console.log("resp " + i + " " + response.toString("hex")) - - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - - // console.log("returnCode " + returnCode) - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } - } - - let { isError, responseResult, rawResponse } = this.checkResponseCode(response) - if (isError) return responseResult - - let result = await this.getResult(rawResponse) - - return { - returnCode: result.returnCode, - errorMessage: result.errorMessage, - nonces: result.data, - } - } catch (e) { - return processErrorResponse(e) - } - } - - async dkgSign(pkRandomness: string, frostSigningPackage: string, nonces: string): Promise { - const blob = serializeDkgSign(pkRandomness, frostSigningPackage, nonces) + async dkgSign(pkRandomness: string, frostSigningPackage: string, txHash: string): Promise { + const blob = serializeDkgSign(pkRandomness, frostSigningPackage, txHash) const chunks = this.prepareChunks(DUMMY_PATH, blob) try { diff --git a/src/serialize.ts b/src/serialize.ts index 92e812e..925a476 100644 --- a/src/serialize.ts +++ b/src/serialize.ts @@ -75,26 +75,12 @@ export const serializeDkgGetCommitments = (identities: string[], tx_hash: string return blob } -export const serializeDkgGetNonces = (identities: string[], tx_hash: string): Buffer => { - let blob = Buffer.alloc(1 + identities.length * 129 + 32) - console.log(`dkgGetNonces msg size: ${blob.byteLength}`) - - blob.writeUint8(identities.length, 0) - - for (let i = 0; i < identities.length; i++) { - blob.fill(Buffer.from(identities[i], 'hex'), 1 + i * 129) - } - - blob.fill(Buffer.from(tx_hash, 'hex'), 1 + identities.length * 129) - return blob -} - -export const serializeDkgSign = (pkRandomness: string, frostSigningPackage: string, nonces: string): Buffer => { +export const serializeDkgSign = (pkRandomness: string, frostSigningPackage: string, txHash: string): Buffer => { let pkRandomnessLen = pkRandomness.length / 2 let frostSigningPackageLen = frostSigningPackage.length / 2 - let noncesLen = nonces.length / 2 + let txHashLen = 32 - let blob = Buffer.alloc(2 + pkRandomnessLen + 2 + frostSigningPackageLen + 2 + noncesLen) + let blob = Buffer.alloc(2 + pkRandomnessLen + 2 + frostSigningPackageLen + txHashLen) console.log(`dkgSign msg size: ${blob.byteLength}`) let pos = 0 @@ -109,9 +95,7 @@ export const serializeDkgSign = (pkRandomness: string, frostSigningPackage: stri blob.fill(Buffer.from(frostSigningPackage, 'hex'), pos) pos += frostSigningPackageLen - blob.writeUint16BE(noncesLen, pos) - pos += 2 - blob.fill(Buffer.from(nonces, 'hex'), pos) + blob.fill(Buffer.from(txHash, 'hex'), pos) return blob } From 7eb1d183b696a3be2b1006a2106c50ab1295e8d7 Mon Sep 17 00:00:00 2001 From: emmanuelm41 Date: Sat, 14 Sep 2024 15:53:59 -0300 Subject: [PATCH 5/8] feat: reduce get commitments arguments --- src/index.ts | 5 ++--- src/serialize.ts | 12 +++--------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/index.ts b/src/index.ts index d456536..2e42e4e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,7 +34,6 @@ import { KeyResponse, ResponseDkgBackupKeys, ResponseDkgGetCommitments, - ResponseDkgGetNonces, ResponseDkgGetPublicPackage, ResponseDkgRound1, ResponseDkgRound2, @@ -346,8 +345,8 @@ export default class IronfishApp extends GenericApp { } } - async dkgGetCommitments(identities: string[], tx_hash: string): Promise { - const blob = serializeDkgGetCommitments(identities, tx_hash) + async dkgGetCommitments(tx_hash: string): Promise { + const blob = serializeDkgGetCommitments(tx_hash) const chunks = this.prepareChunks(DUMMY_PATH, blob) try { diff --git a/src/serialize.ts b/src/serialize.ts index 925a476..11de9e7 100644 --- a/src/serialize.ts +++ b/src/serialize.ts @@ -60,17 +60,11 @@ export const serializeDkgRound3 = ( return blob } -export const serializeDkgGetCommitments = (identities: string[], tx_hash: string): Buffer => { - let blob = Buffer.alloc(1 + identities.length * 129 + 32) +export const serializeDkgGetCommitments = (tx_hash: string): Buffer => { + let blob = Buffer.alloc(32) console.log(`dkgGetCommitment msg size: ${blob.byteLength}`) - blob.writeUint8(identities.length, 0) - - for (let i = 0; i < identities.length; i++) { - blob.fill(Buffer.from(identities[i], 'hex'), 1 + i * 129) - } - - blob.fill(Buffer.from(tx_hash, 'hex'), 1 + identities.length * 129) + blob.fill(Buffer.from(tx_hash, 'hex'), 0) return blob } From 319b01952a2c226a7813fa8e2c7b916bf8c4a323 Mon Sep 17 00:00:00 2001 From: emmanuelm41 Date: Mon, 16 Sep 2024 14:34:45 -0300 Subject: [PATCH 6/8] feat: replace dkgRound3 command to minimal version --- package.json | 1 - src/index.ts | 24 ++++---- src/ironfish.ts | 140 ----------------------------------------------- src/serialize.ts | 104 +++++++++++++++++++++++++++++++---- src/types.ts | 2 +- 5 files changed, 107 insertions(+), 164 deletions(-) delete mode 100644 src/ironfish.ts diff --git a/package.json b/package.json index b4f006c..e9656fd 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "url": "https://github.com/zondax/ledger-ironfish-js/issues" }, "dependencies": { - "@ironfish/rust-nodejs": "../ironfish/ironfish-rust-nodejs", "@zondax/ledger-js": "^0.2.1" }, "devDependencies": { diff --git a/src/index.ts b/src/index.ts index 2e42e4e..8ff8dd6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,7 +27,7 @@ import GenericApp, { import { P2_VALUES, REDJUBJUB_SIGNATURE_LEN } from './consts' import { deserializeDkgRound1, deserializeDkgRound2 } from './deserialize' import { processGetIdentityResponse, processGetKeysResponse } from './helper' -import { serializeDkgGetCommitments, serializeDkgRound1, serializeDkgRound2, serializeDkgRound3, serializeDkgSign } from './serialize' +import { serializeDkgGetCommitments, serializeDkgRound1, serializeDkgRound2, serializeDkgRound3Min, serializeDkgSign } from './serialize' import { IronfishIns, IronfishKeys, @@ -37,7 +37,7 @@ import { ResponseDkgGetPublicPackage, ResponseDkgRound1, ResponseDkgRound2, - ResponseDkgRound3, + ResponseDkgRound3Min, ResponseDkgSign, ResponseIdentity, ResponseSign, @@ -63,7 +63,7 @@ export default class IronfishApp extends GenericApp { DKG_IDENTITY: 0x10, DKG_ROUND_1: 0x11, DKG_ROUND_2: 0x12, - DKG_ROUND_3: 0x13, + DKG_ROUND_3_MIN: 0x13, DKG_GET_COMMITMENTS: 0x14, DKG_SIGN: 0x15, DKG_GET_KEYS: 0x16, @@ -293,13 +293,15 @@ export default class IronfishApp extends GenericApp { } } - async dkgRound3( + async dkgRound3Min( index: number, - round1PublicPackages: string[], - round2PublicPackages: string[], - round2SecretPackage: string - ): Promise { - const blob = serializeDkgRound3(index, round1PublicPackages, round2PublicPackages, round2SecretPackage) + participants: string[], + round1PublicPkgs: string[], + round2PublicPkgs: string[], + round2SecretPackage: string, + gskBytes: string[] + ): Promise { + const blob = serializeDkgRound3Min(index, participants, round1PublicPkgs, round2PublicPkgs, round2SecretPackage, gskBytes) const chunks = this.prepareChunks(DUMMY_PATH, blob) try { @@ -308,7 +310,7 @@ export default class IronfishApp extends GenericApp { let errorCodeData = Buffer.alloc(0) let errorMessage = '' try { - response = await this.sendDkgChunk(this.INS.DKG_ROUND_3, 1, chunks.length, chunks[0]) + response = await this.sendDkgChunk(this.INS.DKG_ROUND_3_MIN, 1, chunks.length, chunks[0]) // console.log("resp 0 " + response.toString("hex")) errorCodeData = response.subarray(-2) @@ -320,7 +322,7 @@ export default class IronfishApp extends GenericApp { for (let i = 1; i < chunks.length; i += 1) { // eslint-disable-next-line no-await-in-loop - response = await this.sendDkgChunk(this.INS.DKG_ROUND_3, 1 + i, chunks.length, chunks[i]) + response = await this.sendDkgChunk(this.INS.DKG_ROUND_3_MIN, 1 + i, chunks.length, chunks[i]) // console.log("resp " + i + " " + response.toString("hex")) errorCodeData = response.subarray(-2) diff --git a/src/ironfish.ts b/src/ironfish.ts deleted file mode 100644 index 54c428e..0000000 --- a/src/ironfish.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { deserializePublicPackage, deserializeRound2CombinedPublicPackage } from '@ironfish/rust-nodejs' - -export const minimizeRound3Inputs = (index: number, round1PublicPackages: string[], round2PublicPackages: string[]) => { - let round1Pkgs = round1PublicPackages.map(p => deserializePublicPackage(p)) - let round2Pkgs = round2PublicPackages.map(p => deserializeRound2CombinedPublicPackage(p)) - - let identity: string = '' - - const participants: string[] = [] - const round1PublicPkgs: string[] = [] - const round2PublicPkgs: string[] = [] - const gskBytes: string[] = [] - - for (let i = 0; i < round1Pkgs.length; i++) { - gskBytes.push(round1Pkgs[i].groupSecretKeyShardEncrypted) - - // TODO: is the index 0-indexed? - if (i == index) { - identity = round1Pkgs[i].identity - } else { - participants.push(round1Pkgs[i].identity) - round1PublicPkgs.push(round1Pkgs[i].frostPackage) - } - } - - for (let i = 0; i < round2Pkgs.length; i++) { - for (let j = 0; j < round2Pkgs[i].packages.length; j++) { - if (round2Pkgs[i].packages[j].recipientIdentity == identity) { - round2PublicPkgs.push(round2Pkgs[i].packages[j].frostPackage) - } - } - } - - return { - participants, - round1PublicPkgs, - round2PublicPkgs, - gskBytes, - } -} - -export const encodeRound3Inputs = ( - index: number, - participants: string[], - round1PublicPkgs: string[], - round2PublicPkgs: string[], - round2SecretPackage: string, - gskBytes: string[] -) => { - let round1PublicPkgsLen = round1PublicPkgs[0].length / 2 - let round2PublicPkgsLen = round2PublicPkgs[0].length / 2 - let round2SecretPkgLen = round2SecretPackage.length / 2 // staying the same - let participantsLen = participants[0].length / 2 - let gskLen = gskBytes[0].length / 2 - - let blob = Buffer.alloc( - 1 + - 1 + - 2 + - round1PublicPkgs.length * round1PublicPkgsLen + - 1 + - 2 + - round2PublicPkgs.length * round2PublicPkgsLen + - 2 + - round2SecretPkgLen + - 1 + - 2 + - participants.length * participantsLen + - 1 + - 2 + - gskBytes.length * gskLen - ) - let pos = 0 - - // identity index - blob.writeUint8(index, pos) - pos += 1 - - // number of round 1 packages - blob.writeUint8(round1PublicPkgs.length, pos) - pos += 1 - - // round 1 package length - blob.writeUint16BE(round1PublicPkgsLen, pos) - pos += 2 - - // round 1 packages - for (let i = 0; i < round1PublicPkgs.length; i++) { - blob.fill(Buffer.from(round1PublicPkgs[i], 'hex'), pos) - pos += round1PublicPkgsLen - } - - // number of round 2 packages - blob.writeUint8(round2PublicPkgs.length, pos) - pos += 1 - // round 2 package length - blob.writeUint16BE(round2PublicPkgsLen, pos) - pos += 2 - - // round 2 packages - for (let i = 0; i < round2PublicPkgs.length; i++) { - blob.fill(Buffer.from(round2PublicPkgs[i], 'hex'), pos) - pos += round2PublicPkgsLen - } - - // round 2 secret - blob.writeUint16BE(round2SecretPkgLen, pos) - pos += 2 - - blob.fill(Buffer.from(round2SecretPackage, 'hex'), pos) - pos += round2SecretPkgLen - - // participants - // number of participants - blob.writeUint8(participants.length, pos) - pos += 1 - - // participant payload length - blob.writeUint16BE(participantsLen, pos) - pos += 2 - - for (let i = 0; i < participants.length; i++) { - blob.fill(Buffer.from(participants[i], 'hex'), pos) - pos += participantsLen - } - - // gsk_bytes - blob.writeUint8(gskBytes.length, pos) - pos += 1 - - blob.writeUint16BE(gskLen, pos) - pos += 2 - - for (let i = 0; i < gskBytes.length; i++) { - blob.fill(Buffer.from(gskBytes[i], 'hex'), pos) - pos += gskLen - } - - return blob -} diff --git a/src/serialize.ts b/src/serialize.ts index 11de9e7..7ad2040 100644 --- a/src/serialize.ts +++ b/src/serialize.ts @@ -1,5 +1,3 @@ -import { encodeRound3Inputs, minimizeRound3Inputs } from './ironfish' - export const serializeDkgRound1 = (index: number, identities: string[], minSigners: number): Buffer => { let blob = Buffer.alloc(1 + 1 + identities.length * 129 + 1) console.log(`dkgRound1 msg size: ${blob.byteLength}`) @@ -44,18 +42,102 @@ export const serializeDkgRound2 = (index: number, round1PublicPackages: string[] return blob } -export const serializeDkgRound3 = ( +export const serializeDkgRound3Min = ( index: number, - round1PublicPackages: string[], - round2PublicPackages: string[], - round2SecretPackage: string + participants: string[], + round1PublicPkgs: string[], + round2PublicPkgs: string[], + round2SecretPackage: string, + gskBytes: string[] ): Buffer => { - const { participants, round2PublicPkgs, round1PublicPkgs, gskBytes } = minimizeRound3Inputs( - index, - round1PublicPackages, - round2PublicPackages + let round1PublicPkgsLen = round1PublicPkgs[0].length / 2 + let round2PublicPkgsLen = round2PublicPkgs[0].length / 2 + let round2SecretPkgLen = round2SecretPackage.length / 2 // staying the same + let participantsLen = participants[0].length / 2 + let gskLen = gskBytes[0].length / 2 + + let blob = Buffer.alloc( + 1 + + 1 + + 2 + + round1PublicPkgs.length * round1PublicPkgsLen + + 1 + + 2 + + round2PublicPkgs.length * round2PublicPkgsLen + + 2 + + round2SecretPkgLen + + 1 + + 2 + + participants.length * participantsLen + + 1 + + 2 + + gskBytes.length * gskLen ) - const blob = encodeRound3Inputs(index, participants, round1PublicPkgs, round2PublicPkgs, round2SecretPackage, gskBytes) + let pos = 0 + + // identity index + blob.writeUint8(index, pos) + pos += 1 + + // number of round 1 packages + blob.writeUint8(round1PublicPkgs.length, pos) + pos += 1 + + // round 1 package length + blob.writeUint16BE(round1PublicPkgsLen, pos) + pos += 2 + + // round 1 packages + for (let i = 0; i < round1PublicPkgs.length; i++) { + blob.fill(Buffer.from(round1PublicPkgs[i], 'hex'), pos) + pos += round1PublicPkgsLen + } + + // number of round 2 packages + blob.writeUint8(round2PublicPkgs.length, pos) + pos += 1 + // round 2 package length + blob.writeUint16BE(round2PublicPkgsLen, pos) + pos += 2 + + // round 2 packages + for (let i = 0; i < round2PublicPkgs.length; i++) { + blob.fill(Buffer.from(round2PublicPkgs[i], 'hex'), pos) + pos += round2PublicPkgsLen + } + + // round 2 secret + blob.writeUint16BE(round2SecretPkgLen, pos) + pos += 2 + + blob.fill(Buffer.from(round2SecretPackage, 'hex'), pos) + pos += round2SecretPkgLen + + // participants + // number of participants + blob.writeUint8(participants.length, pos) + pos += 1 + + // participant payload length + blob.writeUint16BE(participantsLen, pos) + pos += 2 + + for (let i = 0; i < participants.length; i++) { + blob.fill(Buffer.from(participants[i], 'hex'), pos) + pos += participantsLen + } + + // gsk_bytes + blob.writeUint8(gskBytes.length, pos) + pos += 1 + + blob.writeUint16BE(gskLen, pos) + pos += 2 + + for (let i = 0; i < gskBytes.length; i++) { + blob.fill(Buffer.from(gskBytes[i], 'hex'), pos) + pos += gskLen + } return blob } diff --git a/src/types.ts b/src/types.ts index 9f707fa..0006d66 100644 --- a/src/types.ts +++ b/src/types.ts @@ -58,7 +58,7 @@ export interface ResponseDkgRound2 extends ResponseBase { publicPackage?: Buffer secretPackage?: Buffer } -export interface ResponseDkgRound3 extends ResponseBase {} +export interface ResponseDkgRound3Min extends ResponseBase {} export interface ResponseDkgGetCommitments extends ResponseBase { commitments?: Buffer } From 1dd2062a3808d133dc535884681810a68364096a Mon Sep 17 00:00:00 2001 From: emmanuelm41 Date: Wed, 18 Sep 2024 11:36:38 -0300 Subject: [PATCH 7/8] feat: improve dkg cla support --- src/index.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index 8ff8dd6..38b9e21 100644 --- a/src/index.ts +++ b/src/index.ts @@ -48,13 +48,12 @@ export * from './types' const DUMMY_PATH = "m/44'/1338'/0" export default class IronfishApp extends GenericApp { - readonly CLA_DKG: number = 0x63 readonly INS!: IronfishIns - constructor(transport: Transport) { + constructor(transport: Transport, dkgMode?: boolean) { if (transport == null) throw new Error('Transport has not been defined') const params: ConstructorParams = { - cla: 0x59, + cla: dkgMode ? 0x63 : 0x59, ins: { GET_VERSION: 0x00, GET_KEYS: 0x01, @@ -166,7 +165,7 @@ export default class IronfishApp extends GenericApp { data.writeUint8(index) return await this.transport - .send(this.CLA_DKG, this.INS.DKG_IDENTITY, 0, 0, data, [LedgerError.NoErrors]) + .send(this.CLA, this.INS.DKG_IDENTITY, 0, 0, data, [LedgerError.NoErrors]) .then(response => processGetIdentityResponse(response), processErrorResponse) } @@ -179,7 +178,7 @@ export default class IronfishApp extends GenericApp { payloadType = PAYLOAD_TYPE.LAST } - return await this.transport.send(this.CLA_DKG, ins, payloadType, P2_VALUES.DEFAULT, chunk, [ + return await this.transport.send(this.CLA, ins, payloadType, P2_VALUES.DEFAULT, chunk, [ LedgerError.NoErrors, LedgerError.DataIsInvalid, LedgerError.BadKeyHandle, @@ -455,7 +454,7 @@ export default class IronfishApp extends GenericApp { async dkgGetPublicPackage(): Promise { try { - let response = await this.transport.send(this.CLA_DKG, this.INS.DKG_GET_PUBLIC_PACKAGE, 0, 0, Buffer.alloc(0), [LedgerError.NoErrors]) + let response = await this.transport.send(this.CLA, this.INS.DKG_GET_PUBLIC_PACKAGE, 0, 0, Buffer.alloc(0), [LedgerError.NoErrors]) let { isError, responseResult, rawResponse } = this.checkResponseCode(response) if (isError) return responseResult @@ -473,7 +472,7 @@ export default class IronfishApp extends GenericApp { async dkgBackupKeys(): Promise { try { - let response = await this.transport.send(this.CLA_DKG, this.INS.DKG_BACKUP_KEYS, 0, 0, Buffer.alloc(0), [LedgerError.NoErrors]) + let response = await this.transport.send(this.CLA, this.INS.DKG_BACKUP_KEYS, 0, 0, Buffer.alloc(0), [LedgerError.NoErrors]) let { isError, responseResult, rawResponse } = this.checkResponseCode(response) if (isError) return responseResult @@ -490,7 +489,7 @@ export default class IronfishApp extends GenericApp { } async dkgRetrieveKeys(keyType: IronfishKeys): Promise { return await this.transport - .send(this.CLA_DKG, this.INS.DKG_GET_KEYS, 0, keyType, Buffer.alloc(0), [LedgerError.NoErrors]) + .send(this.CLA, this.INS.DKG_GET_KEYS, 0, keyType, Buffer.alloc(0), [LedgerError.NoErrors]) .then(response => processGetKeysResponse(response, keyType), processErrorResponse) } @@ -545,7 +544,7 @@ export default class IronfishApp extends GenericApp { let chunks = rawResponse.readUint8(0) for (let i = 0; i < chunks; i++) { - let response = await this.transport.send(this.CLA_DKG, this.INS.GET_RESULT, i, 0, Buffer.alloc(0)) + let response = await this.transport.send(this.CLA, this.INS.GET_RESULT, i, 0, Buffer.alloc(0)) let { isError, responseResult, rawResponse } = this.checkResponseCode(response) if (isError) return responseResult From a9857eac0be6276d633c783231281e1e0e25de33 Mon Sep 17 00:00:00 2001 From: emmanuelm41 Date: Wed, 18 Sep 2024 13:18:15 -0300 Subject: [PATCH 8/8] feat: bump ledger-js package and implement it --- package.json | 2 +- src/deserialize.ts | 4 +- src/helper.ts | 65 ++---- src/index.ts | 478 ++++++++------------------------------------- src/types.ts | 64 +++--- 5 files changed, 126 insertions(+), 487 deletions(-) diff --git a/package.json b/package.json index e9656fd..f2cc33a 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "url": "https://github.com/zondax/ledger-ironfish-js/issues" }, "dependencies": { - "@zondax/ledger-js": "^0.2.1" + "@zondax/ledger-js": "^1.0.1" }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.3.0", diff --git a/src/deserialize.ts b/src/deserialize.ts index 76d1046..457f4a9 100644 --- a/src/deserialize.ts +++ b/src/deserialize.ts @@ -1,5 +1,5 @@ export const deserializeDkgRound1 = (data?: Buffer) => { - if (!data) return {} + if (!data) throw new Error('unexpected empty data') let pos = 0 const secretPackageLen = data.readUint16BE(pos) @@ -18,7 +18,7 @@ export const deserializeDkgRound1 = (data?: Buffer) => { } export const deserializeDkgRound2 = (data?: Buffer) => { - if (!data) return {} + if (!data) throw new Error('unexpected empty data') let pos = 0 const secretPackageLen = data.readUint16BE(pos) diff --git a/src/helper.ts b/src/helper.ts index a8b8948..6d62780 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -1,80 +1,43 @@ -import { errorCodeToString } from '@zondax/ledger-js' +import { ResponsePayload } from '@zondax/ledger-js/dist/payload' import { ED25519_SIGNATURE_LEN, IDENTITY_LEN, KEY_LENGTH, REDJUBJUB_SIGNATURE_LEN, VERSION } from './consts' import { IronfishKeys, KeyResponse, ResponseIdentity } from './types' -export function processGetKeysResponse(response: Buffer, keyType: IronfishKeys): KeyResponse { - const errorCodeData = response.subarray(-2) - const returnCode = errorCodeData[0] * 256 + errorCodeData[1] - - let requestedKey: KeyResponse = { - returnCode: returnCode, - errorMessage: errorCodeToString(returnCode), - } - +export function processGetKeysResponse(response: ResponsePayload, keyType: IronfishKeys): KeyResponse { switch (keyType) { case IronfishKeys.PublicAddress: { - const publicAddress = Buffer.from(response.subarray(0, KEY_LENGTH)) - requestedKey = { - ...requestedKey, + const publicAddress = response.readBytes(KEY_LENGTH) + return { publicAddress, } - break } case IronfishKeys.ViewKey: { - const viewKey = Buffer.from(response.subarray(0, 2 * KEY_LENGTH)) - response = response.subarray(2 * KEY_LENGTH) + const viewKey = response.readBytes(2 * KEY_LENGTH) + const ivk = response.readBytes(KEY_LENGTH) + const ovk = response.readBytes(KEY_LENGTH) - const ivk = Buffer.from(response.subarray(0, KEY_LENGTH)) - response = response.subarray(KEY_LENGTH) - - const ovk = Buffer.from(response.subarray(0, KEY_LENGTH)) - response = response.subarray(KEY_LENGTH) - - requestedKey = { - ...requestedKey, + return { viewKey, ivk, ovk, } - break } case IronfishKeys.ProofGenerationKey: { - const ak = Buffer.from(response.subarray(0, KEY_LENGTH)) - response = response.subarray(KEY_LENGTH) - - const nsk = Buffer.from(response.subarray(0, KEY_LENGTH)) - response = response.subarray(KEY_LENGTH) + const ak = response.readBytes(KEY_LENGTH) + const nsk = response.readBytes(KEY_LENGTH) - requestedKey = { - ...requestedKey, + return { ak, nsk, } - break } } - - return requestedKey } -export function processGetIdentityResponse(response: Buffer): ResponseIdentity { - const errorCodeData = response.subarray(-2) - const returnCode = errorCodeData[0] * 256 + errorCodeData[1] - - let getIdentityResponse: ResponseIdentity = { - returnCode: returnCode, - errorMessage: errorCodeToString(returnCode), - } - - const identity = Buffer.from(response.subarray(0, IDENTITY_LEN)) - - getIdentityResponse = { - ...getIdentityResponse, - identity, - } +export function processGetIdentityResponse(response: ResponsePayload): ResponseIdentity { + const identity = response.readBytes(IDENTITY_LEN) - return getIdentityResponse + return { identity } } diff --git a/src/index.ts b/src/index.ts index 38b9e21..2682610 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,17 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************* */ -import GenericApp, { - ConstructorParams, - LedgerError, - PAYLOAD_TYPE, - ResponseBase, - Transport, - errorCodeToString, - processErrorResponse, -} from '@zondax/ledger-js' - -import { P2_VALUES, REDJUBJUB_SIGNATURE_LEN } from './consts' +import GenericApp, { ConstructorParams, LedgerError, Transport, processErrorResponse, processResponse } from '@zondax/ledger-js' +import { ResponsePayload } from '@zondax/ledger-js/dist/payload' + +import { P2_VALUES } from './consts' import { deserializeDkgRound1, deserializeDkgRound2 } from './deserialize' import { processGetIdentityResponse, processGetKeysResponse } from './helper' import { serializeDkgGetCommitments, serializeDkgRound1, serializeDkgRound2, serializeDkgRound3Min, serializeDkgSign } from './serialize' @@ -37,7 +30,6 @@ import { ResponseDkgGetPublicPackage, ResponseDkgRound1, ResponseDkgRound2, - ResponseDkgRound3Min, ResponseDkgSign, ResponseIdentity, ResponseSign, @@ -86,209 +78,68 @@ export default class IronfishApp extends GenericApp { const serializedPath = this.serializePath(path) const p1 = showInDevice ? this.P1_VALUES.SHOW_ADDRESS_IN_DEVICE : this.P1_VALUES.ONLY_RETRIEVE - return await this.transport - .send(this.CLA, this.INS.GET_KEYS, p1, keyType, serializedPath, [LedgerError.NoErrors]) - .then(response => processGetKeysResponse(response, keyType), processErrorResponse) - } - - async signChunk(ins: number, chunkIdx: number, chunkNum: number, chunk: Buffer): Promise { - let payloadType = PAYLOAD_TYPE.ADD - if (chunkIdx === 1) { - payloadType = PAYLOAD_TYPE.INIT - } - if (chunkIdx === chunkNum) { - payloadType = PAYLOAD_TYPE.LAST - } - - return await this.transport - .send(this.CLA, ins, payloadType, P2_VALUES.DEFAULT, chunk, [ - LedgerError.NoErrors, - LedgerError.DataIsInvalid, - LedgerError.BadKeyHandle, - LedgerError.SignVerifyError, - ]) - .then((response: Buffer) => { - const errorCodeData = response.subarray(-2) - const returnCode = errorCodeData[0] * 256 + errorCodeData[1] - let errorMessage = errorCodeToString(returnCode) - - if ( - returnCode === LedgerError.BadKeyHandle || - returnCode === LedgerError.DataIsInvalid || - returnCode === LedgerError.SignVerifyError - ) { - errorMessage = `${errorMessage} : ${response.subarray(0, response.length - 2).toString('ascii')}` - } - - if (returnCode === LedgerError.NoErrors && response.length == 2 + REDJUBJUB_SIGNATURE_LEN) { - const signature = response.subarray(0, REDJUBJUB_SIGNATURE_LEN) - - return { - signature, - returnCode, - errorMessage, - } - } - - return { - returnCode, - errorMessage, - } - }, processErrorResponse) - } - - signSendChunk(chunkIdx: number, chunkNum: number, chunk: Buffer): Promise { - return this.signChunk(this.INS.SIGN, chunkIdx, chunkNum, chunk) + const response = await this.transport.send(this.CLA, this.INS.GET_KEYS, p1, keyType, serializedPath, [LedgerError.NoErrors]) + const payload = processResponse(response) + return processGetKeysResponse(payload, keyType) } async sign(path: string, blob: Buffer): Promise { - const chunks = this.prepareChunks(path, blob) - return await this.signSendChunk(1, chunks.length, chunks[0]).then(async response => { - let result: ResponseSign = { - returnCode: response.returnCode, - errorMessage: response.errorMessage, + try { + const chunks = this.prepareChunks(path, blob) + + let result: any + for (let i = 0; i < chunks.length; i += 1) { + result = await this.sendGenericChunk(this.INS.SIGN, P2_VALUES.DEFAULT, 1, chunks.length, chunks[0]) } - for (let i = 1; i < chunks.length; i += 1) { - // eslint-disable-next-line no-await-in-loop - result = await this.signSendChunk(1 + i, chunks.length, chunks[i]) - if (result.returnCode !== LedgerError.NoErrors) { - break - } + return { + signature: result.readBytes(result.length()), } - return result - }, processErrorResponse) + } catch (e) { + throw processErrorResponse(e) + } } async dkgGetIdentity(index: number): Promise { - let data = Buffer.alloc(1) - data.writeUint8(index) - - return await this.transport - .send(this.CLA, this.INS.DKG_IDENTITY, 0, 0, data, [LedgerError.NoErrors]) - .then(response => processGetIdentityResponse(response), processErrorResponse) - } + let req = Buffer.alloc(1) + req.writeUint8(index) - async sendDkgChunk(ins: number, chunkIdx: number, chunkNum: number, chunk: Buffer): Promise { - let payloadType = PAYLOAD_TYPE.ADD - if (chunkIdx === 1) { - payloadType = PAYLOAD_TYPE.INIT - } - if (chunkIdx === chunkNum) { - payloadType = PAYLOAD_TYPE.LAST - } - - return await this.transport.send(this.CLA, ins, payloadType, P2_VALUES.DEFAULT, chunk, [ - LedgerError.NoErrors, - LedgerError.DataIsInvalid, - LedgerError.BadKeyHandle, - LedgerError.SignVerifyError, - ]) + const response = await this.transport.send(this.CLA, this.INS.DKG_IDENTITY, 0, 0, req, [LedgerError.NoErrors]) + const data = processResponse(response) + return processGetIdentityResponse(data) } async dkgRound1(index: number, identities: string[], minSigners: number): Promise { - const blob = serializeDkgRound1(index, identities, minSigners) - const chunks = this.prepareChunks(DUMMY_PATH, blob) - try { - let response = Buffer.alloc(0) - let returnCode = 0 - let errorCodeData = Buffer.alloc(0) - let errorMessage = '' - try { - response = await this.sendDkgChunk(this.INS.DKG_ROUND_1, 1, chunks.length, chunks[0]) - // console.log("resp 0 " + response.toString("hex")) - - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - } catch (e) { - // console.log(e) - } + const blob = serializeDkgRound1(index, identities, minSigners) + const chunks = this.prepareChunks(DUMMY_PATH, blob) - for (let i = 1; i < chunks.length; i += 1) { - // eslint-disable-next-line no-await-in-loop - response = await this.sendDkgChunk(this.INS.DKG_ROUND_1, 1 + i, chunks.length, chunks[i]) - // console.log("resp " + i + " " + response.toString("hex")) - - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - - // console.log("returnCode " + returnCode) - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } + let rawResponse: any + for (let i = 0; i < chunks.length; i += 1) { + rawResponse = await this.sendGenericChunk(this.INS.DKG_ROUND_1, P2_VALUES.DEFAULT, 1 + i, chunks.length, chunks[i]) } - let { isError, responseResult, rawResponse } = this.checkResponseCode(response) - if (isError) return responseResult - let result = await this.getResult(rawResponse) - - return { - returnCode: result.returnCode, - errorMessage: result.errorMessage, - ...deserializeDkgRound1(result.data), - } + return deserializeDkgRound1(result) } catch (e) { - return processErrorResponse(e) + throw processErrorResponse(e) } } async dkgRound2(index: number, round1PublicPackages: string[], round1SecretPackage: string): Promise { - const blob = serializeDkgRound2(index, round1PublicPackages, round1SecretPackage) - const chunks = this.prepareChunks(DUMMY_PATH, blob) - try { - let response = Buffer.alloc(0) - let returnCode = 0 - let errorCodeData = Buffer.alloc(0) - let errorMessage = '' - try { - response = await this.sendDkgChunk(this.INS.DKG_ROUND_2, 1, chunks.length, chunks[0]) - // console.log("resp 0 " + response.toString("hex")) - - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - } catch (e) { - // console.log(e) - } + const blob = serializeDkgRound2(index, round1PublicPackages, round1SecretPackage) + const chunks = this.prepareChunks(DUMMY_PATH, blob) - for (let i = 1; i < chunks.length; i += 1) { - // eslint-disable-next-line no-await-in-loop - response = await this.sendDkgChunk(this.INS.DKG_ROUND_2, 1 + i, chunks.length, chunks[i]) - // console.log("resp " + i + " " + response.toString("hex")) - - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - - // console.log("returnCode " + returnCode) - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } + let rawResponse: any + for (let i = 0; i < chunks.length; i += 1) { + rawResponse = await this.sendGenericChunk(this.INS.DKG_ROUND_2, P2_VALUES.DEFAULT, 1 + i, chunks.length, chunks[i]) } - let { isError, responseResult, rawResponse } = this.checkResponseCode(response) - if (isError) return responseResult - let result = await this.getResult(rawResponse) - - return { - returnCode: result.returnCode, - errorMessage: result.errorMessage, - ...deserializeDkgRound2(result.data), - } + return deserializeDkgRound2(result) } catch (e) { - return processErrorResponse(e) + throw processErrorResponse(e) } } @@ -299,285 +150,114 @@ export default class IronfishApp extends GenericApp { round2PublicPkgs: string[], round2SecretPackage: string, gskBytes: string[] - ): Promise { - const blob = serializeDkgRound3Min(index, participants, round1PublicPkgs, round2PublicPkgs, round2SecretPackage, gskBytes) - const chunks = this.prepareChunks(DUMMY_PATH, blob) - + ): Promise { try { - let response = Buffer.alloc(0) - let returnCode = 0 - let errorCodeData = Buffer.alloc(0) - let errorMessage = '' - try { - response = await this.sendDkgChunk(this.INS.DKG_ROUND_3_MIN, 1, chunks.length, chunks[0]) - // console.log("resp 0 " + response.toString("hex")) - - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - } catch (e) { - // console.log(e) - } - - for (let i = 1; i < chunks.length; i += 1) { - // eslint-disable-next-line no-await-in-loop - response = await this.sendDkgChunk(this.INS.DKG_ROUND_3_MIN, 1 + i, chunks.length, chunks[i]) - // console.log("resp " + i + " " + response.toString("hex")) - - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - - // console.log("returnCode " + returnCode) - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } - } + const blob = serializeDkgRound3Min(index, participants, round1PublicPkgs, round2PublicPkgs, round2SecretPackage, gskBytes) + const chunks = this.prepareChunks(DUMMY_PATH, blob) - return { - returnCode, - errorMessage, + for (let i = 0; i < chunks.length; i += 1) { + await this.sendGenericChunk(this.INS.DKG_ROUND_3_MIN, P2_VALUES.DEFAULT, 1 + i, chunks.length, chunks[i]) } } catch (e) { - return processErrorResponse(e) + throw processErrorResponse(e) } } async dkgGetCommitments(tx_hash: string): Promise { - const blob = serializeDkgGetCommitments(tx_hash) - const chunks = this.prepareChunks(DUMMY_PATH, blob) - try { - let response = Buffer.alloc(0) - let returnCode = 0 - let errorCodeData = Buffer.alloc(0) - let errorMessage = '' - try { - response = await this.sendDkgChunk(this.INS.DKG_GET_COMMITMENTS, 1, chunks.length, chunks[0]) - // console.log("resp 0 " + response.toString("hex")) - - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - } catch (e) { - // console.log(e) - } + const blob = serializeDkgGetCommitments(tx_hash) + const chunks = this.prepareChunks(DUMMY_PATH, blob) - for (let i = 1; i < chunks.length; i += 1) { - // eslint-disable-next-line no-await-in-loop - response = await this.sendDkgChunk(this.INS.DKG_GET_COMMITMENTS, 1 + i, chunks.length, chunks[i]) - // console.log("resp " + i + " " + response.toString("hex")) - - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - - // console.log("returnCode " + returnCode) - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } + let rawResponse: any + for (let i = 0; i < chunks.length; i += 1) { + rawResponse = await this.sendGenericChunk(this.INS.DKG_GET_COMMITMENTS, P2_VALUES.DEFAULT, 1 + i, chunks.length, chunks[i]) } - let { isError, responseResult, rawResponse } = this.checkResponseCode(response) - if (isError) return responseResult - let result = await this.getResult(rawResponse) - return { - returnCode: result.returnCode, - errorMessage: result.errorMessage, - commitments: result.data, + commitments: result, } } catch (e) { - return processErrorResponse(e) + throw processErrorResponse(e) } } async dkgSign(pkRandomness: string, frostSigningPackage: string, txHash: string): Promise { - const blob = serializeDkgSign(pkRandomness, frostSigningPackage, txHash) - const chunks = this.prepareChunks(DUMMY_PATH, blob) - try { - let response = Buffer.alloc(0) - let returnCode = 0 - let errorCodeData = Buffer.alloc(0) - let errorMessage = '' - try { - response = await this.sendDkgChunk(this.INS.DKG_SIGN, 1, chunks.length, chunks[0]) - // console.log("resp 0 " + response.toString("hex")) - - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - } catch (e) { - // console.log(e) - } + const blob = serializeDkgSign(pkRandomness, frostSigningPackage, txHash) + const chunks = this.prepareChunks(DUMMY_PATH, blob) - for (let i = 1; i < chunks.length; i += 1) { - // eslint-disable-next-line no-await-in-loop - response = await this.sendDkgChunk(this.INS.DKG_SIGN, 1 + i, chunks.length, chunks[i]) - // console.log("resp " + i + " " + response.toString("hex")) - - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - - // console.log("returnCode " + returnCode) - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } + let rawResponse: any + for (let i = 0; i < chunks.length; i += 1) { + rawResponse = await this.sendGenericChunk(this.INS.DKG_SIGN, P2_VALUES.DEFAULT, 1 + i, chunks.length, chunks[i]) } - let { isError, responseResult, rawResponse } = this.checkResponseCode(response) - if (isError) return responseResult - let result = await this.getResult(rawResponse) - return { - returnCode: result.returnCode, - errorMessage: result.errorMessage, - signature: result.data, + signature: result, } } catch (e) { - return processErrorResponse(e) + throw processErrorResponse(e) } } async dkgGetPublicPackage(): Promise { try { let response = await this.transport.send(this.CLA, this.INS.DKG_GET_PUBLIC_PACKAGE, 0, 0, Buffer.alloc(0), [LedgerError.NoErrors]) - let { isError, responseResult, rawResponse } = this.checkResponseCode(response) - if (isError) return responseResult + let data = processResponse(response) - let result = await this.getResult(rawResponse) + let result = await this.getResult(data) return { - returnCode: result.returnCode, - errorMessage: result.errorMessage, - publicPackage: result.data, + publicPackage: result, } } catch (e) { - return processErrorResponse(e) + throw processErrorResponse(e) } } async dkgBackupKeys(): Promise { try { let response = await this.transport.send(this.CLA, this.INS.DKG_BACKUP_KEYS, 0, 0, Buffer.alloc(0), [LedgerError.NoErrors]) - let { isError, responseResult, rawResponse } = this.checkResponseCode(response) - if (isError) return responseResult + let data = processResponse(response) - let result = await this.getResult(rawResponse) + let result = await this.getResult(data) return { - returnCode: result.returnCode, - errorMessage: result.errorMessage, - encryptedKeys: result.data, + encryptedKeys: result, } } catch (e) { - return processErrorResponse(e) + throw processErrorResponse(e) } } async dkgRetrieveKeys(keyType: IronfishKeys): Promise { - return await this.transport - .send(this.CLA, this.INS.DKG_GET_KEYS, 0, keyType, Buffer.alloc(0), [LedgerError.NoErrors]) - .then(response => processGetKeysResponse(response, keyType), processErrorResponse) + const response = await this.transport.send(this.CLA, this.INS.DKG_GET_KEYS, 0, keyType, Buffer.alloc(0), [LedgerError.NoErrors]) + const data = processResponse(response) + return processGetKeysResponse(data, keyType) } - async dkgRestoreKeys(encryptedKeys: string): Promise { - const chunks = this.prepareChunks(DUMMY_PATH, Buffer.from(encryptedKeys, 'hex')) - + async dkgRestoreKeys(encryptedKeys: string): Promise { try { - let response = Buffer.alloc(0) - let returnCode = 0 - let errorCodeData = Buffer.alloc(0) - let errorMessage = '' - try { - response = await this.sendDkgChunk(this.INS.DKG_RESTORE_KEYS, 1, chunks.length, chunks[0]) - // console.log("resp 0 " + response.toString("hex")) - - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - } catch (e) { - // console.log(e) - } + const chunks = this.prepareChunks(DUMMY_PATH, Buffer.from(encryptedKeys, 'hex')) - for (let i = 1; i < chunks.length; i += 1) { - // eslint-disable-next-line no-await-in-loop - response = await this.sendDkgChunk(this.INS.DKG_RESTORE_KEYS, 1 + i, chunks.length, chunks[i]) - // console.log("resp " + i + " " + response.toString("hex")) - - errorCodeData = response.subarray(-2) - returnCode = errorCodeData[0] * 256 + errorCodeData[1] - errorMessage = errorCodeToString(returnCode) - - // console.log("returnCode " + returnCode) - if (returnCode !== LedgerError.NoErrors) { - return { - returnCode, - errorMessage, - } - } - } - - return { - returnCode, - errorMessage, + for (let i = 0; i < chunks.length; i += 1) { + await this.sendGenericChunk(this.INS.DKG_RESTORE_KEYS, P2_VALUES.DEFAULT, 1 + i, chunks.length, chunks[i]) } } catch (e) { - return processErrorResponse(e) + throw processErrorResponse(e) } } - async getResult(rawResponse: Buffer): Promise<{ data?: Buffer; returnCode: number; errorMessage: string }> { + async getResult(rawResponse: ResponsePayload): Promise { let data = Buffer.alloc(0) - let chunks = rawResponse.readUint8(0) + let chunks = rawResponse.readBytes(1).readUint8() for (let i = 0; i < chunks; i++) { - let response = await this.transport.send(this.CLA, this.INS.GET_RESULT, i, 0, Buffer.alloc(0)) - let { isError, responseResult, rawResponse } = this.checkResponseCode(response) - if (isError) return responseResult - - data = Buffer.concat([data, rawResponse]) + let result = await this.transport.send(this.CLA, this.INS.GET_RESULT, i, 0, Buffer.alloc(0)) + let response = processResponse(result) + data = Buffer.concat([data, response.getCompleteBuffer()]) } - return { data, returnCode: LedgerError.NoErrors, errorMessage: errorCodeToString(LedgerError.NoErrors) } - } - - checkResponseCode(response: Buffer) { - let errorCodeData = response.subarray(-2) - let returnCode = errorCodeData[0] * 256 + errorCodeData[1] - let errorMessage = errorCodeToString(returnCode) - - // console.log("returnCode " + returnCode) - if (returnCode !== LedgerError.NoErrors) { - return { - isError: true, - rawResponse: response.subarray(0, response.length - 2), - responseResult: { - returnCode, - errorMessage, - }, - } - } else { - return { - isError: false, - rawResponse: response.subarray(0, response.length - 2), - responseResult: { - returnCode, - errorMessage, - }, - } - } + return data } } diff --git a/src/types.ts b/src/types.ts index 0006d66..db0ec2f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -import { INSGeneric, ResponseBase } from '@zondax/ledger-js' +import { INSGeneric } from '@zondax/ledger-js' export interface IronfishIns extends INSGeneric { GET_VERSION: 0x00 @@ -7,11 +7,10 @@ export interface IronfishIns extends INSGeneric { DKG_IDENTITY: 0x10 DKG_ROUND_1: 0x11 DKG_ROUND_2: 0x12 - DKG_ROUND_3: 0x13 + DKG_ROUND_3_MIN: 0x13 DKG_GET_COMMITMENTS: 0x14 DKG_SIGN: 0x15 DKG_GET_KEYS: 0x16 - DKG_GET_NONCES: 0x17 DKG_GET_PUBLIC_PACKAGE: 0x18 DKG_BACKUP_KEYS: 0x19 DKG_RESTORE_KEYS: 0x1a @@ -20,23 +19,23 @@ export interface IronfishIns extends INSGeneric { export type KeyResponse = ResponseAddress | ResponseViewKey | ResponseProofGenKey -export interface ResponseAddress extends ResponseBase { - publicAddress?: Buffer +export interface ResponseAddress { + publicAddress: Buffer } -export interface ResponseViewKey extends ResponseBase { - viewKey?: Buffer - ivk?: Buffer - ovk?: Buffer +export interface ResponseViewKey { + viewKey: Buffer + ivk: Buffer + ovk: Buffer } -export interface ResponseProofGenKey extends ResponseBase { - ak?: Buffer - nsk?: Buffer +export interface ResponseProofGenKey { + ak: Buffer + nsk: Buffer } -export interface ResponseSign extends ResponseBase { - signature?: Buffer +export interface ResponseSign { + signature: Buffer } export enum IronfishKeys { @@ -45,32 +44,29 @@ export enum IronfishKeys { ProofGenerationKey = 0x02, } -export interface ResponseIdentity extends ResponseBase { - identity?: Buffer +export interface ResponseIdentity { + identity: Buffer } -export interface ResponseDkgRound1 extends ResponseBase { - publicPackage?: Buffer - secretPackage?: Buffer +export interface ResponseDkgRound1 { + publicPackage: Buffer + secretPackage: Buffer } -export interface ResponseDkgRound2 extends ResponseBase { - publicPackage?: Buffer - secretPackage?: Buffer +export interface ResponseDkgRound2 { + publicPackage: Buffer + secretPackage: Buffer } -export interface ResponseDkgRound3Min extends ResponseBase {} -export interface ResponseDkgGetCommitments extends ResponseBase { - commitments?: Buffer -} -export interface ResponseDkgGetNonces extends ResponseBase { - nonces?: Buffer + +export interface ResponseDkgGetCommitments { + commitments: Buffer } -export interface ResponseDkgSign extends ResponseBase { - signature?: Buffer +export interface ResponseDkgSign { + signature: Buffer } -export interface ResponseDkgGetPublicPackage extends ResponseBase { - publicPackage?: Buffer +export interface ResponseDkgGetPublicPackage { + publicPackage: Buffer } -export interface ResponseDkgBackupKeys extends ResponseBase { - encryptedKeys?: Buffer +export interface ResponseDkgBackupKeys { + encryptedKeys: Buffer }