Skip to content

Commit

Permalink
feat(connect): Cardano message signing
Browse files Browse the repository at this point in the history
  • Loading branch information
jaskp committed Dec 28, 2023
1 parent 9ac430c commit 868af59
Show file tree
Hide file tree
Showing 14 changed files with 427 additions and 2 deletions.
44 changes: 44 additions & 0 deletions packages/connect/e2e/__fixtures__/cardanoSignMessage.ts
Original file line number Diff line number Diff line change
@@ -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],
},
],
};
2 changes: 2 additions & 0 deletions packages/connect/e2e/__fixtures__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -105,6 +106,7 @@ let fixtures = [
cardanoGetNativeScriptHash,
cardanoGetPublicKey,
cardanoSignTransaction,
cardanoSignMessage,
composeTransaction,
eosGetPublicKey,
eosSignTransaction,
Expand Down
122 changes: 122 additions & 0 deletions packages/connect/src/api/cardano/api/cardanoSignMessage.ts
Original file line number Diff line number Diff line change
@@ -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<CardanoSignedMessage> {
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';
}
}
1 change: 1 addition & 0 deletions packages/connect/src/api/cardano/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
3 changes: 2 additions & 1 deletion packages/connect/src/api/cardano/cardanoUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions packages/connect/src/constants/cardano.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ export enum NETWORK_IDS {
mainnet = 1,
testnet = 0,
}

export enum ALGORITHM_IDS {
EdDSA = -8,
}
5 changes: 5 additions & 0 deletions packages/connect/src/data/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
2 changes: 2 additions & 0 deletions packages/connect/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' }),
Expand Down
37 changes: 37 additions & 0 deletions packages/connect/src/types/api/__tests__/cardano.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
};
35 changes: 34 additions & 1 deletion packages/connect/src/types/api/cardano/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PROTO } from '../../../constants';
import type { PROTO, CARDANO } from '../../../constants';
import type { GetPublicKey, PublicKey, DerivationPath } from '../../params';

// cardanoGetAddress
Expand Down Expand Up @@ -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;
}
6 changes: 6 additions & 0 deletions packages/connect/src/types/api/cardanoSignMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { Params, Response } from '../params';
import type { CardanoSignMessage, CardanoSignedMessage } from './cardano';

export declare function cardanoSignMessage(
params: Params<CardanoSignMessage>,
): Response<CardanoSignedMessage>;
4 changes: 4 additions & 0 deletions packages/connect/src/types/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;

Expand Down
Loading

0 comments on commit 868af59

Please sign in to comment.