Skip to content

Commit

Permalink
feat: sign new certificate types
Browse files Browse the repository at this point in the history
  • Loading branch information
mchappell authored and vetalcore committed Apr 17, 2024
1 parent b0c08fd commit 5bae9c7
Show file tree
Hide file tree
Showing 14 changed files with 614 additions and 155 deletions.
2 changes: 1 addition & 1 deletion packages/hardware-ledger/package.json
Expand Up @@ -49,7 +49,7 @@
"typescript": "^4.7.4"
},
"dependencies": {
"@cardano-foundation/ledgerjs-hw-app-cardano": "^6.0.0",
"@cardano-foundation/ledgerjs-hw-app-cardano": "^7.0.0",
"@cardano-sdk/core": "workspace:~",
"@cardano-sdk/crypto": "workspace:~",
"@cardano-sdk/key-management": "workspace:~",
Expand Down
268 changes: 187 additions & 81 deletions packages/hardware-ledger/src/transformers/certificates.ts
@@ -1,119 +1,122 @@
/* eslint-disable complexity */
import * as Crypto from '@cardano-sdk/crypto';
import * as Ledger from '@cardano-foundation/ledgerjs-hw-app-cardano';
import { Cardano } from '@cardano-sdk/core';
import { InvalidArgumentError, Transform } from '@cardano-sdk/util';
import { LedgerTxTransformerContext } from '../types';
import { util } from '@cardano-sdk/key-management';

type StakeKeyCertificateType = Ledger.CertificateType.STAKE_REGISTRATION | Ledger.CertificateType.STAKE_DEREGISTRATION;

type StakeKeyCertificate = {
params: {
stakeCredential: Ledger.StakeCredentialParams;
};
type: StakeKeyCertificateType;
};

const getStakeAddressCertificate = (
certificate: Cardano.StakeAddressCertificate,
context: LedgerTxTransformerContext,
type: StakeKeyCertificateType
): StakeKeyCertificate => {
const knownAddress = context?.knownAddresses.find(
(address) =>
Cardano.RewardAccount.toHash(address.rewardAccount) ===
(certificate.stakeCredential.hash as unknown as Crypto.Ed25519KeyHashHex)
);

const rewardAddress = knownAddress ? Cardano.Address.fromBech32(knownAddress.rewardAccount)?.asReward() : null;
const path = util.stakeKeyPathFromGroupedAddress(knownAddress);
const credentialType = rewardAddress
? rewardAddress.getPaymentCredential().type
: Ledger.StakeCredentialParamsType.SCRIPT_HASH;
const mapAnchorToParams = (certificate: Cardano.Certificate) => ({
...('anchor' in certificate &&
certificate?.anchor && { anchor: { hashHex: certificate.anchor.dataHash, url: certificate.anchor.url } })
});

let credential: Ledger.StakeCredentialParams;
const credentialMapper = (
credential: Cardano.Credential,
credentialType: Cardano.CredentialType | Ledger.CredentialParamsType.SCRIPT_HASH,
path: Crypto.BIP32Path | null
): Ledger.CredentialParams => {
let credentialParams: Ledger.CredentialParams;

switch (credentialType) {
case Cardano.CredentialType.KeyHash: {
credential = path
credentialParams = path
? {
keyPath: path,
type: Ledger.StakeCredentialParamsType.KEY_PATH
type: Ledger.CredentialParamsType.KEY_PATH
}
: {
keyHashHex: certificate.stakeCredential.hash,
type: Ledger.StakeCredentialParamsType.KEY_HASH
keyHashHex: credential.hash,
type: Ledger.CredentialParamsType.KEY_HASH
};
break;
}
case Cardano.CredentialType.ScriptHash:
default: {
credential = {
scriptHashHex: certificate.stakeCredential.hash,
type: Ledger.StakeCredentialParamsType.SCRIPT_HASH
credentialParams = {
scriptHashHex: credential.hash,
type: Ledger.CredentialParamsType.SCRIPT_HASH
};
}
}

return {
params: {
stakeCredential: credential
},
type
};
return credentialParams;
};

export const stakeDelegationCertificate: Transform<
Cardano.StakeDelegationCertificate,
Ledger.Certificate,
LedgerTxTransformerContext
> = (certificate, context): Ledger.Certificate => {
const poolIdKeyHash = Cardano.PoolId.toKeyHash(certificate.poolId);
const stakeCredentialMapper = (
credential: Cardano.Credential,
context: LedgerTxTransformerContext
): Ledger.CredentialParams => {
const knownAddress = context?.knownAddresses.find(
(address) =>
Cardano.RewardAccount.toHash(address.rewardAccount) ===
(certificate.stakeCredential.hash as unknown as Crypto.Ed25519KeyHashHex)
Cardano.RewardAccount.toHash(address.rewardAccount) === (credential.hash as unknown as Crypto.Ed25519KeyHashHex)
);
const rewardAddress = knownAddress ? Cardano.Address.fromBech32(knownAddress.rewardAccount)?.asReward() : null;

const rewardAddress = knownAddress ? Cardano.Address.fromBech32(knownAddress.rewardAccount)?.asReward() : null;
const path = util.stakeKeyPathFromGroupedAddress(knownAddress);
const credentialType = rewardAddress
? rewardAddress.getPaymentCredential().type
: Ledger.StakeCredentialParamsType.SCRIPT_HASH;
: Ledger.CredentialParamsType.SCRIPT_HASH;

const path = util.stakeKeyPathFromGroupedAddress(knownAddress);
return credentialMapper(credential, credentialType, path);
};

let credential: Ledger.StakeCredentialParams;
const getStakeAddressCertificate = (
certificate: Cardano.StakeAddressCertificate,
context: LedgerTxTransformerContext,
type: Ledger.CertificateType.STAKE_REGISTRATION | Ledger.CertificateType.STAKE_DEREGISTRATION
): Ledger.Certificate => ({
params: {
stakeCredential: stakeCredentialMapper(certificate.stakeCredential, context)
},
type
});

switch (credentialType) {
case Cardano.CredentialType.KeyHash: {
credential = path
? {
keyPath: path,
type: Ledger.StakeCredentialParamsType.KEY_PATH
}
: {
keyHashHex: certificate.stakeCredential.hash,
type: Ledger.StakeCredentialParamsType.KEY_HASH
};
break;
}
case Cardano.CredentialType.ScriptHash:
default: {
credential = {
scriptHashHex: certificate.stakeCredential.hash,
type: Ledger.StakeCredentialParamsType.SCRIPT_HASH
};
}
}
const getNewStakeAddressCertificate = (
certificate: Cardano.NewStakeAddressCertificate,
context: LedgerTxTransformerContext,
type: Ledger.CertificateType.STAKE_REGISTRATION_CONWAY | Ledger.CertificateType.STAKE_DEREGISTRATION_CONWAY
): Ledger.Certificate => ({
params: {
deposit: certificate.deposit,
stakeCredential: stakeCredentialMapper(certificate.stakeCredential, context)
},
type
});

return {
params: {
poolKeyHashHex: poolIdKeyHash,
stakeCredential: credential
},
type: Ledger.CertificateType.STAKE_DELEGATION
};
};
const getAuthorizeCommitteeHotCertificate = (
certificate: Cardano.AuthorizeCommitteeHotCertificate,
context: LedgerTxTransformerContext
): Ledger.Certificate => ({
params: {
coldCredential: stakeCredentialMapper(certificate.coldCredential, context),
hotCredential: stakeCredentialMapper(certificate.hotCredential, context)
},
type: Ledger.CertificateType.AUTHORIZE_COMMITTEE_HOT
});

const getResignCommitteeColdCertificate = (
certificate: Cardano.ResignCommitteeColdCertificate,
context: LedgerTxTransformerContext
): Ledger.Certificate => ({
params: {
...mapAnchorToParams(certificate),
coldCredential: stakeCredentialMapper(certificate.coldCredential, context)
},
type: Ledger.CertificateType.RESIGN_COMMITTEE_COLD
});

export const stakeDelegationCertificate: Transform<
Cardano.StakeDelegationCertificate,
Ledger.Certificate,
LedgerTxTransformerContext
> = (certificate, context): Ledger.Certificate => ({
params: {
poolKeyHashHex: Cardano.PoolId.toKeyHash(certificate.poolId),
stakeCredential: stakeCredentialMapper(certificate.stakeCredential, context!)
},
type: Ledger.CertificateType.STAKE_DELEGATION
});

const toPoolMetadata: Transform<Cardano.PoolMetadataJson, Ledger.PoolMetadataParams> = (metadataJson) => ({
metadataHashHex: metadataJson.hash,
Expand Down Expand Up @@ -245,7 +248,86 @@ const poolRetirementCertificate: Transform<
};
};

const toCert = (cert: Cardano.Certificate, context: LedgerTxTransformerContext) => {
const checkDrepPublicKeyAgainstCredential = (
dRepPublicKey: Crypto.Ed25519PublicKeyHex | undefined,
hash: Crypto.Hash28ByteBase16
) => {
if (!dRepPublicKey || dRepPublicKey !== Crypto.Ed25519PublicKeyHex(hash)) {
throw new InvalidArgumentError('certificate', 'dRepPublicKey does not match certificate drep credential.');
}
};

const drepRegistrationCertificate = (
certificate: Cardano.RegisterDelegateRepresentativeCertificate | Cardano.UnRegisterDelegateRepresentativeCertificate,
context: LedgerTxTransformerContext,
type: Extract<
Ledger.CertificateType,
Ledger.CertificateType.DREP_REGISTRATION | Ledger.CertificateType.DREP_DEREGISTRATION
>
): Ledger.Certificate => {
checkDrepPublicKeyAgainstCredential(context?.dRepPublicKey, certificate.dRepCredential.hash);

const params: Ledger.DRepRegistrationParams = {
...mapAnchorToParams(certificate),
dRepCredential: credentialMapper(certificate.dRepCredential, certificate.dRepCredential.type, null),
deposit: certificate.deposit
};

return {
params,
type
};
};

const drepParamsMapper = (
drep: Cardano.Credential,
path: Crypto.BIP32Path | null
): Ledger.KeyPathDRepParams | Ledger.KeyHashDRepParams | Ledger.ScriptHashDRepParams => {
let dRepParams: Ledger.DRepParams;

switch (drep.type) {
case Cardano.CredentialType.KeyHash: {
dRepParams = path
? {
keyPath: path,
type: Ledger.DRepParamsType.KEY_PATH
}
: {
keyHashHex: drep.hash,
type: Ledger.DRepParamsType.KEY_HASH
};
break;
}
case Cardano.CredentialType.ScriptHash:
default: {
dRepParams = {
scriptHashHex: drep.hash,
type: Ledger.DRepParamsType.SCRIPT_HASH
};
}
}

return dRepParams;
};

// TODO: do we need to do the same as in stakeCredentialMapper?
const drepMapper = (drep: Cardano.DelegateRepresentative): Ledger.DRepParams => {
if (Cardano.isDRepAlwaysAbstain(drep)) {
return {
type: Ledger.DRepParamsType.ABSTAIN
};
} else if (Cardano.isDRepAlwaysNoConfidence(drep)) {
return {
type: Ledger.DRepParamsType.NO_CONFIDENCE
};
} else if (Cardano.isDRepCredential(drep)) {
// TODO: review
return drepParamsMapper(drep, null);
}
throw new Error('incorrect drep supplied');
};

const toCert = (cert: Cardano.Certificate, context: LedgerTxTransformerContext): Ledger.Certificate => {
switch (cert.__typename) {
case Cardano.CertificateType.StakeRegistration:
return getStakeAddressCertificate(cert, context, Ledger.CertificateType.STAKE_REGISTRATION);
Expand All @@ -257,6 +339,30 @@ const toCert = (cert: Cardano.Certificate, context: LedgerTxTransformerContext)
return poolRegistrationCertificate(cert, context);
case Cardano.CertificateType.PoolRetirement:
return poolRetirementCertificate(cert, context);

// Conway Era Certs
case Cardano.CertificateType.Registration:
return getNewStakeAddressCertificate(cert, context, Ledger.CertificateType.STAKE_DEREGISTRATION_CONWAY);
case Cardano.CertificateType.Unregistration:
return getNewStakeAddressCertificate(cert, context, Ledger.CertificateType.STAKE_DEREGISTRATION_CONWAY);
case Cardano.CertificateType.VoteDelegation:
return {
params: {
// TODO: review
dRep: drepMapper(cert.dRep),
stakeCredential: stakeCredentialMapper(cert.stakeCredential, context)
},
type: Ledger.CertificateType.VOTE_DELEGATION
};
case Cardano.CertificateType.AuthorizeCommitteeHot:
return getAuthorizeCommitteeHotCertificate(cert, context);
case Cardano.CertificateType.ResignCommitteeCold:
return getResignCommitteeColdCertificate(cert, context);
case Cardano.CertificateType.RegisterDelegateRepresentative:
return drepRegistrationCertificate(cert, context, Ledger.CertificateType.DREP_REGISTRATION);
case Cardano.CertificateType.UnregisterDelegateRepresentative:
return drepRegistrationCertificate(cert, context, Ledger.CertificateType.DREP_DEREGISTRATION);
case Cardano.CertificateType.UpdateDelegateRepresentative:
default:
throw new InvalidArgumentError('cert', `Certificate ${cert.__typename} not supported.`);
}
Expand Down
1 change: 1 addition & 0 deletions packages/hardware-ledger/src/transformers/index.ts
Expand Up @@ -9,3 +9,4 @@ export * from './tx';
export * from './txIn';
export * from './txOut';
export * from './withdrawals';
export * from './votingProcedures';
4 changes: 4 additions & 0 deletions packages/hardware-ledger/src/transformers/tx.ts
Expand Up @@ -12,12 +12,14 @@ import { mapTokenMap } from './assets';
import { mapTxIns } from './txIn';
import { mapTxOuts } from './txOut';
import { mapWithdrawals } from './withdrawals';
import { mapVotingProcedures } from './votingProcedures';

export const LedgerTxTransformer: Transformer<Cardano.TxBody, Ledger.Transaction, LedgerTxTransformerContext> = {
auxiliaryData: ({ auxiliaryDataHash }) => mapAuxiliaryData(auxiliaryDataHash),
certificates: ({ certificates }, context) => mapCerts(certificates, context!),
collateralInputs: ({ collaterals }, context) => mapCollateralTxIns(collaterals, context!),
collateralOutput: ({ collateralReturn }, context) => mapCollateralTxOut(collateralReturn, context!),
donation: ({ donation }) => donation,
fee: ({ fee }) => fee,
includeNetworkId: ({ networkId }) => !!networkId,
inputs: ({ inputs }, context) => mapTxIns(inputs, context!),
Expand All @@ -31,8 +33,10 @@ export const LedgerTxTransformer: Transformer<Cardano.TxBody, Ledger.Transaction
requiredSigners: ({ requiredExtraSignatures }, context) => mapRequiredSigners(requiredExtraSignatures, context!),
scriptDataHashHex: ({ scriptIntegrityHash }) => scriptIntegrityHash?.toString(),
totalCollateral: ({ totalCollateral }) => totalCollateral,
treasury: ({ treasuryValue }) => treasuryValue,
ttl: ({ validityInterval }) => validityInterval?.invalidHereafter,
validityIntervalStart: ({ validityInterval }) => validityInterval?.invalidBefore,
votingProcedures: ({ votingProcedures }) => mapVotingProcedures(votingProcedures),
withdrawals: ({ withdrawals }, context) => mapWithdrawals(withdrawals, context!)
};

Expand Down

0 comments on commit 5bae9c7

Please sign in to comment.