Skip to content
Open
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cardano-hw-cli",
"version": "1.6.2",
"version": "1.7.0",
"engines": {
"node": "12.16.2"
},
Expand Down Expand Up @@ -43,7 +43,7 @@
"cardano-crypto.js": "^5.3.6-rc.6",
"cbor": "^7.0.4",
"rw": "1.3.3",
"trezor-connect": "https://github.com/vacuumlabs/connect/releases/download/8.1.27-catalyst-rc.3-extended/trezor-connect-v8.1.27-catalyst-rc.3-extended.tgz"
"trezor-connect": "./trezor-connect-v8.1.29-extended.tgz"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.2.0",
Expand Down
29 changes: 9 additions & 20 deletions src/crypto-providers/ledgerCryptoProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,20 @@ import {
TxRelayTypes,
TxWitnessKeys,
_MultiAsset,
VotingRegistrationAuxiliaryData,
VotingRegistrationMetaDataCborHex,
_UnsignedTxParsed,
TxWitnesses,
} from '../transaction/types'
import {
Address,
BIP32Path,
HexString,
HwSigningData,
Network,
VotePublicKeyHex,
XPubKeyHex,
} from '../types'
import { encodeCbor, partition } from '../util'
import { partition } from '../util'
import { LEDGER_VERSIONS } from './constants'
import { LedgerCryptoProviderFeature, LedgerWitness } from './ledgerTypes'
import { CryptoProvider, _AddressParameters } from './types'
Expand All @@ -58,14 +58,13 @@ import {
isDeviceVersionGTE,
filterSigningFiles,
getAddressParameters,
formatVotingRegistrationMetaData,
splitXPubKeyCborHex,
extractStakePubKeyFromHwSigningData,
validateVotingRegistrationAddressType,
findSigningPathForKey,
encodeVotingRegistrationMetaData,
} from './util'

const { blake2b, bech32 } = require('cardano-crypto.js')
const { bech32 } = require('cardano-crypto.js')

const TransportNodeHid = require('@ledgerhq/hw-transport-node-hid').default

