Skip to content

Commit

Permalink
Add STX support for Ledger accounts (#212)
Browse files Browse the repository at this point in the history
* Add STX support for Ledger accounts

* Update `signLedgerStxTransaction` method to use `transactionBuffer` instead of `transaction`

* Update package-lock.json

* Fix typo in `addSignatureToStxTransaction` method

* Update package-lock.json

* Add `StacksRecipient` type to `transactions/stx.ts`

* Add `LedgerErrors` enum and throw an error when user rejects stx address
  • Loading branch information
dhriaznov authored Sep 7, 2023
1 parent 3036ee5 commit a2df5e0
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 16 deletions.
28 changes: 18 additions & 10 deletions ledger/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import {
makeLedgerCompatibleUnsignedAuthResponsePayload,
signStxJWTAuth,
} from './helper';
import { Bip32Derivation, LedgerStxJWTAuthProfile, TapBip32Derivation, Transport } from './types';
import { Bip32Derivation, LedgerStxJWTAuthProfile, TapBip32Derivation, Transport, LedgerErrors } from './types';
import StacksApp, { ResponseSign } from '@zondax/ledger-stacks';
import { StacksTransaction, AddressVersion } from '@stacks/transactions';
import {
getTransactionData,
addSignitureToStxTransaction,
addSignatureToStxTransaction,
createNativeSegwitPsbt,
createTaprootPsbt,
createMixedPsbt,
Expand Down Expand Up @@ -521,41 +521,49 @@ export async function signSimpleBip322Message({
/**
* This function is used to get the stx account data from the ledger
* @param transport - the transport object with connected ledger device
* @param network - the network type (Mainnet or Testnet)
* @param accountIndex - the account index of the account to sign with
* @param addressIndex - the index of the account address to sign with
* @param showAddress - show address on the wallet's screen
* @returns the address and the public key in compressed format
* */
export async function importStacksAccountFromLedger(
transport: Transport,
network: NetworkType,
accountIndex = 0,
addressIndex = 0,
showAddress = false,
): Promise<{ address: string; publicKey: string }> {
const appStacks = new StacksApp(transport);
const path = `m/44'/5757'/${accountIndex}'/0/${addressIndex}`;
const version = network === 'Mainnet' ? AddressVersion.MainnetSingleSig : AddressVersion.TestnetSingleSig;
const { address, publicKey } = showAddress
? await appStacks.showAddressAndPubKey(path, version)
: await appStacks.getAddressAndPubKey(path, version);

const { address, publicKey } = await appStacks.getAddressAndPubKey(
`m/44'/5757'/${accountIndex}'/0/${addressIndex}`,
network === 'Mainnet' ? AddressVersion.MainnetSingleSig : AddressVersion.TestnetSingleSig,
);
if (!publicKey) {
throw new Error(LedgerErrors.NO_PUBLIC_KEY);
}

return { address, publicKey: publicKey.toString('hex') };
}

/**
* This function is used to sign a Stacks transaction with the ledger
* @param transport - the transport object with connected ledger device
* @param transaction - the transaction to sign
* @param transactionBuffer - the transaction to sign
* @param addressIndex - the address index of the account to sign with
* @returns the signed transaction ready to be broadcasted
* */
export async function signLedgerStxTransaction(
transport: Transport,
transaction: StacksTransaction,
transactionBuffer: Buffer,
addressIndex: number,
): Promise<StacksTransaction> {
const appStacks = new StacksApp(transport);
const path = `m/44'/5757'/${0}'/0/${addressIndex}`;
const transactionBuffer = transaction.serialize();
const resp = await appStacks.sign(path, transactionBuffer);
const signedTx = addSignitureToStxTransaction(transactionBuffer, resp.signatureVRS);
const signedTx = addSignatureToStxTransaction(transactionBuffer, resp.signatureVRS);

return signedTx; // TX ready to be broadcast
}
Expand Down
8 changes: 4 additions & 4 deletions ledger/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,11 @@ export async function createNativeSegwitPsbt(
return psbt;
}

export function addSignitureToStxTransaction(transaction: string | Buffer, signatureVRS: Buffer) {
const deserialzedTx = deserializeTransaction(transaction);
export function addSignatureToStxTransaction(transaction: string | Buffer, signatureVRS: Buffer) {
const deserializedTx = deserializeTransaction(transaction);
const spendingCondition = createMessageSignature(signatureVRS.toString('hex'));
(deserialzedTx.auth.spendingCondition as SingleSigSpendingCondition).signature = spendingCondition;
return deserialzedTx;
(deserializedTx.auth.spendingCondition as SingleSigSpendingCondition).signature = spendingCondition;
return deserializedTx;
}

/**
Expand Down
6 changes: 4 additions & 2 deletions ledger/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { StacksNetwork } from '@stacks/network';
import { AnchorMode } from '@stacks/transactions';
import AppClient from 'ledger-bitcoin';

export type Transport = ConstructorParameters<typeof AppClient>[0];
Expand All @@ -20,3 +18,7 @@ export interface LedgerStxJWTAuthProfile {
testnet: string;
};
}

export enum LedgerErrors {
NO_PUBLIC_KEY = 'No public key returned from Ledger device',
}
6 changes: 6 additions & 0 deletions transactions/stx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ import {
} from '../types/api/stacks/transaction';
import { getStxAddressKeyChain } from '../wallet/index';
import { getNewNonce, makeFungiblePostCondition, makeNonFungiblePostCondition } from './helper';
import BigNumber from 'bignumber.js';

export interface StacksRecipient {
address: string;
amountMicrostacks: BigNumber;
}

export async function signTransaction(
unsignedTx: StacksTransaction,
Expand Down

0 comments on commit a2df5e0

Please sign in to comment.