Skip to content

Commit

Permalink
feat(cardano): add support for catalyst voting registration
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielKerekes authored and szymonlesisz committed Apr 29, 2021
1 parent ecfae54 commit 96b843b
Show file tree
Hide file tree
Showing 8 changed files with 408 additions and 26 deletions.
64 changes: 49 additions & 15 deletions docs/methods/cardanoSignTransaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,20 @@ TrezorConnect.cardanoSignTransaction(params).then(function(result) {
});
```

### Notes
**Unfortunately we are aware of the fact that currently at most ~14 inputs are supported per transaction. This should be resolved when the cardano app is updated to support transaction streaming. Meanwhile, a workaround is to send multiple smaller transactions containing less inputs.**

**Also, each serialized transaction output size is currently limited to 512 bytes at Trezor firmware level. This limitation is a mitigation measure to prevent sending large (especially change) outputs containing many tokens that Trezor would not be able to spend given that currently the full Cardano transaction is held in-memory. Once Cardano-transaction signing is refactored to be streamed, this limit can be lifted.**

### Params
[****Optional common params****](commonParams.md)
###### [flowtype](../../src/js/types/networks/cardano.js#L62-L109)
###### [flowtype](../../src/js/types/networks/cardano.js#L61-L171)
* `inputs`*obligatory* `Array` of [CardanoInput](../../src/js/types/networks/cardano.js#L61)
* `outputs` - *obligatory* `Array` of [CardanoOutput](../../src/js/types/networks/cardano.js#L76)
* `fee` - *obligatory* `String`
* `protocolMagic` - *obligatory* `Integer` 764824073 for Mainnet, 42 for Testnet
* `networkId` - *obligatory* `Integer` 1 for Mainnet, 0 for Testnet
* `ttl` - *optional* `String`
* `validityIntervalStart` - *optional* `String`
* `certificates` - *optional* `Array` of [CardanoCertificate](../../src/js/types/networks/cardano.js#L85)
* `withdrawals` - *optional* `Array` of [CardanoWithdrawal](../../src/js/types/networks/cardano.js#L90)
* `metadata` - *optional* `String`
* `certificates` - *optional* `Array` of [CardanoCertificate](../../src/js/types/networks/cardano.js#L123)
* `withdrawals` - *optional* `Array` of [CardanoWithdrawal](../../src/js/types/networks/cardano.js#L130)
* `auixiliaryData` - *optional* [CardanoAuxiliaryData](../../src/js/types/networks/cardano.js#140)
* `metadata` - *removed* - use `auxiliaryData` instead

### Stake pool registration certificate specifics

Expand Down Expand Up @@ -63,7 +59,7 @@ TrezorConnect.cardanoSignTransaction({
},
{
addressParameters: {
addressType: 0,
addressType: 0, // base address type
path: "m/1852'/1815'/0'/0/0",
stakingPath: "m/1852'/1815'/0'/2/0",
},
Expand All @@ -90,15 +86,15 @@ TrezorConnect.cardanoSignTransaction({
validityIntervalStart: "20",
certificates: [
{
type: 0,
type: 0, // stake registration certificate type
path: "m/1852'/1815'/0'/2/0",
},
{
type: 1,
type: 1, // stake deregistration certificate type
path: "m/1852'/1815'/0'/2/0",
},
{
type: 2,
type: 2, // stake delegation certificate type
path: "m/1852'/1815'/0'/2/0",
pool: "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973",
},
Expand All @@ -109,7 +105,9 @@ TrezorConnect.cardanoSignTransaction({
amount: "1000",
}
],
metadata: "a200a11864a118c843aa00ff01a119012c590100aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
auxiliaryData: {
blob: "a200a11864a118c843aa00ff01a119012c590100aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
},
protocolMagic: 764824073,
networkId: 1,
});
Expand Down Expand Up @@ -191,8 +189,44 @@ TrezorConnect.cardanoSignTransaction({
});
```

