Completed
Pull Request — master (#239)
by thomas
49:01 queued 45:29
created

InputSigner::extractFromValues()   C

Complexity

Conditions 13
Paths 36

Size

Total Lines 53
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 53
rs 6.3327
cc 13
eloc 32
nc 36
nop 4

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace BitWasp\Bitcoin\Transaction\Factory;
4
5
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
6
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
8
use BitWasp\Bitcoin\Crypto\Hash;
9
use BitWasp\Bitcoin\Crypto\Random\Rfc6979;
10
use BitWasp\Bitcoin\Key\PublicKeyFactory;
11
use BitWasp\Bitcoin\Script\Classifier\OutputClassifier;
12
use BitWasp\Bitcoin\Script\Opcodes;
13
use BitWasp\Bitcoin\Script\Parser\Operation;
14
use BitWasp\Bitcoin\Script\Script;
15
use BitWasp\Bitcoin\Script\ScriptFactory;
16
use BitWasp\Bitcoin\Script\ScriptInfo\Multisig;
17
use BitWasp\Bitcoin\Script\ScriptInterface;
18
use BitWasp\Bitcoin\Script\ScriptWitness;
19
use BitWasp\Bitcoin\Signature\SignatureSort;
20
use BitWasp\Bitcoin\Signature\TransactionSignature;
21
use BitWasp\Bitcoin\Signature\TransactionSignatureFactory;
22
use BitWasp\Bitcoin\Signature\TransactionSignatureInterface;
23
use BitWasp\Bitcoin\Transaction\SignatureHash\Hasher;
24
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
25
use BitWasp\Bitcoin\Transaction\SignatureHash\V1Hasher;
26
use BitWasp\Bitcoin\Transaction\TransactionInterface;
27
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
28
use BitWasp\Buffertools\BufferInterface;
29
30
class InputSigner
31
{
32
    /**
33
     * @var EcAdapterInterface
34
     */
35
    private $ecAdapter;
36
37
    /**
38
     * @var ScriptInterface $redeemScript
39
     */
40
    private $redeemScript;
41
42
    /**
43
     * @var ScriptInterface $witnessScript
44
     */
45
    private $witnessScript;
46
47
    /**
48
     * @var TransactionInterface
49
     */
50
    private $tx;
51
52
    /**
53
     * @var int
54
     */
55
    private $nInput;
56
57
    /**
58
     * @var PublicKeyInterface[]
59
     */
60
    private $publicKeys = [];
61
62
    /**
63
     * @var int
64
     */
65
    private $sigHashType;
66
67
    /**
68
     * @var TransactionSignatureInterface[]
69
     */
70
    private $signatures = [];
71
72
    /**
73
     * @var int
74
     */
75
    private $requiredSigs = 0;
76
77
    /**
78
     * TxInputSigning constructor.
79
     * @param EcAdapterInterface $ecAdapter
80
     * @param TransactionInterface $tx
81
     * @param int $nInput
82
     * @param TransactionOutputInterface $txOut
83
     * @param int $sigHashType
84
     */
85
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $tx, $nInput, TransactionOutputInterface $txOut, $sigHashType = SigHash::ALL)
86
    {
87
        $this->ecAdapter = $ecAdapter;
88
        $this->tx = $tx;
89
        $this->nInput = $nInput;
90
        $this->txOut = $txOut;
0 ignored issues
show
Bug introduced by
The property txOut does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
91
        $this->sigHashType = $sigHashType;
92
        $this->publicKeys = [];
93
        $this->signatures = [];
94
95
        $this->extractSignatures();
96
    }
97
98
    /**
99
     * @param string $type
100
     * @param ScriptInterface $scriptCode
101
     * @param BufferInterface[] $stack
102
     * @param int $sigVersion
103
     * @return mixed
104
     */
105
    public function extractFromValues($type, ScriptInterface $scriptCode, array $stack, $sigVersion)
106
    {
107
        $size = count($stack);
108
        if ($type === OutputClassifier::PAYTOPUBKEYHASH) {
109
            $this->requiredSigs = 1;
110
            if ($size === 2) {
111
                $this->signatures = [TransactionSignatureFactory::fromHex($stack[0], $this->ecAdapter)];
0 ignored issues
show
Documentation introduced by
$stack[0] 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...
112
                $this->publicKeys = [PublicKeyFactory::fromHex($stack[1], $this->ecAdapter)];
0 ignored issues
show
Documentation introduced by
$stack[1] 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...
113
            }
114
        }
115
116
        if ($type === OutputClassifier::PAYTOPUBKEY) {
117
            $this->requiredSigs = 1;
118
            if ($size === 1) {
119
                $this->signatures = [TransactionSignatureFactory::fromHex($stack[0], $this->ecAdapter)];
0 ignored issues
show
Documentation introduced by
$stack[0] 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...
120
            }
121
        }
122
123
        if ($type === OutputClassifier::MULTISIG) {
124
            $info = new Multisig($scriptCode);
125
            $this->requiredSigs = $info->getRequiredSigCount();
126
            $this->publicKeys = $info->getKeys();
127
128
            if ($size > 1) {
129
                if ($sigVersion === 1) {
130
                    $hasher = new V1Hasher($this->tx, $this->txOut->getValue());
131
                } else {
132
                    $hasher = new Hasher($this->tx);
133
                }
134
135
                $sigSort = new SignatureSort($this->ecAdapter);
136
                $sigs = new \SplObjectStorage;
137
138
                foreach (array_slice($stack, 1, -1) as $item) {
139
                    $txSig = TransactionSignatureFactory::fromHex($item, $this->ecAdapter);
0 ignored issues
show
Documentation introduced by
$item 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...
140
                    $hash = $hasher->calculate($scriptCode, $this->nInput, $txSig->getHashType());
141
                    $linked = $sigSort->link([$txSig->getSignature()], $this->publicKeys, $hash);
142
143
                    foreach ($this->publicKeys as $key) {
144
                        if ($linked->contains($key)) {
145
                            $sigs[$key] = $txSig;
146
                        }
147
                    }
148
                }
149
150
                foreach ($this->publicKeys as $idx => $key) {
151
                    $this->signatures[$idx] = isset($sigs[$key]) ? $sigs[$key]->getBuffer() : null;
152
                }
153
            }
154
        }
155
156
        return $type;
157
    }
158
159
    /**
160
     * @return $this
161
     */
162
    public function extractSignatures()
163
    {
164
        $type = (new OutputClassifier($this->txOut->getScript()))->classify();
165
        $scriptPubKey = $this->txOut->getScript();
166
        $scriptSig = $this->tx->getInput($this->nInput)->getScript();
167
168
        if ($type === OutputClassifier::PAYTOPUBKEYHASH || $type === OutputClassifier::PAYTOPUBKEY || $type === OutputClassifier::MULTISIG) {
169
            $innerSig = array_map(function (Operation $o) {
170
                return $o->getData();
171
            }, $scriptSig->getScriptParser()->decode());
172
173
            $this->extractFromValues($type, $scriptPubKey, $innerSig, 0);
174
        }
175
176
        if ($type === OutputClassifier::PAYTOSCRIPTHASH) {
177
            $decodeSig = $scriptSig->getScriptParser()->decode();
178
            if (count($decodeSig) > 0) {
179
                $redeemScript = new Script(end($decodeSig)->getData());
180
                $p2shType = (new OutputClassifier($redeemScript))->classify();
181
182
                if (count($decodeSig) > 1) {
183
                    $decodeSig = array_slice($decodeSig, 0, -1);
184
                }
185
186
                $internalSig = [];
187
                foreach ($decodeSig as $operation) {
188
                    $internalSig[] = $operation->getData();
189
                }
190
191
                $this->redeemScript = $redeemScript;
192
                $this->extractFromValues($p2shType, $redeemScript, $internalSig, 0);
193
194
                $type = $p2shType;
195
            }
196
        }
197
198
        $witnesses = $this->tx->getWitnesses();
199
        if ($type === OutputClassifier::WITNESS_V0_KEYHASH) {
200
            $this->requiredSigs = 1;
201
            if (isset($witnesses[$this->nInput])) {
202
                $witness = $witnesses[$this->nInput];
203
                $this->signatures = [TransactionSignatureFactory::fromHex($witness[0], $this->ecAdapter)];
204
                $this->publicKeys = [PublicKeyFactory::fromHex($witness[1], $this->ecAdapter)];
205
            }
206
207
        } else if ($type === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
208
            if (isset($witnesses[$this->nInput])) {
209
                $witness = $witnesses[$this->nInput];
210
                $witCount = count($witnesses[$this->nInput]);
211
                if ($witCount > 0) {
212
                    $witnessScript = new Script($witness[$witCount - 1]);
213
                    $vWitness = $witness->all();
214
                    if (count($vWitness) > 1) {
215
                        $vWitness = array_slice($witness->all(), 0, -1);
216
                    }
217
218
                    $witnessType = (new OutputClassifier($witnessScript))->classify();
219
                    $this->extractFromValues($witnessType, $witnessScript, $vWitness, 1);
220
                    $this->witnessScript = $witnessScript;
221
                }
222
            }
223
        }
224
225
        return $this;
226
    }
227
228
    /**
229
     * @param PrivateKeyInterface $key
230
     * @param ScriptInterface $scriptCode
231
     * @param int $sigVersion
232
     * @return TransactionSignature
233
     */
234
    public function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigVersion)
