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