Skip to content

Fix/output mapping on hw #1618

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,16 @@ export class TransactionOutput {
this.#scriptRef = script;
}

/**
* Checks if the output is formatted as legacy array or babbage map.
*
* @returns true if the output is babbage format, false otherwise.
*/
isBabbageOutput(): boolean {
const reader = new CborReader(this.toCbor());
return reader.peekState() === CborReaderState.StartMap;
}

/**
* Gets the size of the serialized map.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,5 +396,14 @@ describe('TransactionOutput', () => {
expect(output.toCore()).toEqual(basicOutput);
});
});

describe('isBabbageOutput', () => {
it('cant distinguish a babbage output from a legacy output', () => {
const babbageOut = TransactionOutput.fromCbor(babbageAllFieldsCbor);
const legacyOutput = TransactionOutput.fromCbor(legacyOutputNoDatumCbor);
expect(babbageOut.isBabbageOutput()).toBe(true);
expect(legacyOutput.isBabbageOutput()).toBe(false);
});
});
});
});
13 changes: 11 additions & 2 deletions packages/hardware-ledger/src/LedgerKeyAgent.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as Crypto from '@cardano-sdk/crypto';
import * as Ledger from '@cardano-foundation/ledgerjs-hw-app-cardano';
import {
AlgorithmId,
CBORValue,
Expand Down Expand Up @@ -720,13 +721,21 @@ export class LedgerKeyAgent extends KeyAgentBase {
const dRepPublicKey = await this.derivePublicKey(util.DREP_KEY_DERIVATION_PATH);
const dRepKeyHashHex = (await Crypto.Ed25519PublicKey.fromHex(dRepPublicKey).hash()).hex();

const outputsFormat = txBody
.outputs()
.map((out) => (out.isBabbageOutput() ? Ledger.TxOutputFormat.MAP_BABBAGE : Ledger.TxOutputFormat.ARRAY_LEGACY));
const collateralReturnFormat = txBody.collateralReturn()?.isBabbageOutput()
? Ledger.TxOutputFormat.MAP_BABBAGE
: Ledger.TxOutputFormat.ARRAY_LEGACY;

const ledgerTxData = await toLedgerTx(body, {
accountIndex: this.accountIndex,
chainId: this.chainId,
collateralReturnFormat,
dRepKeyHashHex,
knownAddresses,
txInKeyPathMap,
useBabbageOutputs: txBody.hasBabbageOutput()
outputsFormat,
txInKeyPathMap
});

const deviceConnection = await LedgerKeyAgent.checkDeviceConnection(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ import { LedgerTxTransformerContext } from '../types';
import { toTxOut } from './txOut';

export const mapCollateralTxOut = (collateralTxOut: Cardano.TxOut | undefined, context: LedgerTxTransformerContext) =>
collateralTxOut ? toTxOut(collateralTxOut, context) : null;
collateralTxOut ? toTxOut({ index: 0, isCollateral: true, txOut: collateralTxOut }, context) : null;
42 changes: 24 additions & 18 deletions packages/hardware-ledger/src/transformers/txOut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,27 +56,33 @@ const getScriptHex = (output: Serialization.TransactionOutput): HexBlob | null =
return scriptRef.toCbor();
};

export const toTxOut: Transform<Cardano.TxOut, Ledger.TxOutput, LedgerTxTransformerContext> = (txOut, context) => {
export const toTxOut: Transform<
{ txOut: Cardano.TxOut; index: number; isCollateral: boolean },
Ledger.TxOutput,
LedgerTxTransformerContext
> = (elem, context) => {
const { txOut, index, isCollateral } = elem;
const output = Serialization.TransactionOutput.fromCore(txOut);
const scriptHex = getScriptHex(output);
const format = isCollateral ? context?.collateralReturnFormat : context?.outputsFormat[index];
const isBabbageFormat = format === Ledger.TxOutputFormat.MAP_BABBAGE;

return context?.useBabbageOutputs
? {
amount: txOut.value.coins,
datum: txOut.datumHash ? toDatumHash(txOut.datumHash) : txOut.datum ? toInlineDatum(txOut.datum) : null,
destination: toDestination(txOut, context),
format: Ledger.TxOutputFormat.MAP_BABBAGE,
referenceScriptHex: scriptHex,
tokenBundle: mapTokenMap(txOut.value.assets)
}
: {
amount: txOut.value.coins,
datumHashHex: txOut.datumHash ? txOut.datumHash : null,
destination: toDestination(txOut, context),
format: Ledger.TxOutputFormat.ARRAY_LEGACY,
tokenBundle: mapTokenMap(txOut.value.assets)
};
return {
amount: txOut.value.coins,
destination: toDestination(txOut, context),
tokenBundle: mapTokenMap(txOut.value.assets),
...(isBabbageFormat
? {
datum: txOut.datumHash ? toDatumHash(txOut.datumHash) : txOut.datum ? toInlineDatum(txOut.datum) : null,
format: Ledger.TxOutputFormat.MAP_BABBAGE,
referenceScriptHex: scriptHex
}
: {
datumHashHex: txOut.datumHash ?? null,
format: Ledger.TxOutputFormat.ARRAY_LEGACY
})
};
};

export const mapTxOuts = (txOuts: Cardano.TxOut[], context: LedgerTxTransformerContext): Ledger.TxOutput[] =>
txOuts.map((txOut) => toTxOut(txOut, context));
txOuts.map((txOut, index) => toTxOut({ index, isCollateral: false, txOut }, context));
7 changes: 5 additions & 2 deletions packages/hardware-ledger/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Cardano } from '@cardano-sdk/core';
import { HID } from 'node-hid';
import { SignTransactionContext } from '@cardano-sdk/key-management';
import { TxOutputFormat } from '@cardano-foundation/ledgerjs-hw-app-cardano';
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid-noevents';
import TransportWebUSB from '@ledgerhq/hw-transport-webusb';

Expand All @@ -21,6 +22,8 @@ export type LedgerTxTransformerContext = {
chainId: Cardano.ChainId;
/** Non-hardened account in cip1852 */
accountIndex: number;
/** Whether to use Babbage output format or not. */
useBabbageOutputs: boolean;
/** The outputs format in the same order as they appear in the transaction. */
outputsFormat: Array<TxOutputFormat>;
/** The collateral return output format. */
collateralReturnFormat: TxOutputFormat | undefined;
} & SignTransactionContext;
11 changes: 7 additions & 4 deletions packages/hardware-ledger/test/testData.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as Crypto from '@cardano-sdk/crypto';
import * as Ledger from '@cardano-foundation/ledgerjs-hw-app-cardano';
import { AddressType, KeyRole } from '@cardano-sdk/key-management';
import { Base64Blob, HexBlob } from '@cardano-sdk/util';
import { Cardano, Serialization } from '@cardano-sdk/core';
Expand Down Expand Up @@ -342,6 +343,7 @@ export const CONTEXT_WITH_KNOWN_ADDRESSES: LedgerTxTransformerContext = {
networkId: Cardano.NetworkId.Testnet,
networkMagic: 999
},
collateralReturnFormat: Ledger.TxOutputFormat.MAP_BABBAGE,
dRepKeyHashHex: Crypto.Ed25519KeyHashHex(dRepCredential.hash),
knownAddresses: [
{
Expand All @@ -357,8 +359,8 @@ export const CONTEXT_WITH_KNOWN_ADDRESSES: LedgerTxTransformerContext = {
type: AddressType.Internal
}
],
txInKeyPathMap: {},
useBabbageOutputs: true
outputsFormat: [Ledger.TxOutputFormat.ARRAY_LEGACY, Ledger.TxOutputFormat.MAP_BABBAGE],
txInKeyPathMap: {}
};