235
    {
236
        if ($sigVersion == 1) {
237
            $hasher = new V1Hasher($this->tx, $this->txOut->getValue());
238
        } else {
239
            $hasher = new Hasher($this->tx);
240
        }
241
242
        $hash = $hasher->calculate($scriptCode, $this->nInput, $this->sigHashType);
243
244
        return new TransactionSignature(
245
            $this->ecAdapter,
246
            $this->ecAdapter->sign(
247
                $hash,
248
                $key,
249
                new Rfc6979(
250
                    $this->ecAdapter,
251
                    $key,
252
                    $hash,
253
                    'sha256'
254
                )
255
            ),
256
            $this->sigHashType
257
        );
258
    }
259
260
    /**
261
     * @return int
262
     */
263
    public function isFullySigned()
264
    {
265
        return $this->requiredSigs !== 0 && $this->requiredSigs === count($this->signatures);
266
    }
267
268
    /**
269
     * The function only returns true when $scriptPubKey could be classified
270
     *
271
     * @param PrivateKeyInterface $key
272
     * @param ScriptInterface $scriptPubKey
273
     * @param int $outputType
274
     * @param BufferInterface[] $results
275
     * @param int $sigVersion
276
     * @return bool
277
     */
278
    private function doSignature(PrivateKeyInterface $key, ScriptInterface $scriptPubKey, &$outputType, array &$results, $sigVersion = 0)
