Completed
Pull Request — master (#744)
by thomas
27:44 queued 18:06
created

MultisigHD   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 128
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 5

Test Coverage

Coverage 79.49%

Importance

Changes 0
Metric Value
dl 0
loc 128
ccs 31
cts 39
cp 0.7949
rs 10
c 0
b 0
f 0
wmc 15
lcom 2
cbo 5

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 20 4
A getKeys() 0 4 1
A getScriptDataFactory() 0 4 1
A getScriptAndSignData() 0 4 1
A getAddress() 0 4 1
A deriveChild() 0 13 3
A derivePath() 0 21 4
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 16
    public function __construct(ScriptDataFactory $scriptDataFactory, HierarchicalKey... $keys)
47
    {
48 16
        if (count($keys) < 1) {
49 1
            throw new \RuntimeException('Must have at least one HierarchicalKey for Multisig HD Script');
50
        }
51
52 15
        if (substr($scriptDataFactory->getScriptType(), 0 - strlen(ScriptType::MULTISIG)) !== ScriptType::MULTISIG) {
53
            throw new \RuntimeException("multi-signature script factory required: {$scriptDataFactory->getScriptType()} given");
54
        }
55
56 15
        $this->keys = $keys;
57 15
        $this->scriptFactory = $scriptDataFactory;
58
59
        // Immediately produce the script to check our inputs are correct
60 15
        $publicKeys = [];
61 15
        foreach ($this->keys as $key) {
62 15
            $publicKeys[] = $key->getPublicKey();
63
        }
64 15
        $this->scriptAndSignData = $this->scriptFactory->convertKey(...$publicKeys);
65 15
    }
66
67
    /**
68
     * Return the composite keys of this MultisigHD wallet entry.
69
     * Note: unlike previous versions, the cosigner indexes are preserved here.
70
     * To obtain the sorted keys, extract them from the script.
71
     *
72
     * @return HierarchicalKey[]
73
     */
74 9
    public function getKeys(): array
75
    {
76 9
        return $this->keys;
77
    }
78
79
    /**
80
     * @return ScriptDataFactory
81
     */
82
    public function getScriptDataFactory(): ScriptDataFactory
83
    {
84
        return $this->scriptFactory;
85
    }
86
87
    /**
88
     * @return ScriptAndSignData
89
     */
90 14
    public function getScriptAndSignData(): ScriptAndSignData
91
    {
92 14
        return $this->scriptAndSignData;
93
    }
94
95
    /**
96
     * @param BaseAddressCreator $addressCreator
97
     * @return \BitWasp\Bitcoin\Address\Address
98
     */
99 8
    public function getAddress(BaseAddressCreator $addressCreator)
100
    {
101 8
        return $this->getScriptAndSignData()->getAddress($addressCreator);
102
    }
103
104
    /**
105
     * @param int $sequence
106
     * @return MultisigHD
107
     * @throws InvalidDerivationException
108
     */
109 8
    public function deriveChild(int $sequence): MultisigHD
110
    {
111 8
        $keys = [];
112 8
        foreach ($this->keys as $cosignerIdx => $key) {
113
            try {
114 8
                $keys[] = $key->deriveChild($sequence);
115
            } catch (InvalidDerivationException $e) {
116 8
                throw new InvalidDerivationException("Cosigner {$cosignerIdx} key derivation failed", 0, $e);
117
            }
118
        }
119
120 8
        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 6
    public function derivePath(string $path): MultisigHD
131
    {
132 6
        $sequences = new HierarchicalKeySequence();
133 6
        $parts = $sequences->decodeRelative($path);
134 6
        $numParts = count($parts);
135
136 6
        $key = $this;
137 6
        for ($i = 0; $i < $numParts; $i++) {
138
            try {
139 6
                $key = $key->deriveChild((int) $parts[$i]);
140
            } catch (InvalidDerivationException $e) {
141
                if ($i === $numParts - 1) {
142
                    throw new InvalidDerivationException($e->getMessage());
143
                } else {
144
                    throw new InvalidDerivationException("Invalid derivation for non-terminal index: cannot use this path!");
145
                }
146
            }
147
        }
148
149 6
        return $key;
150
    }
151
}
152