mirror of
https://github.com/mempool/mempool.git
synced 2026-08-13 12:33:11 +02:00
Merge pull request #6537 from mempool/natsoni/document-taptree
Some checks failed
Backend Integration Tests with MariaDB / Backend Integration Tests - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Backend (dev) - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Backend (prod) - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Cache assets for builds (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Frontend (dev) - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Frontend (prod) - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / E2E tests for liquid (push) Has been cancelled
CI Pipeline for the Backend and Frontend / E2E tests for mempool (push) Has been cancelled
CI Pipeline for the Backend and Frontend / E2E tests for testnet4 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Validate generated backend Docker JSON (push) Has been cancelled
Supply Chain Audit / Backend install-script audit (push) Has been cancelled
Supply Chain Audit / Frontend install-script audit (push) Has been cancelled
Some checks failed
Backend Integration Tests with MariaDB / Backend Integration Tests - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Backend (dev) - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Backend (prod) - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Cache assets for builds (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Frontend (dev) - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Frontend (prod) - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / E2E tests for liquid (push) Has been cancelled
CI Pipeline for the Backend and Frontend / E2E tests for mempool (push) Has been cancelled
CI Pipeline for the Backend and Frontend / E2E tests for testnet4 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Validate generated backend Docker JSON (push) Has been cancelled
Supply Chain Audit / Backend install-script audit (push) Has been cancelled
Supply Chain Audit / Frontend install-script audit (push) Has been cancelled
Add documentation for taproot tree features
This commit is contained in:
commit
b3bfdd8c26
8 changed files with 108 additions and 7 deletions
|
|
@ -37,6 +37,7 @@ interface LeafNode {
|
|||
})
|
||||
export class TaprootAddressScriptsComponent implements OnChanges {
|
||||
@Input() address: string;
|
||||
@Input() network?: string;
|
||||
@Input() scripts: Map<string, ScriptInfo>;
|
||||
@Output() tapTreeIncomplete = new EventEmitter<boolean>(true);
|
||||
|
||||
|
|
@ -117,7 +118,7 @@ export class TaprootAddressScriptsComponent implements OnChanges {
|
|||
// Expand the tree to include public key, internal key and merkle root
|
||||
const internalKeyNode = scripts[0].taprootInfo.scriptPath.internalKey;
|
||||
const merkleRootNode = treeStructure.length > 0 ? treeStructure[0].keys().next().value : leaves.keys().next().value;
|
||||
const outputKeyNode = addressToScriptPubKey(this.address, this.stateService.network || '').scriptPubKey.slice(4);
|
||||
const outputKeyNode = addressToScriptPubKey(this.address, this.network || this.stateService.network || '').scriptPubKey.slice(4);
|
||||
treeStructure.unshift(new Map<string, [string, string]>());
|
||||
treeStructure[0].set(outputKeyNode, [internalKeyNode, merkleRootNode]);
|
||||
this.depth++;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { NgxEchartsModule } from 'ngx-echarts';
|
||||
import { SharedModule } from '@app/shared/shared.module';
|
||||
import { AsmStylerPipe } from '@app/shared/pipes/asm-styler/asm-styler.pipe';
|
||||
import { TaprootAddressScriptsComponent } from '@components/taproot-address-scripts/taproot-address-scripts.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
TaprootAddressScriptsComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
NgxEchartsModule,
|
||||
],
|
||||
exports: [
|
||||
TaprootAddressScriptsComponent,
|
||||
],
|
||||
providers: [
|
||||
AsmStylerPipe,
|
||||
],
|
||||
})
|
||||
export class TaprootAddressScriptsModule { }
|
||||
|
|
@ -12924,6 +12924,20 @@ export const faqData = [
|
|||
fragment: 'why-do-the-projected-block-fee-ranges-overlap',
|
||||
title: 'Why do the projected block fee ranges overlap?',
|
||||
},
|
||||
{
|
||||
type: 'endpoint',
|
||||
category: 'advanced',
|
||||
showConditions: bitcoinNetworks,
|
||||
fragment: 'how-does-the-taproot-tree-work',
|
||||
title: 'How does the Taproot Tree work?',
|
||||
},
|
||||
{
|
||||
type: 'endpoint',
|
||||
category: 'advanced',
|
||||
showConditions: bitcoinNetworks,
|
||||
fragment: 'how-can-i-share-or-verify-taproot-scripts',
|
||||
title: 'How can I share or verify Taproot scripts?',
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
category: 'self-hosting',
|
||||
|
|
|
|||
|
|
@ -466,6 +466,30 @@
|
|||
</dl>
|
||||
</ng-template>
|
||||
|
||||
<ng-template type="how-does-the-taproot-tree-work">
|
||||
<p>Taproot outputs can commit to many spending scripts. Each script is a leaf in a Merkle tree, called the Taproot Tree, and the Taproot output key commits to the root of that tree.</p>
|
||||
<p>This makes Taproot addresses more private: an external observer does not know all script paths, because scripts are only revealed if coins are spent using them.</p>
|
||||
<p>The Taproot Tree widget shows the script paths already known from on-chain spends, and highlights branches where additional spending scripts may exist but have not been published.</p>
|
||||
<div class="taproot-faq-widget" *ngIf="faqTaprootInfo?.published as taprootInfo">
|
||||
<app-taproot-address-scripts [address]="faqTaprootAddress" network="mainnet" [scripts]="taprootInfo.scripts"></app-taproot-address-scripts>
|
||||
</div>
|
||||
<p><a [href]="('/address/' | relativeUrl:'mainnet') + faqTaprootAddress">View an example on the address page</a>.</p>
|
||||
</ng-template>
|
||||
|
||||
<ng-template type="how-can-i-share-or-verify-taproot-scripts">
|
||||
<p>A third party may need to verify unpublished script paths for a Taproot address. One option is to reveal each script by spending coins through every path on-chain, but this hurts privacy and may not be possible if a script is timelocked, for example.</p>
|
||||
<p>A more private option is to share the Taproot data directly with the third party using the Taproot Tree widget. On the address page, use the <fa-icon [icon]="['fas', 'code-fork']" [fixedWidth]="true"></fa-icon> icon to open a form where Taproot data can be entered. The same data can also be shared directly in the URL fragment:</p>
|
||||
<ul>
|
||||
<li><a [href]="('/address/' | relativeUrl:'mainnet') + faqTaprootAddress + '#taptree=01c0462060e531bc7b23e145618de9d21a9240e9cf1909a32e77b688f36ec67901500d58ac202edfc0c6e4166b1d5497d9c7a72e7ed4c83fe03596fe5ce5edf7311ddeddf3b1ba529c01c02220ab46f1bd685e9c768cca20e5b9a5972b4e1ebab9afda82012ffd0a09d340eb39ac'"><code>/address/<address>#taptree=<taptree></code></a></li>
|
||||
<li><a [href]="('/address/' | relativeUrl:'mainnet') + faqTaprootAddress + '#psbt=cHNidP8BAF4CAAAAAaw0pDKziErd65x14VlMyr0dk40sbfnsamlaosoIMs/uAAAAAAD9////ARwUAAAAAAAAIlEgnvbZpjy+K9OYazdE0ZpDDp7aT7dUorK7Vj6Y0E7uqqkAAAAAAAEAXgIAAAABgLeH7oKnqJhBXimT6kZD0aqj5ruNbph8lB9VAPQr3sIAAAAAAP3///8BvBQAAAAAAAAiUSBJJVwiFU5hOZBwUBPCN2A/wOQ9ck4K4MpzwtCbYVTCpQAAAAABASu8FAAAAAAAACJRIEklXCIVTmE5kHBQE8I3YD/A5D1yTgrgynPC0JthVMKlQhXAitqBVHimnBwQrybUyzcOpT3P3sL9AwCmrjUQQVEzwSbXHdkZm5ahJwmj+qvwMlpwzuB/N8Mh7VP8L72/Mr/zS0cgYOUxvHsj4UVhjenSGpJA6c8ZCaMud7aI827GeQFQDVisIC7fwMbkFmsdVJfZx6cuftTIP+A1lv5c5e33MR3e3fOxulKcwEIVwIragVR4ppwcEK8m1Ms3DqU9z97C/QMApq41EEFRM8EmHxKGqo8QrcKRJ/V/AofE38r3/E00jTURzyXSM2o8QowjIKtG8b1oXpx2jMog5bmllytOHrq5r9qCAS/9CgnTQOs5rMABFyCK2oFUeKacHBCvJtTLNw6lPc/ewv0DAKauNRBBUTPBJgAA'"><code>/address/<address>#psbt=<PSBT></code></a></li>
|
||||
<li><a [href]="('/address/' | relativeUrl:'mainnet') + faqTaprootAddress + '#tapleaf=c08ada815478a69c1c10af26d4cb370ea53dcfdec2fd0300a6ae3510415133c126d71dd9199b96a12709a3faabf0325a70cee07f37c321ed53fc2fbdbf32bff34b:2060e531bc7b23e145618de9d21a9240e9cf1909a32e77b688f36ec67901500d58ac202edfc0c6e4166b1d5497d9c7a72e7ed4c83fe03596fe5ce5edf7311ddeddf3b1ba529cc0'"><code>/address/<address>#tapleaf=<control-block>:<script><leaf-version></code></a></li>
|
||||
</ul>
|
||||
<div class="taproot-faq-widget" *ngIf="faqTaprootInfo?.full as taprootInfo">
|
||||
<app-taproot-address-scripts [address]="faqTaprootAddress" network="mainnet" [scripts]="taprootInfo.scripts"></app-taproot-address-scripts>
|
||||
</div>
|
||||
<p>Cryptographic checks on the provided data are done in the browser so the verifier can independently confirm that the provided scripts commit to the Taproot output key for the address.</p>
|
||||
</ng-template>
|
||||
|
||||
<ng-template type="what-are-sigops">
|
||||
<p>A "sigop" is a way of accounting for the cost of "signature operations" in Bitcoin script, like <code>OP_CHECKSIG</code>, <code>OP_CHECKSIGVERIFY</code>, <code>OP_CHECKMULTISIG</code> and <code>OP_CHECKMULTISIGVERIFY</code></p>
|
||||
<p>These signature operations incur different costs depending on whether they are single or multi-sig operations, and on where they appear in a Bitcoin transaction.</p>
|
||||
|
|
|
|||
|
|
@ -288,6 +288,12 @@ h3 {
|
|||
}
|
||||
}
|
||||
|
||||
.taproot-faq-widget {
|
||||
background-color: var(--bg);
|
||||
margin: 1rem 0;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
:host-context(.ltr-layout) {
|
||||
.blockchain-wrapper.time-ltr .blocks-wrapper,
|
||||
.blockchain-wrapper .blocks-wrapper {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@ import { tap, takeUntil } from 'rxjs/operators';
|
|||
import { ActivatedRoute } from '@angular/router';
|
||||
import { faqData, restApiDocsData, wsApiDocsData, electrumApiDocsData } from '@app/docs/api-docs/api-docs-data';
|
||||
import { FaqTemplateDirective } from '@app/docs/faq-template/faq-template.component';
|
||||
import { AddressTypeInfo } from '@app/shared/address-utils';
|
||||
import { convertTextToBuffer, extractTapLeaves, fillTapTree, TapLeaf } from '@app/shared/transaction.utils';
|
||||
|
||||
const FAQ_TAPROOT_ADDRESS = 'bc1pfyj4cgs4fesnnyrs2qfuydmq8lqwg0tjfc9wpjnnctgfkc25c2jshnmyl4';
|
||||
const FAQ_TAPROOT_TAPTREE = '01c0462060e531bc7b23e145618de9d21a9240e9cf1909a32e77b688f36ec67901500d58ac202edfc0c6e4166b1d5497d9c7a72e7ed4c83fe03596fe5ce5edf7311ddeddf3b1ba529c01c02220ab46f1bd685e9c768cca20e5b9a5972b4e1ebab9afda82012ffd0a09d340eb39ac';
|
||||
const FAQ_TAPROOT_INTERNAL_KEY = '8ada815478a69c1c10af26d4cb370ea53dcfdec2fd0300a6ae3510415133c126';
|
||||
|
||||
@Component({
|
||||
selector: 'app-api-docs',
|
||||
|
|
@ -37,6 +43,8 @@ export class ApiDocsComponent implements OnInit, AfterViewInit {
|
|||
timeLtrSubscription: Subscription;
|
||||
timeLtr: boolean = this.stateService.timeLtr.value;
|
||||
isMempoolSpaceBuild = this.stateService.isMempoolSpaceBuild;
|
||||
faqTaprootAddress = FAQ_TAPROOT_ADDRESS;
|
||||
faqTaprootInfo = this.buildFaqTaprootInfo();
|
||||
|
||||
@ViewChildren(FaqTemplateDirective) faqTemplates: QueryList<FaqTemplateDirective>;
|
||||
dict = {};
|
||||
|
|
@ -238,5 +246,27 @@ export class ApiDocsComponent implements OnInit, AfterViewInit {
|
|||
return `${wsHostname}${curlNetwork}/api/v1/ws`;
|
||||
}
|
||||
|
||||
buildFaqTaprootInfo(): { published: AddressTypeInfo, full: AddressTypeInfo } | null {
|
||||
try {
|
||||
const leaves = extractTapLeaves(undefined, [], convertTextToBuffer(FAQ_TAPROOT_TAPTREE), convertTextToBuffer(FAQ_TAPROOT_INTERNAL_KEY));
|
||||
const publishedLeaf = leaves[1];
|
||||
if (!publishedLeaf) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
published: this.fillFaqTaprootInfo([publishedLeaf]),
|
||||
full: this.fillFaqTaprootInfo(leaves),
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn('Failed to build Taproot FAQ example', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
fillFaqTaprootInfo(leaves: TapLeaf[]): AddressTypeInfo {
|
||||
const taprootInfo = new AddressTypeInfo('mainnet', FAQ_TAPROOT_ADDRESS);
|
||||
fillTapTree(taprootInfo, leaves);
|
||||
return taprootInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgxEchartsModule } from 'ngx-echarts';
|
||||
import { SharedModule } from '@app/shared/shared.module';
|
||||
import { ApiDocsComponent } from '@app/docs/api-docs/api-docs.component';
|
||||
import { DocsComponent } from '@app/docs/docs/docs.component';
|
||||
|
|
@ -7,6 +8,7 @@ import { ApiDocsNavComponent } from '@app/docs/api-docs/api-docs-nav.component';
|
|||
import { CodeTemplateComponent } from '@app/docs/code-template/code-template.component';
|
||||
import { DocsRoutingModule } from '@app/docs/docs.routing.module';
|
||||
import { FaqTemplateDirective } from '@app/docs/faq-template/faq-template.component';
|
||||
import { TaprootAddressScriptsModule } from '@components/taproot-address-scripts/taproot-address-scripts.module';
|
||||
@NgModule({
|
||||
declarations: [
|
||||
ApiDocsComponent,
|
||||
|
|
@ -18,7 +20,11 @@ import { FaqTemplateDirective } from '@app/docs/faq-template/faq-template.compon
|
|||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
TaprootAddressScriptsModule,
|
||||
DocsRoutingModule,
|
||||
NgxEchartsModule.forRoot({
|
||||
echarts: () => import('@app/graphs/echarts').then(m => m.echarts),
|
||||
}),
|
||||
]
|
||||
})
|
||||
export class DocsModule { }
|
||||
|
|
|
|||
|
|
@ -45,9 +45,8 @@ import { TreasuriesVerifyProgressComponent } from '@components/treasuries/verify
|
|||
import { UtxoGraphComponent } from '@components/utxo-graph/utxo-graph.component';
|
||||
import { ActiveAccelerationBox } from '@components/acceleration/active-acceleration-box/active-acceleration-box.component';
|
||||
import { AddressesTreemap } from '@components/addresses-treemap/addresses-treemap.component';
|
||||
import { TaprootAddressScriptsComponent } from '@components/taproot-address-scripts/taproot-address-scripts.component';
|
||||
import { TaprootAddressScriptsModule } from '@components/taproot-address-scripts/taproot-address-scripts.module';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { AsmStylerPipe } from '@app/shared/pipes/asm-styler/asm-styler.pipe';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
|
@ -94,11 +93,11 @@ import { AsmStylerPipe } from '@app/shared/pipes/asm-styler/asm-styler.pipe';
|
|||
UtxoGraphComponent,
|
||||
ActiveAccelerationBox,
|
||||
AddressesTreemap,
|
||||
TaprootAddressScriptsComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
TaprootAddressScriptsModule,
|
||||
GraphsRoutingModule,
|
||||
NgxEchartsModule.forRoot({
|
||||
echarts: () => import('@app/graphs/echarts').then(m => m.echarts),
|
||||
|
|
@ -108,8 +107,5 @@ import { AsmStylerPipe } from '@app/shared/pipes/asm-styler/asm-styler.pipe';
|
|||
NgxEchartsModule,
|
||||
ActiveAccelerationBox,
|
||||
],
|
||||
providers: [
|
||||
AsmStylerPipe
|
||||
]
|
||||
})
|
||||
export class GraphsModule { }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue