20 changed files with 761 additions and 79 deletions
@ -0,0 +1,243 @@
|
||||
# -*- coding: utf-8 -*- |
||||
|
||||
import hashlib |
||||
from typing import List, Optional, Tuple, Union, Set, Type |
||||
|
||||
import bitcointx |
||||
from bitcointx.core.key import XOnlyPubKey |
||||
from bitcointx.core.script import SIGVERSION_TAPROOT, SignatureHashSchnorr |
||||
from bitcointx.core import CTxOut |
||||
from bitcointx.core.scripteval import ( |
||||
CScript, ScriptVerifyFlag_Type, CScriptWitness, VerifyScriptError, |
||||
STANDARD_SCRIPT_VERIFY_FLAGS, UNHANDLED_SCRIPT_VERIFY_FLAGS, EvalScript, |
||||
SCRIPT_VERIFY_CLEANSTACK, script_verify_flags_to_string, _CastToBool, |
||||
ensure_isinstance, SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM, |
||||
MAX_SCRIPT_ELEMENT_SIZE, OP_CHECKSIG, OP_EQUALVERIFY, OP_HASH160, OP_DUP) |
||||
from bitcointx.core.scripteval import ( |
||||
SCRIPT_VERIFY_WITNESS, SCRIPT_VERIFY_P2SH, SIGVERSION_WITNESS_V0) |
||||
|
||||
|
||||
def VerifyScriptWithTaproot( |
||||
scriptSig: CScript, scriptPubKey: CScript, |
||||
txTo: 'bitcointx.core.CTransaction', inIdx: int, |
||||
flags: Optional[Union[Tuple[ScriptVerifyFlag_Type, ...], |
||||
Set[ScriptVerifyFlag_Type]]] = None, |
||||
amount: int = 0, witness: Optional[CScriptWitness] = None, |
||||
*, |
||||
spent_outputs: Optional[List[CTxOut]] = None |
||||
) -> None: |
||||
"""Verify a scriptSig satisfies a scriptPubKey |
||||
|
||||
scriptSig - Signature |
||||
|
||||
scriptPubKey - PubKey |
||||
|
||||
txTo - Spending transaction |
||||
|
||||
inIdx - Index of the transaction input containing scriptSig |
||||
|
||||
Raises a ValidationError subclass if the validation fails. |
||||
""" |
||||
|
||||
ensure_isinstance(scriptSig, CScript, 'scriptSig') |
||||
if not type(scriptSig) == type(scriptPubKey): # noqa: exact class check |
||||
raise TypeError( |
||||
"scriptSig and scriptPubKey must be of the same script class") |
||||
|
||||
script_class = scriptSig.__class__ |
||||
|
||||
if flags is None: |
||||
flags = STANDARD_SCRIPT_VERIFY_FLAGS - UNHANDLED_SCRIPT_VERIFY_FLAGS |
||||
else: |
||||
flags = set(flags) # might be passed as tuple |
||||
|
||||
if flags & UNHANDLED_SCRIPT_VERIFY_FLAGS: |
||||
raise VerifyScriptError( |
||||
"some of the flags cannot be handled by current code: {}" |
||||
.format(script_verify_flags_to_string(flags & UNHANDLED_SCRIPT_VERIFY_FLAGS))) |
||||
|
||||
stack: List[bytes] = [] |
||||
EvalScript(stack, scriptSig, txTo, inIdx, flags=flags) |
||||
if SCRIPT_VERIFY_P2SH in flags: |
||||
stackCopy = list(stack) |
||||
EvalScript(stack, scriptPubKey, txTo, inIdx, flags=flags) |
||||
if len(stack) == 0: |
||||
raise VerifyScriptError("scriptPubKey left an empty stack") |
||||
if not _CastToBool(stack[-1]): |
||||
raise VerifyScriptError("scriptPubKey returned false") |
||||
|
||||
hadWitness = False |
||||
if witness is None: |
||||
witness = CScriptWitness([]) |
||||
|
||||
if SCRIPT_VERIFY_WITNESS in flags and scriptPubKey.is_witness_scriptpubkey(): |
||||
hadWitness = True |
||||
|
||||
if scriptSig: |
||||
raise VerifyScriptError("scriptSig is not empty") |
||||
|
||||
VerifyWitnessProgramWithTaproot( |
||||
witness, |
||||
scriptPubKey.witness_version(), |
||||
scriptPubKey.witness_program(), |
||||
txTo, inIdx, flags=flags, amount=amount, |
||||
script_class=script_class, |
||||
spent_outputs=spent_outputs) |
||||
|
||||
# Bypass the cleanstack check at the end. The actual stack is obviously not clean |
||||
# for witness programs. |
||||
stack = stack[:1] |
||||
|
||||
# Additional validation for spend-to-script-hash transactions |
||||
if SCRIPT_VERIFY_P2SH in flags and scriptPubKey.is_p2sh(): |
||||
if not scriptSig.is_push_only(): |
||||
raise VerifyScriptError("P2SH scriptSig not is_push_only()") |
||||
|
||||
# restore stack |
||||
stack = stackCopy |
||||
|
||||
# stack cannot be empty here, because if it was the |
||||
# P2SH HASH <> EQUAL scriptPubKey would be evaluated with |
||||
# an empty stack and the EvalScript above would return false. |
||||
assert len(stack) |
||||
|
||||
pubKey2 = script_class(stack.pop()) |
||||
|
||||
EvalScript(stack, pubKey2, txTo, inIdx, flags=flags) |
||||
|
||||
if not len(stack): |
||||
raise VerifyScriptError("P2SH inner scriptPubKey left an empty stack") |
||||
|
||||
if not _CastToBool(stack[-1]): |
||||
raise VerifyScriptError("P2SH inner scriptPubKey returned false") |
||||
|
||||
# P2SH witness program |
||||
if SCRIPT_VERIFY_WITNESS in flags and pubKey2.is_witness_scriptpubkey(): |
||||
hadWitness = True |
||||
|
||||
if scriptSig != script_class([pubKey2]): |
||||
raise VerifyScriptError("scriptSig is not exactly a single push of the redeemScript") |
||||
|
||||
VerifyWitnessProgramWithTaproot( |
||||
witness, |
||||
pubKey2.witness_version(), |
||||
pubKey2.witness_program(), |
||||
txTo, inIdx, flags=flags, amount=amount, |
||||
script_class=script_class, |
||||
spent_outputs=spent_outputs) |
||||
|
||||
# Bypass the cleanstack check at the end. The actual stack is obviously not clean |
||||
# for witness programs. |
||||
stack = stack[:1] |
||||
|
||||
if SCRIPT_VERIFY_CLEANSTACK in flags: |
||||
if SCRIPT_VERIFY_P2SH not in flags: |
||||
raise ValueError( |
||||
'SCRIPT_VERIFY_CLEANSTACK requires SCRIPT_VERIFY_P2SH') |
||||
|
||||
if len(stack) == 0: |
||||
raise VerifyScriptError("scriptPubKey left an empty stack") |
||||
elif len(stack) != 1: |
||||
raise VerifyScriptError("scriptPubKey left extra items on stack") |
||||
|
||||
if SCRIPT_VERIFY_WITNESS in flags: |
||||
# We can't check for correct unexpected witness data if P2SH was off, so require |
||||
# that WITNESS implies P2SH. Otherwise, going from WITNESS->P2SH+WITNESS would be |
||||
# possible, which is not a softfork. |
||||
if SCRIPT_VERIFY_P2SH not in flags: |
||||
raise ValueError( |
||||
"SCRIPT_VERIFY_WITNESS requires SCRIPT_VERIFY_P2SH") |
||||
|
||||
if not hadWitness and witness: |
||||
raise VerifyScriptError("Unexpected witness") |
||||
|
||||
|
||||
def VerifyWitnessProgramWithTaproot( |
||||
witness: CScriptWitness, |
||||
witversion: int, program: bytes, |
||||
txTo: 'bitcointx.core.CTransaction', |
||||
inIdx: int, |
||||
flags: Set[ScriptVerifyFlag_Type] = set(), |
||||
amount: int = 0, |
||||
script_class: Type[CScript] = CScript, |
||||
*, |
||||
spent_outputs: Optional[List[CTxOut]] = None |
||||
) -> None: |
||||
|
||||
if script_class is None: |
||||
raise ValueError("script class must be specified") |
||||
|
||||
sigversion = None |
||||
|
||||
if witversion == 0: |
||||
sigversion = SIGVERSION_WITNESS_V0 |
||||
stack = list(witness.stack) |
||||
if len(program) == 32: |
||||
# Version 0 segregated witness program: SHA256(CScript) inside the program, |
||||
# CScript + inputs in witness |
||||
if len(stack) == 0: |
||||
raise VerifyScriptError("witness is empty") |
||||
|
||||
scriptPubKey = script_class(stack.pop()) |
||||
hashScriptPubKey = hashlib.sha256(scriptPubKey).digest() |
||||
if hashScriptPubKey != program: |
||||
raise VerifyScriptError("witness program mismatch") |
||||
elif len(program) == 20: |
||||
# Special case for pay-to-pubkeyhash; signature + pubkey in witness |
||||
if len(stack) != 2: |
||||
raise VerifyScriptError("witness program mismatch") # 2 items in witness |
||||
|
||||
scriptPubKey = script_class([OP_DUP, OP_HASH160, program, |
||||
OP_EQUALVERIFY, OP_CHECKSIG]) |
||||
else: |
||||
raise VerifyScriptError("wrong length for witness program") |
||||
elif witversion == 1: |
||||
sigversion = SIGVERSION_TAPROOT |
||||
stack = list(witness.stack) |
||||
if len(program) == 32: |
||||
if len(stack) == 0: |
||||
raise VerifyScriptError("witness is empty") |
||||
if len(stack) != 1: |
||||
raise VerifyScriptError("only key path spend is supported") |
||||
assert spent_outputs |
||||
sig = stack[0] |
||||
pubkey = XOnlyPubKey(program) |
||||
sighash = SignatureHashSchnorr(txTo, inIdx, spent_outputs) |
||||
if pubkey.verify_schnorr(sighash, sig): |
||||
return |
||||
else: |
||||
raise VerifyScriptError("schnorr signature verify failed") |
||||
else: |
||||
raise VerifyScriptError("wrong length for witness program") |
||||
elif SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM in flags: |
||||
raise VerifyScriptError("upgradeable witness program is not accepted") |
||||
else: |
||||
# Higher version witness scripts return true for future softfork compatibility |
||||
return |
||||
|
||||
assert sigversion is not None |
||||
|
||||
for i, elt in enumerate(stack): |
||||
if isinstance(elt, int): |
||||
elt_len = len(script_class([elt])) |
||||
else: |
||||
elt_len = len(elt) |
||||
|
||||
# Disallow stack item size > MAX_SCRIPT_ELEMENT_SIZE in witness stack |
||||
if elt_len > MAX_SCRIPT_ELEMENT_SIZE: |
||||
raise VerifyScriptError( |
||||
"maximum push size exceeded by an item at position {} " |
||||
"on witness stack".format(i)) |
||||
|
||||
EvalScript(stack, scriptPubKey, txTo, inIdx, flags=flags, amount=amount, sigversion=sigversion) |
||||
|
||||
# Scripts inside witness implicitly require cleanstack behaviour |
||||
if len(stack) == 0: |
||||
raise VerifyScriptError("scriptPubKey left an empty stack") |
||||
elif len(stack) != 1: |
||||
raise VerifyScriptError("scriptPubKey left extra items on stack") |
||||
|
||||
if not _CastToBool(stack[-1]): |
||||
raise VerifyScriptError("scriptPubKey returned false") |
||||
|
||||
return |
||||
@ -0,0 +1,49 @@
|
||||
# -*- coding: utf-8 -*- |
||||
|
||||
INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ " |
||||
CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" |
||||
GENERATOR = [0xf5dee51989, 0xa9fdca3312, 0x1bab10e32d, 0x3706b1677a, 0x644d626ffd] |
||||
|
||||
def descsum_polymod(symbols): |
||||
"""Internal function that computes the descriptor checksum.""" |
||||
chk = 1 |
||||
for value in symbols: |
||||
top = chk >> 35 |
||||
chk = (chk & 0x7ffffffff) << 5 ^ value |
||||
for i in range(5): |
||||
chk ^= GENERATOR[i] if ((top >> i) & 1) else 0 |
||||
return chk |
||||
|
||||
def descsum_expand(s): |
||||
"""Internal function that does the character to symbol expansion""" |
||||
groups = [] |
||||
symbols = [] |
||||
for c in s: |
||||
if not c in INPUT_CHARSET: |
||||
return None |
||||
v = INPUT_CHARSET.find(c) |
||||
symbols.append(v & 31) |
||||
groups.append(v >> 5) |
||||
if len(groups) == 3: |
||||
symbols.append(groups[0] * 9 + groups[1] * 3 + groups[2]) |
||||
groups = [] |
||||
if len(groups) == 1: |
||||
symbols.append(groups[0]) |
||||
elif len(groups) == 2: |
||||
symbols.append(groups[0] * 3 + groups[1]) |
||||
return symbols |
||||
|
||||
def descsum_check(s): |
||||
"""Verify that the checksum is correct in a descriptor""" |
||||
if s[-9] != '#': |
||||
return False |
||||
if not all(x in CHECKSUM_CHARSET for x in s[-8:]): |
||||
return False |
||||
symbols = descsum_expand(s[:-9]) + [CHECKSUM_CHARSET.find(x) for x in s[-8:]] |
||||
return descsum_polymod(symbols) == 1 |
||||
|
||||
def descsum_create(s): |
||||
"""Add a checksum to a descriptor without""" |
||||
symbols = descsum_expand(s) + [0, 0, 0, 0, 0, 0, 0, 0] |
||||
checksum = descsum_polymod(symbols) ^ 1 |
||||
return s + '#' + ''.join(CHECKSUM_CHARSET[(checksum >> (5 * (7 - i))) & 31] for i in range(8)) |
||||
Loading…
Reference in new issue