Expand Down Expand Up @@ -607,24 +606,14 @@ export const LedgerCryptoProvider: () => Promise<CryptoProvider> = async () => {
const response = await ledger.signTransaction(dummyTx)
if (!response?.auxiliaryDataSupplement) throw Error(Errors.MissingAuxiliaryDataSupplement)

const stakePubHex = extractStakePubKeyFromHwSigningData(hwStakeSigningFile)
const votingRegistrationMetaData = formatVotingRegistrationMetaData(
Buffer.from(votePublicKeyHex, 'hex'),
Buffer.from(stakePubHex, 'hex'),
return encodeVotingRegistrationMetaData(
hwStakeSigningFile,
votePublicKeyHex,
address,
nonce,
Buffer.from(response.auxiliaryDataSupplement.catalystRegistrationSignatureHex, 'hex'),
response.auxiliaryDataSupplement.auxiliaryDataHashHex as HexString,
response.auxiliaryDataSupplement.catalystRegistrationSignatureHex as HexString,
)

const auxiliaryData: VotingRegistrationAuxiliaryData = [votingRegistrationMetaData, []]
const auxiliaryDataCbor = encodeCbor(auxiliaryData)

const auxiliaryDataHashHex = blake2b(auxiliaryDataCbor, 32).toString('hex')
if (response.auxiliaryDataSupplement.auxiliaryDataHashHex !== auxiliaryDataHashHex) {
throw Error(Errors.MetadataSerializationMismatchError)
}

return encodeCbor(votingRegistrationMetaData).toString('hex')
}

const createWitnesses = (ledgerWitnesses: LedgerWitness[], signingFiles: HwSigningData[]): TxWitnesses => {
Expand Down
160 changes: 117 additions & 43 deletions src/crypto-providers/trezorCryptoProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,28 @@ import {
_PoolRelay,
_MultiAsset,
VotingRegistrationMetaDataCborHex,
VotingRegistrationMetaData,
_UnsignedTxParsed,
TxWitnesses,
TxWitnessKeys,
} from '../transaction/types'
import {
CryptoProvider,
DeviceVersion,
_AddressParameters,
} from './types'
import { TrezorCryptoProviderFeature } from './trezorTypes'
import {
TrezorCryptoProviderFeature,
} from './trezorTypes'
import {
Witness,
TxByronWitness,
TxShelleyWitness,
TxSigned,
} from '../transaction/transaction'
import {
Address,
BIP32Path,
HexString,
HwSigningData,
Network,
PubKeyHex,
VotePublicKeyHex,
XPubKeyHex,
} from '../types'
Expand All @@ -49,9 +52,11 @@ import {
isDeviceVersionGTE,
getAddressParameters,
validateVotingRegistrationAddressType,
splitXPubKeyCborHex,
encodeVotingRegistrationMetaData,
} from './util'
import { Errors } from '../errors'
import { decodeCbor, encodeCbor, removeNullFields } from '../util'
import { partition, removeNullFields } from '../util'
import { TREZOR_VERSIONS } from './constants'
import { KesVKey, OpCertIssueCounter, SignedOpCertCborHex } from '../opCert/opCert'
import { parseBIP32Path } from '../command-parser/parsers'
Expand Down Expand Up @@ -151,6 +156,18 @@ const TrezorCryptoProvider: () => Promise<CryptoProvider> = async () => {
return payload.map((result) => result.publicKey as XPubKeyHex)
}

const determineSigningMode = (certificates: _Certificate[], signingFiles: HwSigningData[]) => {
const poolRegistrationCert = certificates.find(
(cert) => cert.type === TxCertificateKeys.STAKEPOOL_REGISTRATION,
) as _StakepoolRegistrationCert

if (poolRegistrationCert) {
return TrezorTypes.CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER
}

return TrezorTypes.CardanoTxSigningMode.ORDINARY_TRANSACTION
}

const prepareInput = (input: _Input, path: BIP32Path | null): TrezorTypes.CardanoInput => ({
path: path as number[], // TODO: Check if null
prev_hash: input.txHash.toString('hex'),
Expand Down Expand Up @@ -336,9 +353,11 @@ const TrezorCryptoProvider: () => Promise<CryptoProvider> = async () => {
validityIntervalStart != null ? validityIntervalStart.toString() : undefined
)

const prepareMetaDataHex = (metaData: Buffer | null): TrezorTypes.CardanoAuxiliaryData | undefined => (
metaData ? ({
blob: encodeCbor(metaData).toString('hex'),
const prepareMetaDataHashHex = (
metaDataHash: Buffer | null,
): TrezorTypes.CardanoAuxiliaryData | undefined => (
metaDataHash ? ({
hash: metaDataHash.toString('hex'),
}) : undefined
)

Expand All @@ -362,17 +381,55 @@ const TrezorCryptoProvider: () => Promise<CryptoProvider> = async () => {
})
}

const signTx = async (
const createWitnesses = (
trezorWitnesses: TrezorTypes.CardanoSignedTxWitness[],
signingFiles: HwSigningData[],
): TxWitnesses => {
const getSigningFileDataByXPubKey = (pubKey: PubKeyHex): HwSigningData => {
const hwSigningData = signingFiles.find(
(signingFile) => splitXPubKeyCborHex(signingFile.cborXPubKeyHex).pubKey.toString('hex') === pubKey,
)
if (hwSigningData) return hwSigningData
throw Error(Errors.MissingHwSigningDataAtXPubKeyError)
}

const [byronWitnesses, shelleyWitnesses] = partition(
trezorWitnesses.map((witness) => {
const signingFile = getSigningFileDataByXPubKey(witness.pubKey as PubKeyHex)
return ({
...witness,
path: signingFile.path,
pubKey: Buffer.from(witness.pubKey, 'hex'),
chainCode: witness.chainCode
? Buffer.from(witness.chainCode, 'hex')
: splitXPubKeyCborHex(signingFile.cborXPubKeyHex).chainCode,
signature: Buffer.from(witness.signature, 'hex'),
})
}),
(witness) => witness.type === TrezorTypes.CardanoTxWitnessType.BYRON_WITNESS,
)

return ({
byronWitnesses: byronWitnesses.map((witness) => (
TxByronWitness(witness.pubKey, witness.signature, witness.chainCode, {})
)),
shelleyWitnesses: shelleyWitnesses.map((witness) => (
TxShelleyWitness(witness.pubKey, witness.signature)
)),
})
}

const trezorSignTx = async (
unsignedTxParsed: _UnsignedTxParsed,
signingFiles: HwSigningData[],
network: Network,
changeOutputFiles: HwSigningData[],
): Promise<SignedTxCborHex> => {
): Promise<TrezorTypes.CardanoSignedTxWitness[]> => {
ensureFirmwareSupportsParams(unsignedTxParsed)
const {
paymentSigningFiles,
stakeSigningFiles,
} = filterSigningFiles(signingFiles)
const { paymentSigningFiles, stakeSigningFiles } = filterSigningFiles(signingFiles)

const signingMode = determineSigningMode(unsignedTxParsed.certificates, signingFiles)

const inputs = unsignedTxParsed.inputs.map(
(input: _Input, i: number) => prepareInput(input, getSigningPath(paymentSigningFiles, i)),
)
Expand All @@ -389,9 +446,10 @@ const TrezorCryptoProvider: () => Promise<CryptoProvider> = async () => {
(withdrawal: _Withdrawal) => prepareWithdrawal(withdrawal, stakeSigningFiles),
)

const auxiliaryData = prepareMetaDataHex(unsignedTxParsed.meta)
const auxiliaryData = prepareMetaDataHashHex(unsignedTxParsed.metaDataHash)

const request: TrezorTypes.CommonParams & TrezorTypes.CardanoSignTransaction = {
signingMode,
inputs,
outputs,
protocolMagic: network.protocolMagic,
Expand All @@ -414,7 +472,36 @@ const TrezorCryptoProvider: () => Promise<CryptoProvider> = async () => {
throw Error(Errors.TxSerializationMismatchError)
}

return response.payload.serializedTx as SignedTxCborHex
return response.payload.witnesses
}

const signTx = async (
unsignedTxParsed: _UnsignedTxParsed,
signingFiles: HwSigningData[],
network: Network,
changeOutputFiles: HwSigningData[],
): Promise<SignedTxCborHex> => {
const trezorWitnesses = await trezorSignTx(unsignedTxParsed, signingFiles, network, changeOutputFiles)
const { byronWitnesses, shelleyWitnesses } = createWitnesses(trezorWitnesses, signingFiles)
return await TxSigned(unsignedTxParsed.unsignedTxDecoded, byronWitnesses, shelleyWitnesses)
}

const witnessTx = async (
unsignedTxParsed: _UnsignedTxParsed,
signingFiles: HwSigningData[],
network: Network,
changeOutputFiles: HwSigningData[],
): Promise<Array<_ByronWitness | _ShelleyWitness>> => {
const ledgerWitnesses = await trezorSignTx(unsignedTxParsed, signingFiles, network, changeOutputFiles)
const { byronWitnesses, shelleyWitnesses } = await createWitnesses(ledgerWitnesses, signingFiles)
const _byronWitnesses = byronWitnesses.map((byronWitness) => (
{ key: TxWitnessKeys.BYRON, data: byronWitness }
) as _ByronWitness)
const _shelleyWitnesses = shelleyWitnesses.map((shelleyWitness) => (
{ key: TxWitnessKeys.SHELLEY, data: shelleyWitness }
) as _ShelleyWitness)

return [..._shelleyWitnesses, ..._byronWitnesses]
}

const prepareVoteAuxiliaryData = (
Expand Down Expand Up @@ -468,6 +555,7 @@ const TrezorCryptoProvider: () => Promise<CryptoProvider> = async () => {
const prepareDummyTx = (
network: Network, auxiliaryData: TrezorTypes.CardanoAuxiliaryData,
): TrezorTypes.CommonParams & TrezorTypes.CardanoSignTransaction => ({
signingMode: TrezorTypes.CardanoTxSigningMode.ORDINARY_TRANSACTION,
protocolMagic: network.protocolMagic,
networkId: network.networkId,
inputs: [prepareDummyInput()],
Expand All @@ -477,18 +565,6 @@ const TrezorCryptoProvider: () => Promise<CryptoProvider> = async () => {
auxiliaryData,
})

const extractTrezorSignedAuxiliaryData = (
payload: TrezorTypes.CardanoSignedTx,
): VotingRegistrationMetaData => {
try {
const { serializedTx } = payload
const votingRegistrationMetaData = decodeCbor(serializedTx)[2][0]
return votingRegistrationMetaData
} catch (e) {
throw Error(Errors.MissingAuxiliaryDataSupplement)
}
}

const signVotingRegistrationMetaData = async (
rewardAddressSigningFiles: HwSigningData[],
hwStakeSigningFile: HwSigningData,
Expand All @@ -509,24 +585,22 @@ const TrezorCryptoProvider: () => Promise<CryptoProvider> = async () => {
const dummyTx = prepareDummyTx(network, trezorAuxData)

const response = await TrezorConnect.cardanoSignTransaction(dummyTx)

if (!response.success) {
throw Error(response.payload.error)
}
if (!response?.payload?.auxiliaryDataSupplement) throw Error(Errors.MissingAuxiliaryDataSupplement)
if (!response?.payload?.auxiliaryDataSupplement.catalystSignature) {
throw Error(Errors.MissingCatalystVotingSignature)
}

const votingRegistrationMetaData = extractTrezorSignedAuxiliaryData(response.payload)

return encodeCbor(votingRegistrationMetaData).toString('hex')
}

const witnessTx = async (
unsignedTxParsed: _UnsignedTxParsed,
signingFiles: HwSigningData[],
network: Network,
changeOutputFiles: HwSigningData[],
): Promise<Array<_ByronWitness | _ShelleyWitness>> => {
const signedTx = await signTx(unsignedTxParsed, signingFiles, network, changeOutputFiles)
return Witness(signedTx)
return encodeVotingRegistrationMetaData(
hwStakeSigningFile,
votePublicKeyHex,
address,
nonce,
response.payload.auxiliaryDataSupplement.auxiliaryDataHash as HexString,
response.payload.auxiliaryDataSupplement.catalystSignature as HexString,
)
}

const signOperationalCertificate = async (
Expand Down
Loading