Skip to content

Commit bdd14b2

Browse files
committed
frontend/coin-control: add address reuse warning
Add a warning to the coin control modal when a user selects one or more UTXOs from reused addresses. The warning: "One or more UTXOs have a reused address. Be aware the receiver will see all UTXOs associated with those addresses." The UTXOs now have red badge when their address was re-used.
1 parent 9ae91a3 commit bdd14b2

File tree

7 files changed

+61
-14
lines changed

7 files changed

+61
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- Fix Wallet Connect issue where account unspecified by the connecting dapp caused a UI crash
1313
- Fix Wallet Connect issue with required/optionalNamespace and handling all possible namespace definitions
1414
- Add Satoshi as an option in active currencies
15+
- Show address re-use warning and group UTXOs with the same address together in coin control.
1516

1617
## 4.42.0
1718
- Preselect backup when there's only one backup available

backend/coins/btc/handlers/handlers.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -324,16 +324,27 @@ func (handlers *Handlers) getUTXOs(*http.Request) (interface{}, error) {
324324
return result, errp.New("Interface must be of type btc.Account")
325325
}
326326

327+
addressCounts := make(map[string]int)
328+
327329
for _, output := range t.SpendableOutputs() {
330+
address := output.Address.EncodeForHumans()
331+
addressCounts[address]++
332+
}
333+
334+
for _, output := range t.SpendableOutputs() {
335+
address := output.Address.EncodeForHumans()
336+
addressReused := addressCounts[address] > 1
337+
328338
result = append(result,
329339
map[string]interface{}{
330-
"outPoint": output.OutPoint.String(),
331-
"txId": output.OutPoint.Hash.String(),
332-
"txOutput": output.OutPoint.Index,
333-
"amount": handlers.formatBTCAmountAsJSON(btcutil.Amount(output.TxOut.Value), false),
334-
"address": output.Address.EncodeForHumans(),
335-
"scriptType": output.Address.Configuration.ScriptType(),
336-
"note": handlers.account.TxNote(output.OutPoint.Hash.String()),
340+
"outPoint": output.OutPoint.String(),
341+
"txId": output.OutPoint.Hash.String(),
342+
"txOutput": output.OutPoint.Index,
343+
"amount": handlers.formatBTCAmountAsJSON(btcutil.Amount(output.TxOut.Value), false),
344+
"address": address,
345+
"scriptType": output.Address.Configuration.ScriptType(),
346+
"note": handlers.account.TxNote(output.OutPoint.Hash.String()),
347+
"addressReused": addressReused,
337348
})
338349
}
339350

frontends/web/src/api/account.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ export type TUTXO = {
369369
amount: IAmount;
370370
note: string;
371371
scriptType: ScriptType;
372+
addressReused: boolean;
372373
};
373374

374375
export const getUTXOs = (code: AccountCode): Promise<TUTXO[]> => {

frontends/web/src/components/badge/badge.module.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,10 @@
4242
border-color: var(--color-darkyellow);
4343
color: var(--color-olive);
4444
}
45+
46+
/* swiss-red with different opacities */
47+
.danger {
48+
background: rgba(255, 0, 0, 0.2);
49+
border-color: rgba(255, 0, 0, 0.33);
50+
color: rgba(255, 0, 0, 1);
51+
}

frontends/web/src/components/badge/badge.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import { ReactElement, ReactNode } from 'react';
1818
import style from './badge.module.css';
1919

20-
type TBadgeStyles = 'success' | 'warning'; // TODO: not yet implemented 'info' | 'danger'
20+
type TBadgeStyles = 'success' | 'warning' | 'danger'; // TODO: not yet implemented 'info'
2121