#### Catalyst voting key registration
```javascript
TrezorConnect.cardanoSignTransaction({
inputs: [
{
path: "m/1852'/1815'/0'/0/0",
prev_hash: "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
prev_index: 0,
}
],
outputs: [
{
address: "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
amount: "3003112",
}
],
fee: "42",
ttl: "10",
auxiliaryData: {
catalystRegistrationParameters: {
votingPublicKey:
"1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
stakingPath: "m/1852'/1815'/0'/2/0",
rewardAddressParameters: {
addressType: 0, // base address type
path: "m/1852'/1815'/0'/0/0",
stakingPath: "m/1852'/1815'/0'/2/0",
},
nonce: "22634813",
},
},
protocolMagic: 764824073,
networkId: 1,
});
```

### Result
###### [flowtype](../../src/js/types/networks/cardano.js#L107-L110)
###### [flowtype](../../src/js/types/networks/cardano.js#L158-L162)
```javascript
{
success: true,
Expand Down
22 changes: 20 additions & 2 deletions src/js/core/methods/CardanoSignTransaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
addressParametersToProto,
validateAddressParameters,
} from './helpers/cardanoAddressParameters';
import { transformAuxiliaryData } from './helpers/cardanoAuxiliaryData';
import { transformCertificate } from './helpers/cardanoCertificate';
import { validateTokenBundle, tokenBundleToProto } from './helpers/cardanoTokens';
import { ERRORS } from '../../constants';
Expand All @@ -26,6 +27,7 @@ const CardanoSignTransactionFeatures = Object.freeze({
SignStakePoolRegistrationAsOwner: ['0', '2.3.5'],
ValidityIntervalStart: ['0', '2.3.5'],
MultiassetOutputs: ['0', '2.3.5'],
AuxiliaryData: ['0', '2.3.7'],
});

export default class CardanoSignTransaction extends AbstractMethod {
Expand All @@ -42,6 +44,14 @@ export default class CardanoSignTransaction extends AbstractMethod {
this.info = 'Sign Cardano transaction';

const { payload } = message;

if (payload.metadata) {
throw ERRORS.TypedError(
'Method_InvalidParameter',
'Metadata field has been replaced by auxiliaryData.',
);
}

// validate incoming parameters
validateParams(payload, [
{ name: 'inputs', type: 'array', obligatory: true },
Expand All @@ -50,7 +60,6 @@ export default class CardanoSignTransaction extends AbstractMethod {
{ name: 'ttl', type: 'amount' },
{ name: 'certificates', type: 'array', allowEmpty: true },
{ name: 'withdrawals', type: 'array', allowEmpty: true },
{ name: 'metadata', type: 'string' },
{ name: 'validityIntervalStart', type: 'amount' },
{ name: 'protocolMagic', type: 'number', obligatory: true },
{ name: 'networkId', type: 'number', obligatory: true },
Expand Down Expand Up @@ -115,14 +124,19 @@ export default class CardanoSignTransaction extends AbstractMethod {
});
}

let auxiliaryData;
if (payload.auxiliaryData) {
auxiliaryData = transformAuxiliaryData(payload.auxiliaryData);
}

this.params = {
inputs,
outputs,
fee: payload.fee,
ttl: payload.ttl,
certificates,
withdrawals,
metadata: payload.metadata,
auxiliary_data: auxiliaryData,
validity_interval_start: payload.validityIntervalStart,
protocol_magic: payload.protocolMagic,
network_id: payload.networkId,
Expand Down Expand Up @@ -156,6 +170,10 @@ export default class CardanoSignTransaction extends AbstractMethod {
this._ensureFeatureIsSupported('MultiassetOutputs');
}
});

if (params.auxiliary_data) {
this._ensureFeatureIsSupported('AuxiliaryData');
}
}

async run() {
Expand Down
56 changes: 56 additions & 0 deletions src/js/core/methods/helpers/cardanoAuxiliaryData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* @flow */
import { addressParametersToProto, validateAddressParameters } from './cardanoAddressParameters';
import { validateParams } from './paramsValidator';
import { validatePath } from '../../../utils/pathUtils';

import type {
CardanoTxAuxiliaryDataType,
CardanoCatalystRegistrationParametersType,
} from '../../../types/trezor/protobuf';
import type {
CardanoAuxiliaryData,
CardanoCatalystRegistrationParameters,
} from '../../../types/networks/cardano';

const transformCatalystRegistrationParameters = (
catalystRegistrationParameters: CardanoCatalystRegistrationParameters,
): CardanoCatalystRegistrationParametersType => {
validateParams(catalystRegistrationParameters, [
{ name: 'votingPublicKey', type: 'string', obligatory: true },
{ name: 'stakingPath', obligatory: true },
{ name: 'nonce', type: 'amount', obligatory: true },
]);
validateAddressParameters(catalystRegistrationParameters.rewardAddressParameters);

return {
voting_public_key: catalystRegistrationParameters.votingPublicKey,
staking_path: validatePath(catalystRegistrationParameters.stakingPath, 3),
reward_address_parameters: addressParametersToProto(
catalystRegistrationParameters.rewardAddressParameters,
),
nonce: catalystRegistrationParameters.nonce,
};
};

export const transformAuxiliaryData = (
auxiliaryData: CardanoAuxiliaryData,
): CardanoTxAuxiliaryDataType => {
validateParams(auxiliaryData, [
{
name: 'blob',
type: 'string',
},
]);

let catalystRegistrationParameters;
if (auxiliaryData.catalystRegistrationParameters) {
catalystRegistrationParameters = transformCatalystRegistrationParameters(
auxiliaryData.catalystRegistrationParameters,
);
}

return {
blob: auxiliaryData.blob,
catalyst_registration_parameters: catalystRegistrationParameters,
};
};
73 changes: 71 additions & 2 deletions src/js/types/__tests__/cardano.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ export const cardanoSignTransaction = async () => {
{
address: 'Ae2..',
amount: '3003112',
tokenBundle: [
{
policyId: 'aaff00..',
tokenAmounts: [{ assetNameBytes: 'aaff00..', amount: '3003112' }],
},
],
},
{
addressParameters: {
Expand All @@ -218,11 +224,74 @@ export const cardanoSignTransaction = async () => {
},
},
amount: '3003112',
tokenBundle: [
{
policyId: 'aaff00..',
tokenAmounts: [{ assetNameBytes: 'aaff00..', amount: '3003112' }],
},
],
},
],
certificates: [
{
type: 0,
path: 'm/44',
pool: 'aaff00..',
poolParameters: {
poolId: 'aaff00..',
vrfKeyHash: 'aaff00..',
pledge: '500000000',
cost: '340000000',
margin: {
numerator: '1',
denominator: '2',
},
rewardAccount: 'stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el',
owners: [
{
stakingKeyPath: "m/1852'",
stakingKeyHash: 'aaff00..',
},
{
stakingKeyHash: 'aaff00..',
},
],
relays: [
{
type: 0,
ipv4Address: '192.168.0.1',
ipv6Address: '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
port: 1234,
hostName: 'www.test2.test',
},
],
metadata: {
url: 'https://www.test.test',
hash: 'aaff00..',
},
},
},
],
certificates: [{ type: 0, path: 'm/44', pool: 'aaff00..' }],
withdrawals: [{ path: 'm/44', amount: '3003112' }],
metadata: 'aaff00..',
auxiliaryData: {
blob: 'aaff00..',
catalystRegistrationParameters: {
votingPublicKey: 'aaff00..',
stakingPath: 'm/44',
rewardAddressParameters: {
addressType: 0,
path: 'm/44',
stakingPath: 'm/44',
stakingKeyHash: 'aaff00..',
certificatePointer: {
blockIndex: 0,
txIndex: 0,
certificateIndex: 0,
},
},
nonce: '0',
},
},
fee: '42',
ttl: '10',
protocolMagic: 0,
Expand Down
14 changes: 13 additions & 1 deletion src/js/types/networks/cardano.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,29 @@ export type CardanoWithdrawal = {
amount: string,
};

export type CardanoCatalystRegistrationParameters = {
votingPublicKey: string,
stakingPath: string | number[],
rewardAddressParameters: CardanoAddressParameters,
nonce: string,
};

export type CardanoAuxiliaryData = {
blob?: string,
catalystRegistrationParameters?: CardanoCatalystRegistrationParameters,
};

export type CardanoSignTransaction = {
inputs: CardanoInput[],
outputs: CardanoOutput[],
fee: string,
ttl?: string,
certificates?: CardanoCertificate[],
withdrawals?: CardanoWithdrawal[],
metadata?: string,
validityIntervalStart?: string,
protocolMagic: number,
networkId: number,
auxiliaryData?: CardanoAuxiliaryData,
};

export type CardanoSignedTx = {
Expand Down
Loading

0 comments on commit 96b843b

Please sign in to comment.