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

Enable undelegation on hardware wallets #292

Merged
merged 8 commits into from
Jan 31, 2024
Merged
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
22 changes: 21 additions & 1 deletion scripts/ledger.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import createXpub from 'create-xpub';
import { ALERTS, tr, translation } from './i18n.js';

Check warning on line 2 in scripts/ledger.js

View workflow job for this annotation

GitHub Actions / Run linters

'translation' is defined but never used
import AppBtc from '@ledgerhq/hw-app-btc';

Check warning on line 3 in scripts/ledger.js

View workflow job for this annotation

GitHub Actions / Run linters

'AppBtc' is defined but never used
import TransportWebUSB from '@ledgerhq/hw-transport-webusb';

Check warning on line 4 in scripts/ledger.js

View workflow job for this annotation

GitHub Actions / Run linters

'TransportWebUSB' is defined but never used
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, bytesToHex } from './utils.js';
import { OP } from './script.js';

/**
* @type{TransportWebUSB}
Expand Down Expand Up @@ -156,11 +158,14 @@

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)
Expand All @@ -174,10 +179,25 @@
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.slice(1, sigLength + 1),
OP['FALSE'],
...bytes.slice(sigLength + 1),
]);
}
}
return transaction;
}

function createTxConfirmation(outputs) {
Expand Down
35 changes: 22 additions & 13 deletions scripts/legacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,17 @@ export async function createAndSendTransaction({
delegateChange = false,
changeDelegationAddress = null,
isProposal = false,
changeAddress = '',
delegationOwnerAddress,
}) {
const tx = wallet.createTransaction(address, amount, {
isDelegation,
useDelegatedInputs,
delegateChange,
changeDelegationAddress,
isProposal,
changeAddress,
returnAddress: delegationOwnerAddress,
});
if (!wallet.isHardwareWallet()) await wallet.sign(tx);
else {
Expand All @@ -54,18 +58,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(
Expand All @@ -74,16 +76,20 @@ 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({
address,
amount: nAmount,
isDelegation: false,
useDelegatedInputs: true,
delegateChange: true,
delegateChange: !wallet.isHardwareWallet(),
changeDelegationAddress: await wallet.getColdStakingAddress(),
changeAddress: address,
});

if (!cTxRes.ok && cTxRes.err === 'No change addr') {
Expand All @@ -103,6 +109,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() &&
Expand Down
6 changes: 4 additions & 2 deletions scripts/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,8 @@ export class Wallet {
delegateChange = false,
changeDelegationAddress = null,
isProposal = false,
changeAddress = '',
returnAddress = '',
} = {}
) {
const balance = useDelegatedInputs
Expand All @@ -689,7 +691,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,
Expand All @@ -709,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,
Expand Down
32 changes: 32 additions & 0 deletions tests/unit/wallet.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Loading