Skip to content

Commit

Permalink
Throw if provided path is incorrect
Browse files Browse the repository at this point in the history
Comment requirement
  • Loading branch information
neithanmo committed Nov 20, 2024
1 parent 4ab04cf commit 0a5d766
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 83 deletions.
123 changes: 51 additions & 72 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************* */
import { P2_VALUES, PREHASH_LEN, RANDOMIZER_LEN, SIGRSLEN } from "./consts";
import { ResponseAddress, ResponseFvk, PenumbraIns, ResponseSign, AddressIndex } from "./types";

import BaseApp, {
ConstructorParams,
LedgerError,
PAYLOAD_TYPE,
processErrorResponse,
Transport,
BIP32Path,
} from "@zondax/ledger-js";
import { processGetAddrResponse, processGetFvkResponse } from "./helper";
import BaseApp, { BIP32Path, ConstructorParams, LedgerError, PAYLOAD_TYPE, Transport, processErrorResponse } from '@zondax/ledger-js'

import { DEFAULT_PATH, P2_VALUES, PREHASH_LEN, RANDOMIZER_LEN, SIGRSLEN } from './consts'
import { processGetAddrResponse, processGetFvkResponse } from './helper'
import { AddressIndex, PenumbraIns, ResponseAddress, ResponseFvk, ResponseSign } from './types'

// https://buf.build/penumbra-zone/penumbra/docs/main:penumbra.custody.v1#penumbra.custody.v1.ConfirmAddressRequest

export * from "./types";
export * from './types'

export class PenumbraApp extends BaseApp {
readonly INS!: PenumbraIns;
readonly INS!: PenumbraIns
constructor(transport: Transport) {
if (transport == null) throw new Error("Transport has not been defined");
if (transport == null) throw new Error('Transport has not been defined')

const params: ConstructorParams = {
cla: 0x80,
Expand All @@ -48,125 +41,111 @@ export class PenumbraApp extends BaseApp {
ONLY_RETRIEVE: 0x00,
SHOW_ADDRESS_IN_DEVICE: 0x01,
},
// // Penumbra uses a fixed BIP44 path with exactly 3 levels: m/44'/6532'/0'
acceptedPathLengths: [3],
chunkSize: 250,
};
super(transport, params);
}
super(transport, params)
}

async getAddress(
path: string,
addressIndex: AddressIndex,
): Promise<ResponseAddress> {
const data = this._prepareAddressData(path, addressIndex);
async getAddress(path: string, addressIndex: AddressIndex): Promise<ResponseAddress> {
const data = this._prepareAddressData(path, addressIndex)
try {
const responseBuffer = await this.transport.send(
this.CLA,
this.INS.GET_ADDR,
this.P1_VALUES.ONLY_RETRIEVE,
P2_VALUES.DEFAULT,
data,
);
const responseBuffer = await this.transport.send(this.CLA, this.INS.GET_ADDR, this.P1_VALUES.ONLY_RETRIEVE, P2_VALUES.DEFAULT, data)

const response = processGetAddrResponse(responseBuffer);
const response = processGetAddrResponse(responseBuffer)

return {
address: response.address,
} as ResponseAddress;
} as ResponseAddress
} catch (e) {
throw processErrorResponse(e);
throw processErrorResponse(e)
}
}

async showAddress(
path: string,
addressIndex: AddressIndex,
): Promise<ResponseAddress> {
const data = this._prepareAddressData(path, addressIndex);
async showAddress(path: string, addressIndex: AddressIndex): Promise<ResponseAddress> {
const data = this._prepareAddressData(path, addressIndex)

try {
const responseBuffer = await this.transport.send(
this.CLA,
this.INS.GET_ADDR,
this.P1_VALUES.SHOW_ADDRESS_IN_DEVICE,
P2_VALUES.DEFAULT,
data,
);
data
)

const response = processGetAddrResponse(responseBuffer);
const response = processGetAddrResponse(responseBuffer)

return {
address: response.address,
} as ResponseAddress;
} as ResponseAddress
} catch (e) {
throw processErrorResponse(e);
throw processErrorResponse(e)
}
}

