Completed
Pull Request — master (#744)
by thomas
39:40
created

MultisigHD::getScriptAndSignData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Key\Deterministic;
6
7
use BitWasp\Bitcoin\Address\BaseAddressCreator;
8
use BitWasp\Bitcoin\Exceptions\InvalidDerivationException;
9
use BitWasp\Bitcoin\Key\KeyToScript\ScriptAndSignData;
10
use BitWasp\Bitcoin\Key\KeyToScript\ScriptDataFactory;
11
use BitWasp\Bitcoin\Script\ScriptType;
12
13
/**
14
 * Implements a multisignature HD node, which like HierarchicalKey
15
 * adapts the type of script (p2sh? p2wsh? nested?) with the ScriptDataFactory.
16
 * Older versions used to contain the absolute BIP32 path, which has been removed.
17
18
 * Older versions also used to sort the keys returned by getKeys, but now they are
19
 * returned in signer first order.
20
 *
21
 * The ScriptDataFactory must be configured for the desired m-on-n, and sorting parameters
22
 * as this is purely a concern for script creation.
23
 */
24
class MultisigHD
25
{
26
    /**
27
     * @var HierarchicalKey[]
28
     */
29
    private $keys;
30
31
    /**
32
     * @var ScriptDataFactory
33
     */
34
    private $scriptFactory;
35
36
    /**
37
     * @var ScriptAndSignData
38
     */
39
    private $scriptAndSignData;
40
41
    /**
42
     * MultisigHD constructor.
43
     * @param ScriptDataFactory $scriptDataFactory
44
     * @param HierarchicalKey ...$keys
45
     */
46
    public function __construct(ScriptDataFactory $scriptDataFactory, HierarchicalKey... $keys)
47
    {
48
        if (count($keys) < 1) {
49
            throw new \RuntimeException('Must have at least one HierarchicalKey for Multisig HD Script');
50
        }
51
52
        if (substr($scriptDataFactory->getScriptType(), 0 - strlen(ScriptType::MULTISIG)) !== ScriptType::MULTISIG) {
53 10
            throw new \RuntimeException("multi-signature script factory required: {$scriptDataFactory->getScriptType()} given");
54
        }
55 10
56 1
        $this->keys = $keys;
57
        $this->scriptFactory = $scriptDataFactory;
58
59
        // Immediately produce the script to check our inputs are correct
60 9
        $publicKeys = [];
61 8
        foreach ($this->keys as $key) {
62
            $publicKeys[] = $key->getPublicKey();
63
        }
64 9
        $this->scriptAndSignData = $this->scriptFactory->convertKey(...$publicKeys);
65 9
    }
66
67
    /**
68 9
     * Return the composite keys of this MultisigHD wallet entry.
69 9
     * Note: unlike previous versions, the cosigner indexes are preserved here.
70 9
     * To obtain the sorted keys, extract them from the script.
71 9
     *
72 9
     * @return HierarchicalKey[]
73 9
     */
74 9
    public function getKeys(): array
75 9
    {
76 9
        return $this->keys;
77 9
    }
78 9
79
    /**
80
     * @return ScriptDataFactory
81
     */
82
    public function getScriptDataFactory(): ScriptDataFactory
83
    {
84
        return $this->scriptFactory;
85
    }
86 8
87 8
    /**
88 8
     * @return ScriptAndSignData
89
     */
90
    public function getScriptAndSignData(): ScriptAndSignData
91
    {
92
        return $this->scriptAndSignData;
93
    }
94 1
95
    /**
96 1
     * @param BaseAddressCreator $addressCreator
97
     * @return \BitWasp\Bitcoin\Address\Address
98
     */
99
    public function getAddress(BaseAddressCreator $addressCreator)
100
    {
101
        return $this->getScriptAndSignData()->getAddress($addressCreator);
102
    }
103
104
    /**
105 7
     * @param int $sequence
106
     * @return MultisigHD
107 7
     * @throws InvalidDerivationException
108
     */
109
    public function deriveChild(int $sequence): MultisigHD
110
    {
111
        $keys = [];
112
        foreach ($this->keys as $cosignerIdx => $key) {
113
            try {
114
                $keys[] = $key->deriveChild($sequence);
115 4
            } catch (InvalidDerivationException $e) {
116
                throw new InvalidDerivationException("Cosigner {$cosignerIdx} key derivation failed", 0, $e);
117 4
            }
118
        }
119
120
        return new self($this->scriptFactory, ...$keys);
121
    }
122
123
    /**
124
     * Decodes a BIP32 path into actual 32bit sequence numbers and derives the child key
125
     *
126
     * @param string $path
127
     * @return MultisigHD
128
     * @throws \Exception
129
     */
130
    public function derivePath(string $path): MultisigHD
131 4
    {
132
        $sequences = new HierarchicalKeySequence();
133 4
        $parts = $sequences->decodeRelative($path);
134
        $numParts = count($parts);
135
136
        $key = $this;
137
        for ($i = 0; $i < $numParts; $i++) {
138
            try {
139
                $key = $key->deriveChild((int) $parts[$i]);
140
            } catch (InvalidDerivationException $e) {
141
                if ($i === $numParts - 1) {
142 4
                    throw new InvalidDerivationException($e->getMessage());
143
                } else {
144 4
                    throw new InvalidDerivationException("Invalid derivation for non-terminal index: cannot use this path!");
145 4
                }
146 4
            }
147 4
        }
148 4
149
        return $key;
150
    }
151
}
152