Skip to content

Commit

Permalink
Merge pull request #1368 from input-output-hk/fix/lw-10852-ledger-hw-…
Browse files Browse the repository at this point in the history
…sign-voting-proposals

Fix lw-10852 Ledger hw sign voting proposals
  • Loading branch information
mirceahasegan authored Jul 19, 2024
2 parents 9d68d1b + efa7c9c commit ccaa53a
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 131 deletions.
26 changes: 23 additions & 3 deletions packages/hardware-ledger/src/LedgerKeyAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ import _LedgerConnection, {
PoolOwnerType,
Transaction,
TransactionSigningMode,
TxOutputDestinationType
TxOutputDestinationType,
VoterType,
VoterVotes
} from '@cardano-foundation/ledgerjs-hw-app-cardano';
import _TransportWebUSB from '@ledgerhq/hw-transport-webusb';
import type LedgerTransport from '@ledgerhq/hw-transport';
Expand Down Expand Up @@ -501,6 +503,21 @@ export class LedgerKeyAgent extends KeyAgentBase {
);
}

private static isKeyHashOrScriptHashVoter(votingProcedures?: VoterVotes[] | null): boolean {
return !!votingProcedures?.some((votingProcedure) => {
switch (votingProcedure.voter.type) {
case VoterType.COMMITTEE_KEY_HASH:
case VoterType.COMMITTEE_SCRIPT_HASH:
case VoterType.DREP_KEY_HASH:
case VoterType.DREP_SCRIPT_HASH:
case VoterType.STAKE_POOL_KEY_HASH:
return true;
default:
return false;
}
});
}

