Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ada support conway #376

Draft
wants to merge 1 commit into
base: onekey
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 80 additions & 2 deletions packages/core/src/api/cardano/CardanoSignTransaction.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import semver from 'semver';
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
import { BaseMethod } from '../BaseMethod';
import { PROTO } from '../../constants';
import { UI_REQUEST } from '../../constants/ui-request';
Expand All @@ -24,7 +26,10 @@ import type {
CardanoSignedTxData,
CardanoSignedTxWitness,
CardanoAuxiliaryDataSupplement,
CardanoSignTransaction as CardanoSignTransactionType,
} from '../../types/api/cardano';
import { DeviceFirmwareRange } from '../../types';
import { getDeviceFirmwareVersion, getMethodVersionRange } from '../../utils';

export default class CardanoSignTransaction extends BaseMethod<any> {
hasBundle?: boolean;
Expand All @@ -48,6 +53,16 @@ export default class CardanoSignTransaction extends BaseMethod<any> {

const { payload } = this;

// convert legacy parameters to new parameter
// payload.auxiliaryData.governanceRegistrationParameters are legacy params kept for backward compatibility (for now)
if (payload.auxiliaryData && payload.auxiliaryData.governanceRegistrationParameters) {
ByteZhang1024 marked this conversation as resolved.
Show resolved Hide resolved
console.warn(
'Please use cVoteRegistrationParameters instead of governanceRegistrationParameters.'
);
ByteZhang1024 marked this conversation as resolved.
Show resolved Hide resolved
payload.auxiliaryData.cVoteRegistrationParameters =
payload.auxiliaryData.governanceRegistrationParameters;
}

// validate incoming parameters
validateParams(payload, [
{ name: 'signingMode', type: 'number', required: true },
Expand All @@ -69,6 +84,8 @@ export default class CardanoSignTransaction extends BaseMethod<any> {
{ name: 'additionalWitnessRequests', type: 'array', allowEmpty: true },
{ name: 'derivationType', type: 'number' },
{ name: 'includeNetworkId', type: 'boolean' },
{ name: 'chunkify', type: 'boolean' },
{ name: 'tagCborSets', type: 'boolean' },
]);

const inputsWithPath = payload.inputs.map(transformInput);
Expand Down Expand Up @@ -172,9 +189,65 @@ export default class CardanoSignTransaction extends BaseMethod<any> {
? payload.derivationType
: PROTO.CardanoDerivationType.ICARUS,
includeNetworkId: payload.includeNetworkId,
chunkify: payload.chunkify,
tagCborSets: payload.tagCborSets,
};
}

hasConway = () => {
const payload = this.payload as CardanoSignTransactionType;
if (payload.tagCborSets != null) {
return true;
}
if (payload.auxiliaryData?.cVoteRegistrationParameters != null) {
return true;
}
for (const certificate of payload.certificates ?? []) {
if (certificate.dRep != null) {
return true;
}
if (certificate.deposit != null) {
return true;
}
if (
certificate.type === PROTO.CardanoCertificateType.STAKE_REGISTRATION_CONWAY ||
certificate.type === PROTO.CardanoCertificateType.STAKE_DEREGISTRATION_CONWAY ||
certificate.type === PROTO.CardanoCertificateType.VOTE_DELEGATION
) {
return true;
}
}

return false;
};

supportConwayVersionRange = (): DeviceFirmwareRange => ({
model_touch: {
min: '4.11.0',
},
});

checkSupportConway = () => {
const firmwareVersion = getDeviceFirmwareVersion(this.device.features)?.join('.');

ByteZhang1024 marked this conversation as resolved.
Show resolved Hide resolved
const versionRange = getMethodVersionRange(
this.device.features,
type => this.supportConwayVersionRange()[type]
);

if (!versionRange) {
return;
}

if (!semver.valid(firmwareVersion) || semver.lt(firmwareVersion, versionRange.min)) {
throw ERRORS.TypedError(
HardwareErrorCode.CallMethodNeedUpgradeFirmware,
`Device firmware version is too low, please update to ${versionRange.min}`,
{ current: firmwareVersion, require: versionRange.min }
);
}
};
ByteZhang1024 marked this conversation as resolved.
Show resolved Hide resolved

async signTx(): Promise<CardanoSignedTxData> {
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());

Expand Down Expand Up @@ -202,6 +275,8 @@ export default class CardanoSignTransaction extends BaseMethod<any> {
reference_inputs_count: this.params.referenceInputs.length,
derivation_type: this.params.derivationType,
include_network_id: this.params.includeNetworkId,
chunkify: this.params.chunkify,
tag_cbor_sets: this.params.tagCborSets,
};

// init
Expand Down Expand Up @@ -250,9 +325,10 @@ export default class CardanoSignTransaction extends BaseMethod<any> {
auxiliaryDataSupplement = {
type: auxiliaryDataType,
auxiliaryDataHash: message.auxiliary_data_hash as unknown as string,
cVoteRegistrationSignature: message.cvote_registration_signature,
// @ts-expect-error
governanceSignature: message.governance_signature,
catalystSignature: message.governance_signature,
catalystSignature: message.cvote_registration_signature,
governanceSignature: message.cvote_registration_signature,
ByteZhang1024 marked this conversation as resolved.
Show resolved Hide resolved
};
}
await typedCall('CardanoTxHostAck', 'CardanoTxItemAck');
Expand Down Expand Up @@ -310,6 +386,8 @@ export default class CardanoSignTransaction extends BaseMethod<any> {
}

run() {
this.checkSupportConway();

return this.signTx();
}
}
87 changes: 56 additions & 31 deletions packages/core/src/api/cardano/helper/auxiliaryData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,65 @@ import { validateParams } from '../../helpers/paramsValidator';
import { validatePath } from '../../helpers/pathUtils';
import type {
CardanoAuxiliaryData,
CardanoGovernanceRegistrationDelegation,
CardanoGovernanceRegistrationParameters,
CardanoCVoteRegistrationParameters,
CardanoCVoteRegistrationDelegation,
} from '../../../types/api/cardano';
import { PROTO } from '../../../constants';

const MAX_DELEGATION_COUNT = 32;

const transformDelegation = (
delegation: CardanoGovernanceRegistrationDelegation
): PROTO.CardanoGovernanceRegistrationDelegation => {
delegation: CardanoCVoteRegistrationDelegation
): PROTO.CardanoCVoteRegistrationDelegation => {
// @ts-expect-error votingPublicKey is a legacy param kept for backward compatibility (for now)
if (delegation.votingPublicKey) {
console.warn('Please use votePublicKey instead of votingPublicKey.');
// @ts-expect-error
delegation.votePublicKey = delegation.votingPublicKey;
}

Comment on lines +22 to +27
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

避免修改函数参数

transformDelegation 函数中,直接修改了传入的 delegation 对象。为避免副作用,建议创建新的对象来处理参数转换。

建议修改如下:

if (delegation.votingPublicKey) {
  console.warn('Please use votePublicKey instead of votingPublicKey.');
- delegation.votePublicKey = delegation.votingPublicKey;
+ const votePublicKey = delegation.votingPublicKey;
} else {
+ const votePublicKey = delegation.votePublicKey;
}

return {
- vote_public_key: delegation.votePublicKey,
+ vote_public_key: votePublicKey,
  weight: delegation.weight,
};

Committable suggestion was skipped due to low confidence.

validateParams(delegation, [
{ name: 'votingPublicKey', type: 'string', required: true },
{ name: 'weight', type: 'uint', required: true },
]);

return {
voting_public_key: delegation.votingPublicKey,
vote_public_key: delegation.votePublicKey,
weight: delegation.weight,
};
};

ByteZhang1024 marked this conversation as resolved.
Show resolved Hide resolved
const transformGovernanceRegistrationParameters = (
governanceRegistrationParameters: CardanoGovernanceRegistrationParameters
): PROTO.CardanoGovernanceRegistrationParametersType => {
validateParams(governanceRegistrationParameters, [
{ name: 'votingPublicKey', type: 'string' },
const transformCvoteRegistrationParameters = (
cVoteRegistrationParameters: CardanoCVoteRegistrationParameters
): PROTO.CardanoCVoteRegistrationParametersType => {
// votingPublicKey and rewardAddressParameters are legacy params kept for backward compatibility (for now)
// @ts-expect-error
if (cVoteRegistrationParameters.votingPublicKey) {
console.warn('Please use votePublicKey instead of votingPublicKey.');
// @ts-expect-error
cVoteRegistrationParameters.votePublicKey = cVoteRegistrationParameters.votingPublicKey;
}
// @ts-expect-error
if (cVoteRegistrationParameters.rewardAddressParameters) {
console.warn('Please use paymentAddressParameters instead of rewardAddressParameters.');
cVoteRegistrationParameters.paymentAddressParameters =
// @ts-expect-error
cVoteRegistrationParameters.rewardAddressParameters;
}
Comment on lines +44 to +55
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

避免修改函数参数

同样地,在 transformCvoteRegistrationParameters 函数中,直接修改了传入的 cVoteRegistrationParameters 对象。建议使用新的对象来处理,避免对原始参数的修改。

建议修改如下:

let { votePublicKey, paymentAddressParameters } = cVoteRegistrationParameters;

if (cVoteRegistrationParameters.votingPublicKey) {
  console.warn('Please use votePublicKey instead of votingPublicKey.');
- cVoteRegistrationParameters.votePublicKey = cVoteRegistrationParameters.votingPublicKey;
+ votePublicKey = cVoteRegistrationParameters.votingPublicKey;
}

if (cVoteRegistrationParameters.rewardAddressParameters) {
  console.warn('Please use paymentAddressParameters instead of rewardAddressParameters.');
- cVoteRegistrationParameters.paymentAddressParameters = cVoteRegistrationParameters.rewardAddressParameters;
+ paymentAddressParameters = cVoteRegistrationParameters.rewardAddressParameters;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (cVoteRegistrationParameters.votingPublicKey) {
console.warn('Please use votePublicKey instead of votingPublicKey.');
// @ts-expect-error
cVoteRegistrationParameters.votePublicKey = cVoteRegistrationParameters.votingPublicKey;
}
// @ts-expect-error
if (cVoteRegistrationParameters.rewardAddressParameters) {
console.warn('Please use paymentAddressParameters instead of rewardAddressParameters.');
cVoteRegistrationParameters.paymentAddressParameters =
// @ts-expect-error
cVoteRegistrationParameters.rewardAddressParameters;
}
let { votePublicKey, paymentAddressParameters } = cVoteRegistrationParameters;
if (cVoteRegistrationParameters.votingPublicKey) {
console.warn('Please use votePublicKey instead of votingPublicKey.');
votePublicKey = cVoteRegistrationParameters.votingPublicKey;
}
if (cVoteRegistrationParameters.rewardAddressParameters) {
console.warn('Please use paymentAddressParameters instead of rewardAddressParameters.');
paymentAddressParameters = cVoteRegistrationParameters.rewardAddressParameters;
}


validateParams(cVoteRegistrationParameters, [
{ name: 'votePublicKey', type: 'string' },
{ name: 'stakingPath', required: true },
{ name: 'nonce', type: 'uint', required: true },
{ name: 'format', type: 'number' },
{ name: 'delegations', type: 'array', allowEmpty: true },
{ name: 'votingPurpose', type: 'uint' },
{ name: 'paymentAddress', type: 'string' },
]);
validateAddressParameters(governanceRegistrationParameters.rewardAddressParameters);
const { paymentAddressParameters } = cVoteRegistrationParameters;
validateAddressParameters(paymentAddressParameters);
Comment on lines +66 to +67
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

检查 'paymentAddressParameters' 是否为 undefined

在调用 validateAddressParameters(paymentAddressParameters) 之前,应该检查 paymentAddressParameters 是否为 undefined,以防止可能的错误。

建议修改如下:

const { paymentAddressParameters } = cVoteRegistrationParameters;
+ if (paymentAddressParameters) {
    validateAddressParameters(paymentAddressParameters);
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { paymentAddressParameters } = cVoteRegistrationParameters;
validateAddressParameters(paymentAddressParameters);
const { paymentAddressParameters } = cVoteRegistrationParameters;
if (paymentAddressParameters) {
validateAddressParameters(paymentAddressParameters);
}


const { delegations } = governanceRegistrationParameters;
const { delegations } = cVoteRegistrationParameters;
if (delegations && delegations.length > MAX_DELEGATION_COUNT) {
throw ERRORS.TypedError(
HardwareErrorCode.CallMethodInvalidParameter,
Expand All @@ -51,15 +75,16 @@ const transformGovernanceRegistrationParameters = (
}

return {
voting_public_key: governanceRegistrationParameters.votingPublicKey,
staking_path: validatePath(governanceRegistrationParameters.stakingPath, 3),
reward_address_parameters: addressParametersToProto(
governanceRegistrationParameters.rewardAddressParameters
),
nonce: governanceRegistrationParameters.nonce as unknown as number,
format: governanceRegistrationParameters.format,
delegations: delegations?.map(transformDelegation) as any,
voting_purpose: governanceRegistrationParameters.votingPurpose,
vote_public_key: cVoteRegistrationParameters.votePublicKey,
staking_path: validatePath(cVoteRegistrationParameters.stakingPath, 3),
payment_address_parameters: paymentAddressParameters
? addressParametersToProto(paymentAddressParameters)
: undefined,
nonce: cVoteRegistrationParameters.nonce as unknown as number,
ByteZhang1024 marked this conversation as resolved.
Show resolved Hide resolved
format: cVoteRegistrationParameters.format,
delegations: delegations?.map(transformDelegation) ?? [],
voting_purpose: cVoteRegistrationParameters.votingPurpose,
payment_address: cVoteRegistrationParameters.paymentAddress,
};
};

Expand All @@ -73,32 +98,32 @@ export const transformAuxiliaryData = (
},
]);

let governanceRegistrationParameters;
if (auxiliaryData.governanceRegistrationParameters) {
governanceRegistrationParameters = transformGovernanceRegistrationParameters(
auxiliaryData.governanceRegistrationParameters
let cVoteRegistrationParameters;
if (auxiliaryData.cVoteRegistrationParameters) {
cVoteRegistrationParameters = transformCvoteRegistrationParameters(
auxiliaryData.cVoteRegistrationParameters
);
}

return {
hash: auxiliaryData.hash,
governance_registration_parameters: governanceRegistrationParameters,
cvote_registration_parameters: cVoteRegistrationParameters,
};
};

export const modifyAuxiliaryDataForBackwardsCompatibility = (
auxiliary_data: PROTO.CardanoTxAuxiliaryData
): PROTO.CardanoTxAuxiliaryData => {
const { governance_registration_parameters } = auxiliary_data;
if (governance_registration_parameters) {
governance_registration_parameters.reward_address_parameters =
const { cvote_registration_parameters } = auxiliary_data;
if (cvote_registration_parameters?.payment_address_parameters) {
cvote_registration_parameters.payment_address_parameters =
modifyAddressParametersForBackwardsCompatibility(
governance_registration_parameters.reward_address_parameters
cvote_registration_parameters.payment_address_parameters
);

return {
...auxiliary_data,
governance_registration_parameters,
cvote_registration_parameters,
};
}

Expand Down
47 changes: 47 additions & 0 deletions packages/core/src/api/cardano/helper/certificate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
CardanoPoolMetadata,
CardanoPoolRelay,
CardanoPoolOwner,
CardanoDRep,
} from '../../../types/api/cardano';
import { PROTO } from '../../../constants';

Expand Down Expand Up @@ -156,6 +157,37 @@ const transformPoolParameters = (
};
};

const transformDRep = (dRep: CardanoDRep | undefined): PROTO.CardanoDRep | undefined => {
if (!dRep) {
return undefined;
}

validateParams(dRep, [
{ name: 'type', type: 'number', required: true },
{ name: 'keyHash', type: 'string' },
{ name: 'scriptHash', type: 'string' },
]);

if (dRep.type === PROTO.CardanoDRepType.KEY_HASH && !dRep.keyHash) {
throw ERRORS.TypedError(
HardwareErrorCode.CallMethodInvalidParameter,
'key_hash must be supplied for key_hash type'
);
}

if (dRep.type === PROTO.CardanoDRepType.SCRIPT_HASH && !dRep.scriptHash) {
throw ERRORS.TypedError(
HardwareErrorCode.CallMethodInvalidParameter,
'script_hash must be supplied for script_hash type'
);
}
return {
type: dRep.type,
key_hash: dRep.keyHash,
script_hash: dRep.scriptHash,
};
};
Comment on lines +160 to +189
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

建议处理未知的 dRep.type

当前的 transformDRep 函数只处理了 KEY_HASHSCRIPT_HASH 类型。如果出现未知的 dRep.type,函数可能返回未定义的 key_hashscript_hash,可能导致意外行为。建议添加错误处理,以捕获未知的 dRep.type,提高代码的健壮性。

可以考虑以下修改:

   if (dRep.type === PROTO.CardanoDRepType.SCRIPT_HASH && !dRep.scriptHash) {
     throw ERRORS.TypedError(
       HardwareErrorCode.CallMethodInvalidParameter,
       'script_hash must be supplied for script_hash type'
     );
+  } else if (dRep.type !== PROTO.CardanoDRepType.KEY_HASH && dRep.type !== PROTO.CardanoDRepType.SCRIPT_HASH) {
+    throw ERRORS.TypedError(
+      HardwareErrorCode.CallMethodInvalidParameter,
+      `Unsupported dRep type: ${dRep.type}`
+    );
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const transformDRep = (dRep: CardanoDRep | undefined): PROTO.CardanoDRep | undefined => {
if (!dRep) {
return undefined;
}
validateParams(dRep, [
{ name: 'type', type: 'number', required: true },
{ name: 'keyHash', type: 'string' },
{ name: 'scriptHash', type: 'string' },
]);
if (dRep.type === PROTO.CardanoDRepType.KEY_HASH && !dRep.keyHash) {
throw ERRORS.TypedError(
HardwareErrorCode.CallMethodInvalidParameter,
'key_hash must be supplied for key_hash type'
);
}
if (dRep.type === PROTO.CardanoDRepType.SCRIPT_HASH && !dRep.scriptHash) {
throw ERRORS.TypedError(
HardwareErrorCode.CallMethodInvalidParameter,
'script_hash must be supplied for script_hash type'
);
}
return {
type: dRep.type,
key_hash: dRep.keyHash,
script_hash: dRep.scriptHash,
};
};
const transformDRep = (dRep: CardanoDRep | undefined): PROTO.CardanoDRep | undefined => {
if (!dRep) {
return undefined;
}
validateParams(dRep, [
{ name: 'type', type: 'number', required: true },
{ name: 'keyHash', type: 'string' },
{ name: 'scriptHash', type: 'string' },
]);
if (dRep.type === PROTO.CardanoDRepType.KEY_HASH && !dRep.keyHash) {
throw ERRORS.TypedError(
HardwareErrorCode.CallMethodInvalidParameter,
'key_hash must be supplied for key_hash type'
);
}
if (dRep.type === PROTO.CardanoDRepType.SCRIPT_HASH && !dRep.scriptHash) {
throw ERRORS.TypedError(
HardwareErrorCode.CallMethodInvalidParameter,
'script_hash must be supplied for script_hash type'
);
} else if (dRep.type !== PROTO.CardanoDRepType.KEY_HASH && dRep.type !== PROTO.CardanoDRepType.SCRIPT_HASH) {
throw ERRORS.TypedError(
HardwareErrorCode.CallMethodInvalidParameter,
`Unsupported dRep type: ${dRep.type}`
);
}
return {
type: dRep.type,
key_hash: dRep.keyHash,
script_hash: dRep.scriptHash,
};
};


export const transformCertificate = (
certificate: CardanoCertificate
): CertificateWithPoolOwnersAndRelays => {
Expand All @@ -176,12 +208,25 @@ export const transformCertificate = (
paramsToValidate.push({ name: 'poolParameters', type: 'object', required: true });
}

if (
certificate.type === PROTO.CardanoCertificateType.STAKE_REGISTRATION_CONWAY ||
certificate.type === PROTO.CardanoCertificateType.STAKE_DEREGISTRATION_CONWAY
) {
paramsToValidate.push({ name: 'deposit', required: true });
}
ByteZhang1024 marked this conversation as resolved.
Show resolved Hide resolved

if (certificate.type === PROTO.CardanoCertificateType.VOTE_DELEGATION) {
paramsToValidate.push({ name: 'dRep', type: 'object', required: true });
}

validateParams(certificate, paramsToValidate);

const { poolParameters, poolOwners, poolRelays } = transformPoolParameters(
certificate.poolParameters
);

const dRep = transformDRep(certificate.dRep);

return {
certificate: {
type: certificate.type,
Expand All @@ -190,6 +235,8 @@ export const transformCertificate = (
key_hash: certificate.keyHash,
pool: certificate.pool,
pool_parameters: poolParameters,
deposit: certificate.deposit,
drep: dRep,
},
poolOwners,
poolRelays,
Expand Down
Loading
Loading