From a9a29e402bf13c8a4fcdce7ba671409037f58e1b Mon Sep 17 00:00:00 2001 From: Duddino Date: Tue, 30 Jan 2024 13:16:37 +0100 Subject: [PATCH 1/8] Enable undelegation on hardware wallets --- scripts/legacy.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/scripts/legacy.js b/scripts/legacy.js index 31360a682..4e18a1c70 100644 --- a/scripts/legacy.js +++ b/scripts/legacy.js @@ -54,18 +54,16 @@ export async function createAndSendTransaction({ * @deprecated use the new wallet method instead */ export async function undelegateGUI() { - if (wallet.isHardwareWallet()) { - return createAlert('warning', ALERTS.STAKING_LEDGER_NO_SUPPORT, 6000); - } - // Ensure the wallet is unlocked - if ( - wallet.isViewOnly() && - !(await restoreWallet( - `${translation.walletUnlockUnstake} ${cChainParams.current.TICKER}!` - )) - ) - return; + if (!wallet.isHardwareWallet()) { + if ( + wallet.isViewOnly() && + !(await restoreWallet( + `${translation.walletUnlockUnstake} ${cChainParams.current.TICKER}!` + )) + ) + return; + } // Verify the amount const nAmount = Math.round( From b80af795e78774abcd9cbb83b68d8d26d6bc6bf2 Mon Sep 17 00:00:00 2001 From: Duddino Date: Tue, 30 Jan 2024 13:53:03 +0100 Subject: [PATCH 2/8] Don't delegate change on ledgers --- scripts/legacy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/legacy.js b/scripts/legacy.js index 4e18a1c70..24d311496 100644 --- a/scripts/legacy.js +++ b/scripts/legacy.js @@ -80,7 +80,7 @@ export async function undelegateGUI() { amount: nAmount, isDelegation: false, useDelegatedInputs: true, - delegateChange: true, + delegateChange: !wallet.isHardwareWallet(), changeDelegationAddress: await wallet.getColdStakingAddress(), }); From 694615144f69417ac5b940914d6198f91f2c268e Mon Sep 17 00:00:00 2001 From: Duddino Date: Tue, 30 Jan 2024 14:09:24 +0100 Subject: [PATCH 3/8] Add change address, to use in ledger address confirmation --- scripts/legacy.js | 8 +++++++- scripts/wallet.js | 3 ++- tests/unit/wallet.spec.js | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/scripts/legacy.js b/scripts/legacy.js index 24d311496..fe5e3ab84 100644 --- a/scripts/legacy.js +++ b/scripts/legacy.js @@ -30,6 +30,7 @@ export async function createAndSendTransaction({ delegateChange = false, changeDelegationAddress = null, isProposal = false, + changeAddress = '', }) { const tx = wallet.createTransaction(address, amount, { isDelegation, @@ -37,6 +38,7 @@ export async function createAndSendTransaction({ delegateChange, changeDelegationAddress, isProposal, + changeAddress, }); if (!wallet.isHardwareWallet()) await wallet.sign(tx); else { @@ -72,7 +74,10 @@ export async function undelegateGUI() { if (!validateAmount(nAmount)) return; // Generate a new address to undelegate towards - const [address] = wallet.getNewAddress(1); + + const [address] = await getNewAddress({ + verify: wallet.isHardwareWallet(), + }); // Perform the TX const cTxRes = await createAndSendTransaction({ @@ -82,6 +87,7 @@ export async function undelegateGUI() { useDelegatedInputs: true, delegateChange: !wallet.isHardwareWallet(), changeDelegationAddress: await wallet.getColdStakingAddress(), + changeAddress: address, }); if (!cTxRes.ok && cTxRes.err === 'No change addr') { diff --git a/scripts/wallet.js b/scripts/wallet.js index bf4e98de4..5d3811582 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -666,6 +666,7 @@ export class Wallet { delegateChange = false, changeDelegationAddress = null, isProposal = false, + changeAddress = '', } = {} ) { const balance = useDelegatedInputs @@ -689,7 +690,7 @@ export class Wallet { // Add change output if (changeValue > 0) { - const [changeAddress] = this.getNewAddress(1); + if (!changeAddress) [changeAddress] = this.getNewAddress(1); if (delegateChange && changeValue > 1.01 * COIN) { transactionBuilder.addColdStakeOutput({ address: changeAddress, diff --git a/tests/unit/wallet.spec.js b/tests/unit/wallet.spec.js index a34a4d106..0f62f0230 100644 --- a/tests/unit/wallet.spec.js +++ b/tests/unit/wallet.spec.js @@ -77,6 +77,38 @@ describe('Wallet transaction tests', () => { ); }); + it('Creates a tx with change address', async () => { + const wallet = new Wallet(0, false); + wallet.setMasterKey(getLegacyMainnet()); + const tx = wallet.createTransaction( + 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + 0.05 * 10 ** 8, + { changeAddress: 'D8Ervc3Ka6TuKgvXZH9Eo4ou24AiVwTbL6' } + ); + expect(tx.version).toBe(1); + expect(tx.vin[0]).toStrictEqual( + new CTxIn({ + outpoint: new COutpoint({ + txid: 'f8f968d80ac382a7b64591cc166489f66b7c4422f95fbd89f946a5041d285d7c', + n: 1, + }), + scriptSig: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', // Script sig must be the UTXO script since it's not signed + }) + ); + expect(tx.vout[0]).toStrictEqual( + new CTxOut({ + script: '76a91421ff8214d09d60713b89809bb413a0651ee6931488ac', + value: 4992400, + }) + ); + expect(tx.vout[1]).toStrictEqual( + new CTxOut({ + script: '76a914a95cc6408a676232d61ec29dc56a180b5847835788ac', + value: 5000000, + }) + ); + }); + it('Creates a proposal tx correctly', async () => { const wallet = new Wallet(0, false); wallet.setMasterKey(getLegacyMainnet()); From 9f05ea425f046663400bb261f13ac2d8c0da864e Mon Sep 17 00:00:00 2001 From: Duddino Date: Tue, 30 Jan 2024 16:01:42 +0100 Subject: [PATCH 4/8] Fix ledger error --- scripts/legacy.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/legacy.js b/scripts/legacy.js index fe5e3ab84..b9584bd7e 100644 --- a/scripts/legacy.js +++ b/scripts/legacy.js @@ -107,6 +107,9 @@ export async function undelegateGUI() { * @deprecated use the new wallet method instead */ export async function delegateGUI() { + if (wallet.isHardwareWallet()) { + return createAlert('warning', ALERTS.STAKING_LEDGER_NO_SUPPORT); + } // Ensure the wallet is unlocked if ( wallet.isViewOnly() && From 6a7d84f0046e643e5c19d54b36c950ddec99839e Mon Sep 17 00:00:00 2001 From: Duddino Date: Tue, 30 Jan 2024 18:08:24 +0100 Subject: [PATCH 5/8] Add OP_FALSE to ledger inputs --- scripts/ledger.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/scripts/ledger.js b/scripts/ledger.js index 78d8d0dc2..d5884e899 100644 --- a/scripts/ledger.js +++ b/scripts/ledger.js @@ -6,6 +6,7 @@ import { confirmPopup, createAlert } from './misc.js'; import { getNetwork } from './network.js'; import { Transaction } from './transaction.js'; import { COIN, cChainParams } from './chain_params.js'; +import { hexToBytes } from './utils'; /** * @type{TransportWebUSB} @@ -156,11 +157,14 @@ export async function ledgerSignTransaction(wallet, transaction) { const associatedKeysets = []; const inputs = []; + const isColdStake = []; for (const input of transaction.vin) { const { hex } = await getNetwork().getTxInfo(input.outpoint.txid); + const { type } = wallet.getAddressesFromScript(input.scriptSig); inputs.push([cHardwareWallet.splitTransaction(hex), input.outpoint.n]); // ScriptSig is the script at this point, since it's not signed associatedKeysets.push(wallet.getPath(input.scriptSig)); + isColdStake.push(type === 'p2cs'); } const outputScriptHex = cHardwareWallet .serializeTransactionOutputs(ledgerTx) @@ -174,10 +178,25 @@ export async function ledgerSignTransaction(wallet, transaction) { outputScriptHex, }), }); + const signedTx = Transaction.fromHex(hex); // Update vin with signatures transaction.vin = signedTx.vin; - return signedTx; + for (let i = 0; i < transaction.vin.length; i++) { + const input = transaction.vin[i]; + // if it's a cold stake tx we need to add OP_FALSE + if (isColdStake[i]) { + const bytes = hexToBytes(input.scriptSig); + const sigLength = bytes[0]; + input.scriptSig = bytesToHex([ + bytes[0], + ...bytes.splice(1, sigLength), + OP['FALSE'], + ...bytes.splice(sigLength + 1), + ]); + } + } + return transaction; } function createTxConfirmation(outputs) { From 0cdb9690df3e6fc185ac11cbd099b29f3b07e97a Mon Sep 17 00:00:00 2001 From: Duddino Date: Tue, 30 Jan 2024 18:13:06 +0100 Subject: [PATCH 6/8] Add .js --- scripts/ledger.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ledger.js b/scripts/ledger.js index d5884e899..eca701783 100644 --- a/scripts/ledger.js +++ b/scripts/ledger.js @@ -6,7 +6,7 @@ import { confirmPopup, createAlert } from './misc.js'; import { getNetwork } from './network.js'; import { Transaction } from './transaction.js'; import { COIN, cChainParams } from './chain_params.js'; -import { hexToBytes } from './utils'; +import { hexToBytes } from './utils.js'; /** * @type{TransportWebUSB} From 062832991fa939e5f2bb560f512164a839dd2d2f Mon Sep 17 00:00:00 2001 From: Duddino Date: Tue, 30 Jan 2024 18:16:00 +0100 Subject: [PATCH 7/8] Add missing imports --- scripts/ledger.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/ledger.js b/scripts/ledger.js index eca701783..283601f2a 100644 --- a/scripts/ledger.js +++ b/scripts/ledger.js @@ -6,7 +6,8 @@ import { confirmPopup, createAlert } from './misc.js'; import { getNetwork } from './network.js'; import { Transaction } from './transaction.js'; import { COIN, cChainParams } from './chain_params.js'; -import { hexToBytes } from './utils.js'; +import { hexToBytes, bytesToHex } from './utils.js'; +import { OP } from './script.js'; /** * @type{TransportWebUSB} From 0f8a54b7ad9dded878a749019ef97b0d9d9b5046 Mon Sep 17 00:00:00 2001 From: Duddino Date: Wed, 31 Jan 2024 13:43:03 +0100 Subject: [PATCH 8/8] Fix owner address and p2cs spend on ledger --- scripts/ledger.js | 4 ++-- scripts/legacy.js | 2 ++ scripts/wallet.js | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/ledger.js b/scripts/ledger.js index 283601f2a..1bf177db7 100644 --- a/scripts/ledger.js +++ b/scripts/ledger.js @@ -191,9 +191,9 @@ export async function ledgerSignTransaction(wallet, transaction) { const sigLength = bytes[0]; input.scriptSig = bytesToHex([ bytes[0], - ...bytes.splice(1, sigLength), + ...bytes.slice(1, sigLength + 1), OP['FALSE'], - ...bytes.splice(sigLength + 1), + ...bytes.slice(sigLength + 1), ]); } } diff --git a/scripts/legacy.js b/scripts/legacy.js index b9584bd7e..6a10ddef7 100644 --- a/scripts/legacy.js +++ b/scripts/legacy.js @@ -31,6 +31,7 @@ export async function createAndSendTransaction({ changeDelegationAddress = null, isProposal = false, changeAddress = '', + delegationOwnerAddress, }) { const tx = wallet.createTransaction(address, amount, { isDelegation, @@ -39,6 +40,7 @@ export async function createAndSendTransaction({ changeDelegationAddress, isProposal, changeAddress, + returnAddress: delegationOwnerAddress, }); if (!wallet.isHardwareWallet()) await wallet.sign(tx); else { diff --git a/scripts/wallet.js b/scripts/wallet.js index 5d3811582..ad1908c19 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -667,6 +667,7 @@ export class Wallet { changeDelegationAddress = null, isProposal = false, changeAddress = '', + returnAddress = '', } = {} ) { const balance = useDelegatedInputs @@ -710,7 +711,7 @@ export class Wallet { // Add primary output if (isDelegation) { - const [returnAddress] = this.getNewAddress(1); + if (!returnAddress) [returnAddress] = this.getNewAddress(1); transactionBuilder.addColdStakeOutput({ address: returnAddress, addressColdStake: address,