mirror of
https://github.com/mempool/mempool.git
synced 2025-04-23 23:10:45 +02:00
Add signature annotations to legacy scriptsigs
This commit is contained in:
parent
29b0fb9b3b
commit
f83e35699e
@ -155,7 +155,14 @@
|
||||
<ng-template [ngIf]="vin.scriptsig">
|
||||
<tr>
|
||||
<td i18n="transactions-list.scriptsig.asm|ScriptSig (ASM)">ScriptSig (ASM)</td>
|
||||
<td style="text-align: left;" [innerHTML]="vin.scriptsig_asm | asmStyler"></td>
|
||||
<td style="text-align: left;">
|
||||
<app-asm
|
||||
[asm]="vin.scriptsig_asm"
|
||||
[annotations]="{ signatures: tx['_sigmap'], selectedSig: selectedSig?.txIndex === i && selectedSig?.vindex === vindex ? selectedSig.sig : null }"
|
||||
(showSigInfo)="showSigInfo(i, vindex, $event)"
|
||||
(hideSigInfo)="hideSigInfo()"
|
||||
></app-asm>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="transactions-list.scriptsig.hex|ScriptSig (HEX)">ScriptSig (HEX)</td>
|
||||
|
@ -15,7 +15,7 @@ import { OrdApiService } from '@app/services/ord-api.service';
|
||||
import { Inscription } from '@app/shared/ord/inscription.utils';
|
||||
import { Etching, Runestone } from '@app/shared/ord/rune.utils';
|
||||
import { ADDRESS_SIMILARITY_THRESHOLD, AddressMatch, AddressSimilarity, AddressType, AddressTypeInfo, checkedCompareAddressStrings, detectAddressType } from '@app/shared/address-utils';
|
||||
import { processInputSignatures, Sighash, SigInfo } from '../../shared/transaction.utils';
|
||||
import { processInputSignatures, Sighash, SigInfo, SighashLabels } from '@app/shared/transaction.utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-transactions-list',
|
||||
@ -62,15 +62,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
|
||||
selectedSig: { txIndex: number, vindex: number, sig: SigInfo } | null = null;
|
||||
sigHighlights: { vin: boolean[], vout: boolean[] } = { vin: [], vout: [] };
|
||||
sighashLabels: { [sighash: string]: string } = {
|
||||
'0': 'SIGHASH_DEFAULT',
|
||||
'1': 'SIGHASH_ALL',
|
||||
'2': 'SIGHASH_NONE',
|
||||
'3': 'SIGHASH_SINGLE',
|
||||
'129': 'SIGHASH_ALL | ACP',
|
||||
'130': 'SIGHASH_NONE | ACP',
|
||||
'131': 'SIGHASH_SINGLE | ACP',
|
||||
};
|
||||
sighashLabels = SighashLabels;
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
|
30
frontend/src/app/shared/components/asm/asm.component.html
Normal file
30
frontend/src/app/shared/components/asm/asm.component.html
Normal file
@ -0,0 +1,30 @@
|
||||
<div class="asm">
|
||||
@for (instruction of instructions; track instruction.instruction) {
|
||||
<span [class]='opcodeStyles.get(instruction.instruction)'>OP_{{instruction.instruction}}</span>
|
||||
@for (arg of instruction.args; track arg) {
|
||||
<ng-container *ngIf="annotations.signatures[arg] as sigInfo; else plainArg">
|
||||
<span class="sig sig-key sig-inline sighash-{{sigInfo.sig.sighash}}"
|
||||
[class.hovered]="annotations.selectedSig && annotations.selectedSig === sigInfo.sig"
|
||||
(mouseenter)="doShowSigInfo(sigInfo.sig)"
|
||||
(mouseleave)="doHideSigInfo()"
|
||||
[title]="sighashLabels[sigInfo.sig.sighash]">
|
||||
<fa-icon [icon]="['fas', 'key']" [fixedWidth]="true"></fa-icon>
|
||||
</span>
|
||||
<ng-container *ngIf="sigInfo.sig.sighash !== 0; else plainSig">
|
||||
{{arg.slice(0, -2)}}<span class="sig sighash-{{sigInfo.sig.sighash}}"
|
||||
[class.hovered]="annotations.selectedSig && annotations.selectedSig === sigInfo.sig"
|
||||
(mouseenter)="doShowSigInfo(sigInfo.sig)"
|
||||
(mouseleave)="doHideSigInfo()"
|
||||
[title]="sighashLabels[sigInfo.sig.sighash]">{{arg.slice(-2)}}</span>
|
||||
</ng-container>
|
||||
<ng-template #plainSig>
|
||||
{{ arg }}
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-template #plainArg>
|
||||
{{ arg }}
|
||||
</ng-template>
|
||||
}
|
||||
<br>
|
||||
}
|
||||
</div>
|
53
frontend/src/app/shared/components/asm/asm.component.scss
Normal file
53
frontend/src/app/shared/components/asm/asm.component.scss
Normal file
@ -0,0 +1,53 @@
|
||||
.sig {
|
||||
cursor: pointer;
|
||||
|
||||
|
||||
&.sighash-0 {
|
||||
--sigColor: var(--green);
|
||||
}
|
||||
&.sighash-1 {
|
||||
--sigColor: var(--green);
|
||||
}
|
||||
&.sighash-2 {
|
||||
--sigColor: gold;
|
||||
}
|
||||
&.sighash-3 {
|
||||
--sigColor: cornflowerblue;
|
||||
}
|
||||
&.sighash-129 {
|
||||
--sigColor: darkviolet;
|
||||
}
|
||||
&.sighash-130 {
|
||||
--sigColor: darkorange;
|
||||
}
|
||||
&.sighash-131 {
|
||||
--sigColor: var(--pink);
|
||||
}
|
||||
&.sig-no-lock {
|
||||
--sigColor: var(--red);
|
||||
}
|
||||
color: var(--sigColor);
|
||||
|
||||
&:hover, &.hovered {
|
||||
color: color-mix(in srgb, var(--sigColor) 60%, white);
|
||||
}
|
||||
|
||||
::ng-deep svg path {
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.sig-key {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transition: transform 0.2s ease;
|
||||
pointer-events: none;
|
||||
|
||||
&.sig-inline {
|
||||
position: relative;
|
||||
pointer-events: auto;
|
||||
margin-left: 4px;
|
||||
margin-right: -4px;
|
||||
}
|
||||
}
|
178
frontend/src/app/shared/components/asm/asm.component.ts
Normal file
178
frontend/src/app/shared/components/asm/asm.component.ts
Normal file
@ -0,0 +1,178 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core';
|
||||
import { SigInfo, SighashLabels } from '@app/shared/transaction.utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-asm',
|
||||
templateUrl: './asm.component.html',
|
||||
styleUrls: ['./asm.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AsmComponent {
|
||||
@Input() asm: string;
|
||||
@Input() crop: number = 0;
|
||||
@Input() annotations: {
|
||||
signatures: Record<string, { sig: SigInfo, vindex: number }>,
|
||||
selectedSig: SigInfo | null
|
||||
} = {
|
||||
signatures: {},
|
||||
selectedSig: null
|
||||
};
|
||||
@Output() showSigInfo = new EventEmitter<SigInfo>();
|
||||
@Output() hideSigInfo = new EventEmitter<void>();
|
||||
|
||||
instructions: { instruction: string, args: string[] }[] = [];
|
||||
sighashLabels: Record<number, string> = SighashLabels;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.parseASM();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes['asm']) {
|
||||
this.parseASM();
|
||||
}
|
||||
}
|
||||
|
||||
parseASM(): void {
|
||||
let instructions = this.asm.split('OP_');
|
||||
// trim instructions to a whole number of instructions with at most `crop` characters total
|
||||
if (this.crop) {
|
||||
let chars = 0;
|
||||
for (let i = 0; i < instructions.length; i++) {
|
||||
chars += instructions[i].length + 3;
|
||||
if (chars > this.crop) {
|
||||
instructions = instructions.slice(0, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.instructions = instructions.filter(instruction => instruction.trim() !== '').map(instruction => {
|
||||
const parts = instruction.split(' ');
|
||||
return {
|
||||
instruction: parts[0],
|
||||
args: parts.slice(1)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
doShowSigInfo(sig: SigInfo): void {
|
||||
this.showSigInfo.emit(sig);
|
||||
}
|
||||
|
||||
doHideSigInfo(): void {
|
||||
this.hideSigInfo.emit();
|
||||
}
|
||||
|
||||
readonly opcodeStyles: Map<string, string> = new Map([
|
||||
// Constants
|
||||
['0', 'constants'],
|
||||
['FALSE', 'constants'],
|
||||
['TRUE', 'constants'],
|
||||
...Array.from({length: 75}, (_, i) => [`PUSHBYTES_${i + 1}`, 'constants']),
|
||||
['PUSHDATA1', 'constants'],
|
||||
['PUSHDATA2', 'constants'],
|
||||
['PUSHDATA4', 'constants'],
|
||||
['PUSHNUM_NEG1', 'constants'],
|
||||
...Array.from({length: 16}, (_, i) => [`PUSHNUM_${i + 1}`, 'constants']),
|
||||
|
||||
// Control flow
|
||||
['NOP', 'control'],
|
||||
['IF', 'control'],
|
||||
['NOTIF', 'control'],
|
||||
['ELSE', 'control'],
|
||||
['ENDIF', 'control'],
|
||||
['VERIFY', 'control'],
|
||||
...Array.from({length: 70}, (_, i) => [`RETURN_${i + 186}`, 'control']),
|
||||
|
||||
// Stack
|
||||
['TOALTSTACK', 'stack'],
|
||||
['FROMALTSTACK', 'stack'],
|
||||
['IFDUP', 'stack'],
|
||||
['DEPTH', 'stack'],
|
||||
['DROP', 'stack'],
|
||||
['DUP', 'stack'],
|
||||
['NIP', 'stack'],
|
||||
['OVER', 'stack'],
|
||||
['PICK', 'stack'],
|
||||
['ROLL', 'stack'],
|
||||
['ROT', 'stack'],
|
||||
['SWAP', 'stack'],
|
||||
['TUCK', 'stack'],
|
||||
['2DROP', 'stack'],
|
||||
['2DUP', 'stack'],
|
||||
['3DUP', 'stack'],
|
||||
['2OVER', 'stack'],
|
||||
['2ROT', 'stack'],
|
||||
['2SWAP', 'stack'],
|
||||
|
||||
// String
|
||||
['CAT', 'splice'],
|
||||
['SUBSTR', 'splice'],
|
||||
['LEFT', 'splice'],
|
||||
['RIGHT', 'splice'],
|
||||
['SIZE', 'splice'],
|
||||
|
||||
// Logic
|
||||
['INVERT', 'logic'],
|
||||
['AND', 'logic'],
|
||||
['OR', 'logic'],
|
||||
['XOR', 'logic'],
|
||||
['EQUAL', 'logic'],
|
||||
['EQUALVERIFY', 'logic'],
|
||||
|
||||
// Arithmetic
|
||||
['1ADD', 'arithmetic'],
|
||||
['1SUB', 'arithmetic'],
|
||||
['2MUL', 'arithmetic'],
|
||||
['2DIV', 'arithmetic'],
|
||||
['NEGATE', 'arithmetic'],
|
||||
['ABS', 'arithmetic'],
|
||||
['NOT', 'arithmetic'],
|
||||
['0NOTEQUAL', 'arithmetic'],
|
||||
['ADD', 'arithmetic'],
|
||||
['SUB', 'arithmetic'],
|
||||
['MUL', 'arithmetic'],
|
||||
['DIV', 'arithmetic'],
|
||||
['MOD', 'arithmetic'],
|
||||
['LSHIFT', 'arithmetic'],
|
||||
['RSHIFT', 'arithmetic'],
|
||||
['BOOLAND', 'arithmetic'],
|
||||
['BOOLOR', 'arithmetic'],
|
||||
['NUMEQUAL', 'arithmetic'],
|
||||
['NUMEQUALVERIFY', 'arithmetic'],
|
||||
['NUMNOTEQUAL', 'arithmetic'],
|
||||
['LESSTHAN', 'arithmetic'],
|
||||
['GREATERTHAN', 'arithmetic'],
|
||||
['LESSTHANOREQUAL', 'arithmetic'],
|
||||
['GREATERTHANOREQUAL', 'arithmetic'],
|
||||
['MIN', 'arithmetic'],
|
||||
['MAX', 'arithmetic'],
|
||||
['WITHIN', 'arithmetic'],
|
||||
|
||||
// Crypto
|
||||
['RIPEMD160', 'crypto'],
|
||||
['SHA1', 'crypto'],
|
||||
['SHA256', 'crypto'],
|
||||
['HASH160', 'crypto'],
|
||||
['HASH256', 'crypto'],
|
||||
['CODESEPARATOR', 'crypto'],
|
||||
['CHECKSIG', 'crypto'],
|
||||
['CHECKSIGVERIFY', 'crypto'],
|
||||
['CHECKMULTISIG', 'crypto'],
|
||||
['CHECKMULTISIGVERIFY', 'crypto'],
|
||||
['CHECKSIGADD', 'crypto'],
|
||||
|
||||
// Locktime
|
||||
['CLTV', 'locktime'],
|
||||
['CSV', 'locktime'],
|
||||
|
||||
// Reserved
|
||||
['RESERVED', 'reserved'],
|
||||
['VER', 'reserved'],
|
||||
['VERIF', 'reserved'],
|
||||
['VERNOTIF', 'reserved'],
|
||||
['RESERVED1', 'reserved'],
|
||||
['RESERVED2', 'reserved'],
|
||||
...Array.from({length: 10}, (_, i) => [`NOP${i + 1}`, 'reserved'])
|
||||
] as [string, string][]);
|
||||
}
|
@ -18,6 +18,7 @@ import { Hex2asciiPipe } from '@app/shared/pipes/hex2ascii/hex2ascii.pipe';
|
||||
import { Decimal2HexPipe } from '@app/shared/pipes/decimal2hex/decimal2hex.pipe';
|
||||
import { FeeRoundingPipe } from '@app/shared/pipes/fee-rounding/fee-rounding.pipe';
|
||||
import { AsmStylerPipe } from '@app/shared/pipes/asm-styler/asm-styler.pipe';
|
||||
import { AsmComponent } from '@app/shared/components/asm/asm.component';
|
||||
import { AbsolutePipe } from '@app/shared/pipes/absolute/absolute.pipe';
|
||||
import { RelativeUrlPipe } from '@app/shared/pipes/relative-url/relative-url.pipe';
|
||||
import { ScriptpubkeyTypePipe } from '@app/shared/pipes/scriptpubkey-type-pipe/scriptpubkey-type.pipe';
|
||||
@ -147,6 +148,7 @@ import { GithubLogin } from '../components/github-login.component/github-login.c
|
||||
NoSanitizePipe,
|
||||
Hex2asciiPipe,
|
||||
AsmStylerPipe,
|
||||
AsmComponent,
|
||||
AbsolutePipe,
|
||||
BytesPipe,
|
||||
VbytesPipe,
|
||||
|
@ -104,6 +104,16 @@ export type SighashValue =
|
||||
(SighashFlag.SINGLE & SighashFlag.ANYONECANPAY) |
|
||||
(SighashFlag.ALL & SighashFlag.NONE);
|
||||
|
||||
export const SighashLabels: Record<number, string> = {
|
||||
'0': 'SIGHASH_DEFAULT',
|
||||
'1': 'SIGHASH_ALL',
|
||||
'2': 'SIGHASH_NONE',
|
||||
'3': 'SIGHASH_SINGLE',
|
||||
'129': 'SIGHASH_ALL | ACP',
|
||||
'130': 'SIGHASH_NONE | ACP',
|
||||
'131': 'SIGHASH_SINGLE | ACP',
|
||||
};
|
||||
|
||||
export interface SigInfo {
|
||||
signature: string;
|
||||
sighash: SighashValue;
|
||||
@ -172,7 +182,7 @@ export function extractDERSignaturesASM(script_asm: string): SigInfo[] {
|
||||
if (isDERSig(hexData)) {
|
||||
const sighash = decodeSighashFlag(parseInt(hexData.slice(-2), 16));
|
||||
signatures.push({
|
||||
signature: hexData.slice(0, -2), // Remove sighash byte
|
||||
signature: hexData,
|
||||
sighash
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user