async getFVK(path: string, addressIndex: AddressIndex): Promise<ResponseFvk> {
const data = this._prepareAddressData(path, addressIndex);
const data = this._prepareAddressData(path, addressIndex)

// Fvk can be retrieved without user confirmation
try {
const responseBuffer = await this.transport.send(
this.CLA,
this.INS.FVK,
this.P1_VALUES.ONLY_RETRIEVE,
P2_VALUES.DEFAULT,
data,
);
const responseBuffer = await this.transport.send(this.CLA, this.INS.FVK, this.P1_VALUES.ONLY_RETRIEVE, P2_VALUES.DEFAULT, data)

const response = processGetFvkResponse(responseBuffer);
const response = processGetFvkResponse(responseBuffer)

return {
fvk: response.fvk,
} as ResponseFvk;
} as ResponseFvk
} catch (e) {
throw processErrorResponse(e);
throw processErrorResponse(e)
}
}

async sign(path: BIP32Path, addressIndex: AddressIndex, blob: Buffer): Promise<ResponseSign> {
const chunks = this.prepareChunks(path, blob);
const chunks = this.prepareChunks(path, blob)
try {
let signatureResponse = await this.signSendChunk(this.INS.SIGN, 1, chunks.length, chunks[0]);
let signatureResponse = await this.signSendChunk(this.INS.SIGN, 1, chunks.length, chunks[0])

for (let i = 1; i < chunks.length; i += 1) {
signatureResponse = await this.signSendChunk(this.INS.SIGN, 1 + i, chunks.length, chunks[i]);
signatureResponse = await this.signSendChunk(this.INS.SIGN, 1 + i, chunks.length, chunks[i])
}
return {
signature: signatureResponse.readBytes(signatureResponse.length()),
};
}
} catch (e) {
throw processErrorResponse(e);
throw processErrorResponse(e)
}
}

private _prepareAddressData(path: string, addressIndex: AddressIndex): Buffer {
const serializedPath = this.serializePath(path);
const accountBuffer = this.serializeAccountIndex(addressIndex);

// Path must always be this
// according to penumbra team
if (path !== DEFAULT_PATH) {
throw new Error("Invalid derivation path. Must be m/44'/6532'/0'")
}
// Enforce exactly 3 levels
// this was set in our class constructor [3]
const serializedPath = this.serializePath(path)
const accountBuffer = this.serializeAccountIndex(addressIndex)

// concatenate data
const concatenatedBuffer: Buffer = Buffer.concat([
serializedPath,
accountBuffer,
]);
const concatenatedBuffer: Buffer = Buffer.concat([serializedPath, accountBuffer])

return concatenatedBuffer;
return concatenatedBuffer
}

private serializeAccountIndex(accountIndex: AddressIndex): Buffer {
const accountBuffer = Buffer.alloc(4);
accountBuffer.writeUInt32LE(accountIndex.account);
const accountBuffer = Buffer.alloc(4)
accountBuffer.writeUInt32LE(accountIndex.account)

const hasRandomizerBuffer = Buffer.from([accountIndex.randomizer ? 1 : 0]);
const randomizerBuffer = accountIndex.randomizer ?? Buffer.alloc(RANDOMIZER_LEN);
const hasRandomizerBuffer = Buffer.from([accountIndex.randomizer ? 1 : 0])
const randomizerBuffer = accountIndex.randomizer ?? Buffer.alloc(RANDOMIZER_LEN)
// Ensure randomizerBuffer does not exceed 12 bytes
if (randomizerBuffer && randomizerBuffer.length > RANDOMIZER_LEN) {
throw new Error("randomizerBuffer exceeds the maximum allowed length of 12 bytes");
throw new Error('randomizerBuffer exceeds the maximum allowed length of 12 bytes')
}

return Buffer.concat([accountBuffer, hasRandomizerBuffer, randomizerBuffer]);
return Buffer.concat([accountBuffer, hasRandomizerBuffer, randomizerBuffer])
}
}
17 changes: 9 additions & 8 deletions src/consts.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
export const APP_KEY = "PENUMBRA";
export const APP_KEY = 'PENUMBRA'
export const DEFAULT_PATH = "m/44'/6532'/0'"

export const P2_VALUES = {
DEFAULT: 0x00,
};
}

export const PKLEN = 65;
export const ADDRLEN = 80;
export const FVKLEN = 64;
export const SIGRSLEN = 64;
export const PREHASH_LEN = 32;
export const RANDOMIZER_LEN = 12;
export const PKLEN = 65
export const ADDRLEN = 80
export const FVKLEN = 64
export const SIGRSLEN = 64
export const PREHASH_LEN = 32
export const RANDOMIZER_LEN = 12
3 changes: 0 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ export interface AddressIndex {
}

export interface ResponseAddress {
// publicKey?: Buffer;
// principal?: Buffer;
address?: Buffer;
// principalText?: string;
}

export interface ResponseFvk {
Expand Down

0 comments on commit 0a5d766

Please sign in to comment.