From 868af59ef14be8750a4eeaa769cf75f69b74b134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Jas=CC=8Cko?= Date: Thu, 28 Dec 2023 23:43:59 +0100 Subject: [PATCH] feat(connect): Cardano message signing --- .../e2e/__fixtures__/cardanoSignMessage.ts | 44 +++++++ packages/connect/e2e/__fixtures__/index.ts | 2 + .../src/api/cardano/api/cardanoSignMessage.ts | 122 ++++++++++++++++++ packages/connect/src/api/cardano/api/index.ts | 1 + .../connect/src/api/cardano/cardanoUtils.ts | 3 +- packages/connect/src/constants/cardano.ts | 4 + packages/connect/src/data/config.ts | 5 + packages/connect/src/factory.ts | 2 + .../src/types/api/__tests__/cardano.ts | 37 ++++++ .../connect/src/types/api/cardano/index.ts | 35 ++++- .../src/types/api/cardanoSignMessage.ts | 6 + packages/connect/src/types/api/index.ts | 4 + packages/protobuf/messages.json | 122 ++++++++++++++++++ packages/protobuf/src/messages.ts | 42 ++++++ 14 files changed, 427 insertions(+), 2 deletions(-) create mode 100644 packages/connect/e2e/__fixtures__/cardanoSignMessage.ts create mode 100644 packages/connect/src/api/cardano/api/cardanoSignMessage.ts create mode 100644 packages/connect/src/types/api/cardanoSignMessage.ts diff --git a/packages/connect/e2e/__fixtures__/cardanoSignMessage.ts b/packages/connect/e2e/__fixtures__/cardanoSignMessage.ts new file mode 100644 index 000000000000..6a5722ac7c55 --- /dev/null +++ b/packages/connect/e2e/__fixtures__/cardanoSignMessage.ts @@ -0,0 +1,44 @@ +import { NETWORK_IDS, PROTOCOL_MAGICS, ALGORITHM_IDS } from '../../src/constants/cardano'; + +const legacyResults = { + beforeMessageSigning: { + rules: ['<2.6.4', '1'], + success: false, + }, +}; + +export default { + method: 'cardanoSignMessage', + setup: { + mnemonic: 'mnemonic_all', + }, + tests: [ + { + description: 'Sign short payload hash', + params: { + path: "m/1852'/1815'/0'/0/0", + payload: 'Test', + hashPayload: true, + protocolMagic: PROTOCOL_MAGICS.mainnet, + networkId: NETWORK_IDS.mainnet, + keyPath: "m/1852'/1815'/0'/0/0", + }, + result: { + payload: 'Test', + signature: + 'd1e0a7a110676aa67c70f086ad3e4e80ab94a4f6b8676eb38c8cba25edec1920dbf5e68909d410e085058062c5ac1351c0fe361c3f28550a23751ab723c0580b', + headers: { + protected: { + 1: ALGORITHM_IDS.EdDSA, + address: '80f9e2c88e6c817008f3a812ed889b4a4da8e0bd103f86e7335422aa', + }, + unprotected: { + hashed: true, + version: 1, + }, + }, + }, + legacyResults: [legacyResults.beforeMessageSigning], + }, + ], +}; diff --git a/packages/connect/e2e/__fixtures__/index.ts b/packages/connect/e2e/__fixtures__/index.ts index 58421102f2b6..79a663d35c0e 100644 --- a/packages/connect/e2e/__fixtures__/index.ts +++ b/packages/connect/e2e/__fixtures__/index.ts @@ -8,6 +8,7 @@ import cardanoGetAddressDerivations from './cardanoGetAddressDerivations'; import cardanoGetNativeScriptHash from './cardanoGetNativeScriptHash'; import cardanoGetPublicKey from './cardanoGetPublicKey'; import cardanoSignTransaction from './cardanoSignTransaction'; +import cardanoSignMessage from './cardanoSignMessage'; import composeTransaction from './composeTransaction'; import eosGetPublicKey from './eosGetPublicKey'; import eosSignTransaction from './eosSignTransaction'; @@ -105,6 +106,7 @@ let fixtures = [ cardanoGetNativeScriptHash, cardanoGetPublicKey, cardanoSignTransaction, + cardanoSignMessage, composeTransaction, eosGetPublicKey, eosSignTransaction, diff --git a/packages/connect/src/api/cardano/api/cardanoSignMessage.ts b/packages/connect/src/api/cardano/api/cardanoSignMessage.ts new file mode 100644 index 000000000000..b3b5e0d9f182 --- /dev/null +++ b/packages/connect/src/api/cardano/api/cardanoSignMessage.ts @@ -0,0 +1,122 @@ +import { AbstractMethod } from '../../../core/AbstractMethod'; +import { PROTO, CARDANO } from '../../../constants'; +import { getFirmwareRange, validateParams } from '../../common/paramsValidator'; +import { getMiscNetwork } from '../../../data/coinInfo'; +import { Path } from '../cardanoInputs'; +import { validatePath } from '../../../utils/pathUtils'; +import { hexStringByteLength, sendChunkedHexString } from '../cardanoUtils'; +import type { CardanoMessageHeaders, CardanoSignedMessage } from '../../../types/api/cardano'; +import { addressParametersToProto, validateAddressParameters } from '../cardanoAddressParameters'; + +export type CardanoSignMessageParams = { + path: Path; + payload: string; + hashPayload: boolean; + networkId: number; + protocolMagic: number; + addressParameters?: PROTO.CardanoAddressParametersType; + keyPath?: Path; + derivationType: PROTO.CardanoDerivationType; + preferHexDisplay?: boolean; +}; + +export default class CardanoSignMessage extends AbstractMethod< + 'cardanoSignMessage', + CardanoSignMessageParams +> { + private static VERSION = 1; + + init(): void { + this.requiredPermissions = ['read', 'write']; + this.firmwareRange = getFirmwareRange( + this.name, + getMiscNetwork('Cardano'), + this.firmwareRange, + ); + + const { payload } = this; + + validateParams(payload, [ + { name: 'path', type: 'string', required: true }, + { name: 'payload', type: 'string' }, + { name: 'hashPayload', type: 'boolean' }, + { name: 'networkId', type: 'number' }, + { name: 'protocolMagic', type: 'number' }, + { name: 'addressParameters', type: 'object' }, + { name: 'keyPath', type: 'string' }, + { name: 'derivationType', type: 'number' }, + { name: 'preferHexDisplay', type: 'boolean' }, + ]); + + if (payload.addressParameters) { + validateAddressParameters(payload.addressParameters); + } + + this.params = { + path: validatePath(payload.path, 5), + payload: payload.payload, + hashPayload: payload.hashPayload, + networkId: payload.networkId, + protocolMagic: payload.protocolMagic, + addressParameters: + payload.addressParameters && addressParametersToProto(payload.addressParameters), + keyPath: payload.keyPath != null ? validatePath(payload.keyPath, 5) : undefined, + derivationType: payload.derivationType ?? PROTO.CardanoDerivationType.ICARUS_TREZOR, + preferHexDisplay: payload.preferHexDisplay, + }; + } + + async run(): Promise { + const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands()); + + const payloadSize = hexStringByteLength(this.params.payload); + const MAX_CHUNK_SIZE = 1024 * 2; // 1024 hex-encoded bytes + + await typedCall('CardanoSignMessageInit', 'CardanoMessageItemAck', { + path: this.params.path, + payload_size: payloadSize, + hash_payload: this.params.hashPayload, + network_id: this.params.networkId, + protocol_magic: this.params.protocolMagic, + address_parameters: this.params.addressParameters, + key_path: this.params.keyPath ?? [], + derivation_type: this.params.derivationType, + prefer_hex_display: this.params.preferHexDisplay, + }); + + await sendChunkedHexString( + typedCall, + this.params.payload, + MAX_CHUNK_SIZE, + 'CardanoMessagePayloadChunk', + 'CardanoMessageItemAck', + ); + + const { + message: { signature, address }, + } = await typedCall('CardanoMessageItemHostAck', 'CardanoSignMessageFinished'); + + return { + signature, + payload: this.params.payload, + headers: this.createHeaders(address), + }; + } + + private createHeaders(address: string): CardanoMessageHeaders { + return { + protected: { + 1: CARDANO.ALGORITHM_IDS.EdDSA, + address, + }, + unprotected: { + hashed: this.params.hashPayload, + version: CardanoSignMessage.VERSION, + }, + }; + } + + get info() { + return 'Sign Cardano message'; + } +} diff --git a/packages/connect/src/api/cardano/api/index.ts b/packages/connect/src/api/cardano/api/index.ts index 0ec3de894cee..a83e79021ba2 100644 --- a/packages/connect/src/api/cardano/api/index.ts +++ b/packages/connect/src/api/cardano/api/index.ts @@ -3,3 +3,4 @@ export { default as cardanoGetNativeScriptHash } from './cardanoGetNativeScriptH export { default as cardanoGetPublicKey } from './cardanoGetPublicKey'; export { default as cardanoSignTransaction } from './cardanoSignTransaction'; export { default as cardanoComposeTransaction } from './cardanoComposeTransaction'; +export { default as cardanoSignMessage } from './cardanoSignMessage'; diff --git a/packages/connect/src/api/cardano/cardanoUtils.ts b/packages/connect/src/api/cardano/cardanoUtils.ts index c6f1b29440ad..0c5fddd2ff35 100644 --- a/packages/connect/src/api/cardano/cardanoUtils.ts +++ b/packages/connect/src/api/cardano/cardanoUtils.ts @@ -97,11 +97,12 @@ export const sendChunkedHexString = async ( data: string, chunkSize: number, messageType: string, + responseType = 'CardanoTxItemAck', ) => { let processedSize = 0; while (processedSize < data.length) { const chunk = data.slice(processedSize, processedSize + chunkSize); - await typedCall(messageType, 'CardanoTxItemAck', { + await typedCall(messageType, responseType, { data: chunk, }); processedSize += chunkSize; diff --git a/packages/connect/src/constants/cardano.ts b/packages/connect/src/constants/cardano.ts index db8f34f9558b..d6f16c65fcd5 100644 --- a/packages/connect/src/constants/cardano.ts +++ b/packages/connect/src/constants/cardano.ts @@ -10,3 +10,7 @@ export enum NETWORK_IDS { mainnet = 1, testnet = 0, } + +export enum ALGORITHM_IDS { + EdDSA = -8, +} diff --git a/packages/connect/src/data/config.ts b/packages/connect/src/data/config.ts index 01253d370046..3bfe73fc3a28 100644 --- a/packages/connect/src/data/config.ts +++ b/packages/connect/src/data/config.ts @@ -134,6 +134,11 @@ export const config = { min: { T1B1: '0', T2T1: '2.4.3' }, comment: ['Cardano GetNativeScriptHash call added in 2.4.3'], }, + { + methods: ['cardanoSignMessage'], + min: { T1B1: '0', T2T1: '2.6.4' }, + comment: ['Cardano SignMessage call added in 2.6.4'], + }, { methods: ['tezosSignTransaction'], min: { T1B1: '0', T2T1: '2.1.8' }, diff --git a/packages/connect/src/factory.ts b/packages/connect/src/factory.ts index dc2f8edf81bc..56b42b691fe7 100644 --- a/packages/connect/src/factory.ts +++ b/packages/connect/src/factory.ts @@ -103,6 +103,8 @@ export const factory = ({ cardanoComposeTransaction: params => call({ ...params, method: 'cardanoComposeTransaction' }), + cardanoSignMessage: params => call({ ...params, method: 'cardanoSignMessage' }), + cipherKeyValue: params => call({ ...params, method: 'cipherKeyValue' }), composeTransaction: params => call({ ...params, method: 'composeTransaction' }), diff --git a/packages/connect/src/types/api/__tests__/cardano.ts b/packages/connect/src/types/api/__tests__/cardano.ts index caf38add058c..62eeedc4d814 100644 --- a/packages/connect/src/types/api/__tests__/cardano.ts +++ b/packages/connect/src/types/api/__tests__/cardano.ts @@ -421,3 +421,40 @@ export const cardanoSignTransaction = async (api: TrezorConnect) => { } } }; + +export const cardanoSignMessage = async (api: TrezorConnect) => { + const sign = await api.cardanoSignMessage({ + path: 'm/44', + payload: 'Test..', + hashPayload: true, + networkId: 0, + protocolMagic: 0, + addressParameters: { + addressType: CardanoAddressType.BASE, + path: 'm/44', + stakingPath: 'm/44', + stakingKeyHash: 'aaff00..', + stakingScriptHash: 'aaff00..', + paymentScriptHash: 'aaff00..', + certificatePointer: { + blockIndex: 0, + txIndex: 0, + certificateIndex: 0, + }, + }, + keyPath: 'm/44', + derivationType: PROTO.CardanoDerivationType.ICARUS_TREZOR, + preferHexDisplay: true, + }); + + if (sign.success) { + const { payload } = sign; + payload.payload.toLowerCase(); + payload.signature.toLowerCase(); + const { headers } = payload; + headers.protected[1].toFixed(); + headers.protected.address.toLowerCase(); + [true, false].includes(headers.unprotected.hashed); + headers.unprotected.version.toFixed(); + } +}; diff --git a/packages/connect/src/types/api/cardano/index.ts b/packages/connect/src/types/api/cardano/index.ts index a2509fc83cfa..964afa23e1df 100644 --- a/packages/connect/src/types/api/cardano/index.ts +++ b/packages/connect/src/types/api/cardano/index.ts @@ -1,4 +1,4 @@ -import type { PROTO } from '../../../constants'; +import type { PROTO, CARDANO } from '../../../constants'; import type { GetPublicKey, PublicKey, DerivationPath } from '../../params'; // cardanoGetAddress @@ -238,3 +238,36 @@ export interface CardanoSignedTxData { witnesses: CardanoSignedTxWitness[]; auxiliaryDataSupplement?: CardanoAuxiliaryDataSupplement; } + +export interface CardanoSignMessage { + path: DerivationPath; + payload: string; + hashPayload: boolean; + networkId: number; + protocolMagic: number; + keyPath?: DerivationPath; + addressParameters?: CardanoAddressParameters; + derivationType?: PROTO.CardanoDerivationType; + preferHexDisplay?: boolean; +} + +export interface CardanoMessageProtectedHeaders { + 1: CARDANO.ALGORITHM_IDS.EdDSA; + address: string; +} + +export interface CardanoMessageUnprotectedHeaders { + hashed: boolean; + version: number; +} + +export interface CardanoMessageHeaders { + protected: CardanoMessageProtectedHeaders; + unprotected: CardanoMessageUnprotectedHeaders; +} + +export interface CardanoSignedMessage { + headers: CardanoMessageHeaders; + payload: string; + signature: string; +} diff --git a/packages/connect/src/types/api/cardanoSignMessage.ts b/packages/connect/src/types/api/cardanoSignMessage.ts new file mode 100644 index 000000000000..cd5e7d07b18e --- /dev/null +++ b/packages/connect/src/types/api/cardanoSignMessage.ts @@ -0,0 +1,6 @@ +import type { Params, Response } from '../params'; +import type { CardanoSignMessage, CardanoSignedMessage } from './cardano'; + +export declare function cardanoSignMessage( + params: Params, +): Response; diff --git a/packages/connect/src/types/api/index.ts b/packages/connect/src/types/api/index.ts index 9aedbf230eda..56d20926ce6e 100644 --- a/packages/connect/src/types/api/index.ts +++ b/packages/connect/src/types/api/index.ts @@ -22,6 +22,7 @@ import { cardanoGetAddress } from './cardanoGetAddress'; import { cardanoGetNativeScriptHash } from './cardanoGetNativeScriptHash'; import { cardanoGetPublicKey } from './cardanoGetPublicKey'; import { cardanoSignTransaction } from './cardanoSignTransaction'; +import { cardanoSignMessage } from './cardanoSignMessage'; import { cardanoComposeTransaction } from './cardanoComposeTransaction'; import { changePin } from './changePin'; import { cipherKeyValue } from './cipherKeyValue'; @@ -165,6 +166,9 @@ export interface TrezorConnect { cardanoComposeTransaction: typeof cardanoComposeTransaction; + // todo: link docs + cardanoSignMessage: typeof cardanoSignMessage; + // https://github.com/trezor/trezor-suite/blob/develop/docs/packages/connect/methods/changePin.md changePin: typeof changePin; diff --git a/packages/protobuf/messages.json b/packages/protobuf/messages.json index 2da876edbc27..4edb9752a183 100644 --- a/packages/protobuf/messages.json +++ b/packages/protobuf/messages.json @@ -2474,6 +2474,91 @@ "CardanoSignTxFinished": { "fields": {} }, + "CardanoSignMessageInit": { + "fields": { + "protocol_magic": { + "rule": "required", + "type": "uint32", + "id": 1 + }, + "network_id": { + "rule": "required", + "type": "uint32", + "id": 2 + }, + "path": { + "rule": "repeated", + "type": "uint32", + "id": 3, + "options": { + "packed": false + } + }, + "payload_size": { + "rule": "required", + "type": "uint32", + "id": 4 + }, + "hash_payload": { + "rule": "required", + "type": "bool", + "id": 5 + }, + "key_path": { + "rule": "repeated", + "type": "uint32", + "id": 6, + "options": { + "packed": false + } + }, + "address_parameters": { + "type": "CardanoAddressParametersType", + "id": 7 + }, + "derivation_type": { + "rule": "required", + "type": "CardanoDerivationType", + "id": 8 + }, + "prefer_hex_display": { + "type": "bool", + "id": 9, + "options": { + "default": false + } + } + } + }, + "CardanoMessageItemAck": { + "fields": {} + }, + "CardanoMessagePayloadChunk": { + "fields": { + "data": { + "rule": "required", + "type": "bytes", + "id": 1 + } + } + }, + "CardanoMessageItemHostAck": { + "fields": {} + }, + "CardanoSignMessageFinished": { + "fields": { + "signature": { + "rule": "required", + "type": "bytes", + "id": 1 + }, + "address": { + "rule": "required", + "type": "bytes", + "id": 2 + } + } + }, "Success": { "fields": { "message": { @@ -7154,6 +7239,19 @@ } } }, + "StellarClaimClaimableBalanceOp": { + "fields": { + "source_account": { + "type": "string", + "id": 1 + }, + "balance_id": { + "rule": "required", + "type": "bytes", + "id": 2 + } + } + }, "StellarSignedTx": { "fields": { "public_key": { @@ -8258,6 +8356,9 @@ "MessageType_StellarPathPaymentStrictSendOp": { "(wire_in)": true }, + "MessageType_StellarClaimClaimableBalanceOp": { + "(wire_in)": true + }, "MessageType_StellarSignedTx": { "(wire_out)": true }, @@ -8348,6 +8449,21 @@ "MessageType_CardanoTxReferenceInput": { "(wire_in)": true }, + "MessageType_CardanoSignMessageInit": { + "(wire_in)": true + }, + "MessageType_CardanoMessagePayloadChunk": { + "(wire_in)": true + }, + "MessageType_CardanoMessageItemAck": { + "(wire_out)": true + }, + "MessageType_CardanoMessageItemHostAck": { + "(wire_out)": true + }, + "MessageType_CardanoSignMessageFinished": { + "(wire_in)": true + }, "MessageType_RippleGetAddress": { "(wire_in)": true }, @@ -8691,6 +8807,7 @@ "MessageType_StellarBumpSequenceOp": 221, "MessageType_StellarManageBuyOfferOp": 222, "MessageType_StellarPathPaymentStrictSendOp": 223, + "MessageType_StellarClaimClaimableBalanceOp": 225, "MessageType_StellarSignedTx": 230, "MessageType_CardanoGetPublicKey": 305, "MessageType_CardanoPublicKey": 306, @@ -8721,6 +8838,11 @@ "MessageType_CardanoTxInlineDatumChunk": 335, "MessageType_CardanoTxReferenceScriptChunk": 336, "MessageType_CardanoTxReferenceInput": 337, + "MessageType_CardanoSignMessageInit": 338, + "MessageType_CardanoMessagePayloadChunk": 339, + "MessageType_CardanoMessageItemAck": 340, + "MessageType_CardanoMessageItemHostAck": 341, + "MessageType_CardanoSignMessageFinished": 342, "MessageType_RippleGetAddress": 400, "MessageType_RippleAddress": 401, "MessageType_RippleSignTx": 402, diff --git a/packages/protobuf/src/messages.ts b/packages/protobuf/src/messages.ts index 7315d963959c..039e57788eb9 100644 --- a/packages/protobuf/src/messages.ts +++ b/packages/protobuf/src/messages.ts @@ -930,6 +930,36 @@ export type CardanoTxBodyHash = { // CardanoSignTxFinished export type CardanoSignTxFinished = {}; +// CardanoSignMessageInit +export type CardanoSignMessageInit = { + protocol_magic: number; + network_id: number; + path: number[]; + payload_size: number; + hash_payload: boolean; + key_path: number[]; + address_parameters?: CardanoAddressParametersType; + derivation_type: CardanoDerivationType; + prefer_hex_display?: boolean; +}; + +// CardanoMessageItemAck +export type CardanoMessageItemAck = {}; + +// CardanoMessagePayloadChunk +export type CardanoMessagePayloadChunk = { + data: string; +}; + +// CardanoMessageItemHostAck +export type CardanoMessageItemHostAck = {}; + +// CardanoSignMessageFinished +export type CardanoSignMessageFinished = { + signature: string; + address: string; +}; + // Success export type Success = { message: string; @@ -2212,6 +2242,12 @@ export type StellarBumpSequenceOp = { bump_to: UintType; }; +// StellarClaimClaimableBalanceOp +export type StellarClaimClaimableBalanceOp = { + source_account?: string; + balance_id: string; +}; + // StellarSignedTx export type StellarSignedTx = { public_key: string; @@ -2449,6 +2485,11 @@ export type MessageType = { CardanoTxHostAck: CardanoTxHostAck; CardanoTxBodyHash: CardanoTxBodyHash; CardanoSignTxFinished: CardanoSignTxFinished; + CardanoSignMessageInit: CardanoSignMessageInit; + CardanoMessageItemAck: CardanoMessageItemAck; + CardanoMessagePayloadChunk: CardanoMessagePayloadChunk; + CardanoMessageItemHostAck: CardanoMessageItemHostAck; + CardanoSignMessageFinished: CardanoSignMessageFinished; Success: Success; Failure: Failure; ButtonRequest: ButtonRequest; @@ -2606,6 +2647,7 @@ export type MessageType = { StellarAccountMergeOp: StellarAccountMergeOp; StellarManageDataOp: StellarManageDataOp; StellarBumpSequenceOp: StellarBumpSequenceOp; + StellarClaimClaimableBalanceOp: StellarClaimClaimableBalanceOp; StellarSignedTx: StellarSignedTx; TezosGetAddress: TezosGetAddress; TezosAddress: TezosAddress;