279
    {
280
        $return = [];
281
        $outputType = (new OutputClassifier($scriptPubKey))->classify($return);
282
        if ($outputType === OutputClassifier::UNKNOWN) {
283
            throw new \RuntimeException('Cannot sign unknown script type');
284
        }
285
286
        if ($outputType === OutputClassifier::PAYTOPUBKEY) {
287
            $publicKeyBuffer = $return;
288
            $results[] = $publicKeyBuffer;
289
            $this->requiredSigs = 1;
290
            $publicKey = PublicKeyFactory::fromHex($publicKeyBuffer);
0 ignored issues
show
Documentation introduced by
$publicKeyBuffer is of type object<BitWasp\Buffertoo...\BufferInterface>>|null, 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...
291
292
            if ($publicKey->getBinary() === $key->getPublicKey()->getBinary()) {
293
                $this->signatures[0] = $this->calculateSignature($key, $scriptPubKey, $sigVersion);
294
            }
295
296
            return true;
297
        }
298
299
        if ($outputType === OutputClassifier::PAYTOPUBKEYHASH) {
300
            /** @var BufferInterface $pubKeyHash */
301
            $pubKeyHash = $return;
302
            $results[] = $pubKeyHash;
303
            $this->requiredSigs = 1;
304
            if ($pubKeyHash->getBinary() === $key->getPublicKey()->getPubKeyHash()->getBinary()) {
305
                $this->signatures[0] = $this->calculateSignature($key, $scriptPubKey, $sigVersion);
306
                $this->publicKeys[0] = $key->getPublicKey();
307
            }
308
309
            return true;
310
        }
311
312
        if ($outputType === OutputClassifier::MULTISIG) {
313
            $info = new Multisig($scriptPubKey);
314
315
            foreach ($info->getKeys() as $publicKey) {
316
                $results[] = $publicKey->getBuffer();
317
            }
318
319
            $this->publicKeys = $info->getKeys();
320
            $this->requiredSigs = $info->getKeyCount();
321
322
            foreach ($this->publicKeys as $keyIdx => $publicKey) {
323
                if ($publicKey->getBinary() == $key->getPublicKey()->getBinary()) {
324
                    $this->signatures[$keyIdx] = $this->calculateSignature($key, $scriptPubKey, $sigVersion);
325
                } else {
326
                    return false;
327
                }
328
            }
329
330
            return true;
331
        }
332
333
        if ($outputType === OutputClassifier::PAYTOSCRIPTHASH) {
334
            /** @var BufferInterface $scriptHash */
335
            $scriptHash = $return;
336
            $results[] = $scriptHash;
337
            return true;
338
        }
339
340
        if ($outputType === OutputClassifier::WITNESS_V0_KEYHASH) {
341
            /** @var BufferInterface $pubKeyHash */
342
            $pubKeyHash = $return;
343
            $results[] = $pubKeyHash;
344
            $this->requiredSigs = 1;
345
346
            if ($pubKeyHash->getBinary() === $key->getPublicKey()->getPubKeyHash()->getBinary()) {
347
                $script = ScriptFactory::sequence([Opcodes::OP_DUP, Opcodes::OP_HASH160, $pubKeyHash, Opcodes::OP_EQUALVERIFY, Opcodes::OP_CHECKSIG]);
348
                $this->signatures[0] = $this->calculateSignature($key, $script, 1);
349
                $this->publicKeys[0] = $key->getPublicKey();
350
            }
351
352
            return true;
353
        }
354
355
        if ($outputType === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
356
            /** @var BufferInterface $scriptHash */
357
            $scriptHash = $return;
358
            $results[] = $scriptHash;
359
360
            return true;
361
        }
362
363
        return false;
364
    }
