Skip to content

Commit

Permalink
app split (XS app)
Browse files Browse the repository at this point in the history
  • Loading branch information
janmazak committed Sep 18, 2023
1 parent 9260aa5 commit 860f4a5
Show file tree
Hide file tree
Showing 17 changed files with 294 additions and 37 deletions.
24 changes: 24 additions & 0 deletions src/interactions/deriveAddress.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import {DeviceVersionUnsupported} from '../errors'
import {getVersionString} from '../utils'
import type {ParsedAddressParams, Version} from '../types/internal'
import {AddressType} from '../types/public'
import type {DerivedAddress} from '../types/public'
import {INS} from './common/ins'
import type {Interaction, SendParams} from './common/types'
import {ensureLedgerAppVersionCompatible, getCompatibility} from './getVersion'
import {serializeAddressParams} from './serialization/addressParams'

const send = (params: {
Expand All @@ -11,10 +15,30 @@ const send = (params: {
expectedResponseLength?: number
}): SendParams => ({ins: INS.DERIVE_ADDRESS, ...params})

export function ensureAddressDerivationSupportedByAppVersion(
version: Version,
addressParams: ParsedAddressParams,
): void {
ensureLedgerAppVersionCompatible(version)

if (
addressParams.type === AddressType.BYRON &&
!getCompatibility(version).supportsByronAddressDerivation
) {
throw new DeviceVersionUnsupported(
`Byron address parameters not supported by Ledger app version ${getVersionString(
version,
)}.`,
)
}
}

export function* deriveAddress(
version: Version,
addressParams: ParsedAddressParams,
): Interaction<DerivedAddress> {
ensureAddressDerivationSupportedByAppVersion(version, addressParams)

const P1_RETURN = 0x01
const P2_UNUSED = 0x00

Expand Down
15 changes: 11 additions & 4 deletions src/interactions/getVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ export function* getVersion(): Interaction<Version> {

const FLAG_IS_DEBUG = 1
// const FLAG_IS_HEADLESS = 2;
const FLAG_IS_APP_XS = 4

const flags = {
// eslint-disable-next-line no-bitwise
isDebug: (flags_value & FLAG_IS_DEBUG) === FLAG_IS_DEBUG,
// eslint-disable-next-line no-bitwise
isAppXS: (flags_value & FLAG_IS_APP_XS) === FLAG_IS_APP_XS,
}
return {major, minor, patch, flags}
}
Expand Down Expand Up @@ -83,18 +86,22 @@ export function getCompatibility(version: Version): DeviceCompatibility {
isLedgerAppVersionAtLeast(version, 6, 0) &&
isLedgerAppVersionAtMost(version, 6, Infinity)

const isAppXS = version.flags.isAppXS

return {
isCompatible: v2_2,
recommendedVersion: v2_2 ? null : '6.0',
supportsByronAddressDerivation: v2_2 && !isAppXS,
supportsMary: v2_2,
supportsCatalystRegistration: v2_3, // CIP-15
supportsCIP36: v6_0,
supportsZeroTtl: v2_3,
supportsPoolRegistrationAsOperator: v2_4,
supportsPoolRetirement: v2_4,
supportsNativeScriptHashDerivation: v3_0,
supportsPoolRegistrationAsOwner: v2_2 && !isAppXS,
supportsPoolRegistrationAsOperator: v2_4 && !isAppXS,
supportsPoolRetirement: v2_4 && !isAppXS,
supportsNativeScriptHashDerivation: v3_0 && !isAppXS,
supportsMultisigTransaction: v3_0,
supportsMint: v3_0,
supportsMint: v3_0 && !isAppXS,
supportsAlonzo: v4_0,
supportsReqSignersInOrdinaryTx: v4_1,
supportsBabbage: v5_0,
Expand Down
7 changes: 6 additions & 1 deletion src/interactions/serialization/txInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,14 @@ export function serializeTxInit(
numWitnesses: number,
version: Version,
) {
const mintBuffer = getCompatibility(version).supportsMint
const appAwareOfMint =
getCompatibility(version).supportsMint || version.flags.isAppXS
// even though XS app doesn't support minting, it does expect the flag value
// (we want to keep the APDU serialization uniform)
const mintBuffer = appAwareOfMint
? serializeOptionFlag(tx.mint != null)
: Buffer.from([])

const scriptDataHashBuffer = getCompatibility(version).supportsAlonzo
? serializeOptionFlag(tx.scriptDataHashHex != null)
: Buffer.from([])
Expand Down
5 changes: 3 additions & 2 deletions src/interactions/showAddress.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type {ParsedAddressParams, Version} from '../types/internal'
import {INS} from './common/ins'
import type {Interaction, SendParams} from './common/types'
import {ensureLedgerAppVersionCompatible} from './getVersion'
import {serializeAddressParams} from './serialization/addressParams'
import {ensureAddressDerivationSupportedByAppVersion} from './deriveAddress'

const send = (params: {
p1: number
Expand All @@ -15,7 +15,8 @@ export function* showAddress(
version: Version,
addressParams: ParsedAddressParams,
): Interaction<void> {
ensureLedgerAppVersionCompatible(version)
ensureAddressDerivationSupportedByAppVersion(version, addressParams)

const P1_DISPLAY = 0x02
const P2_UNUSED = 0x00

Expand Down
45 changes: 45 additions & 0 deletions src/interactions/signTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,17 @@ function ensureRequestSupportedByAppVersion(
): void {
// signing modes

if (
request.signingMode === TransactionSigningMode.POOL_REGISTRATION_AS_OWNER &&
!getCompatibility(version).supportsPoolRegistrationAsOwner
) {
throw new DeviceVersionUnsupported(
`Pool registration as owner not supported by Ledger app version ${getVersionString(
version,
)}.`,
)
}

if (
request.signingMode ===
TransactionSigningMode.POOL_REGISTRATION_AS_OPERATOR &&
Expand Down Expand Up @@ -1000,6 +1011,26 @@ function ensureRequestSupportedByAppVersion(

// transaction elements

const isOutputByron = (o: ParsedOutput | null) =>
o != null &&
o.destination.type === TxOutputDestinationType.DEVICE_OWNED &&
o.destination.addressParams.type === AddressType.BYRON

const hasByronAddressParam =
request.tx.outputs.some(isOutputByron) ||
isOutputByron(request.tx.collateralOutput)
// Byron collateral outputs forbidden by the security policy
if (
hasByronAddressParam &&
!getCompatibility(version).supportsByronAddressDerivation
) {
throw new DeviceVersionUnsupported(
`Byron address parameters not supported by Ledger app version ${getVersionString(
version,
)}.`,
)
}

if (
hasScriptHashInAddressParams(request.tx) &&
!getCompatibility(version).supportsMultisigTransaction
Expand Down Expand Up @@ -1064,6 +1095,20 @@ function ensureRequestSupportedByAppVersion(
)
}

const hasPoolRegistration = request.tx.certificates.some(
(c) => c.type === CertificateType.STAKE_POOL_REGISTRATION,
)
const supportsPoolRegistration =
getCompatibility(version).supportsPoolRegistrationAsOwner ||
getCompatibility(version).supportsPoolRegistrationAsOperator
if (hasPoolRegistration && !supportsPoolRegistration) {
throw new DeviceVersionUnsupported(
`Pool registration certificate not supported by Ledger app version ${getVersionString(
version,
)}.`,
)
}

const hasPoolRetirement = request.tx.certificates.some(
(c) => c.type === CertificateType.STAKE_POOL_RETIREMENT,
)
Expand Down
11 changes: 10 additions & 1 deletion src/types/public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,7 @@ export type Withdrawal = {
*/
export type Flags = {
isDebug: boolean
isAppXS: boolean
}

/**
Expand Down Expand Up @@ -910,6 +911,10 @@ export type DeviceCompatibility = {
* Clients of SDK should check whether this is null and if not, urge users to upgrade.
*/
recommendedVersion: string | null
/**
* Whether we support Byron address parameters in transaction outputs
*/
supportsByronAddressDerivation: boolean
/**
* Whether we support Mary features
*/
Expand All @@ -928,7 +933,11 @@ export type DeviceCompatibility = {
*/
supportsZeroTtl: boolean
/**
* Whether we support operational certificate signing
* Whether we support pool registration certificate signing by pool owners
*/
supportsPoolRegistrationAsOwner: boolean
/**
* Whether we support operational and pool registration certificate signing by pool operators
*/
supportsPoolRegistrationAsOperator: boolean
/**
Expand Down
3 changes: 2 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,6 @@ export default {
}

export function getVersionString(version: Version): string {
return `${version.major}.${version.minor}.${version.patch}`
const xs = version.flags.isAppXS ? ' XS' : ''
return `${version.major}.${version.minor}.${version.patch}${xs}`
}
6 changes: 5 additions & 1 deletion test/integration/__fixtures__/deriveAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@ type RejectTestCase = {
addressParams: DeviceOwnedAddress
errCls: new (...args: any[]) => ErrorBase
errMsg: string
unsupportedInAppXS?: boolean
}

const rejectTestCaseBase = {
Expand All @@ -542,7 +543,7 @@ const rejectTestCaseBase = {
errMsg: DeviceStatusMessages[DeviceStatusCodes.ERR_REJECTED_BY_POLICY],
}

export const RejectTestCases: RejectTestCase[] = [
export const rejectTestCases: RejectTestCase[] = [
{
testName: 'path too short',
...rejectTestCaseBase,
Expand All @@ -552,6 +553,7 @@ export const RejectTestCases: RejectTestCase[] = [
spendingPath: str_to_path("44'/1815'/1'"),
},
},
unsupportedInAppXS: true,
},
{
testName: 'invalid path',
Expand All @@ -562,6 +564,7 @@ export const RejectTestCases: RejectTestCase[] = [
spendingPath: str_to_path("44'/1815'/1'/5/10'"),
},
},
unsupportedInAppXS: true,
},
{
testName: 'Byron with Shelley path',
Expand All @@ -572,6 +575,7 @@ export const RejectTestCases: RejectTestCase[] = [
spendingPath: str_to_path("1852'/1815'/1'/0/10"),
},
},
unsupportedInAppXS: true,
},
{
testName: 'base key/key with Byron spending path',
Expand Down
7 changes: 7 additions & 0 deletions test/integration/__fixtures__/signTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type SignTxTestCase = {
txBody?: string
txAuxiliaryData?: string
expectedResult: SignedTransactionData
unsupportedInAppXS?: boolean
}

export const testsByron: SignTxTestCase[] = [
Expand Down Expand Up @@ -520,6 +521,7 @@ export const testsShelleyWithCertificates: SignTxTestCase[] = [
},
{
testName: 'Sign tx with pool retirement combined with stake registration',
unsupportedInAppXS: true,
tx: {
...shelleyBase,
inputs: [
Expand Down Expand Up @@ -574,6 +576,7 @@ export const testsShelleyWithCertificates: SignTxTestCase[] = [
},
{
testName: 'Sign tx with pool retirement combined with stake deregistration',
unsupportedInAppXS: true,
tx: {
...shelleyBase,
inputs: [
Expand Down Expand Up @@ -1010,6 +1013,7 @@ export const testsMary: SignTxTestCase[] = [
},
{
testName: 'Sign tx with mint fields with various amounts',
unsupportedInAppXS: true,
tx: {
...mainnetFeeTtl,
inputs: [inputs.utxoShelley],
Expand All @@ -1034,6 +1038,7 @@ export const testsMary: SignTxTestCase[] = [
},
{
testName: 'Sign tx with mint with decimal places',
unsupportedInAppXS: true,
tx: {
network: Networks.Mainnet,
inputs: [inputs.utxoShelley],
Expand All @@ -1059,6 +1064,7 @@ export const testsMary: SignTxTestCase[] = [
},
{
testName: 'Sign tx with mint fields among other fields',
unsupportedInAppXS: true,
tx: {
network: Networks.Mainnet,
inputs: [inputs.utxoShelley],
Expand Down Expand Up @@ -1092,6 +1098,7 @@ export const testsMary: SignTxTestCase[] = [
export const testsAlonzoTrezorComparison: SignTxTestCase[] = [
{
testName: 'Full test for trezor feature parity',
unsupportedInAppXS: true,
tx: {
// "protocol_magic": 764824073,
// "network_id": 1,
Expand Down
Loading

0 comments on commit 860f4a5

Please sign in to comment.