export const CONTEXT_WITHOUT_KNOWN_ADDRESSES: LedgerTxTransformerContext = {
Expand All @@ -367,9 +369,10 @@ export const CONTEXT_WITHOUT_KNOWN_ADDRESSES: LedgerTxTransformerContext = {
networkId: Cardano.NetworkId.Testnet,
networkMagic: 999
},
collateralReturnFormat: Ledger.TxOutputFormat.ARRAY_LEGACY,
knownAddresses: [],
txInKeyPathMap: {},
useBabbageOutputs: true
outputsFormat: [Ledger.TxOutputFormat.ARRAY_LEGACY, Ledger.TxOutputFormat.MAP_BABBAGE],
txInKeyPathMap: {}
};

export const votes = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,21 @@ export const createTxInKeyPathMapMock = (knownAddresses: GroupedAddress[]): TxIn
const mockContext: LedgerTxTransformerContext = {
accountIndex: 0,
chainId: createChainId(1, 764_824_073),
collateralReturnFormat: Ledger.TxOutputFormat.ARRAY_LEGACY,

dRepKeyHashHex: undefined,

handleResolutions: [],

knownAddresses: [
createGroupedAddress(address1, ownRewardAccount, AddressType.External, 0, stakeKeyPath),
createGroupedAddress(address2, ownRewardAccount, AddressType.External, 1, stakeKeyPath)
],
outputsFormat: [Ledger.TxOutputFormat.ARRAY_LEGACY, Ledger.TxOutputFormat.MAP_BABBAGE],
sender: undefined,
txInKeyPathMap: createTxInKeyPathMapMock([
createGroupedAddress(address1, ownRewardAccount, AddressType.External, 0, stakeKeyPath),
createGroupedAddress(address2, ownRewardAccount, AddressType.External, 1, stakeKeyPath)
]),
useBabbageOutputs: true
])
};