/**
* Gets the mode in which we want to sign the transaction.
* Ledger has certain limitations due to which it cannot sign arbitrary combination of all transaction features.
Expand Down Expand Up @@ -529,7 +546,8 @@ export class LedgerKeyAgent extends KeyAgentBase {
* VotingProcedures: We are currently supporting only keyHash and scriptHash voter types in voting procedures.
* To sign tx with keyHash and scriptHash voter type we have to use PLUTUS_TRANSACTION signing mode
*/
if (tx.collateralInputs || tx.votingProcedures) {

if (tx.collateralInputs || LedgerKeyAgent.isKeyHashOrScriptHashVoter(tx.votingProcedures)) {
return TransactionSigningMode.PLUTUS_TRANSACTION;
}

Expand All @@ -552,10 +570,12 @@ export class LedgerKeyAgent extends KeyAgentBase {
{ knownAddresses, txInKeyPathMap }: SignTransactionContext
): Promise<Cardano.Signatures> {
try {
const dRepPublicKey = await this.derivePublicKey(util.DREP_KEY_DERIVATION_PATH);
const dRepKeyHashHex = (await Crypto.Ed25519PublicKey.fromHex(dRepPublicKey).hash()).hex();
const ledgerTxData = await toLedgerTx(body, {
accountIndex: this.accountIndex,
chainId: this.chainId,
dRepPublicKey: await this.derivePublicKey(util.DREP_KEY_DERIVATION_PATH),
dRepKeyHashHex,
knownAddresses,
txInKeyPathMap
});
Expand Down
35 changes: 16 additions & 19 deletions packages/hardware-ledger/src/transformers/certificates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,25 +283,22 @@ const poolRetirementCertificate: Transform<
};
};

const checkDrepPublicKeyAgainstCredential = async (
dRepPublicKey: Crypto.Ed25519PublicKeyHex | undefined,
const checkDrepPublicKeyAgainstCredential = (
dRepKeyHashHex: Crypto.Ed25519KeyHashHex | undefined,
hash: Crypto.Hash28ByteBase16
) => {
if (
!dRepPublicKey ||
(await Crypto.Ed25519PublicKey.fromHex(dRepPublicKey).hash()).hex() !== Crypto.Ed25519KeyHashHex(hash)
) {
if (!dRepKeyHashHex || dRepKeyHashHex !== Crypto.Ed25519KeyHashHex(hash)) {
throw new InvalidArgumentError('certificate', 'dRepPublicKey does not match certificate drep credential.');
}
};

const drepRegistrationCertificate: Transform<
Cardano.RegisterDelegateRepresentativeCertificate | Cardano.UnRegisterDelegateRepresentativeCertificate,
Promise<Ledger.Certificate>,
Ledger.Certificate,
LedgerTxTransformerContext
> = async (certificate, context): Promise<Ledger.Certificate> => {
> = (certificate, context): Ledger.Certificate => {
if (!context) throw new InvalidArgumentError('LedgerTxTransformerContext', 'values was not provided');
await checkDrepPublicKeyAgainstCredential(context?.dRepPublicKey, certificate.dRepCredential.hash);
checkDrepPublicKeyAgainstCredential(context?.dRepKeyHashHex, certificate.dRepCredential.hash);

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

const updateDRepCertificate: Transform<
Cardano.UpdateDelegateRepresentativeCertificate,
Promise<Ledger.Certificate>,
Ledger.Certificate,
LedgerTxTransformerContext
> = async (certificate, context): Promise<Ledger.Certificate> => {
> = (certificate, context): Ledger.Certificate => {
if (!context) throw new InvalidArgumentError('LedgerTxTransformerContext', 'values was not provided');

await checkDrepPublicKeyAgainstCredential(context?.dRepPublicKey, certificate.dRepCredential.hash);
checkDrepPublicKeyAgainstCredential(context?.dRepKeyHashHex, certificate.dRepCredential.hash);

const params: Ledger.DRepUpdateParams = {
...mapAnchorToParams(certificate),
Expand Down Expand Up @@ -377,7 +374,7 @@ export const voteDelegationCertificate: Transform<
type: Ledger.CertificateType.VOTE_DELEGATION
});

const toCert = async (cert: Cardano.Certificate, context: LedgerTxTransformerContext): Promise<Ledger.Certificate> => {
const toCert = (cert: Cardano.Certificate, context: LedgerTxTransformerContext): Ledger.Certificate => {
switch (cert.__typename) {
case Cardano.CertificateType.StakeRegistration:
return getStakeAddressCertificate(cert, context);
Expand All @@ -398,21 +395,21 @@ const toCert = async (cert: Cardano.Certificate, context: LedgerTxTransformerCon
case Cardano.CertificateType.VoteDelegation:
return voteDelegationCertificate(cert, context);
case Cardano.CertificateType.RegisterDelegateRepresentative:
return await drepRegistrationCertificate(cert, context);
return drepRegistrationCertificate(cert, context);
case Cardano.CertificateType.UnregisterDelegateRepresentative:
return await drepRegistrationCertificate(cert, context);
return drepRegistrationCertificate(cert, context);
case Cardano.CertificateType.UpdateDelegateRepresentative:
return await updateDRepCertificate(cert, context);
return updateDRepCertificate(cert, context);
default:
throw new InvalidArgumentError('cert', `Certificate ${cert.__typename} not supported.`);
}
};

export const mapCerts = async (
export const mapCerts = (
certs: Cardano.Certificate[] | undefined,
context: LedgerTxTransformerContext
): Promise<Ledger.Certificate[] | null> => {
): Ledger.Certificate[] | null => {
if (!certs) return null;

return Promise.all(certs.map((coreCert) => toCert(coreCert, context)));
return certs.map((coreCert) => toCert(coreCert, context));
};
4 changes: 2 additions & 2 deletions packages/hardware-ledger/src/transformers/tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { mapWithdrawals } from './withdrawals';

export const LedgerTxTransformer: Transformer<Cardano.TxBody, Ledger.Transaction, LedgerTxTransformerContext> = {
auxiliaryData: ({ auxiliaryDataHash }) => mapAuxiliaryData(auxiliaryDataHash),
certificates: async ({ certificates }, context) => await mapCerts(certificates, context!),
certificates: ({ certificates }, context) => mapCerts(certificates, context!),
collateralInputs: ({ collaterals }, context) => mapCollateralTxIns(collaterals, context!),
collateralOutput: ({ collateralReturn }, context) => mapCollateralTxOut(collateralReturn, context!),
donation: ({ donation }) => donation,
Expand All @@ -36,7 +36,7 @@ export const LedgerTxTransformer: Transformer<Cardano.TxBody, Ledger.Transaction
treasury: ({ treasuryValue }) => treasuryValue,
ttl: ({ validityInterval }) => validityInterval?.invalidHereafter,
validityIntervalStart: ({ validityInterval }) => validityInterval?.invalidBefore,
votingProcedures: ({ votingProcedures }) => mapVotingProcedures(votingProcedures),
votingProcedures: ({ votingProcedures }, context) => mapVotingProcedures(votingProcedures, context!),
withdrawals: ({ withdrawals }, context) => mapWithdrawals(withdrawals, context!)
};

Expand Down
34 changes: 23 additions & 11 deletions packages/hardware-ledger/src/transformers/votingProcedures.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as Crypto from '@cardano-sdk/crypto';
import * as Ledger from '@cardano-foundation/ledgerjs-hw-app-cardano';
import { Cardano } from '@cardano-sdk/core';
import { Transform } from '@cardano-sdk/util';
import { InvalidArgumentError, Transform } from '@cardano-sdk/util';
import { LedgerTxTransformerContext } from '..';
import { util } from '@cardano-sdk/key-management';

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

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

case 'dRepKeyHash':
case 'dRepKeyHash': {
if (!context.dRepKeyHashHex || context.dRepKeyHashHex !== Crypto.Ed25519KeyHashHex(voter.credential.hash)) {
throw new Error('Foreign voter drepKeyHash');
}
return {
keyHashHex: voter.credential.hash,
type: Ledger.VoterType.DREP_KEY_HASH
} as Ledger.DRepKeyHashVoter;

keyPath: util.accountKeyDerivationPathToBip32Path(context.accountIndex, util.DREP_KEY_DERIVATION_PATH),
type: Ledger.VoterType.DREP_KEY_PATH
} as Ledger.DRepKeyPathVoter;
}
case 'dRepScriptHash':
return {
scriptHashHex: voter.credential.hash,
Expand Down Expand Up @@ -97,8 +104,12 @@ export const toVotes: Transform<Cardano.VotingProcedureVote[], Ledger.Vote[]> =
* @param votingProcedure A single voting procedure obj from Core.
* @returns {Ledger.VoterVotes} LedgerJS-compatible voting records
*/
export const toVotingProcedure: Transform<Cardano.VotingProcedures[0], Ledger.VoterVotes> = (votingProcedure) => ({
voter: toVoter(votingProcedure.voter),
export const toVotingProcedure: Transform<
Cardano.VotingProcedures[0],
Ledger.VoterVotes,
LedgerTxTransformerContext
> = (votingProcedure, context) => ({
voter: toVoter(votingProcedure.voter, context),
votes: toVotes(votingProcedure.votes)
});

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

export const mapVotingProcedures = (
votingProcedures: Cardano.VotingProcedures | undefined
votingProcedures: Cardano.VotingProcedures | undefined,
context: LedgerTxTransformerContext
): Ledger.VoterVotes[] | null =>
votingProcedures ? votingProcedures.map((votingProcedure) => toVotingProcedure(votingProcedure)) : null;
votingProcedures ? votingProcedures.map((votingProcedure) => toVotingProcedure(votingProcedure, context)) : null;
4 changes: 2 additions & 2 deletions packages/hardware-ledger/test/testData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ export const CONTEXT_WITH_KNOWN_ADDRESSES: LedgerTxTransformerContext = {
networkId: Cardano.NetworkId.Testnet,
networkMagic: 999
},
dRepPublicKey: Crypto.Ed25519PublicKeyHex('deeb8f82f2af5836ebbc1b450b6dbf0b03c93afe5696f10d49e8a8304ebfac01'),
dRepKeyHashHex: Crypto.Ed25519KeyHashHex(dRepCredential.hash),
knownAddresses: [
{
accountIndex: 0,
Expand Down Expand Up @@ -409,7 +409,7 @@ export const votes = [
export const dRepKeyHashVoter: Cardano.Voter = {
__typename: Cardano.VoterType.dRepKeyHash,
credential: {
hash: stakeCredential.hash,
hash: dRepCredential.hash,
type: Cardano.CredentialType.KeyHash
}
};
Expand Down
Loading

0 comments on commit ccaa53a

Please sign in to comment.