365
366
    /**
367
     * @param PrivateKeyInterface $key
368
     * @param ScriptInterface|null $redeemScript
369
     * @param ScriptInterface|null $witnessScript
370
     * @return bool
371
     */
372
    public function sign(PrivateKeyInterface $key, ScriptInterface $redeemScript = null, ScriptInterface $witnessScript = null)
373
    {
374
        /** @var BufferInterface[] $return */
375
        $type = null;
376
        $return = [];
377
        $solved = $this->doSignature($key, $this->txOut->getScript(), $type, $return, 0);
378
379
        if ($solved && $type === OutputClassifier::PAYTOSCRIPTHASH) {
380
            $redeemScriptBuffer = $return[0];
381
382
            if (!$redeemScript instanceof ScriptInterface) {
383
                throw new \InvalidArgumentException('Must provide redeem script for P2SH');
384
            }
385
386
            if (!$redeemScript->getScriptHash()->getBinary() === $redeemScriptBuffer->getBinary()) {
387
                throw new \InvalidArgumentException("Incorrect redeem script - hash doesn't match");
388
            }
389
390
            $results = []; // ???
391
            $solved = $solved && $this->doSignature($key, $redeemScript, $type, $results, 0) && $type !== OutputClassifier::PAYTOSCRIPTHASH;
392
            if ($solved) {
393
                $this->redeemScript = $redeemScript;
394
            }
395
        }
396
397
        if ($solved && $type === OutputClassifier::WITNESS_V0_KEYHASH) {
398
            $pubKeyHash = $return[0];
399
            $witnessScript = ScriptFactory::sequence([Opcodes::OP_DUP, Opcodes::OP_HASH160, $pubKeyHash, Opcodes::OP_EQUALVERIFY, Opcodes::OP_CHECKSIG]);
400
            $subType = null;
401
            $subResults = [];
402
            $solved = $solved && $this->doSignature($key, $witnessScript, $subType, $subResults, 1);
403
        } else if ($solved && $type === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
404
            $scriptHash = $return[0];
405
406
            if (!$witnessScript instanceof ScriptInterface) {
407
                throw new \InvalidArgumentException('Must provide witness script for witness v0 scripthash');
408
            }
409
410
            if (!Hash::sha256($witnessScript->getBuffer())->getBinary() === $scriptHash->getBinary()) {
411
                throw new \InvalidArgumentException("Incorrect witness script - hash doesn't match");
412
            }
413
414
            $subType = null;
415
            $subResults = [];
416
417
            $solved = $solved && $this->doSignature($key, $witnessScript, $subType, $subResults, 1)
418
                && $subType !== OutputClassifier::PAYTOSCRIPTHASH
419
                && $subType !== OutputClassifier::WITNESS_V0_SCRIPTHASH
420
                && $subType !== OutputClassifier::WITNESS_V0_KEYHASH;
421
422
            if ($solved) {
423
                $this->witnessScript = $witnessScript;
424
            }
425
        }
426
427
        return $solved;
428
    }