2222
type TProps = {
2323
children?: ReactNode;

frontends/web/src/locales/en/app.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,6 +1525,7 @@
15251525
"button": "Review",
15261526
"coincontrol": {
15271527
"address": "Address",
1528+
"addressReused": "Address re-used",
15281529
"outpoint": "Outpoint",
15291530
"title": "Send from output"
15301531
},
@@ -1790,6 +1791,7 @@
17901791
"walletConnect": "WalletConnect"
17911792
},
17921793
"warning": {
1794+
"coincontrol": "One or more UTXOs have a reused address. Be aware the receiver will see all UTXOs associated with those addresses.",
17931795
"receivePairing": "Please pair the BitBox to enable secure address verification. Go to 'Manage device' in the sidebar.",
17941796
"sdcard": "Keep the microSD card stored separate from the BitBox, unless you want to manage backups.",
17951797
"sendPairing": "Please pair the BitBox to securely verify transaction details. Go to 'Manage device' in the sidebar."

frontends/web/src/routes/account/send/utxos.tsx

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import { ExternalLink } from '../../../components/icon';
3232
import { Amount } from '../../../components/amount/amount';
3333
import { FiatConversion } from '../../../components/rates/rates';
3434
import { getScriptName } from '../utils';
35+
import { Message } from '../../../components/message/message';
36+
import { Badge } from '../../../components/badge/badge';
3537
import style from './utxos.module.css';
3638

3739
export type TSelectedUTXOs = {
@@ -56,6 +58,7 @@ export const UTXOs = ({
5658
const { t } = useTranslation();
5759
const [utxos, setUtxos] = useState<TUTXO[]>([]);
5860
const [selectedUTXOs, setSelectedUTXOs] = useState<TSelectedUTXOs>({});
61+
const [reusedAddressUTXOs, setReusedAddressUTXOs] = useState(0);
5962

6063
useEffect(() => {
6164
getUTXOs(accountCode).then(setUtxos);
@@ -71,14 +74,22 @@ export const UTXOs = ({
7174
return () => unsubscribe();
7275
}, [accountCode]);
7376

74-
const handleUTXOChange = (event: ChangeEvent<HTMLInputElement>) => {
77+
const handleUTXOChange = (
78+
event: ChangeEvent<HTMLInputElement>,
79+
utxo: TUTXO,
80+
) => {
7581
const target = event.target;
76-
const outPoint = target.dataset.outpoint as string;
7782
const proposedUTXOs = Object.assign({}, selectedUTXOs);
7883
if (target.checked) {
79-
proposedUTXOs[outPoint] = true;
84+
proposedUTXOs[utxo.outPoint] = true;
85+
if (utxo.addressReused) {
86+
setReusedAddressUTXOs(reusedAddressUTXOs + 1);
87+
}
8088
} else {
81-
delete proposedUTXOs[outPoint];
89+
delete proposedUTXOs[utxo.outPoint];
90+
if (utxo.addressReused) {
91+
setReusedAddressUTXOs(reusedAddressUTXOs - 1);
92+
}
8293
}
8394
setSelectedUTXOs(proposedUTXOs);
8495
onChange(proposedUTXOs);
@@ -98,8 +109,7 @@ export const UTXOs = ({
98109
<Checkbox
99110
checked={!!selectedUTXOs[utxo.outPoint]}
100111
id={'utxo-' + utxo.outPoint}
101-
data-outpoint={utxo.outPoint}
102-
onChange={handleUTXOChange}>
112+
onChange={event => handleUTXOChange(event, utxo)}>
103113
{utxo.note && (
104114
<div className={style.note}>
105115
<strong>{utxo.note}{' '}</strong>
@@ -124,6 +134,14 @@ export const UTXOs = ({
124134
<span className={style.shrink}>
125135
{utxo.address}
126136
</span>
137+
<div className="m-left-quarter">
138+
{utxo.addressReused ?
139+
<Badge type="danger">
140+
{t('send.coincontrol.addressReused')}
141+
</Badge> :
142+
null
143+
}
144+
</div>
127145
</div>
128146
<div className={style.transaction}>
129147
<span className={style.label}>
@@ -155,6 +173,13 @@ export const UTXOs = ({
155173
title={t('send.coincontrol.title')}
156174
large
157175
onClose={onClose}>
176+
<div>
177+
{(reusedAddressUTXOs > 0) && (
178+
<Message type="warning">
179+
{t('warning.coincontrol')}
180+
</Message>
181+
)}
182+
</div>
158183
<div>
159184
{ allScriptTypes.map(renderUTXOs) }
160185
<div className="buttons text-center m-top-none m-bottom-half">

0 commit comments

Comments
 (0)