From 64520ffb1e19ff00f4beecdf1173eb88e3d216bc Mon Sep 17 00:00:00 2001 From: Jan Mazak Date: Sat, 6 Jan 2024 15:08:28 +0100 Subject: [PATCH] feature: add cbor set tag 258 --- src/interactions/serialization/txInit.ts | 24 ++++++++++++++++++ src/interactions/signTx.ts | 12 +++++++-- src/parsing/transaction.ts | 23 ++++++++++++++--- src/types/internal.ts | 5 ++++ src/types/public.ts | 21 ++++++++++++---- test/integration/__fixtures__/signTx.ts | 32 ++++++++++++++++++++++++ test/test_utils.ts | 2 ++ 7 files changed, 109 insertions(+), 10 deletions(-) diff --git a/src/interactions/serialization/txInit.ts b/src/interactions/serialization/txInit.ts index 884030f4..088e42e6 100644 --- a/src/interactions/serialization/txInit.ts +++ b/src/interactions/serialization/txInit.ts @@ -3,6 +3,8 @@ import type { Uint8_t, Uint32_t, Version, + ParsedTransactionOptions, + Uint64_str, } from '../../types/internal' import {TransactionSigningMode} from '../../types/internal' import {assert} from '../../utils/assert' @@ -10,6 +12,7 @@ import { serializeOptionFlag, uint8_to_buf, uint32_to_buf, + uint64_to_buf, } from '../../utils/serialize' import {getCompatibility} from '../getVersion' @@ -27,12 +30,32 @@ const _serializeSigningMode = (mode: TransactionSigningMode): Buffer => { return uint8_to_buf(value) } +const enum OptionFlags { + TAG_CBOR_SETS = 1, +} + +function serializeTxOptions(options: ParsedTransactionOptions): Buffer { + // we reserve 64 bits for options in the APDU for future use + // the code below would need to be changed if a typescript number + // was not enough to fit all the future flags + let optionFlags = 0 + if (options.tagCborSets) { + optionFlags += OptionFlags.TAG_CBOR_SETS + } + return uint64_to_buf(optionFlags.toString() as Uint64_str) +} + export function serializeTxInit( tx: ParsedTransaction, signingMode: TransactionSigningMode, numWitnesses: number, + options: ParsedTransactionOptions, version: Version, ) { + const optionsBuffer = getCompatibility(version).supportsConway + ? serializeTxOptions(options) + : Buffer.from([]) + const appAwareOfMint = getCompatibility(version).supportsMint || version.flags.isAppXS // even if XS app doesn't support minting, it does expect the flag value @@ -82,6 +105,7 @@ export function serializeTxInit( : Buffer.from([]) return Buffer.concat([ + optionsBuffer, uint8_to_buf(tx.network.networkId), uint32_to_buf(tx.network.protocolMagic), serializeOptionFlag(tx.ttl != null), diff --git a/src/interactions/signTx.ts b/src/interactions/signTx.ts index 847502b7..1429fa85 100644 --- a/src/interactions/signTx.ts +++ b/src/interactions/signTx.ts @@ -9,6 +9,7 @@ import type { ParsedRequiredSigner, ParsedSigningRequest, ParsedTransaction, + ParsedTransactionOptions, ParsedTxAuxiliaryData, ParsedVoterVotes, ParsedWithdrawal, @@ -133,6 +134,7 @@ function* signTx_init( tx: ParsedTransaction, signingMode: TransactionSigningMode, witnessPaths: ValidBIP32Path[], + options: ParsedTransactionOptions, version: Version, ): Interaction { const enum P2 { @@ -141,7 +143,13 @@ function* signTx_init( yield send({ p1: P1.STAGE_INIT, p2: P2.UNUSED, - data: serializeTxInit(tx, signingMode, witnessPaths.length, version), + data: serializeTxInit( + tx, + signingMode, + witnessPaths.length, + options, + version, + ), expectedResponseLength: 0, }) } @@ -1444,7 +1452,7 @@ export function* signTransaction( const witnessPaths = gatherWitnessPaths(request) // init - yield* signTx_init(tx, signingMode, witnessPaths, version) + yield* signTx_init(tx, signingMode, witnessPaths, request.options, version) // auxiliary data let auxiliaryDataSupplement = null diff --git a/src/parsing/transaction.ts b/src/parsing/transaction.ts index 54272903..ff3a7ab6 100644 --- a/src/parsing/transaction.ts +++ b/src/parsing/transaction.ts @@ -1,6 +1,10 @@ import {InvalidData} from '../errors' import {InvalidDataReason} from '../errors/invalidDataReason' -import type {ParsedSigningRequest, ParsedTransaction} from '../types/internal' +import type { + ParsedSigningRequest, + ParsedTransaction, + ParsedTransactionOptions, +} from '../types/internal' import { ParsedCertificate, ParsedInput, @@ -17,7 +21,11 @@ import { CredentialType, TX_HASH_LENGTH, } from '../types/internal' -import type {SignTransactionRequest, Transaction} from '../types/public' +import type { + SignTransactionRequest, + Transaction, + TransactionOptions, +} from '../types/public' import { Certificate, RequiredSigner, @@ -392,11 +400,20 @@ export function parseTransaction(tx: Transaction): ParsedTransaction { } } +function parseTxOptions( + options: TransactionOptions | undefined, +): ParsedTransactionOptions { + return { + tagCborSets: options?.tagCborSets || false, + } +} + export function parseSignTransactionRequest( request: SignTransactionRequest, ): ParsedSigningRequest { const tx = parseTransaction(request.tx) const signingMode = parseSigningMode(request.signingMode) + const options = parseTxOptions(request.options) validate( isArray(request.additionalWitnessPaths ?? []), @@ -861,5 +878,5 @@ export function parseSignTransactionRequest( unreachable(signingMode) } - return {tx, signingMode, additionalWitnessPaths} + return {tx, signingMode, additionalWitnessPaths, options} } diff --git a/src/types/internal.ts b/src/types/internal.ts index bf96cb80..aa381c2f 100644 --- a/src/types/internal.ts +++ b/src/types/internal.ts @@ -349,10 +349,15 @@ export type ParsedTransaction = { donation: Uint64_str | null } +export type ParsedTransactionOptions = { + tagCborSets: boolean +} + export type ParsedSigningRequest = { tx: ParsedTransaction signingMode: TransactionSigningMode additionalWitnessPaths: ValidBIP32Path[] + options: ParsedTransactionOptions } export type ScriptDataHash = FixLenHexString diff --git a/src/types/public.ts b/src/types/public.ts index 6d871859..1ea51ff0 100644 --- a/src/types/public.ts +++ b/src/types/public.ts @@ -1806,6 +1806,14 @@ export enum TransactionSigningMode { PLUTUS_TRANSACTION = 'plutus_transaction', } +export type TransactionOptions = { + /** + * If true, serialize transactions with 258 tags for all sets (optional since Conway). + * If false or not given, do not use the tags. + */ + tagCborSets?: boolean +} + /** * Transaction signing request. * This represents the transaction user wants to sign. @@ -1813,6 +1821,10 @@ export enum TransactionSigningMode { * @category Basic types */ export type SignTransactionRequest = { + /** + * Transaction to be signed + */ + tx: Transaction /** * Mode in which we want to sign the transaction. * Ledger has certain limitations (see [[TransactionSigningMode]] in detail) due to which @@ -1820,15 +1832,14 @@ export type SignTransactionRequest = { * The mode specifies which use-case the user want to use and triggers additional validation on `tx` field. */ signingMode: TransactionSigningMode - /** - * Transaction to be signed - */ - tx: Transaction - /** * Additional witness paths that are not gathered from the transaction body, eg. mint witnesses */ additionalWitnessPaths?: BIP32Path[] + /** + * Additional options used in transaction processing (e.g. details of serialization). + */ + options?: TransactionOptions } /** diff --git a/test/integration/__fixtures__/signTx.ts b/test/integration/__fixtures__/signTx.ts index c1d53a03..fce704de 100644 --- a/test/integration/__fixtures__/signTx.ts +++ b/test/integration/__fixtures__/signTx.ts @@ -9,6 +9,7 @@ import { DRepParamsType, VoterType, VoteOption, + TransactionOptions, } from '../../../src/types/public' import {str_to_path} from '../../../src/utils/address' import { @@ -25,6 +26,7 @@ export type SignTxTestCase = { tx: Transaction signingMode: TransactionSigningMode additionalWitnessPaths?: BIP32Path[] + options?: TransactionOptions txBody?: string txAuxiliaryData?: string expectedResult: SignedTransactionData @@ -124,6 +126,9 @@ export const testsShelleyNoCertificates: SignTxTestCase[] = [ }, signingMode: TransactionSigningMode.ORDINARY_TRANSACTION, additionalWitnessPaths: undefined, + options: { + tagCborSets: false, + }, txBody: 'a400818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b700018002182a030a', expectedResult: { @@ -139,6 +144,33 @@ export const testsShelleyNoCertificates: SignTxTestCase[] = [ auxiliaryDataSupplement: null, }, }, + { + testName: 'Sign tx with 258 tag on inputs', + tx: { + ...mainnetFeeTtl, + inputs: [inputs.utxoShelley], + outputs: [], + }, + signingMode: TransactionSigningMode.ORDINARY_TRANSACTION, + additionalWitnessPaths: undefined, + options: { + tagCborSets: true, + }, + txBody: + 'a400d90102818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b700018002182a030a', + expectedResult: { + txHashHex: + '063d3a3670a43699a2648df93eedc1f93e8fda898ab79d3a795142a4ad573b7b', + witnesses: [ + { + path: str_to_path("1852'/1815'/0'/0/0"), + witnessSignatureHex: + 'b842908ce71f3ad1e1a1e2261c3bfdbfdb48c3fe58484c3e0521588e94e48fdb001f30908b0cd041e6c1b9d9400739ea52d0ca7289b3d807d26d06d73961f609', + }, + ], + auxiliaryDataSupplement: null, + }, + }, { testName: 'Sign tx without change address', tx: { diff --git a/test/test_utils.ts b/test/test_utils.ts index 9f4fa627..49e47e08 100644 --- a/test/test_utils.ts +++ b/test/test_utils.ts @@ -216,6 +216,7 @@ export function describeSignTxPositiveTest(name: string, tests: any[]) { tx, signingMode, additionalWitnessPaths, + options, txBody, expectedResult, unsupportedInAppXS, @@ -233,6 +234,7 @@ export function describeSignTxPositiveTest(name: string, tests: any[]) { tx, signingMode, additionalWitnessPaths, + options, }) if (isAppXS && unsupportedInAppXS) {