Skip to content

Commit ccaa53a

Browse files
Merge pull request #1368 from input-output-hk/fix/lw-10852-ledger-hw-sign-voting-proposals
Fix lw-10852 Ledger hw sign voting proposals
2 parents 9d68d1b + efa7c9c commit ccaa53a

File tree

12 files changed

+174
-131
lines changed

12 files changed

+174
-131
lines changed

packages/hardware-ledger/src/LedgerKeyAgent.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ import _LedgerConnection, {
3131
PoolOwnerType,
3232
Transaction,
3333
TransactionSigningMode,
34-
TxOutputDestinationType
34+
TxOutputDestinationType,
35+
VoterType,
36+
VoterVotes
3537
} from '@cardano-foundation/ledgerjs-hw-app-cardano';
3638
import _TransportWebUSB from '@ledgerhq/hw-transport-webusb';
3739
import type LedgerTransport from '@ledgerhq/hw-transport';
@@ -501,6 +503,21 @@ export class LedgerKeyAgent extends KeyAgentBase {
501503
);
502504
}
503505

506+
private static isKeyHashOrScriptHashVoter(votingProcedures?: VoterVotes[] | null): boolean {
507+
return !!votingProcedures?.some((votingProcedure) => {
508+
switch (votingProcedure.voter.type) {
509+
case VoterType.COMMITTEE_KEY_HASH:
510+
case VoterType.COMMITTEE_SCRIPT_HASH:
511+
case VoterType.DREP_KEY_HASH:
512+
case VoterType.DREP_SCRIPT_HASH:
513+
case VoterType.STAKE_POOL_KEY_HASH:
514+
return true;
515+
default:
516+
return false;
517+
}
518+
});
519+
}
520+
504521
/**
505522
* Gets the mode in which we want to sign the transaction.
506523
* Ledger has certain limitations due to which it cannot sign arbitrary combination of all transaction features.
@@ -529,7 +546,8 @@ export class LedgerKeyAgent extends KeyAgentBase {
529546
* VotingProcedures: We are currently supporting only keyHash and scriptHash voter types in voting procedures.
530547
* To sign tx with keyHash and scriptHash voter type we have to use PLUTUS_TRANSACTION signing mode
531548
*/
532-
if (tx.collateralInputs || tx.votingProcedures) {
549+
550+
if (tx.collateralInputs || LedgerKeyAgent.isKeyHashOrScriptHashVoter(tx.votingProcedures)) {
533551
return TransactionSigningMode.PLUTUS_TRANSACTION;
534552
}
535553

@@ -552,10 +570,12 @@ export class LedgerKeyAgent extends KeyAgentBase {
552570
{ knownAddresses, txInKeyPathMap }: SignTransactionContext
553571
): Promise<Cardano.Signatures> {
554572
try {
573+
const dRepPublicKey = await this.derivePublicKey(util.DREP_KEY_DERIVATION_PATH);
574+
const dRepKeyHashHex = (await Crypto.Ed25519PublicKey.fromHex(dRepPublicKey).hash()).hex();
555575
const ledgerTxData = await toLedgerTx(body, {
556576
accountIndex: this.accountIndex,
557577
chainId: this.chainId,
558-
dRepPublicKey: await this.derivePublicKey(util.DREP_KEY_DERIVATION_PATH),
578+
dRepKeyHashHex,
559579
knownAddresses,
560580
txInKeyPathMap
561581
});

packages/hardware-ledger/src/transformers/certificates.ts

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -283,25 +283,22 @@ const poolRetirementCertificate: Transform<
283283
};
284284
};
285285

286-
const checkDrepPublicKeyAgainstCredential = async (
287-
dRepPublicKey: Crypto.Ed25519PublicKeyHex | undefined,
286+
const checkDrepPublicKeyAgainstCredential = (
287+
dRepKeyHashHex: Crypto.Ed25519KeyHashHex | undefined,
288288
hash: Crypto.Hash28ByteBase16
289289
) => {
290-
if (
291-
!dRepPublicKey ||
292-
(await Crypto.Ed25519PublicKey.fromHex(dRepPublicKey).hash()).hex() !== Crypto.Ed25519KeyHashHex(hash)
293-
) {
290+
if (!dRepKeyHashHex || dRepKeyHashHex !== Crypto.Ed25519KeyHashHex(hash)) {
294291
throw new InvalidArgumentError('certificate', 'dRepPublicKey does not match certificate drep credential.');
295292
}
296293
};
297294

298295
const drepRegistrationCertificate: Transform<
299296
Cardano.RegisterDelegateRepresentativeCertificate | Cardano.UnRegisterDelegateRepresentativeCertificate,
300-
Promise<Ledger.Certificate>,
297+
Ledger.Certificate,
301298
LedgerTxTransformerContext
302-
> = async (certificate, context): Promise<Ledger.Certificate> => {
299+
> = (certificate, context): Ledger.Certificate => {
303300
if (!context) throw new InvalidArgumentError('LedgerTxTransformerContext', 'values was not provided');
304-
await checkDrepPublicKeyAgainstCredential(context?.dRepPublicKey, certificate.dRepCredential.hash);
301+
checkDrepPublicKeyAgainstCredential(context?.dRepKeyHashHex, certificate.dRepCredential.hash);
305302

306303
const params: Ledger.DRepRegistrationParams = {
307304
...mapAnchorToParams(certificate),
@@ -324,12 +321,12 @@ const drepRegistrationCertificate: Transform<
324321

325322
const updateDRepCertificate: Transform<
326323
Cardano.UpdateDelegateRepresentativeCertificate,
327-
Promise<Ledger.Certificate>,
324+
Ledger.Certificate,
328325
LedgerTxTransformerContext
329-
> = async (certificate, context): Promise<Ledger.Certificate> => {
326+
> = (certificate, context): Ledger.Certificate => {
330327
if (!context) throw new InvalidArgumentError('LedgerTxTransformerContext', 'values was not provided');
331328

332-
await checkDrepPublicKeyAgainstCredential(context?.dRepPublicKey, certificate.dRepCredential.hash);
329+
checkDrepPublicKeyAgainstCredential(context?.dRepKeyHashHex, certificate.dRepCredential.hash);
333330

334331
const params: Ledger.DRepUpdateParams = {
335332
...mapAnchorToParams(certificate),
@@ -377,7 +374,7 @@ export const voteDelegationCertificate: Transform<
377374
type: Ledger.CertificateType.VOTE_DELEGATION
378375
});
379376

380-
const toCert = async (cert: Cardano.Certificate, context: LedgerTxTransformerContext): Promise<Ledger.Certificate> => {
377+
const toCert = (cert: Cardano.Certificate, context: LedgerTxTransformerContext): Ledger.Certificate => {
381378
switch (cert.__typename) {
382379
case Cardano.CertificateType.StakeRegistration:
383380
return getStakeAddressCertificate(cert, context);
@@ -398,21 +395,21 @@ const toCert = async (cert: Cardano.Certificate, context: LedgerTxTransformerCon
398395
case Cardano.CertificateType.VoteDelegation:
399396
return voteDelegationCertificate(cert, context);
400397
case Cardano.CertificateType.RegisterDelegateRepresentative:
401-
return await drepRegistrationCertificate(cert, context);
398+
return drepRegistrationCertificate(cert, context);
402399
case Cardano.CertificateType.UnregisterDelegateRepresentative:
403-
return await drepRegistrationCertificate(cert, context);
400+
return drepRegistrationCertificate(cert, context);
404401
case Cardano.CertificateType.UpdateDelegateRepresentative:
405-
return await updateDRepCertificate(cert, context);
402+
return updateDRepCertificate(cert, context);
406403
default:
407404
throw new InvalidArgumentError('cert', `Certificate ${cert.__typename} not supported.`);
408405
}
409406
};
410407

411-
export const mapCerts = async (
408+
export const mapCerts = (
412409
certs: Cardano.Certificate[] | undefined,
413410
context: LedgerTxTransformerContext
414-
): Promise<Ledger.Certificate[] | null> => {
411+
): Ledger.Certificate[] | null => {
415412
if (!certs) return null;
416413

417-
return Promise.all(certs.map((coreCert) => toCert(coreCert, context)));
414+
return certs.map((coreCert) => toCert(coreCert, context));
418415
};

packages/hardware-ledger/src/transformers/tx.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { mapWithdrawals } from './withdrawals';
1616

1717
export const LedgerTxTransformer: Transformer<Cardano.TxBody, Ledger.Transaction, LedgerTxTransformerContext> = {
1818
auxiliaryData: ({ auxiliaryDataHash }) => mapAuxiliaryData(auxiliaryDataHash),
19-
certificates: async ({ certificates }, context) => await mapCerts(certificates, context!),
19+
certificates: ({ certificates }, context) => mapCerts(certificates, context!),
2020
collateralInputs: ({ collaterals }, context) => mapCollateralTxIns(collaterals, context!),
2121
collateralOutput: ({ collateralReturn }, context) => mapCollateralTxOut(collateralReturn, context!),
2222
donation: ({ donation }) => donation,
@@ -36,7 +36,7 @@ export const LedgerTxTransformer: Transformer<Cardano.TxBody, Ledger.Transaction
3636
treasury: ({ treasuryValue }) => treasuryValue,
3737
ttl: ({ validityInterval }) => validityInterval?.invalidHereafter,
3838
validityIntervalStart: ({ validityInterval }) => validityInterval?.invalidBefore,
39-
votingProcedures: ({ votingProcedures }) => mapVotingProcedures(votingProcedures),
39+
votingProcedures: ({ votingProcedures }, context) => mapVotingProcedures(votingProcedures, context!),
4040
withdrawals: ({ withdrawals }, context) => mapWithdrawals(withdrawals, context!)
4141
};
4242

packages/hardware-ledger/src/transformers/votingProcedures.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import * as Crypto from '@cardano-sdk/crypto';
12
import * as Ledger from '@cardano-foundation/ledgerjs-hw-app-cardano';
23
import { Cardano } from '@cardano-sdk/core';
3-
import { Transform } from '@cardano-sdk/util';
4+
import { InvalidArgumentError, Transform } from '@cardano-sdk/util';
5+
import { LedgerTxTransformerContext } from '..';
6+
import { util } from '@cardano-sdk/key-management';
47

58
/**
69
* Maps a Voter to a Voter from the LedgerJS public types.
@@ -9,7 +12,8 @@ import { Transform } from '@cardano-sdk/util';
912
* @returns {Ledger.Voter} Corresponding Voter object for use with LedgerJS.
1013
*/
1114

12-
export const toVoter: Transform<Cardano.Voter, Ledger.Voter> = (voter) => {
15+
export const toVoter: Transform<Cardano.Voter, Ledger.Voter, LedgerTxTransformerContext> = (voter, context) => {
16+
if (!context) throw new InvalidArgumentError('LedgerTxTransformerContext', 'values was not provided');
1317
switch (voter.__typename) {
1418
case 'ccHotKeyHash':
1519
return {
@@ -23,12 +27,15 @@ export const toVoter: Transform<Cardano.Voter, Ledger.Voter> = (voter) => {
2327
type: Ledger.VoterType.COMMITTEE_SCRIPT_HASH
2428
} as Ledger.CommitteeScriptHashVoter;
2529

26-
case 'dRepKeyHash':
30+
case 'dRepKeyHash': {
31+
if (!context.dRepKeyHashHex || context.dRepKeyHashHex !== Crypto.Ed25519KeyHashHex(voter.credential.hash)) {
32+
throw new Error('Foreign voter drepKeyHash');
33+
}
2734
return {
28-
keyHashHex: voter.credential.hash,
29-
type: Ledger.VoterType.DREP_KEY_HASH
30-
} as Ledger.DRepKeyHashVoter;
31-
35+
keyPath: util.accountKeyDerivationPathToBip32Path(context.accountIndex, util.DREP_KEY_DERIVATION_PATH),
36+
type: Ledger.VoterType.DREP_KEY_PATH
37+
} as Ledger.DRepKeyPathVoter;
38+
}
3239
case 'dRepScriptHash':
3340
return {
3441
scriptHashHex: voter.credential.hash,
@@ -97,8 +104,12 @@ export const toVotes: Transform<Cardano.VotingProcedureVote[], Ledger.Vote[]> =
97104
* @param votingProcedure A single voting procedure obj from Core.
98105
* @returns {Ledger.VoterVotes} LedgerJS-compatible voting records
99106
*/
100-
export const toVotingProcedure: Transform<Cardano.VotingProcedures[0], Ledger.VoterVotes> = (votingProcedure) => ({
101-
voter: toVoter(votingProcedure.voter),
107+
export const toVotingProcedure: Transform<
108+
Cardano.VotingProcedures[0],
109+
Ledger.VoterVotes,
110+
LedgerTxTransformerContext
111+
> = (votingProcedure, context) => ({
112+
voter: toVoter(votingProcedure.voter, context),
102113
votes: toVotes(votingProcedure.votes)
103114
});
104115

@@ -113,6 +124,7 @@ export const toVotingProcedure: Transform<Cardano.VotingProcedures[0], Ledger.Vo
113124
*/
114125

115126
export const mapVotingProcedures = (
116-
votingProcedures: Cardano.VotingProcedures | undefined
127+
votingProcedures: Cardano.VotingProcedures | undefined,
128+
context: LedgerTxTransformerContext
117129
): Ledger.VoterVotes[] | null =>
118-
votingProcedures ? votingProcedures.map((votingProcedure) => toVotingProcedure(votingProcedure)) : null;
130+
votingProcedures ? votingProcedures.map((votingProcedure) => toVotingProcedure(votingProcedure, context)) : null;

packages/hardware-ledger/test/testData.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ export const CONTEXT_WITH_KNOWN_ADDRESSES: LedgerTxTransformerContext = {
342342
networkId: Cardano.NetworkId.Testnet,
343343
networkMagic: 999
344344
},
345-
dRepPublicKey: Crypto.Ed25519PublicKeyHex('deeb8f82f2af5836ebbc1b450b6dbf0b03c93afe5696f10d49e8a8304ebfac01'),
345+
dRepKeyHashHex: Crypto.Ed25519KeyHashHex(dRepCredential.hash),
346346
knownAddresses: [
347347
{
348348
accountIndex: 0,
@@ -409,7 +409,7 @@ export const votes = [
409409
export const dRepKeyHashVoter: Cardano.Voter = {
410410
__typename: Cardano.VoterType.dRepKeyHash,
411411
credential: {
412-
hash: stakeCredential.hash,
412+
hash: dRepCredential.hash,
413413
type: Cardano.CredentialType.KeyHash
414414
}
415415
};

0 commit comments

Comments
 (0)