This commit is contained in:
Rodrigo Bonilla 2026-08-13 02:26:10 -06:00 committed by GitHub
commit ea0e391764
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 122 additions and 0 deletions

View file

@ -1,4 +1,7 @@
<div class="container-xl" [class.liquid-address]="network === 'liquid' || network === 'liquidtestnet'">
@if (showColdcardWarning && !isLoadingAddress && !error) {
<ng-container *ngTemplateOutlet="coldcardWarning"></ng-container>
}
<div class="title-address">
<h1 i18n="shared.address">Address</h1>
<div class="tx-link">
@ -16,6 +19,7 @@
<div class="clearfix"></div>
<ng-template [ngIf]="!isLoadingAddress && !error">
<div class="box">
@ -261,6 +265,13 @@
<br>
<ng-template #coldcardWarning>
<div class="alert alert-danger alert-dismissible coldcard-warning" role="alert">
<a [routerLink]="['/docs/faq' | relativeUrl]" fragment="why-may-my-multisig-funds-be-at-risk"><span>⚠️ </span><span i18n="address.coldcard.warning">Warning for ColdCard users: The funds in this address may be at risk under default multisig setups.</span></a>
<button type="button" class="btn-close btn-close-white" aria-label="Close" i18n-aria-label (click)="dismissWarning()"></button>
</div>
</ng-template>
<ng-template #confidentialTd>
<td i18n="shared.confidential">Confidential</td>
</ng-template>

View file

@ -133,3 +133,17 @@ h1 {
padding-bottom: 0;
font-size: 0.8rem;
}
.coldcard-warning {
display: flex;
align-items: center;
position: relative;
background-color: #b71c1c;
border-color: #b71c1c;
}
.btn-close:hover,
.btn-close:focus {
opacity: 1;
}

View file

