Completed
Pull Request — master (#240)
by thomas
73:04
created

TxSigCreator::makeScript()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 26
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 26
rs 8.439
cc 6
eloc 18
nc 5
nop 1
1
<?php
2
3
namespace BitWasp\Bitcoin\Transaction\Factory;
4
5
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
6
use BitWasp\Bitcoin\Crypto\Random\Rfc6979;
7
use BitWasp\Bitcoin\Key\PublicKeyFactory;
8
use BitWasp\Bitcoin\Script\Classifier\OutputClassifier;
9
use BitWasp\Bitcoin\Script\Opcodes;
10
use BitWasp\Bitcoin\Script\Parser\Operation;
11
use BitWasp\Bitcoin\Script\Script;
12
use BitWasp\Bitcoin\Script\ScriptFactory;
13
use BitWasp\Bitcoin\Script\ScriptInfo\Multisig;
14
use BitWasp\Bitcoin\Script\ScriptInterface;
15
use BitWasp\Bitcoin\Script\ScriptWitness;
16
use BitWasp\Bitcoin\Signature\SignatureSort;
17
use BitWasp\Bitcoin\Signature\TransactionSignature;
18
use BitWasp\Bitcoin\Signature\TransactionSignatureFactory;
19
use BitWasp\Bitcoin\Transaction\SignatureHash\Hasher;
20
use BitWasp\Bitcoin\Transaction\SignatureHash\V1Hasher;
21
use BitWasp\Bitcoin\Transaction\TransactionInterface;
22
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
23
use BitWasp\Buffertools\Buffer;
24
use BitWasp\Buffertools\BufferInterface;
25
26
class TxSigCreator
27
{
28
29
    /**
30
     * @var EcAdapterInterface
31
     */
32
    private $ecAdapter;
33
34
    /**
35
     * @var TransactionInterface
36
     */
37
    private $tx;
38
39
    /**
40
     * @var int
41
     */
42
    private $nInput;
43
44
    /**
45
     * @var int
46
     */
47
    private $amount;
48
49
    /**
50
     * @var int
51
     */
52
    private $requiredSigs = 1;
53
54
    /**
55
     * TxSigCreator constructor.
56
     * @param EcAdapterInterface $adapter
57
     * @param TransactionInterface $tx
58
     * @param int $inputToSign
59
     * @param int $amount
60
     */
61
    public function __construct(EcAdapterInterface $adapter, TransactionInterface $tx, $inputToSign, $amount)
62
    {
63
        if (!isset($tx->getInputs()[$inputToSign])) {
64
            throw new \InvalidArgumentException('Input does not exist in transaction');
65
        }
66
67
        $this->ecAdapter = $adapter;
68
        $this->tx = $tx;
69
        $this->nInput = $inputToSign;
70
        $this->amount = $amount;
71
    }
72
73
    /**
74
     * @param PrivateKeyInterface $privKey
75
     * @param ScriptInterface $scriptPubKey
76
     * @param int $sigHashType
77
     * @param int $sigVersion
78
     * @return TransactionSignature
79
     */
80
    public function makeSignature(PrivateKeyInterface $privKey, ScriptInterface $scriptPubKey, $sigHashType, $sigVersion)
81
    {
82
        if ($sigVersion == 1) {
83
            $hasher = new V1Hasher($this->tx, $this->amount);
84
        } else {
85
            $hasher = new Hasher($this->tx);
86
        }
87
88
        $hash = $hasher->calculate($scriptPubKey, $this->nInput, $sigHashType);
89
        return new TransactionSignature(
90
            $this->ecAdapter,
91
            $this->ecAdapter->sign(
92
                $hash,
93
                $privKey,
94
                new Rfc6979(
95
                    $this->ecAdapter,
96
                    $privKey,
97
                    $hash,
98
                    'sha256'
99
                )
100
            ),
101
            $sigHashType
102
        );
103
104
    }
105
106
    /**
107
     * @param SignatureData $sigData
108
     * @param ScriptInterface $scriptSig
109
     * @param ScriptInterface|null $redeemScript
110
     * @return $this
111
     */
112
    public function extractSignatures(SignatureData $sigData, ScriptInterface $scriptSig, ScriptInterface $redeemScript = null)
113
    {
114
        $parsed = $scriptSig->getScriptParser()->decode();
115
        $size = count($parsed);
116
        $witnessCount = isset($this->tx->getWitnesses()[$this->nInput]) ? count($this->tx->getWitnesses()[$this->nInput]) : 0;
117
118
        $sigData->signatures = [];
119
        switch ($sigData->innerScriptType) {
120
            case OutputClassifier::PAYTOPUBKEYHASH:
121
                // Supply signature and public key in scriptSig
122
                if ($size === 2) {
123
                    $sigData->signatures = [TransactionSignatureFactory::fromHex($parsed[0]->getData(), $this->ecAdapter)->getBuffer()];
0 ignored issues
show
Documentation introduced by
$parsed[0]->getData() is of type object<BitWasp\Buffertools\BufferInterface>, but the function expects a object<BitWasp\Buffertools\Buffer>|string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
124
                    $sigData->publicKeys = [PublicKeyFactory::fromHex($parsed[1]->getData(), $this->ecAdapter)];
0 ignored issues
show
Documentation introduced by
$parsed[1]->getData() is of type object<BitWasp\Buffertools\BufferInterface>, but the function expects a object<BitWasp\Buffertools\Buffer>|string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
125
                }
126
                break;
127
            case OutputClassifier::PAYTOPUBKEY:
128
                // Only has a signature in the scriptSig
129
                if ($size === 1) {
130
                    $sigData->signatures = [TransactionSignatureFactory::fromHex($parsed[0]->getData(), $this->ecAdapter)->getBuffer()];
0 ignored issues
show
Documentation introduced by
$parsed[0]->getData() is of type object<BitWasp\Buffertools\BufferInterface>, but the function expects a object<BitWasp\Buffertools\Buffer>|string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
131
                }
132
133
                break;
134
            case OutputClassifier::MULTISIG:
135
                $info = new Multisig($redeemScript);
0 ignored issues
show
Bug introduced by
It seems like $redeemScript defined by parameter $redeemScript on line 112 can be null; however, BitWasp\Bitcoin\Script\S...Multisig::__construct() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
136
                $keyCount = $info->getKeyCount();
137
                $this->requiredSigs = $info->getRequiredSigCount();
138
                $sigData->publicKeys = $info->getKeys();
139
                $sigData->p2shScript = $redeemScript;
140
                if ($size > 2 && $size <= $keyCount + 2) {
141
                    $sigHash = $this->tx->getSignatureHash();
142
                    $sigSort = new SignatureSort($this->ecAdapter);
143
                    $sigs = new \SplObjectStorage;
144
145
                    foreach (array_slice($parsed, 1, -1) as $item) {
146
                        /** @var \BitWasp\Bitcoin\Script\Parser\Operation $item */
147
                        if ($item->isPush()) {
148
                            $txSig = TransactionSignatureFactory::fromHex($item->getData(), $this->ecAdapter);
0 ignored issues
show
Documentation introduced by
$item->getData() is of type object<BitWasp\Buffertools\BufferInterface>, but the function expects a object<BitWasp\Buffertools\Buffer>|string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
149
                            $hash = $sigHash->calculate($redeemScript, $this->nInput, $txSig->getHashType());
0 ignored issues
show
Bug introduced by
It seems like $redeemScript defined by parameter $redeemScript on line 112 can be null; however, BitWasp\Bitcoin\Transact...sh\SigHash::calculate() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
150
                            $linked = $sigSort->link([$txSig->getSignature()], $sigData->publicKeys, $hash);
151
152
                            foreach ($sigData->publicKeys as $key) {
153
                                if ($linked->contains($key)) {
154
                                    $sigs[$key] = $txSig->getBuffer();
155
                                }
156
                            }
157
                        }
158
                    }
159
160
                    // We have all the signatures from the input now. array_shift the sigs for a public key, as it's encountered.
161
                    foreach ($sigData->publicKeys as $idx => $key) {
162
                        $sigData->signatures[$idx] = isset($sigs[$key]) ? $sigs[$key]->getBuffer() : null;
163
                    }
164
                }
165
166
                break;
167
            case OutputClassifier::WITNESS_V0_KEYHASH:
168
                if ($witnessCount === 2) {
169
                    $witness = $this->tx->getWitness($this->nInput);
170
                    $sigData->signatures = [TransactionSignatureFactory::fromHex($witness[0]->getData(), $this->ecAdapter)->getBuffer()];
171
                    $sigData->publicKeys = [PublicKeyFactory::fromHex($witness[1]->getData(), $this->ecAdapter)];
172
                    $sigData->p2shScript = $redeemScript;
173
                }
174
        }
175
176
        return $this;
177
    }
178
179
    private function signStep($txoType, SignatureData $sigData, PrivateKeyInterface $privateKey, ScriptInterface $scriptPubKey, $sigVersion, $sigHashType)
180
    {
181
        if (count($sigData->signatures) < $this->requiredSigs) {
182
            if ($txoType === OutputClassifier::MULTISIG) {
183
                foreach ($sigData->publicKeys as $keyIdx => $publicKey) {
184
                    if ($publicKey->getBinary() == $privateKey->getPublicKey()->getBinary()) {
185
                        $sigData->signatures[$keyIdx] = $this->makeSignature($privateKey, $scriptPubKey, $sigHashType, $sigVersion);
186
                    }
187
                }
188
189
                return true;
190
            }
191
192
            if ($txoType === OutputClassifier::PAYTOPUBKEY) {
193
                $publicKey = PublicKeyFactory::fromHex($sigData->solution[0]);
194
                if ($publicKey !== $privateKey->getPublicKey()) {
195
                    return false;
196
                }
197
            }
198
199
            if ($txoType === OutputClassifier::PAYTOPUBKEYHASH) {
200
                $publicKey = $privateKey->getPublicKey();
201
                if ($publicKey->getPubKeyHash()->getBinary() != $sigData->solution->getBinary()) {
202
                    return false;
203
                }
204
205
                $sigData->publicKeys[0] = $publicKey;
206
            }
207
208
            if ($sigData->scriptType === OutputClassifier::WITNESS_V0_KEYHASH) {
209
                $publicKey = $privateKey->getPublicKey();
210
                if ($publicKey->getPubKeyHash()->getBinary() !== $sigData->solution->getBinary()) {
211
                    return false;
212
                }
213
214
                $scriptPubKey = ScriptFactory::sequence([Opcodes::OP_DUP, Opcodes::OP_HASH160, $sigData->solution, Opcodes::OP_EQUALVERIFY, Opcodes::OP_CHECKSIG]);
215
                $sigData->publicKeys[0] = $publicKey;
216
            }
217
218
            $sigData->signatures[0] = $this->makeSignature($privateKey, $scriptPubKey, $sigHashType, $sigVersion)->getBuffer();
219
        }
220
221
        return true;
222
    }
223
224
    /**
225
     * @param SignatureData $sigData
226
     * @param PrivateKeyInterface $privateKey
227
     * @param ScriptInterface $scriptPubKey
228
     * @param int $sigHashType
229
     * @return $this|bool
230
     */
231
    public function signInput(SignatureData $sigData, PrivateKeyInterface $privateKey, ScriptInterface $scriptPubKey, $sigHashType)
232
    {
233
        if ($sigData->scriptType !== OutputClassifier::UNKNOWN) {
234
            $scriptType = $sigData->scriptType;
235
            $sigVersion = 0;
236
            if ($scriptType === OutputClassifier::PAYTOSCRIPTHASH) {
237
                $scriptType = $sigData->innerScriptType;
238
            }
239
240
            if ($scriptType === OutputClassifier::WITNESS_V0_SCRIPTHASH || $scriptType === OutputClassifier::WITNESS_V0_KEYHASH) {
241
                $sigVersion = 1;
242
            }
243
244
            if ($scriptType === OutputClassifier::MULTISIG ||
245
                $scriptType === OutputClassifier::PAYTOPUBKEY ||
246
                $scriptType === OutputClassifier::PAYTOPUBKEYHASH ||
247
                $scriptType === OutputClassifier::WITNESS_V0_KEYHASH) {
248
249
                $this->signStep($scriptType, $sigData, $privateKey, $scriptPubKey, $sigHashType, $sigVersion);
250
            }
251
        }
252
253
        return true;
254
    }
255
256
    /**
257
     * @param SignatureData $sigData
258
     * @return ScriptInterface
259
     */
260
    private function makeScript(SignatureData $sigData)
261
    {
262
        $script = ScriptFactory::create();
263
264
        if (count($sigData->signatures) === $this->requiredSigs) {
265
            echo 'signed';
266
            switch ($sigData->innerScriptType) {
267
                case OutputClassifier::PAYTOPUBKEY:
268
                    $script->push($sigData->signatures[0]);
269
                    break;
270
                case OutputClassifier::PAYTOPUBKEYHASH:
271
                    $script->sequence([$sigData->signatures[0], $sigData->publicKeys[0]->getBuffer()]);
272
                    break;
273
                case OutputClassifier::MULTISIG:
274
                    $sequence = [Opcodes::OP_0];
275
                    foreach ($sigData->signatures as $sig) {
276
                        $sequence[] = $sig->getBuffer();
0 ignored issues
show
Bug introduced by
The method getBuffer() does not seem to exist on object<BitWasp\Buffertools\BufferInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
277
                    }
278
279
                    $script->sequence($sequence);
280
                    break;
281
            }
282
        }
283
284
        return $script->getScript();
285
    }
286
287
    public function serializeSig(SignatureData $sigData, & $scriptWitness = null)
288
    {
289
        $script = $this->makeScript($sigData);
290
291
        if ($sigData->scriptType === OutputClassifier::PAYTOSCRIPTHASH) {
292
            $sig = $script->getBuffer();
293
            $pushScript = $sigData->p2shScript;
294
            if ($sigData->innerScriptType === OutputClassifier::WITNESS_V0_KEYHASH) {
295
                $sig = new Buffer();
296
            }
297
298
            if ($sigData->innerScriptType === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
299
                $sig = new Buffer();
300
            }
301
302
            $script = ScriptFactory::create($sig)
303
                ->push($pushScript->getBuffer())
304
                ->getScript();
305
        }
306
307
        if ($sigData->innerScriptType === OutputClassifier::WITNESS_V0_KEYHASH) {
308
            $script = new Script(); // Required, otherwise we introduce malleability.
309
            $values = $sigData->signatures;
310
            $values[] = $sigData->publicKeys[0]->getBuffer();
311
            $scriptWitness = new ScriptWitness($values);
312
        }
313
314
        return $script;
315
    }
316
}
317