Completed
Pull Request — master (#524)
by thomas
71:45
created

Multisig::fromDecodedScript()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 17
nc 6
nop 3
dl 0
loc 29
ccs 8
cts 8
cp 1
crap 5
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
namespace BitWasp\Bitcoin\Script\ScriptInfo;
4
5
use BitWasp\Bitcoin\Bitcoin;
6
use BitWasp\Bitcoin\Crypto\EcAdapter\EcSerializer;
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PublicKey;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
9
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PublicKeySerializerInterface;
10
use BitWasp\Bitcoin\Script\Opcodes;
11
use BitWasp\Bitcoin\Script\Parser\Operation;
12
use BitWasp\Bitcoin\Script\ScriptInterface;
13
use BitWasp\Bitcoin\Script\ScriptType;
14
use BitWasp\Buffertools\BufferInterface;
15
16
class Multisig
17
{
18
    /**
19
     * @var int
20
     */
21
    private $m;
22
23
    /**
24
     * @var int
25
     */
26
    private $n;
27
28
    /**
29
     * @var bool
30
     */
31
    private $verify = false;
32
33
    /**
34
     * @var BufferInterface[]
35
     */
36
    private $keyBuffers = [];
37
38
    /**
39
     * @var PublicKeySerializerInterface
40 26
     */
41
    private $pubKeySerializer;
42 26
43 6
    /**
44
     * Multisig constructor.
45
     * @param int $requiredSigs
46 26
     * @param BufferInterface[] $keys
47 26
     * @param int $opcode
48
     * @param bool $allowVerify
49
     * @param PublicKeySerializerInterface|null $pubKeySerializer
50
     */
51 26
    public function __construct($requiredSigs, array $keys, $opcode, $allowVerify = false, PublicKeySerializerInterface $pubKeySerializer = null)
52 26
    {
53
        if ($opcode === Opcodes::OP_CHECKMULTISIG) {
54 26
            $verify = false;
55 26
        } else if ($allowVerify && $opcode === Opcodes::OP_CHECKMULTISIGVERIFY) {
56 26
            $verify = true;
57
        } else {
58 26
            throw new \InvalidArgumentException('Malformed multisig script');
59
        }
60
61
        foreach ($keys as $key) {
62 26
            if (!PublicKey::isCompressedOrUncompressed($key)) {
63 26
                throw new \RuntimeException("Malformed public key");
64
            }
65
        }
66 26
67 26
        $keyCount = count($keys);
68
        if ($requiredSigs < 0 || $requiredSigs > $keyCount) {
69
            throw new \RuntimeException("Invalid number of required signatures");
70 26
        }
71 26
72 26
        if ($keyCount < 1 || $keyCount > 16) {
73
            throw new \RuntimeException("Invalid number of public keys");
74
        }
75
76
        if (null === $pubKeySerializer) {
77 24
            $pubKeySerializer = EcSerializer::getSerializer(PublicKeySerializerInterface::class, false, Bitcoin::getEcAdapter());
78
        }
79 24
80
        $this->verify = $verify;
81
        $this->m = $requiredSigs;
82
        $this->n = $keyCount;
83
        $this->keyBuffers = $keys;
84
        $this->pubKeySerializer = $pubKeySerializer;
85 22
    }
86
87 22
    /**
88
     * @param Operation[] $decoded
89
     * @param PublicKeySerializerInterface|null $pubKeySerializer
90
     * @param bool $allowVerify
91
     * @return static
92
     */
93
    public static function fromDecodedScript(array $decoded, PublicKeySerializerInterface $pubKeySerializer = null, $allowVerify = false)
94 2
    {
95
        if (count($decoded) < 4) {
96 2
            throw new \InvalidArgumentException('Malformed multisig script');
97 2
        }
98 2
99 2
        $mCode = $decoded[0]->getOp();
100
        $nCode = $decoded[count($decoded) - 2]->getOp();
101
        $opCode = end($decoded)->getOp();
102
103 2
        $requiredSigs = \BitWasp\Bitcoin\Script\decodeOpN($mCode);
104
        $publicKeyBuffers = [];
105
        foreach (array_slice($decoded, 1, -2) as $key) {
106
            /** @var \BitWasp\Bitcoin\Script\Parser\Operation $key */
107
            if (!$key->isPush()) {
108
                throw new \RuntimeException('Malformed multisig script');
109 24
            }
110
111 24
            $buffer = $key->getData();
112
            $publicKeyBuffers[] = $buffer;
113
        }
114
115
        $keyCount = \BitWasp\Bitcoin\Script\decodeOpN($nCode);
116
        if ($keyCount !== count($publicKeyBuffers)) {
117
            throw new \LogicException('No public keys found in script');
118
        }
119
120
        return new static($requiredSigs, $publicKeyBuffers, $opCode, $allowVerify, $pubKeySerializer);
121
    }
122
123
    /**
124
     * @param ScriptInterface $script
125
     * @param PublicKeySerializerInterface|null $pubKeySerializer
126
     * @param bool $allowVerify
127
     * @return Multisig
128
     */
129
    public static function fromScript(ScriptInterface $script, PublicKeySerializerInterface $pubKeySerializer = null, $allowVerify = false)
130
    {
131
        return static::fromDecodedScript($script->getScriptParser()->decode(), $pubKeySerializer, $allowVerify);
132
    }
133
134
    /**
135
     * @return string
136
     */
137
    public function getType()
138
    {
139
        return ScriptType::MULTISIG;
140
    }
141
142
    /**
143
     * @return int
144
     */
145
    public function getRequiredSigCount()
146
    {
147
        return $this->m;
148
    }
149
150
    /**
151
     * @return int
152
     */
153
    public function getKeyCount()
154
    {
155
        return $this->n;
156
    }
157
158
    /**
159
     * @return bool
160
     */
161
    public function isChecksigVerify()
162
    {
163
        return $this->verify;
164
    }
165
166
    /**
167
     * @param PublicKeyInterface $publicKey
168
     * @return bool
169
     */
170
    public function checkInvolvesKey(PublicKeyInterface $publicKey)
171
    {
172
        $buffer = $this->pubKeySerializer->serialize($publicKey);
173
        foreach ($this->keyBuffers as $key) {
174
            if ($key->equals($buffer)) {
175
                return true;
176
            }
177
        }
178
179
        return false;
180
    }
181
182
    /**
183
     * @return array|BufferInterface[]
184
     */
185
    public function getKeyBuffers()
186
    {
187
        return $this->keyBuffers;
188
    }
189
}
190