Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Script Visualization #4620

Open
Rob1Ham opened this issue Jan 27, 2024 · 1 comment
Open

Script Visualization #4620

Rob1Ham opened this issue Jan 27, 2024 · 1 comment
Assignees

Comments

@Rob1Ham
Copy link

Rob1Ham commented Jan 27, 2024

Description

When currently viewing a bitcoin transaction details on mempool.space, the script stack and corresponding witness is shown in raw OP codes and stack values (pubkeys, numbers for timelocks). It would be a great feature to visualize this in more human readable form, providing a better UX and opportunity to better learn how Bitcoin Script works. This is currently done for simple multisigs of N of M (2 of 3, 3 of 5 etc). The feature request is to provide greater context on how this can be achieved for more robust script templates. If mempool had the ability to parse miniscript script stacks, a generalizeable tool set could be built to visualize dynamic scripts.

Problem to be solved

Bitcoin Script isn't intuitive to most users of the Bitcoin Protocol, by providing visual aids to identify the rules in which a UTXO is encumbered by, would allow for an opportunity to demistify how bitcoin works for users of mempool.space, and the network.

Proposed solution

Similar to mempool googles, being able to have an option when clicking on the details to see either how the stack is visualized today, or a "script goggles" option to see how the bitcoin is secured with better abstracted diagrams.

Additional info

For reference, here is a testnet transaction I did yesterday:

https://mempool.space/testnet/tx/7cafffe638fe32a74f3c02e89084fb89226834338f59a3e4e0a9610844bef127

The P2WSH script is as follows:

OP_PUSHNUM_2
OP_PUSHBYTES_33 02d2c579a78092e946d2845b3eb30b0285d6f7f5552ca6ed0c784a77bac51e1a69
OP_PUSHBYTES_33 03ebfd5122214b100e9da78c22f5fb50c76d29ba832eff0051206b6aa697fd2c95
OP_PUSHBYTES_33 03d450e676a34fa4539e3a3cd9b2ee7e7a7c323a9ca519c7a6e62f519d60e52eea
OP_PUSHNUM_3
OP_CHECKMULTISIG
OP_NOTIF
OP_DUP
OP_HASH160
OP_PUSHBYTES_20 d5e3235b082acb32c6072ba7a7ce51c598fcb727
OP_EQUALVERIFY
OP_CHECKSIG
OP_TOALTSTACK
OP_DUP
OP_HASH160
OP_PUSHBYTES_20 00cd08227b0263209dd74d532b28d0a219999cef
OP_EQUALVERIFY
OP_CHECKSIG
OP_FROMALTSTACK
OP_ADD
OP_TOALTSTACK
OP_DUP
OP_HASH160
OP_PUSHBYTES_20 6dcde9844a479637cc97f7721ba92d2f0297ff0f
OP_EQUALVERIFY
OP_CHECKSIG
OP_FROMALTSTACK
OP_ADD
OP_PUSHNUM_2
OP_EQUALVERIFY
OP_PUSHBYTES_4 80abd963
OP_CLTV
OP_ELSE
OP_PUSHBYTES_33 03fffee31d8aa067721cc8fd9754a26ab67d42724923a8dd0f5ea71e4eb9377e64
OP_CHECKSIG
OP_NOTIF
OP_DUP
OP_HASH160
OP_PUSHBYTES_20 dc19a8b23be1d1a930bafe95019e37636fc257ec
OP_EQUALVERIFY
OP_CHECKSIG
OP_TOALTSTACK
OP_DUP
OP_HASH160
OP_PUSHBYTES_20 a10a049eb8a6183dfab65de6342cf004d87a5644
OP_EQUALVERIFY
OP_CHECKSIG
OP_FROMALTSTACK
OP_ADD
OP_TOALTSTACK
OP_DUP
OP_HASH160
OP_PUSHBYTES_20 ad0b1f7bc01c23174193210cf37b8700d9707942
OP_EQUALVERIFY
OP_CHECKSIG
OP_FROMALTSTACK
OP_ADD
OP_PUSHNUM_2
OP_EQUAL
OP_ELSE
OP_PUSHBYTES_4 00cdb063
OP_CLTV
OP_ENDIF
OP_ENDIF

