Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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++;
Expand Down
Original file line number Diff line number Diff line change
@@ -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 { }
14 changes: 14 additions & 0 deletions frontend/src/app/docs/api-docs/api-docs-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/app/docs/api-docs/api-docs.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,30 @@ <h3 *ngIf="( item.type === 'category' ) && ( item.showConditions.indexOf(network
</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/&lt;address&gt;#taptree=&lt;taptree&gt;</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/&lt;address&gt;#psbt=&lt;PSBT&gt;</code></a></li>
<li><a [href]="('/address/' | relativeUrl:'mainnet') + faqTaprootAddress + '#tapleaf=c08ada815478a69c1c10af26d4cb370ea53dcfdec2fd0300a6ae3510415133c126d71dd9199b96a12709a3faabf0325a70cee07f37c321ed53fc2fbdbf32bff34b:2060e531bc7b23e145618de9d21a9240e9cf1909a32e77b688f36ec67901500d58ac202edfc0c6e4166b1d5497d9c7a72e7ed4c83fe03596fe5ce5edf7311ddeddf3b1ba529cc0'"><code>/address/&lt;address&gt;#tapleaf=&lt;control-block&gt;:&lt;script&gt;&lt;leaf-version&gt;</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>
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/app/docs/api-docs/api-docs.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
30 changes: 30 additions & 0 deletions frontend/src/app/docs/api-docs/api-docs.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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 = {};
Expand Down Expand Up @@ -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;
}
}

6 changes: 6 additions & 0 deletions frontend/src/app/docs/docs.module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
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';
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,
Expand All @@ -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 { }
8 changes: 2 additions & 6 deletions frontend/src/app/graphs/graphs.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down Expand Up @@ -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),
Expand All @@ -108,8 +107,5 @@ import { AsmStylerPipe } from '@app/shared/pipes/asm-styler/asm-styler.pipe';
NgxEchartsModule,
ActiveAccelerationBox,
],
providers: [
AsmStylerPipe
]
})
export class GraphsModule { }
Loading