File: taptree.py

package info (click to toggle)
python-ledger-bitcoin 0.4.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 720 kB
  • sloc: python: 9,357; makefile: 2
file content (151 lines) | stat: -rw-r--r-- 4,775 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
from .errors import MiniscriptError
from .base import DescriptorBase
from .miniscript import Miniscript
from ..hashes import tagged_hash
from ..script import Script


class TapLeaf(DescriptorBase):
    def __init__(self, miniscript=None, version=0xC0):
        self.miniscript = miniscript
        self.version = version

    def __str__(self):
        return str(self.miniscript)

    @classmethod
    def read_from(cls, s):
        ms = Miniscript.read_from(s, taproot=True)
        return cls(ms)

    def serialize(self):
        if self.miniscript is None:
            return b""
        return bytes([self.version]) + Script(self.miniscript.compile()).serialize()

    @property
    def keys(self):
        return self.miniscript.keys

    def derive(self, *args, **kwargs):
        if self.miniscript is None:
            return type(self)(None, version=self.version)
        return type(self)(
            self.miniscript.derive(*args, **kwargs),
            self.version,
        )

    def branch(self, *args, **kwargs):
        if self.miniscript is None:
            return type(self)(None, version=self.version)
        return type(self)(
            self.miniscript.branch(*args, **kwargs),
            self.version,
        )

    def to_public(self, *args, **kwargs):
        if self.miniscript is None:
            return type(self)(None, version=self.version)
        return type(self)(
            self.miniscript.to_public(*args, **kwargs),
            self.version,
        )


def _tweak_helper(tree):
    # https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs
    if isinstance(tree, TapTree):
        tree = tree.tree
    if isinstance(tree, TapLeaf):
        # one leaf on this branch
        h = tagged_hash("TapLeaf", tree.serialize())
        return ([(tree, b"")], h)
    left, left_h = _tweak_helper(tree[0])
    right, right_h = _tweak_helper(tree[1])
    ret = [(leaf, c + right_h) for leaf, c in left] + [
        (leaf, c + left_h) for leaf, c in right
    ]
    if right_h < left_h:
        left_h, right_h = right_h, left_h
    return (ret, tagged_hash("TapBranch", left_h + right_h))


class TapTree(DescriptorBase):
    def __init__(self, tree=None):
        """tree can be None, TapLeaf or a tuple (taptree, taptree)"""
        self.tree = tree
        # make sure all keys are taproot
        for k in self.keys:
            k.taproot = True

    def __bool__(self):
        return bool(self.tree)

    def tweak(self):
        if self.tree is None:
            return b""
        _, h = _tweak_helper(self.tree)
        return h

    @property
    def keys(self):
        if self.tree is None:
            return []
        if isinstance(self.tree, TapLeaf):
            return self.tree.keys
        left, right = self.tree
        return left.keys + right.keys

    @classmethod
    def read_from(cls, s):
        c = s.read(1)
        if len(c) == 0:
            return cls()
        if c == b"{":  # more than one miniscript
            left = cls.read_from(s)
            c = s.read(1)
            if c == b"}":
                return left
            if c != b",":
                raise MiniscriptError("Invalid taptree syntax: expected ','")
            right = cls.read_from(s)
            if s.read(1) != b"}":
                raise MiniscriptError("Invalid taptree syntax: expected '}'")
            return cls((left, right))
        s.seek(-1, 1)
        ms = TapLeaf.read_from(s)
        return cls(ms)

    def derive(self, *args, **kwargs):
        if self.tree is None:
            return type(self)(None)
        if isinstance(self.tree, TapLeaf):
            return type(self)(self.tree.derive(*args, **kwargs))
        left, right = self.tree
        return type(self)((left.derive(*args, **kwargs), right.derive(*args, **kwargs)))

    def branch(self, *args, **kwargs):
        if self.tree is None:
            return type(self)(None)
        if isinstance(self.tree, TapLeaf):
            return type(self)(self.tree.branch(*args, **kwargs))
        left, right = self.tree
        return type(self)((left.branch(*args, **kwargs), right.branch(*args, **kwargs)))

    def to_public(self, *args, **kwargs):
        if self.tree is None:
            return type(self)(None)
        if isinstance(self.tree, TapLeaf):
            return type(self)(self.tree.to_public(*args, **kwargs))
        left, right = self.tree
        return type(self)(
            (left.to_public(*args, **kwargs), right.to_public(*args, **kwargs))
        )

    def __str__(self):
        if self.tree is None:
            return ""
        if isinstance(self.tree, TapLeaf):
            return str(self.tree)
        (left, right) = self.tree
        return "{%s,%s}" % (left, right)