const EXAMPLE_URL = 'https://example.com';
Expand Down
11 changes: 3 additions & 8 deletions packages/hardware-ledger/test/transformers/tx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ describe('tx', () => {
txInKeyPathMap: {
[TxInId(tx.body.inputs[0])]: paymentKeyPath,
[TxInId(tx.body.collaterals![0])]: paymentKeyPath
},
useBabbageOutputs: false
}
})
).toEqual({
auxiliaryData: {
Expand Down Expand Up @@ -181,6 +180,7 @@ describe('tx', () => {
expect(
await toLedgerTx(babbageTxWithoutScript.body, {
...CONTEXT_WITH_KNOWN_ADDRESSES,
outputsFormat: [Ledger.TxOutputFormat.MAP_BABBAGE],
txInKeyPathMap: {
[TxInId(babbageTxWithoutScript.body.inputs[0])]: paymentKeyPath
}
Expand Down Expand Up @@ -266,12 +266,7 @@ describe('tx', () => {
]
};

expect(
await toLedgerTx(txBodyWithRegistrationCert, {
...CONTEXT_WITH_KNOWN_ADDRESSES,
useBabbageOutputs: false
})
).toEqual({
expect(await toLedgerTx(txBodyWithRegistrationCert, CONTEXT_WITH_KNOWN_ADDRESSES)).toEqual({
auxiliaryData: {
params: {
hashHex: '2ceb364d93225b4a0f004a0975a13eb50c3cc6348474b4fe9121f8dc72ca0cfa'
Expand Down
23 changes: 18 additions & 5 deletions packages/hardware-ledger/test/transformers/txOut.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ describe('txOut', () => {
txOutWithReferenceScriptWithInlineDatum,
txOutWithReferenceScriptWithInlineDatum
],
CONTEXT_WITH_KNOWN_ADDRESSES
{
...CONTEXT_WITH_KNOWN_ADDRESSES,
outputsFormat: [
Ledger.TxOutputFormat.MAP_BABBAGE,
Ledger.TxOutputFormat.MAP_BABBAGE,
Ledger.TxOutputFormat.MAP_BABBAGE
]
}
);

expect(txOuts.length).toEqual(3);
Expand Down Expand Up @@ -64,7 +71,7 @@ describe('txOut', () => {

describe('toTxOut', () => {
it('can map a simple txOut to third party address', async () => {
const out = toTxOut(txOut, { ...CONTEXT_WITH_KNOWN_ADDRESSES, useBabbageOutputs: false });
const out = toTxOut({ index: 0, isCollateral: false, txOut }, CONTEXT_WITH_KNOWN_ADDRESSES);

expect(out).toEqual({
amount: 10n,
Expand Down Expand Up @@ -114,7 +121,7 @@ describe('txOut', () => {
});

it('can map a simple txOut to owned address', async () => {
const out = toTxOut(txOutToOwnedAddress, { ...CONTEXT_WITH_KNOWN_ADDRESSES, useBabbageOutputs: false });
const out = toTxOut({ index: 0, isCollateral: false, txOut: txOutToOwnedAddress }, CONTEXT_WITH_KNOWN_ADDRESSES);

expect(out).toEqual({
amount: 10n,
Expand Down Expand Up @@ -179,7 +186,10 @@ describe('txOut', () => {
});

it('can map a txOut with a reference script - datum hash', async () => {
const out = toTxOut(txOutWithReferenceScript, CONTEXT_WITH_KNOWN_ADDRESSES);
const out = toTxOut(
{ index: 1, isCollateral: false, txOut: txOutWithReferenceScript },
CONTEXT_WITH_KNOWN_ADDRESSES
);

expect(out).toEqual({
amount: 10n,
Expand Down Expand Up @@ -216,7 +226,10 @@ describe('txOut', () => {
});

it('can map a txOut with a reference script - inline datum', async () => {
const out = toTxOut(txOutWithReferenceScriptWithInlineDatum, CONTEXT_WITH_KNOWN_ADDRESSES);
const out = toTxOut(
{ index: 1, isCollateral: false, txOut: txOutWithReferenceScriptWithInlineDatum },
CONTEXT_WITH_KNOWN_ADDRESSES
);

expect(out).toEqual({
amount: 10n,
Expand Down
16 changes: 14 additions & 2 deletions packages/hardware-trezor/src/TrezorKeyAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,13 +246,25 @@ export class TrezorKeyAgent extends KeyAgentBase {
const body = txBody.toCore();
const hash = txBody.hash() as unknown as HexBlob;

const outputsFormat = txBody
.outputs()
.map((out) =>
out.isBabbageOutput()
? Trezor.PROTO.CardanoTxOutputSerializationFormat.MAP_BABBAGE
: Trezor.PROTO.CardanoTxOutputSerializationFormat.ARRAY_LEGACY
);
const collateralReturnFormat = txBody.collateralReturn()?.isBabbageOutput()
? Trezor.PROTO.CardanoTxOutputSerializationFormat.MAP_BABBAGE
: Trezor.PROTO.CardanoTxOutputSerializationFormat.ARRAY_LEGACY;

const trezorTxData = await txToTrezor(body, {
accountIndex: this.accountIndex,
chainId: this.chainId,
collateralReturnFormat,
knownAddresses,
outputsFormat,
tagCborSets: txBody.hasTaggedSets(),
txInKeyPathMap,
useBabbageOutputs: txBody.hasBabbageOutput()
txInKeyPathMap
});

const signingMode = TrezorKeyAgent.matchSigningMode(trezorTxData);
Expand Down
2 changes: 1 addition & 1 deletion packages/hardware-trezor/src/transformers/tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const trezorTxTransformer: Transformer<
certificates: ({ certificates }, context) => (certificates ? mapCerts(certificates, context!) : undefined),
collateralInputs: ({ collaterals }, context) => (collaterals ? mapTxIns(collaterals, context!) : undefined),
collateralReturn: ({ collateralReturn }, context) =>
collateralReturn ? toTxOut(collateralReturn, context!) : undefined,
collateralReturn ? toTxOut({ index: 0, isCollateral: true, txOut: collateralReturn }, context!) : undefined,
fee: ({ fee }) => fee.toString(),
inputs: ({ inputs }, context) => mapTxIns(inputs, context!),
mint: ({ mint }) => mapTokenMap(mint, true),
Expand Down
40 changes: 18 additions & 22 deletions packages/hardware-trezor/src/transformers/txOut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,31 +42,27 @@ const getScriptHex = (output: Serialization.TransactionOutput): HexBlob | undefi

const getInlineDatum = (datum: Cardano.PlutusData): string => Serialization.PlutusData.fromCore(datum).toCbor();

export const toTxOut: Transform<Cardano.TxOut, Trezor.CardanoOutput, TrezorTxTransformerContext> = (txOut, context) => {
const destination = toDestination(txOut, context);
export const toTxOut: Transform<
{ txOut: Cardano.TxOut; index: number; isCollateral: boolean },
Trezor.CardanoOutput,
TrezorTxTransformerContext
> = (elem, context) => {
const { txOut, index, isCollateral } = elem;
const output = Serialization.TransactionOutput.fromCore(txOut);
const scriptHex = getScriptHex(output);
const format = isCollateral ? context?.collateralReturnFormat : context?.outputsFormat[index];
const isBabbage = format === Trezor.PROTO.CardanoTxOutputSerializationFormat.MAP_BABBAGE;

return context?.useBabbageOutputs
? {
...destination,
amount: txOut.value.coins.toString(),
datumHash: txOut.datumHash?.toString(),
format: Trezor.PROTO.CardanoTxOutputSerializationFormat.MAP_BABBAGE,
inlineDatum: txOut.datum ? getInlineDatum(txOut.datum) : undefined,
referenceScript: scriptHex,
tokenBundle: mapTokenMap(txOut.value.assets)
}
: {
...destination,
amount: txOut.value.coins.toString(),
datumHash: txOut.datumHash?.toString(),
format: Trezor.PROTO.CardanoTxOutputSerializationFormat.ARRAY_LEGACY,
inlineDatum: undefined,
referenceScript: undefined,
tokenBundle: mapTokenMap(txOut.value.assets)
};
return {
...toDestination(txOut, context),
amount: txOut.value.coins.toString(),
datumHash: txOut.datumHash?.toString(),
format,
inlineDatum: isBabbage ? (txOut.datum ? getInlineDatum(txOut.datum) : undefined) : undefined,
referenceScript: isBabbage ? scriptHex : undefined,
tokenBundle: mapTokenMap(txOut.value.assets)
};
};

export const mapTxOuts = (txOuts: Cardano.TxOut[], context: TrezorTxTransformerContext): Trezor.CardanoOutput[] =>
txOuts.map((txOut) => toTxOut(txOut, context));
txOuts.map((txOut, index) => toTxOut({ index, isCollateral: false, txOut }, context));
6 changes: 4 additions & 2 deletions packages/hardware-trezor/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ export type TrezorTxTransformerContext = {
accountIndex: number;
/** Whether sets should be encoded as tagged set in CBOR */
tagCborSets: boolean;
/** Whether to use Babbage output format or not. */
useBabbageOutputs: boolean;
/** The outputs format in the same order as they appear in the transaction. */
outputsFormat: Array<Trezor.PROTO.CardanoTxOutputSerializationFormat>;
/** The collateral return output format. */
collateralReturnFormat: Trezor.PROTO.CardanoTxOutputSerializationFormat | undefined;
} & SignTransactionContext;

export type TrezorTxOutputDestination =
Expand Down
Loading
Loading