What does this mean in plain english though? I generated this script using miniscript, so the script stack can actually be abstracted to miniscript:

wsh(andor(multi(2,[30c7c72b/48'/1'/0'/2']tpubDEXQJ7zfCxN3Fm1VUoZZ5EBwb6yaT813Dt2EPcUgHkcdp5ETtY7rZbTimiVwFpBi4xcpdJ4W9mxZGkbAgfYqdRGrNtHULYTcyTLLQFR7JpD/*,[62fa764a/48'/1'/0'/2']tpubDFRjazvAAGtQjKGTTSch6FKnsXYnDNibnd5syZ3oXqSraCwbW9mhtm7CiGKQ7zZ7cpNCSXVyjYZwSb8YskRzVqoNSVX1jXJ9efRpJFPY4sf/*,[277dcebc/48'/1'/0'/2']tpubDEmEWF2iixNg9rSKtBRL6qCi9M1DpnAuwsmDYiyEi5TMnA2UfuJ2oTnpvrEvotbYtgNupL72rV4HjsM7o4EyKgqid47RM7DiYj3FgCm4HKa/*),andor(pk([562f7833/48'/1'/0'/2']tpubDFULx2EDJgEQtXLEd6zKizLP1LfMUMkY31zME6tvFReS3y3hvsZLeTDwZiZ8YxqNQWaLnHe4fdhb5EAjrrhfZD8cFRa69wTnMYutJxw3WuF/*),after(1672531200),thresh(2,pkh([860a2400/48'/1'/0'/2']tpubDEa2Qdj8t9RkyjGqixGZuvHjVYKhti2uD6YJtiUmbhYYWBvJBuT7JYWPF3fPX2inGjeTv3rVCooK1L2vLdK4Rv3psCbbGHEyDiiLaMvzvJW/*),a:pkh([6355bbe3/48'/1'/0'/2']tpubDEMVcMk6t7b3nfpLdngdudhaEnQSD4inorHmCuj1RdgJaAuRuJGe99Ssq6BPeSWZrZYGGo7qcRa1m48LGuYD9dK7B4chUTk3tANseHd5tEY/*),a:pkh([72c7e86b/48'/1'/0'/2']tpubDExTVsbh95Ps92deZWmSbazDNthPEq6osvi6r61SqRMtMxp2vSJ6JbH2TkhFkDq5R4dnbftRDeUVMfV8NTVx1daE8Z9kxADY3NoEQurVwGZ/*))),and_v(v:thresh(2,pkh([9463f6aa/48'/1'/0'/2']tpubDEtVwVxs3LCbhgrhL72LDDjvRuaJSzXiMU9B6MXy4B2aRvb9GFJ5emNgEt83EZ65yYrCqxdTocC6g2iCoLcEPQqxeVJMmQmFQqGsD6UyJ3w/*),a:pkh([f90fc832/48'/1'/0'/2']tpubDFQ3CMPN1ETYsfWMNe5nfbUMRN6Ckph2BS3wJZGiGMoNh2Nz2CG33zrYEsJ6WYG4WKpdgDNagnsDQpwVhz9B9aq1K8Vti6GRH3UAAoeqFx3/*),a:pkh([d28d56f7/48'/1'/0'/2']tpubDE63xn1YqAidzwguM5gu3LPVXaWS9xfE1Q7UwHyPYRKaiVyByoDpa63C18cM7Q5mcsTMKKGxhHDGaZTXHR3b3yXxik3hTczj5rhg6DugpZ2/*)),after(1675209600))))#f0h8g4ev

So, to generate the specific UTXO: tb1qsjafrqfwrnkzay8x97zzpvju5g8ns2fqcarqwjv2nnnpxdz79xjsfnmkll you'd take the 0/0 index for each of these tpubs, as this was the first recieve address for the wallet, so swapping the tpubs for public keys gets you:

wsh(andor(multi(2,02d2c579a78092e946d2845b3eb30b0285d6f7f5552ca6ed0c784a77bac51e1a69,03ebfd5122214b100e9da78c22f5fb50c76d29ba832eff0051206b6aa697fd2c95,03d450e676a34fa4539e3a3cd9b2ee7e7a7c323a9ca519c7a6e62f519d60e52eea),andor(pk(03fffee31d8aa067721cc8fd9754a26ab67d42724923a8dd0f5ea71e4eb9377e64),after(1672531200),thresh(2,pkh(035b7f5811a7863db0ef70eada285ffc1098bdc612edd7413d0cf5ac38b5f42b59),a:pkh(02457dd2c9741d718f3fe2e8fb2e9c844f710683cde6441be442ac5aa87ef6bb15),a:pkh(024bb627f22745740e74482344b000d66d7d06a405a9309df4610e124fa0fcdff2))),and_v(v:thresh(2,pkh(023ab4f3af852c13d274a9a3b4eb53227a336b335f883459ee80b5e4cd59b021a1),a:pkh(034cd1d366c0d0077971fc01e331b0a294d8f4bb9097ddd1f5d99980a5988913e4),a:pkh(03efc52a221512f7b6618e5c3f11a8619ddfe7ca56ef411be1baf28b05766db322)),after(1675209600))))

This is then compiled into bitcoin script, and shown as the P2WSH op code stack shown above. There are some transformations, for example, if an output descriptor fragment has pkh, it takes the hash of the public key, for example:

02457dd2c9741d718f3fe2e8fb2e9c844f710683cde6441be442ac5aa87ef6bb15 when ran through OP_HASH16 becomes a10a049eb8a6183dfab65de6342cf004d87a5644 - which is the actual element on the script stack.

Additionally, the after() fragments are absolute timelocks, which are seen with the OP_CLTV elements of the P2WSH:

after(1672531200) is an epoch timestamp, which is January 1st, 2023 at Midnight GMT. On the stack, this is visualized as:

OP_PUSHBYTES_4 00cdb063
OP_CLTV

00cdb063 in little endian represents 63B0CD00 in HEX, which converts to 1672531200 in decimal form. In Bitcoin Script, when a number is larger than 500,000,000, timelocks are interpreted as epoch timestamps. If lower than 500,000,000, they are interpreted as block height based timelocks, as CLTV uses the nLockTime field in a transaction. This transaction complies with CLTV, since the locktime for the transaction is: ‎1,672,531,200.

A low hanging fruit would be providing a calendar date/time reference for a CLTV/CSV timelock upon hovering over that block of the script stack.

As for the actual rules for how this script is being secured, one could evaluate the output descriptor:

wsh(
	andor(
		multi(
			2,
				02d2c579a78092e946d2845b3eb30b0285d6f7f5552ca6ed0c784a77bac51e1a69,
				03ebfd5122214b100e9da78c22f5fb50c76d29ba832eff0051206b6aa697fd2c95,
				03d450e676a34fa4539e3a3cd9b2ee7e7a7c323a9ca519c7a6e62f519d60e52eea
			)
			,
			andor(
				pk(
					03fffee31d8aa067721cc8fd9754a26ab67d42724923a8dd0f5ea71e4eb9377e64),
					after(1672531200)
				,
				thresh(
					2,pkh(035b7f5811a7863db0ef70eada285ffc1098bdc612edd7413d0cf5ac38b5f42b59),
					a:pkh(02457dd2c9741d718f3fe2e8fb2e9c844f710683cde6441be442ac5aa87ef6bb15),
					a:pkh(024bb627f22745740e74482344b000d66d7d06a405a9309df4610e124fa0fcdff2))
				),
		and_v(
			v:thresh(
				2,
				pkh(023ab4f3af852c13d274a9a3b4eb53227a336b335f883459ee80b5e4cd59b021a1),
				a:pkh(034cd1d366c0d0077971fc01e331b0a294d8f4bb9097ddd1f5d99980a5988913e4),
				a:pkh(03efc52a221512f7b6618e5c3f11a8619ddfe7ca56ef411be1baf28b05766db322)
				),
			after(1675209600)
			)
		)
)

Ok, to actually make this plain english, this is a WSH address that can be spent several ways:

Option 1:

2 of 3 of :

				03ebfd5122214b100e9da78c22f5fb50c76d29ba832eff0051206b6aa697fd2c95,
				03d450e676a34fa4539e3a3cd9b2ee7e7a7c323a9ca519c7a6e62f519d60e52eea

AND

either:

03fffee31d8aa067721cc8fd9754a26ab67d42724923a8dd0f5ea71e4eb9377e64 AND after(1672531200) (january 1st 2023 at midnight GMT)

OR

2 of 3:

035b7f5811a7863db0ef70eada285ffc1098bdc612edd7413d0cf5ac38b5f42b59
02457dd2c9741d718f3fe2e8fb2e9c844f710683cde6441be442ac5aa87ef6bb15
024bb627f22745740e74482344b000d66d7d06a405a9309df4610e124fa0fcdff2

Option 2:

2 of 3:

034cd1d366c0d0077971fc01e331b0a294d8f4bb9097ddd1f5d99980a5988913e4
03efc52a221512f7b6618e5c3f11a8619ddfe7ca56ef411be1baf28b05766db322

AND
after(1675209600) (February 1st, 2023 at Midnight GMT)

Using the BDK Playground, this can be visualized as:

image

This results in the miniscript policy of:

or(99@and(thresh(2,pk(A),pk(B),pk(C)),or(thresh(2,pk(D),pk(E),pk(F)),99@and(pk(G),after(1672531200)))),and(thresh(2,pk(H),pk(I),pk(J)),after(1675209600)))

Note: Miniscript Policy, unlike a miniscript output descriptor, can not be derived from Bitcoin Script. Going from a miniscript policy to script/output descriptors is a one way function. This is provided as a reference for a cleaner way to diagram how this bitcoin is secured.

If you take the diagram above, and swap in the mapping for Keys A-J, you can see how this is executed on the script stack in my testnet transaction:

Key Name Public Key
A 02d2c579a78092e946d2845b3eb30b0285d6f7f5552ca6ed0c784a77bac51e1a69
B 03ebfd5122214b100e9da78c22f5fb50c76d29ba832eff0051206b6aa697fd2c95
C 03d450e676a34fa4539e3a3cd9b2ee7e7a7c323a9ca519c7a6e62f519d60e52eea
D 03fffee31d8aa067721cc8fd9754a26ab67d42724923a8dd0f5ea71e4eb9377e64
E 035b7f5811a7863db0ef70eada285ffc1098bdc612edd7413d0cf5ac38b5f42b59
F 02457dd2c9741d718f3fe2e8fb2e9c844f710683cde6441be442ac5aa87ef6bb15
G 024bb627f22745740e74482344b000d66d7d06a405a9309df4610e124fa0fcdff2
H 023ab4f3af852c13d274a9a3b4eb53227a336b335f883459ee80b5e4cd59b021a1
I 034cd1d366c0d0077971fc01e331b0a294d8f4bb9097ddd1f5d99980a5988913e4
J 03efc52a221512f7b6618e5c3f11a8619ddfe7ca56ef411be1baf28b05766db322

There are other elements to consider as well for generlaized miniscript, specifically older() fragments, which use OP_CSV for relative timelocks, block height specific timelocks, hashlocks, and thresholds which combine more than just keys, for example a 3 of 4 multisig with 3 keys and ` timelocks (in practice, this is a 3 of 3 multisig, which becomes a 2 of 3 multisig after a timelock is satisfied).

There are added visualizations to be considered as well for taproot addresses and traversing to a given tapleaf, but any logic built for P2WSH/P2SH scripts could be extended to taproot.

I'd be happy to brainstorm other ways of visualizing this data as well, I think having a site like mempool provide neutral visualization of how scripts are secured will bring about better user confidence and understanding of how bitcoin is being secured!

--

@josibake
Copy link

Since y'all are thinking about this, here is a somewhat related PR in bitcoin-core around making scripts more readable: bitcoin/bitcoin#28824

Dropping it here for context / in case its useful. Maybe some of the conventions discussed on the linked PR might also be useful here.

Either way, excited to see some interest in this kinda stuff! Better visualizers for Bitcoin script is a huge win; for usability and educational purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants