Skip to content

Instantly share code, notes, and snippets.

@jachiang
Last active July 26, 2019 14:28
Show Gist options
  • Save jachiang/75d70d0fb6959f81d91ade9e5f87531a to your computer and use it in GitHub Desktop.
Save jachiang/75d70d0fb6959f81d91ade9e5f87531a to your computer and use it in GitHub Desktop.
Optech Tapscript

Tapscript

Note: Taproot descriptors in this chapter and associated demo library are illustrative of the ongoing design intent, but do not represent the final version. The taproot descriptors described below are based on this proposal.

Tapscript is a new Bitcoin output script language which is evaluated when the script path is used during the spending of a Taproot output. With a few noted exceptions (CHECKSIG opcodes), it carries many of the familiar op_codes and evaluation logic which applies to Bitcoin script.

Standard Tapscript Types.

Nonetheless, for most use-cases, a set of standard tapscripts can be described with the following tapscript descriptors. Tapscript descriptors are encapsulated with ts().

  • Pay to public key: ts(pk(key))
  • Pay to n keys: ts(csa(key0, key1, ... ))
  • Pay to musig key: ts(musig(key0+key1+...+keyn))

Other tapscripts can be expressed with the raw hex representation of the script.

  • Raw script: ts(raw(hex))

optech_tapscript

Tapscript descriptors allow us to conveniently describe the tapscript spending conditions and also encode the tapscript in human-readable form. This is useful for the taproot-aware wallet, as it can conveniently recreate outputs it needs to be spend or watch.

Tapscript: Pay-to-Pubkey

The most basic tapscript is the one that is spendable by a single private key. We can construct a tp(pk(key)) tapscript object from a compressed public key as shown below.

Constructing a Pay-to-Pubkey Tapscript (Github):

sk = ECKey()
sk.generate()
pk = sk.get_pubkey()
print("Pubkey from pk tapscript: ", pk.get_bytes().hex())

ts_pk = TapLeaf()
ts_pk.from_keys([pk])

# Print output descriptor.
print(ts_pk.desc)
> Pubkey from pk tapscript:  
> 0323955476dfac69df00f049dfa3d5eaeb56447fcae71d1f7aca4be0fa521b7376
> ts(pk(0323955476dfac69df00f049dfa3d5eaeb56447fcae71d1f7aca4be0fa521b7376))

Given a tapscript descriptor in string form we can decode it into a tapscript object.

Constructing a Pay-to-Pubkey tapscript from a descriptor (Github):

ts_desc = "ts(pk(026bf6d12e669cb96afb170daedcc0affe36fad226e9bf2b49c2ef9519361bb882))"
ts = TapLeaf()
ts.from_desc(ts_desc)

# Assert descriptor decoding and encoding result in same string.
assert(ts.desc == ts_desc)

# Print out tapscript operation by operation.
for op in ts.script:
    if isinstance(op, bytes):
        print(op.hex())
    else:
        print(op)    
> 026bf6d12e669cb96afb170daedcc0affe36fad226e9bf2b49c2ef9519361bb882
> OP_CHECKSIG

Tapscript: n-of-m Threshold Conditions

In tapscript, OP_CHECKMULTISIG has been disabled, since the signature order could otherwise be provided in any order, often resulting in an inefficient re-evaluation of signatures, thus making Schnorr batch signature verification impossible (correct order of signatures must be known before hand).

Instead, OP_CHECKSIGADD has been to tapscript, which can be used to create a n-of-n output spending condition.

  • Tapscript descriptor: tp(csa(key0, key1, key2, ... keyN))
  • Tapscript opcodes: [pk0] [pk1] [pk2] ... [pkN] OP_CHECKSIGASDD N OP_EQUAL
  • Spending Witness Elements: [sigN] ... [sig2] [sig1] [sig0]

TODO: Other checksig changes.

optech_csa

Constructing Pay to n-Pubkeys Tapscript (Github):

sks = []
pks = []
for i in range(3):
    sks.append(ECKey())
    sks[i].generate()
    pks.append(sks[i].get_pubkey())
            
ts_csa = TapLeaf()
ts_csa.from_keys(pks)

print(ts_csa.desc)

for op in ts_csa.script:
    if isinstance(op, bytes):
        print(op.hex())
    else:
        print(op)
> ts(csa(02b0dbca6dd91141fd94834e99f63ac0f39462f20e3307d55420284189d4403daf,03006c05f3af04c9d781160bb3c49565feb30a70da52c14eb3bd1a857f8060ae27,03ef3d4679ebd1744a5db35d5d26a971a7525ecf86916a074948e683f020ba4d13))
> 02b0dbca6dd91141fd94834e99f63ac0f39462f20e3307d55420284189d4403daf
> OP_CHECKSIG
> 03006c05f3af04c9d781160bb3c49565feb30a70da52c14eb3bd1a857f8060ae27
> OP_CHECKSIGADD
> 03ef3d4679ebd1744a5db35d5d26a971a7525ecf86916a074948e683f020ba4d13
> OP_CHECKSIGADD
> 3

Generating n-of-m Pubkeys Threshold Tapscripts (Github):

In order to create a n-of-m threshold spending condition, we can simply determine all necessary combinations of n signers from m possible spenders, and create corresponding pay-to-n-pubkey tapscripts, which can only be executed exclusively in separate tapscripts.

optech_threshold

sks = []
pks = []
for i in range(4):
    sks.append(ECKey())
    sks[i].generate()
    pks.append(sks[i].get_pubkey())
            
tss , pk_map = TapLeaf.generate_threshold_csa(2, pks)

for ts in tss:
    print(ts.desc)
> ts(csa(022563c72d2c2d465b72e775b80b444ba7e5346b71722f473a8f30cfc9f908c15d,02a7af595138c84d4d9f96e57362eaff10506468381efa84a20588bcb2455862c6,038eb9e77c488e2c94eb16589e018364715a8eb2c5bee36f3b77d073d15498df36))
> ts(csa(022563c72d2c2d465b72e775b80b444ba7e5346b71722f473a8f30cfc9f908c15d,02a7af595138c84d4d9f96e57362eaff10506468381efa84a20588bcb2455862c6,0398378227136efb07e26c1586b6bdc52d165363fa62d8db945d5757d525a31aae))
> ts(csa(022563c72d2c2d465b72e775b80b444ba7e5346b71722f473a8f30cfc9f908c15d,02a7af595138c84d4d9f96e57362eaff10506468381efa84a20588bcb2455862c6,039f303a852a65a6b3e92d87e1bffa4591feb27351c64c72c8db54dc604295e51b))
> ts(csa(022563c72d2c2d465b72e775b80b444ba7e5346b71722f473a8f30cfc9f908c15d,038eb9e77c488e2c94eb16589e018364715a8eb2c5bee36f3b77d073d15498df36,0398378227136efb07e26c1586b6bdc52d165363fa62d8db945d5757d525a31aae))
> ts(csa(022563c72d2c2d465b72e775b80b444ba7e5346b71722f473a8f30cfc9f908c15d,038eb9e77c488e2c94eb16589e018364715a8eb2c5bee36f3b77d073d15498df36,039f303a852a65a6b3e92d87e1bffa4591feb27351c64c72c8db54dc604295e51b))
> ts(csa(022563c72d2c2d465b72e775b80b444ba7e5346b71722f473a8f30cfc9f908c15d,0398378227136efb07e26c1586b6bdc52d165363fa62d8db945d5757d525a31aae,039f303a852a65a6b3e92d87e1bffa4591feb27351c64c72c8db54dc604295e51b))
> ts(csa(02a7af595138c84d4d9f96e57362eaff10506468381efa84a20588bcb2455862c6,038eb9e77c488e2c94eb16589e018364715a8eb2c5bee36f3b77d073d15498df36,0398378227136efb07e26c1586b6bdc52d165363fa62d8db945d5757d525a31aae))
> ts(csa(02a7af595138c84d4d9f96e57362eaff10506468381efa84a20588bcb2455862c6,038eb9e77c488e2c94eb16589e018364715a8eb2c5bee36f3b77d073d15498df36,039f303a852a65a6b3e92d87e1bffa4591feb27351c64c72c8db54dc604295e51b))
> ts(csa(02a7af595138c84d4d9f96e57362eaff10506468381efa84a20588bcb2455862c6,0398378227136efb07e26c1586b6bdc52d165363fa62d8db945d5757d525a31aae,039f303a852a65a6b3e92d87e1bffa4591feb27351c64c72c8db54dc604295e51b))
> ts(csa(038eb9e77c488e2c94eb16589e018364715a8eb2c5bee36f3b77d073d15498df36,0398378227136efb07e26c1586b6bdc52d165363fa62d8db945d5757d525a31aae,039f303a852a65a6b3e92d87e1bffa4591feb27351c64c72c8db54dc604295e51b))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment