From a12033118bf617beda6b1444f151eaf695d978af Mon Sep 17 00:00:00 2001 From: yushi Date: Sat, 8 Jul 2023 01:42:40 +0800 Subject: [PATCH 1/4] fall back to CIP15 format for Ledger app 5 catalyst registration --- packages/yoroi-extension/app/api/ada/index.js | 2 + .../app/api/ada/lib/cardanoCrypto/catalyst.js | 71 +++++++++++++++++++ .../api/ada/transactions/shelley/ledgerTx.js | 67 +++++++++++------ .../ada/transactions/shelley/ledgerTx.test.js | 1 + .../app/stores/ada/send/LedgerSendStore.js | 46 +++++++++--- .../app/utils/hwConnectHandler.js | 28 +++++++- .../ledger/stores/ConnectStore.js | 43 +++++++---- 7 files changed, 211 insertions(+), 47 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/index.js b/packages/yoroi-extension/app/api/ada/index.js index b4ea6fb92f..ce6abe04b5 100644 --- a/packages/yoroi-extension/app/api/ada/index.js +++ b/packages/yoroi-extension/app/api/ada/index.js @@ -297,6 +297,7 @@ export type CreateLedgerSignTxDataRequest = {| signRequest: HaskellShelleyTxSignRequest, network: $ReadOnly, addressingMap: string => (void | $PropertyType), + cip36: boolean, |}; export type CreateLedgerSignTxDataResponse = {| ledgerSignTxPayload: SignTransactionRequest, @@ -961,6 +962,7 @@ export default class AdaApi { byronNetworkMagic: config.ByronNetworkId, networkId: Number.parseInt(config.ChainNetworkId, 10), addressingMap: request.addressingMap, + cip36: request.cip36, }); Logger.debug(`${nameof(AdaApi)}::${nameof(this.createLedgerSignTxData)} success: ` + stringifyData(ledgerSignTxPayload)); diff --git a/packages/yoroi-extension/app/api/ada/lib/cardanoCrypto/catalyst.js b/packages/yoroi-extension/app/api/ada/lib/cardanoCrypto/catalyst.js index 3cfcb38382..dec3ab97ca 100644 --- a/packages/yoroi-extension/app/api/ada/lib/cardanoCrypto/catalyst.js +++ b/packages/yoroi-extension/app/api/ada/lib/cardanoCrypto/catalyst.js @@ -102,3 +102,74 @@ export function generateRegistration(request: {| (hashedMetadata) => request.stakePrivateKey.sign(hashedMetadata).to_hex(), ); } + +export function generateCip15RegistrationMetadata( + votingPublicKey: string, + stakingPublicKey: string, + rewardAddress: string, + nonce: number, + signer: Uint8Array => string, +): RustModule.WalletV4.AuxiliaryData { + + /** + * Catalyst follows a certain standard to prove the voting power + * A transaction is submitted with following metadata format for the registration process + * label: 61284 + * { + * 1: "pubkey generated for catalyst app", + * 2: "stake key public key", + * 3: "address to receive rewards to" + * 4: "slot number" + * } + * label: 61285 + * { + * 1: "signature of blake2b-256 hash of the metadata signed using stakekey" + * } + */ + + const registrationData = RustModule.WalletV4.encode_json_str_to_metadatum( + JSON.stringify({ + '1': prefix0x(votingPublicKey), + '2': prefix0x(stakingPublicKey), + '3': prefix0x(rewardAddress), + '4': nonce, + }), + RustModule.WalletV4.MetadataJsonSchema.BasicConversions + ); + const generalMetadata = RustModule.WalletV4.GeneralTransactionMetadata.new(); + generalMetadata.insert( + RustModule.WalletV4.BigNum.from_str(CatalystLabels.DATA.toString()), + registrationData + ); + + const hashedMetadata = blake2b(256 / 8).update( + generalMetadata.to_bytes() + ).digest('binary'); + + generalMetadata.insert( + RustModule.WalletV4.BigNum.from_str(CatalystLabels.SIG.toString()), + RustModule.WalletV4.encode_json_str_to_metadatum( + JSON.stringify({ + '1': prefix0x(signer(hashedMetadata)), + }), + RustModule.WalletV4.MetadataJsonSchema.BasicConversions + ) + ); + + // This is how Ledger constructs the metadata. We must be consistent with it. + const metadataList = RustModule.WalletV4.MetadataList.new(); + metadataList.add( + RustModule.WalletV4.TransactionMetadatum.from_bytes( + generalMetadata.to_bytes() + ) + ); + metadataList.add( + RustModule.WalletV4.TransactionMetadatum.new_list( + RustModule.WalletV4.MetadataList.new() + ) + ); + + return RustModule.WalletV4.AuxiliaryData.from_bytes( + metadataList.to_bytes() + ); +} diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js index 9e09523034..acb323f966 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.js @@ -49,6 +49,7 @@ export async function createLedgerSignTxPayload(request: {| byronNetworkMagic: number, networkId: number, addressingMap: string => (void | $PropertyType), + cip36: boolean, |}): Promise { const txBody = request.signRequest.unsignedTx.build(); @@ -94,32 +95,54 @@ export async function createLedgerSignTxPayload(request: {| const { votingPublicKey, stakingKeyPath, nonce, paymentKeyPath, } = request.signRequest.ledgerNanoCatalystRegistrationTxSignData; - auxiliaryData = { - type: TxAuxiliaryDataType.CIP36_REGISTRATION, - params: { - format: CIP36VoteRegistrationFormat.CIP_36, - delegations: [ - { - type: CIP36VoteDelegationType.KEY, - voteKeyHex: votingPublicKey.replace(/^0x/, ''), - weight: 1, + if (request.cip36) { + auxiliaryData = { + type: TxAuxiliaryDataType.CIP36_REGISTRATION, + params: { + format: CIP36VoteRegistrationFormat.CIP_36, + delegations: [ + { + type: CIP36VoteDelegationType.KEY, + voteKeyHex: votingPublicKey.replace(/^0x/, ''), + weight: 1, + }, + ], + stakingPath: stakingKeyPath, + paymentDestination: { + type: TxOutputDestinationType.DEVICE_OWNED, + params: { + type: AddressType.BASE_PAYMENT_KEY_STAKE_KEY, + params: { + spendingPath: paymentKeyPath, + stakingPath: stakingKeyPath, + }, + }, }, - ], - stakingPath: stakingKeyPath, - paymentDestination: { - type: TxOutputDestinationType.DEVICE_OWNED, - params: { - type: AddressType.BASE_PAYMENT_KEY_STAKE_KEY, + nonce, + votingPurpose: 0, + } + }; + } else { + auxiliaryData = { + type: TxAuxiliaryDataType.CIP36_REGISTRATION, + params: { + format: CIP36VoteRegistrationFormat.CIP_15, + voteKeyHex: votingPublicKey.replace(/^0x/, ''), + stakingPath: stakingKeyPath, + paymentDestination: { + type: TxOutputDestinationType.DEVICE_OWNED, params: { - spendingPath: paymentKeyPath, - stakingPath: stakingKeyPath, + type: AddressType.BASE_PAYMENT_KEY_STAKE_KEY, + params: { + spendingPath: paymentKeyPath, + stakingPath: stakingKeyPath, + }, }, }, - }, - nonce, - votingPurpose: 0, - } - }; + nonce, + } + }; + } } else if (request.signRequest.metadata != null) { auxiliaryData = { type: TxAuxiliaryDataType.ARBITRARY_HASH, diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js index dffd77cd99..a174171235 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js @@ -351,6 +351,7 @@ test('Create Ledger transaction', async () => { } return undefined; }, + cip36: true, }); expect(response).toStrictEqual(({ diff --git a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js index 0c895d8abf..031485cd39 100644 --- a/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js +++ b/packages/yoroi-extension/app/stores/ada/send/LedgerSendStore.js @@ -45,7 +45,10 @@ import type { import { genAddressingLookup } from '../../stateless/addressStores'; import type { ActionsMap } from '../../../actions/index'; import type { StoresMap } from '../../index'; -import { generateRegistrationMetadata } from '../../../api/ada/lib/cardanoCrypto/catalyst'; +import { + generateRegistrationMetadata, + generateCip15RegistrationMetadata, +} from '../../../api/ada/lib/cardanoCrypto/catalyst'; /** Note: Handles Ledger Signing */ export default class LedgerSendStore extends Store { @@ -207,18 +210,29 @@ export default class LedgerSendStore extends Store { locale: this.stores.profile.currentLocale, }); + let cip36: boolean = false; + if (request.signRequest.ledgerNanoCatalystRegistrationTxSignData) { + const getVersionResponse = await ledgerConnect.getVersion({ + serial: request.expectedSerial, + dontCloseTab: true, + }); + cip36 = getVersionResponse.compatibility.supportsCIP36Vote === true; + } + const network = request.publicDeriver.getParent().getNetworkInfo(); const { ledgerSignTxPayload } = await this.api.ada.createLedgerSignTxData({ signRequest: request.signRequest, network, addressingMap: request.addressingMap, + cip36, }); const ledgerSignTxResp: LedgerSignTxResponse = await ledgerConnect.signTransaction({ serial: request.expectedSerial, params: ledgerSignTxPayload, + useOpenTab: true, }); // There is no need of ledgerConnect after this line. @@ -247,15 +261,27 @@ export default class LedgerSendStore extends Store { const { cip36VoteRegistrationSignatureHex } = ledgerSignTxResp.auxiliaryDataSupplement; - metadata = generateRegistrationMetadata( - votingPublicKey, - stakingKey, - paymentAddress, - nonce, - (_hashedMetadata) => { - return cip36VoteRegistrationSignatureHex; - }, - ); + if (cip36) { + metadata = generateRegistrationMetadata( + votingPublicKey, + stakingKey, + paymentAddress, + nonce, + (_hashedMetadata) => { + return cip36VoteRegistrationSignatureHex; + }, + ); + } else { + metadata = generateCip15RegistrationMetadata( + votingPublicKey, + stakingKey, + paymentAddress, + nonce, + (_hashedMetadata) => { + return cip36VoteRegistrationSignatureHex; + }, + ); + } // We can verify that // Buffer.from( // blake2b(256 / 8).update(metadata.to_bytes()).digest('binary') diff --git a/packages/yoroi-extension/app/utils/hwConnectHandler.js b/packages/yoroi-extension/app/utils/hwConnectHandler.js index 8f4ecf9ef3..686a8af6fe 100644 --- a/packages/yoroi-extension/app/utils/hwConnectHandler.js +++ b/packages/yoroi-extension/app/utils/hwConnectHandler.js @@ -29,6 +29,7 @@ type ShowAddressRequestWrapper = {| export class LedgerConnect { locale: string; + tabId: ?number; constructor(params: {| locale: string |}) { this.locale = params.locale; @@ -59,11 +60,14 @@ export class LedgerConnect { signTransaction: {| serial: ?string, params: SignTransactionRequest, + useOpenTab?: boolean, |} => Promise = (request) => { return this._requestLedger( OPERATION_NAME.SIGN_TX, request.params, request.serial, + false, + request.useOpenTab === true, ); } @@ -78,12 +82,34 @@ export class LedgerConnect { ); } + getVersion: {| + serial: ?string, + dontCloseTab?: boolean, + |} => Promise = (request) => { + return this._requestLedger( + OPERATION_NAME.GET_LEDGER_VERSION, + undefined, + request.serial, + true, + ); + } + async _requestLedger( action: string, params: any, serial: ?string, + dontCloseTab?: boolean = false, + useOpenTab?: boolean = false, ): any { - const tabId = await this._createLedgerTab(); + let tabId; + if (useOpenTab && this.tabId != null) { + tabId = this.tabId; + } else { + tabId = await this._createLedgerTab(); + if (dontCloseTab) { + this.tabId = tabId; + } + } return new Promise((resolve, reject) => { chrome.tabs.sendMessage( diff --git a/packages/yoroi-extension/ledger/stores/ConnectStore.js b/packages/yoroi-extension/ledger/stores/ConnectStore.js index a838136759..1928a40eb0 100644 --- a/packages/yoroi-extension/ledger/stores/ConnectStore.js +++ b/packages/yoroi-extension/ledger/stores/ConnectStore.js @@ -64,7 +64,7 @@ export default class ConnectStore { @observable response: void | MessageType; @observable expectedSerial: ?string; @observable extension: ?string; - userInteractableRequest: RequestType; + userInteractableRequest: ?RequestType; sendResponseFunc: ?(any) => void; constructor(transportId: TransportIdType) { @@ -192,6 +192,9 @@ export default class ConnectStore { this.setProgressState(PROGRESS_STATE.DEVICE_TYPE_SELECTED); }); + if (!this.userInteractableRequest) { + return; + } const actn = this.userInteractableRequest.action; const { params } = this.userInteractableRequest; @@ -387,7 +390,7 @@ export default class ConnectStore { const adaApp = new AdaApp(transport); const resp: GetVersionResponse = await adaApp.getVersion(); - this._replyMessageWrap(actn, true, resp); + this._replyMessageWrap(actn, true, resp, true); } catch (err) { this._replyError(actn, err); } finally { @@ -512,13 +515,18 @@ export default class ConnectStore { * @param {*} success success status boolean * @param {*} payload payload object */ - _replyMessageWrap: (string, boolean, any) => void = (actn, success, payload) => { - this._replyMessage({ - success, - payload, - action: actn, - extension: this.extension, - }); + _replyMessageWrap: (string, boolean, any, ?boolean) => void = ( + actn, success, payload, dontClose, + ) => { + this._replyMessage( + { + success, + payload, + action: actn, + extension: this.extension, + }, + dontClose, + ); } /** @@ -538,12 +546,19 @@ export default class ConnectStore { * Reply message to Content Script [ Website ==> Content Script ] * @param {*} msg MessageType object as reply */ - _replyMessage: (MessageType) => void = (msg) => { - if (ENV.isDevelopment) { - this.setResponse(msg); - this.setProgressState(PROGRESS_STATE.DEVICE_RESPONSE); + _replyMessage: (MessageType, ?boolean) => void = (msg, dontClose) => { + if (dontClose) { + runInAction(() => { + this.userInteractableRequest = null; + this.setProgressState(PROGRESS_STATE.DEVICE_TYPE_SELECTED); + }); } else { - window.close(); + if (ENV.isDevelopment) { + this.setResponse(msg); + this.setProgressState(PROGRESS_STATE.DEVICE_RESPONSE); + } else { + window.close(); + } } msg.action = `${msg.action}-reply`; if (this.sendResponseFunc != null) { From 0c241e8ad73f7a15e6fd1be251ae9c46bdec0f5f Mon Sep 17 00:00:00 2001 From: yushi Date: Mon, 10 Jul 2023 20:15:49 +0800 Subject: [PATCH 2/4] submitted tx metadata should be from the unsigned tx --- packages/yoroi-extension/app/api/ada/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/index.js b/packages/yoroi-extension/app/api/ada/index.js index ce6abe04b5..c2d089862e 100644 --- a/packages/yoroi-extension/app/api/ada/index.js +++ b/packages/yoroi-extension/app/api/ada/index.js @@ -2384,6 +2384,8 @@ export default class AdaApi { const usedUtxos = signRequest.senderUtxos.map(utxo => ( { txHash: utxo.tx_hash, index: utxo.tx_index } )); + const metadata = signRequest.unsignedTx.get_auxiliary_data; + const transaction = CardanoShelleyTransaction.fromData({ txid: txId, type: isIntraWallet ? 'self' : 'expend', @@ -2399,8 +2401,8 @@ export default class AdaApi { block: null, certificates: [], ttl: new BigNumber(String(signRequest.unsignedTx.build().ttl())), - metadata: signRequest.metadata - ? Buffer.from(signRequest.metadata.to_bytes()).toString('hex') + metadata: metadata + ? Buffer.from(metadata.to_bytes()).toString('hex') : null, withdrawals: signRequest.withdrawals().map(withdrawal => ({ address: withdrawal.address, From 0f5a8f9d00f09ae88fedda575804744878ee498b Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 18 Jul 2023 15:19:20 +0300 Subject: [PATCH 3/4] fixing function call --- packages/yoroi-extension/app/api/ada/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/api/ada/index.js b/packages/yoroi-extension/app/api/ada/index.js index c2d089862e..430475048f 100644 --- a/packages/yoroi-extension/app/api/ada/index.js +++ b/packages/yoroi-extension/app/api/ada/index.js @@ -2384,7 +2384,7 @@ export default class AdaApi { const usedUtxos = signRequest.senderUtxos.map(utxo => ( { txHash: utxo.tx_hash, index: utxo.tx_index } )); - const metadata = signRequest.unsignedTx.get_auxiliary_data; + const metadata = signRequest.unsignedTx.get_auxiliary_data(); const transaction = CardanoShelleyTransaction.fromData({ txid: txId, From ce38d80f67d2d8a073918547b18fa0f4d0d47dfb Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 19 Jul 2023 14:44:08 +0300 Subject: [PATCH 4/4] Version bump: 4.22.100 --- packages/yoroi-extension/package-lock.json | 2 +- packages/yoroi-extension/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/package-lock.json b/packages/yoroi-extension/package-lock.json index 2803c7f99c..715329f69c 100644 --- a/packages/yoroi-extension/package-lock.json +++ b/packages/yoroi-extension/package-lock.json @@ -1,6 +1,6 @@ { "name": "yoroi", - "version": "4.22.0", + "version": "4.22.100", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/yoroi-extension/package.json b/packages/yoroi-extension/package.json index a37a154615..c2a599c1a9 100644 --- a/packages/yoroi-extension/package.json +++ b/packages/yoroi-extension/package.json @@ -1,6 +1,6 @@ { "name": "yoroi", - "version": "4.22.0", + "version": "4.22.100", "description": "Cardano ADA wallet", "scripts": { "dev:build": "rimraf dev/ && babel-node scripts/build --type=debug",