429
430
    /**
431
     * @param $outputType
432
     * @param $answer
433
     * @return bool
434
     */
435
    private function serializeSimpleSig($outputType, &$answer)
436
    {
437
        if ($outputType === OutputClassifier::UNKNOWN) {
438
            throw new \RuntimeException('Cannot sign unknown script type');
439
        }
440
441
        if ($outputType === OutputClassifier::PAYTOPUBKEY && $this->isFullySigned()) {
442
            $answer = new SigValues(ScriptFactory::sequence([$this->signatures[0]->getBuffer()]), new ScriptWitness([]));
443
            return true;
444
        }
445
446
        if ($outputType === OutputClassifier::PAYTOPUBKEYHASH && $this->isFullySigned()) {
447
            $answer = new SigValues(ScriptFactory::sequence([$this->signatures[0]->getBuffer(), $this->publicKeys[0]->getBuffer()]), new ScriptWitness([]));
448
            return true;
449
        }
450
451
        if ($outputType === OutputClassifier::MULTISIG) {
452
            $sequence = [Opcodes::OP_0];
453
            for ($i = 0; $i < $this->requiredSigs; $i++) {
454
                if (isset($this->signatures[$i])) {
455
                    $sequence[] =  $this->signatures[$i]->getBuffer();
456
                }
457
            }
458
459
            $answer = new SigValues(ScriptFactory::sequence($sequence), new ScriptWitness([]));
460
            return true;
461
        }
462
463
        return false;
464
    }
465
466
    /**
467
     * @return SigValues
468
     */
469
    public function serializeSignatures()
470
    {
471
        static $emptyScript = null;
472
        static $emptyWitness = null;
473
        if (is_null($emptyScript) || is_null($emptyWitness)) {
474
            $emptyScript = new Script();
475
            $emptyWitness = new ScriptWitness([]);
476
        }
477
478
        /** @var BufferInterface[] $return */
479
        $outputType = (new OutputClassifier($this->txOut->getScript()))->classify();
480
481
        /** @var SigValues $answer */
482
        $answer = new SigValues($emptyScript, $emptyWitness);
483
        $serialized = $this->serializeSimpleSig($outputType, $answer);
484
485
        $p2sh = false;
486
        if (!$serialized && $outputType === OutputClassifier::PAYTOSCRIPTHASH) {
487
            $p2sh = true;
488
            $outputType = (new OutputClassifier($this->redeemScript))->classify();
489
            $serialized = $this->serializeSimpleSig($outputType, $answer);
490
        }
491
492
        if (!$serialized && $outputType === OutputClassifier::WITNESS_V0_KEYHASH) {
493
            $answer = new SigValues($emptyScript, new ScriptWitness([$this->signatures[0]->getBuffer(), $this->publicKeys[0]->getBuffer()]));
494
495
        } else if (!$serialized && $outputType === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
496
            $outputType = (new OutputClassifier($this->witnessScript))->classify();
497
            $serialized = $this->serializeSimpleSig($outputType, $answer);
498
499
            if ($serialized) {
500
                $data = [];
501
                foreach ($answer->getScriptSig()->getScriptParser()->decode() as $o) {
502
                    $data[] = $o->getData();
503
                }
504
505
                $data[] = $this->witnessScript->getBuffer();
506
                $answer = new SigValues($emptyScript, new ScriptWitness($data));
507
            }
508
        }
509
510
        if ($p2sh) {
511
            $answer = new SigValues(
512
                ScriptFactory::create($answer->getScriptSig()->getBuffer())->push($this->redeemScript->getBuffer())->getScript(),
513
                $answer->getScriptWitness()
514
            );
515
        }
516
517
        return $answer;
518
    }
519
}
520