@ -14,6 +14,7 @@ import { seoDescriptionNetwork } from '@app/shared/common.utils';
import { AddressInformation } from '@interfaces/node-api.interface';
import { AddressTypeInfo } from '@app/shared/address-utils';
import { extractTapLeaves, fillTapTree, convertTextToBuffer, PsbtKeyValue } from '@app/shared/transaction.utils';
import { StorageService } from '@app/services/storage.service';
class AddressStats implements ChainStats {
address: string;
@ -138,6 +139,8 @@ export class AddressComponent implements OnInit, OnDestroy {
now = Date.now() / 1000;
balancePeriod: 'all' | '1m' = 'all';
showColdcardWarning: boolean = false;
private tempTransactions: Transaction[];
private timeTxIndexes: number[];
private lastTransactionTxId: string;
@ -151,6 +154,7 @@ export class AddressComponent implements OnInit, OnDestroy {
private apiService: ApiService,
private seoService: SeoService,
private formBuilder: UntypedFormBuilder,
private storageService: StorageService,
) { }
ngOnInit(): void {
@ -204,6 +208,7 @@ export class AddressComponent implements OnInit, OnDestroy {
this.seoService.setTitle($localize`:@@address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`);
this.seoService.setDescription($localize`:@@meta.description.bitcoin.address:See mempool transactions, confirmed transactions, balance, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} address ${this.addressString}:INTERPOLATION:.`);
this.showColdcardWarning = false;
this.addressTypeInfo = new AddressTypeInfo(this.stateService.network || 'mainnet', this.addressString);
return merge(
@ -337,6 +342,8 @@ export class AddressComponent implements OnInit, OnDestroy {
} else {
this.setBalancePeriod('1m');
}
this.updateShowColdcardWarning();
},
(error) => {
console.log(error);
@ -652,6 +659,29 @@ export class AddressComponent implements OnInit, OnDestroy {
}
}
updateShowColdcardWarning() {
const showCCWarningPref = this.storageService.getValue('showAddrColdcardWarning');
if (showCCWarningPref && showCCWarningPref === 'false') {
return;
}
if (!['p2sh', 'v0_p2wsh', 'p2sh-p2wsh'].includes(this.addressTypeInfo.type) || !this.addressTypeInfo.isMultisig || this.addressTypeInfo.network !== 'mainnet') {
return;
}
if (this.chainStats.balance === 0 && this.mempoolStats.balance === 0) {
return;
}
this.showColdcardWarning = true;
}
dismissWarning() {
this.storageService.setValue('showAddrColdcardWarning', 'false');
this.showColdcardWarning = false;
}
ngOnDestroy(): void {
this.mainSubscription.unsubscribe();
this.mempoolTxSubscription.unsubscribe();

View file

@ -21,6 +21,12 @@
<span i18n="transaction.poison.warning">Warning! This transaction involves deceptively similar addresses. It may be an address poisoning attack.</span>
</div>
}
@if (showColdcardWarning) {
<div class="alert alert-danger alert-dismissible" role="alert">
<a [routerLink]="['/docs/faq' | relativeUrl]" fragment="why-may-my-multisig-funds-be-at-risk"><span>⚠️ </span><span i18n="transaction.coldcard.warning">Warning for ColdCard users: The funds of this transaction may be at risk while unconfirmed under default multisig setups.</span></a>
<button type="button" class="btn-close btn-close-white" aria-label="Close" i18n-aria-label (click)="dismissWarning()"></button>
</div>
}
<div class="row">
<div class="col">
<table class="table table-fixed table-borderless smaller-text table-sm table-tx-vin">

View file

@ -416,6 +416,19 @@ h2 {
}
}
.alert-danger {
display: flex;
align-items: center;
position: relative;
background-color: #b71c1c;
border-color: #b71c1c;
}
.btn-close:hover,
.btn-close:focus {
opacity: 1;
}
@keyframes acceleratePulse {
0% { background-color: #653b9c; box-shadow: #ad7de57f 0px 0px 12px -2px; }
50% { background-color: #8457bb; box-shadow: #ad7de5 0px 0px 18px -2px;}

View file

@ -72,6 +72,7 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
showTaprootControlBlock: { [vinIndex: number]: boolean } = {};
showOrdData: { [key: string]: { show: boolean; inscriptions?: Inscription[]; runestone?: Runestone, runeInfo?: { [id: string]: { etching: Etching; txid: string; } }; } } = {};
similarityMatches: Map<string, Map<string, { score: number, match: AddressMatch, group: number }>> = new Map();
showColdcardWarning: boolean = false;
selectedSig: { txIndex: number, vindex: number, sig: SigInfo } | null = null;
sigHighlights: { vin: boolean[], vout: boolean[] } = { vin: [], vout: [] };
@ -186,6 +187,7 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
});
this.updateAddressSimilarities();
this.updateShowColdcardWarning();
}
refreshPrice(): void {
@ -476,6 +478,33 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
}
}
updateShowColdcardWarning(): void {
const showCCWarningPref = this.storageService.getValue('showTxColdcardWarning');
// there's only one tx, it's the tx page, it's unconfirmed, it contains inputs that are p2sh, v0_p2wsh or p2sh-p2wsh, is multisig and network is mainnet
if (showCCWarningPref && showCCWarningPref === 'false') {
return;
}
if (!this.transactionPage || this.txPreview) {
return;
}
if (this.transactions.length > 1 || this.transactions[0].status.confirmed) {
return;
}
const vinPossibleCC: Vin[] = this.transactions[0].vin.filter((input: Vin) => {
const addressTypeInfo = new AddressTypeInfo(this.network || 'mainnet', input.prevout.scriptpubkey_address, input.prevout?.scriptpubkey_type as AddressType, [input]);
return ['p2sh', 'v0_p2wsh', 'p2sh-p2wsh'].includes(addressTypeInfo.type) && !!addressTypeInfo.isMultisig && addressTypeInfo.network === 'mainnet';
});
if (vinPossibleCC.length === 0) {
return;
}
this.showColdcardWarning = true;
}
// assume any address with 12 or more contiguous repeated substrings is fake
fakeScriptHashRegex = new RegExp(/(.+?)\1{11,}/);
isFakeScripthash(vout: Vout): boolean {
@ -713,6 +742,12 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
}
}
dismissWarning(): void {
this.storageService.setValue('showTxColdcardWarning', 'false');
this.showColdcardWarning = false;
this.ref.markForCheck();
}
ngOnDestroy(): void {
this.outspendsSubscription.unsubscribe();
this.currencyChangeSubscription?.unsubscribe();

View file

@ -12938,6 +12938,13 @@ export const faqData = [
fragment: 'how-can-i-share-or-verify-taproot-scripts',
title: 'How can I share or verify Taproot scripts?',
},
{
type: 'endpoint',
category: 'advanced',
showConditions: bitcoinNetworks,
fragment: 'why-may-my-multisig-funds-be-at-risk',
title: 'Why may my multisig wallet funds be at risk?',
},
{
type: 'category',
category: 'self-hosting',

View file

@ -547,3 +547,9 @@
<ng-template type="address-lookup-issues">
<p>If you're getting errors when doing address lookups, it's probably because of your Electrum server backend.</p><p>Mempool uses an Electrum server to do address lookups. There are several implementations of the Electrum server protocol, and Mempool can use any of them, but the implementation you use affects performance:</p><ol><li><a href="https://github.com/romanz/electrs" target="_blank">romanz/electrs</a>. This is a common choice for its low resource requirements, and most full-node distros use it. But while this implementation works great for basic queries, it will struggle with heavier ones (e.g. looking up addresses with many transactions)—especially when running on low-power hardware like a Raspberry Pi.</li><li><a href="https://github.com/cculianu/Fulcrum" target="_blank">Fulcrum</a>. Fulcrum requires more resources than romanz/electrs but it can still run on a Raspberry Pi, and it handles heavy queries much more efficiently. If you're having issues with romanz/electrs, Fulcrum is worth a try.</li><li><a href="https://github.com/mempool/electrs" target="_blank">mempool/electrs</a>. If you have stronger hardware, you could consider running mempool/electrs, the backend that powers mempool.space. It's a fork of Blockstream's Esplora, which is in turn a fork of romanz/electrs, intended for maximum performance and larger-scale deployments.</li></ol>
</ng-template>
<ng-template type="why-may-my-multisig-funds-be-at-risk">
<p>If a transaction reveals a multisig script (threshold + public keys) and enough of the keys to meet the threshold were generated on a <a href="https://coldcard.com/" target="_blank">Coldcard</a> with the pre-fix firmware (or otherwise weak entropy) and lack sufficient extra entropy/passphrase, the funds may be at elevated risk of theft while unconfirmed. </p>
<p>If an address has been used and reveals a multisig script (threshold + public keys) and enough of the keys to meet the threshold were generated on a <a href="https://coldcard.com/" target="_blank">Coldcard</a> with the pre-fix firmware (or otherwise weak entropy) and lack sufficient extra entropy/passphrase, the funds in the address are vulnerable.</p>
<p><a href="https://blog.coinkite.com/coldcard-mk3-seed-generation-warning/" target="_blank">See Coinkites advisory.</a></p>
</ng-template>