Skip to content

Commit

Permalink
Change address v2 (#246)
Browse files Browse the repository at this point in the history
* Add change addresses to Wallet

* Use change addresses

* Remove reset

* Prettier

* Make Wallet::loadAddresses take the chain as input

* Make addrexxIndex a map
  • Loading branch information
Duddino committed Nov 13, 2023
1 parent dbeba48 commit 60d7cd1
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 39 deletions.
8 changes: 5 additions & 3 deletions scripts/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -972,14 +972,16 @@ export async function sweepAddress(arrUTXOs, sweepingMasterKey, nFixedFee = 0) {
const nFee = nFixedFee || getNetwork().getFee(cTx.serialize().length);

// Use a new address from our wallet to sweep the UTXOs in to
const strAddress = (await getNewAddress(true, false))[0];
const strAddress = (
await getNewAddress({ updateGUI: true, verify: false, nReceiving: 1 })
)[0];

// Sweep the full funds amount, minus the fee, leaving no change from any sweeped UTXOs
cTx.addoutput(strAddress, (nTotal - nFee) / COIN);

// Sign using the given Master Key, then broadcast the sweep, returning the TXID (or a failure)
const sweepingWallet = new Wallet(0, false);
await sweepingWallet.setMasterKey(sweepingMasterKey);
sweepingWallet.setMasterKey(sweepingMasterKey);

const sign = await signTransaction(cTx, sweepingWallet);
return await getNetwork().sendTransaction(sign);
Expand Down Expand Up @@ -1963,7 +1965,7 @@ export async function createProposal() {
// If Advanced Mode is enabled and an address is given, use the provided address, otherwise, generate a new one
const strAddress =
document.getElementById('proposalAddress').value.trim() ||
(await wallet.getNewAddress())[0];
wallet.getNewAddress(1)[0];
const nextSuperblock = await Masternode.getNextSuperblock();
const proposal = {
name: strTitle,
Expand Down
7 changes: 4 additions & 3 deletions scripts/transactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ export async function undelegateGUI() {
if (!validateAmount(nAmount)) return;

// Generate a new address to undelegate towards
const [address] = wallet.getNewAddress();
const [address] = wallet.getNewAddress(1);

// Perform the TX
const cTxRes = await createAndSendTransaction({
Expand Down Expand Up @@ -304,6 +304,7 @@ export async function createAndSendTransaction({
const nChange = cCoinControl.nValue - (nFee + amount);
const [changeAddress, _] = await getNewAddress({
verify: wallet.isHardwareWallet(),
nReceiving: 1,
});

/**
Expand Down Expand Up @@ -342,8 +343,7 @@ export async function createAndSendTransaction({
// For custom Cold Owner Addresses, it could be an external address, so we need the mempool to class it as an 'external send'
const strOwnerAddress = fCustomColdOwner
? delegationOwnerAddress
: (await wallet.getNewAddress())[0];
const strOwnerPath = await wallet.isOwnAddress(strOwnerAddress);
: wallet.getNewAddress(1)[0];

// Create the Delegation output
cTx.addcoldstakingoutput(strOwnerAddress, address, amount / COIN);
Expand Down Expand Up @@ -425,6 +425,7 @@ export async function createMasternode() {
// Generate the Masternode collateral
const [address] = await getNewAddress({
verify: wallet.isHardwareWallet(),
nReceiving: 1,
});
const result = await createAndSendTransaction({
amount: cChainParams.current.collateralInSats,
Expand Down
100 changes: 67 additions & 33 deletions scripts/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ import {
* in future PRs this class will manage balance, UTXOs, masternode etc...
*/
export class Wallet {
/**
* We are using two chains: The external chain, and the internal one (i.e. change addresses)
* See https://github.com/bitcoin/bips/blob/master/bip-0048.mediawiki for more info
* (Change paragraph)
*/
static chains = 2;
/**
* @type {import('./masterkey.js').MasterKey}
*/
Expand All @@ -40,20 +46,23 @@ export class Wallet {
* @type {number}
*/
#nAccount;

/**
* Map bip48 change -> Loaded index
* Number of loaded indexes, loaded means that they are in the ownAddresses map
* @type {number}
* @type {Map<number, number>}
*/
#loadedIndexes = 0;
#loadedIndexes = new Map();
/**
* Map bip48 change -> Highest used index
* Highest index used, where used means that the corresponding address is on chain (for example in a tx)
* @type {number}
* @type {Map<number, number>}
*/
#highestUsedIndex = 0;
#highestUsedIndices = new Map();
/**
* @type {number}
* @type {Map<number, number>}
*/
#addressIndex = 0;
#addressIndices = new Map();
/**
* Map our own address -> Path
* @type {Map<String, String?>}
Expand All @@ -78,6 +87,10 @@ export class Wallet {
this.#nAccount = nAccount;
this.#isMainWallet = isMainWallet;
this.#lockedCoins = new Set();
for (let i = 0; i < Wallet.chains; i++) {
this.#highestUsedIndices.set(i, 0);
this.#loadedIndexes.set(i, 0);
}
}

/**
Expand Down Expand Up @@ -185,16 +198,22 @@ export class Wallet {
if (this.#isMainWallet) {
getNetwork().setWallet(this);
}
this.loadAddresses();
for (let i = 0; i < Wallet.chains; i++) this.loadAddresses(i);
}

/**
* Reset the wallet, indexes address map and so on
*/
reset() {
this.#highestUsedIndex = 0;
this.#loadedIndexes = 0;
this.#highestUsedIndices = new Map();
this.#loadedIndexes = new Map();
this.#ownAddresses = new Map();
this.#addressIndices = new Map();
for (let i = 0; i < Wallet.chains; i++) {
this.#highestUsedIndices.set(i, 0);
this.#loadedIndexes.set(i, 0);
this.#addressIndices.set(i, 0);
}
// TODO: This needs to be refactored
// The wallet could own its own mempool and network?
// Instead of having this isMainWallet flag
Expand All @@ -210,7 +229,7 @@ export class Wallet {
*
*/
getCurrentAddress() {
return this.getAddress(0, this.#addressIndex);
return this.getAddress(0, this.#addressIndices.get(0));
}

/**
Expand Down Expand Up @@ -302,16 +321,26 @@ export class Wallet {
/**
* @return [string, string] Address and its BIP32 derivation path
*/
getNewAddress() {
const last = this.#highestUsedIndex;
this.#addressIndex =
(this.#addressIndex > last ? this.#addressIndex : last) + 1;
if (this.#addressIndex - last > MAX_ACCOUNT_GAP) {
getNewAddress(nReceiving = 0) {
const last = this.#highestUsedIndices.get(nReceiving);
this.#addressIndices.set(
nReceiving,
(this.#addressIndices.get(nReceiving) > last
? this.#addressIndices.get(nReceiving)
: last) + 1
);
if (this.#addressIndices.get(nReceiving) - last > MAX_ACCOUNT_GAP) {
// If the user creates more than ${MAX_ACCOUNT_GAP} empty wallets we will not be able to sync them!
this.#addressIndex = last;
this.#addressIndices.set(nReceiving, last);
}
const path = this.getDerivationPath(0, this.#addressIndex);
const address = this.getAddress(0, this.#addressIndex);
const path = this.getDerivationPath(
nReceiving,
this.#addressIndices.get(nReceiving)
);
const address = this.getAddress(
nReceiving,
this.#addressIndices.get(nReceiving)
);
return [address, path];
}

Expand All @@ -332,34 +361,38 @@ export class Wallet {
);
const path = this.isOwnAddress(address);
if (path) {
this.#highestUsedIndex = Math.max(
parseInt(path.split('/')[5]),
this.#highestUsedIndex
const nReceiving = parseInt(path.split('/')[4]);
this.#highestUsedIndices.set(
nReceiving,
Math.max(
parseInt(path.split('/')[5]),
this.#highestUsedIndices.get(nReceiving)
)
);
if (
this.#highestUsedIndex + MAX_ACCOUNT_GAP >=
this.#loadedIndexes
this.#highestUsedIndices.get(nReceiving) + MAX_ACCOUNT_GAP >=
this.#loadedIndexes.get(nReceiving)
) {
this.loadAddresses();
this.loadAddresses(nReceiving);
}
}
}

/**
* Load MAX_ACCOUNT_GAP inside #ownAddresses map.
* @param {number} chain - Chain to load
*/
loadAddresses() {
loadAddresses(chain) {
if (this.isHD()) {
for (
let i = this.#loadedIndexes;
i <= this.#loadedIndexes + MAX_ACCOUNT_GAP;
i++
) {
const path = this.getDerivationPath(0, i);
const start = this.#loadedIndexes.get(chain);
const end = start + MAX_ACCOUNT_GAP;
for (let i = start; i <= end; i++) {
const path = this.getDerivationPath(chain, i);
const address = this.#masterKey.getAddress(path);
this.#ownAddresses.set(address, path);
}
this.#loadedIndexes += MAX_ACCOUNT_GAP;

this.#loadedIndexes.set(chain, end);
} else {
this.#ownAddresses.set(this.getKeyToExport(), ':)');
}
Expand Down Expand Up @@ -694,8 +727,9 @@ export async function hasEncryptedWallet() {
export async function getNewAddress({
updateGUI = false,
verify = false,
nReceiving = 0,
} = {}) {
const [address, path] = wallet.getNewAddress();
const [address, path] = wallet.getNewAddress(nReceiving);
if (verify && wallet.isHardwareWallet()) {
// Generate address to present to the user without asking to verify
const confAddress = await confirmPopup({
Expand Down

0 comments on commit 60d7cd1

Please sign in to comment.