serum-dex-ui/src/wallet-adapters/ledger/core.ts

134 lines
3.4 KiB
TypeScript

import type Transport from '@ledgerhq/hw-transport';
import type { Transaction } from '@solana/web3.js';
import { PublicKey } from '@solana/web3.js';
const INS_GET_PUBKEY = 0x05;
const INS_SIGN_MESSAGE = 0x06;
const P1_NON_CONFIRM = 0x00;
const P1_CONFIRM = 0x01;
const P2_EXTEND = 0x01;
const P2_MORE = 0x02;
const MAX_PAYLOAD = 255;
const LEDGER_CLA = 0xe0;
/*
* Helper for chunked send of large payloads
*/
async function ledgerSend(
transport: Transport,
instruction: number,
p1: number,
payload: Buffer,
) {
let p2 = 0;
let payloadOffset = 0;
if (payload.length > MAX_PAYLOAD) {
while (payload.length - payloadOffset > MAX_PAYLOAD) {
const chunk = payload.slice(payloadOffset, payloadOffset + MAX_PAYLOAD);
payloadOffset += MAX_PAYLOAD;
console.log(
'send',
(p2 | P2_MORE).toString(16),
chunk.length.toString(16),
chunk,
);
const reply = await transport.send(
LEDGER_CLA,
instruction,
p1,
p2 | P2_MORE,
chunk,
);
if (reply.length !== 2) {
throw new Error('Received unexpected reply payload');
}
p2 |= P2_EXTEND;
}
}
const chunk = payload.slice(payloadOffset);
console.log('send', p2.toString(16), chunk.length.toString(16), chunk);
const reply = await transport.send(LEDGER_CLA, instruction, p1, p2, chunk);
return reply.slice(0, reply.length - 2);
}
const BIP32_HARDENED_BIT = (1 << 31) >>> 0;
function harden(n: number = 0) {
return (n | BIP32_HARDENED_BIT) >>> 0;
}
export function getSolanaDerivationPath(account?: number, change?: number) {
var length;
if (account !== undefined) {
if (change !== undefined) {
length = 4;
} else {
length = 3;
}
} else {
length = 2;
}
var derivationPath = Buffer.alloc(1 + length * 4);
// eslint-disable-next-line
var offset = 0;
offset = derivationPath.writeUInt8(length, offset);
offset = derivationPath.writeUInt32BE(harden(44), offset); // Using BIP44
offset = derivationPath.writeUInt32BE(harden(501), offset); // Solana's BIP44 path
if (length > 2) {
offset = derivationPath.writeUInt32BE(harden(account), offset);
if (length === 4) {
// @FIXME: https://github.com/project-serum/spl-token-wallet/issues/59
offset = derivationPath.writeUInt32BE(harden(change), offset);
}
}
return derivationPath;
}
export async function signTransaction(
transport: Transport,
transaction: Transaction,
derivationPath: Buffer = getSolanaDerivationPath(),
) {
const messageBytes = transaction.serializeMessage();
return signBytes(transport, messageBytes, derivationPath);
}
export async function signBytes(
transport: Transport,
bytes: Buffer,
derivationPath: Buffer = getSolanaDerivationPath(),
) {
const numPaths = Buffer.alloc(1);
numPaths.writeUInt8(1, 0);
const payload = Buffer.concat([numPaths, derivationPath, bytes]);
// @FIXME: must enable blind signing in Solana Ledger App per https://github.com/project-serum/spl-token-wallet/issues/71
// See also https://github.com/project-serum/spl-token-wallet/pull/23#issuecomment-712317053
return ledgerSend(transport, INS_SIGN_MESSAGE, P1_CONFIRM, payload);
}
export async function getPublicKey(
transport: Transport,
derivationPath: Buffer = getSolanaDerivationPath(),
) {
const publicKeyBytes = await ledgerSend(
transport,
INS_GET_PUBKEY,
P1_NON_CONFIRM,
derivationPath,
);
return new PublicKey(publicKeyBytes);
}