Complex classes like InputSigner often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use InputSigner, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
47 | class InputSigner implements InputSignerInterface |
||
48 | { |
||
49 | /** |
||
50 | * @var array |
||
51 | */ |
||
52 | protected static $canSign = [ |
||
53 | ScriptType::P2PKH, |
||
54 | ScriptType::P2PK, |
||
55 | ScriptType::MULTISIG |
||
56 | ]; |
||
57 | |||
58 | /** |
||
59 | * @var array |
||
60 | */ |
||
61 | protected static $validP2sh = [ |
||
62 | ScriptType::P2WKH, |
||
63 | ScriptType::P2WSH, |
||
64 | ScriptType::P2PKH, |
||
65 | ScriptType::P2PK, |
||
66 | ScriptType::MULTISIG |
||
67 | ]; |
||
68 | |||
69 | /** |
||
70 | * @var EcAdapterInterface |
||
71 | */ |
||
72 | private $ecAdapter; |
||
73 | |||
74 | /** |
||
75 | * @var FullyQualifiedScript |
||
76 | */ |
||
77 | private $fqs; |
||
78 | |||
79 | /** |
||
80 | * @var bool |
||
81 | */ |
||
82 | private $padUnsignedMultisigs = false; |
||
83 | |||
84 | /** |
||
85 | * @var bool |
||
86 | */ |
||
87 | private $tolerateInvalidPublicKey = false; |
||
88 | |||
89 | /** |
||
90 | * @var bool |
||
91 | */ |
||
92 | private $allowComplexScripts = false; |
||
93 | |||
94 | /** |
||
95 | * @var SignData |
||
96 | */ |
||
97 | private $signData; |
||
98 | |||
99 | /** |
||
100 | * @var int |
||
101 | */ |
||
102 | private $flags; |
||
103 | |||
104 | /** |
||
105 | * @var TransactionInterface |
||
106 | */ |
||
107 | private $tx; |
||
108 | |||
109 | /** |
||
110 | * @var int |
||
111 | */ |
||
112 | private $nInput; |
||
113 | |||
114 | /** |
||
115 | * @var TransactionOutputInterface |
||
116 | */ |
||
117 | private $txOut; |
||
118 | |||
119 | /** |
||
120 | * @var Interpreter |
||
121 | */ |
||
122 | private $interpreter; |
||
123 | |||
124 | /** |
||
125 | * @var Checker |
||
126 | */ |
||
127 | private $signatureChecker; |
||
128 | |||
129 | /** |
||
130 | * @var TransactionSignatureSerializer |
||
131 | */ |
||
132 | private $txSigSerializer; |
||
133 | |||
134 | /** |
||
135 | * @var PublicKeySerializerInterface |
||
136 | */ |
||
137 | private $pubKeySerializer; |
||
138 | |||
139 | /** |
||
140 | * @var Conditional[]|Checksig[] |
||
141 | */ |
||
142 | private $steps = []; |
||
143 | |||
144 | /** |
||
145 | * InputSigner constructor. |
||
146 | * |
||
147 | * Note, the implementation of this class is considered internal |
||
148 | * and only the methods exposed on InputSignerInterface should |
||
149 | * be depended on to avoid BC breaks. |
||
150 | * |
||
151 | * The only recommended way to produce this class is using Signer::input() |
||
152 | * |
||
153 | * @param EcAdapterInterface $ecAdapter |
||
154 | * @param TransactionInterface $tx |
||
155 | * @param int $nInput |
||
156 | * @param TransactionOutputInterface $txOut |
||
157 | * @param SignData $signData |
||
158 | * @param CheckerBase $checker |
||
159 | * @param TransactionSignatureSerializer|null $sigSerializer |
||
160 | * @param PublicKeySerializerInterface|null $pubKeySerializer |
||
161 | */ |
||
162 | 130 | public function __construct( |
|
186 | |||
187 | /** |
||
188 | * Ensures a FullyQualifiedScript will be accepted |
||
189 | * by the InputSigner. |
||
190 | * |
||
191 | * @param FullyQualifiedScript $script |
||
192 | */ |
||
193 | 88 | public static function ensureAcceptableScripts(FullyQualifiedScript $script) |
|
217 | |||
218 | /** |
||
219 | * It ensures that violating the following prevents instance creation |
||
220 | * - the scriptPubKey can be directly signed, or leads to P2SH/P2WSH/P2WKH |
||
221 | * - the P2SH script covers signable types and P2WSH/P2WKH |
||
222 | * - the witnessScript covers signable types only |
||
223 | * @return $this|InputSigner |
||
224 | * @throws ScriptRuntimeException |
||
225 | * @throws SignerException |
||
226 | * @throws \Exception |
||
227 | */ |
||
228 | 130 | public function extract() |
|
248 | |||
249 | /** |
||
250 | * @param bool $setting |
||
251 | * @return $this |
||
252 | */ |
||
253 | 115 | public function padUnsignedMultisigs(bool $setting) |
|
258 | |||
259 | /** |
||
260 | * @param bool $setting |
||
261 | * @return $this |
||
262 | */ |
||
263 | 115 | public function tolerateInvalidPublicKey(bool $setting) |
|
268 | |||
269 | /** |
||
270 | * @param bool $setting |
||
271 | * @return $this |
||
272 | */ |
||
273 | 115 | public function allowComplexScripts(bool $setting) |
|
278 | |||
279 | /** |
||
280 | * @param BufferInterface $vchPubKey |
||
281 | * @return PublicKeyInterface|null |
||
282 | * @throws \Exception |
||
283 | */ |
||
284 | 97 | protected function parseStepPublicKey(BufferInterface $vchPubKey) |
|
296 | |||
297 | /** |
||
298 | * @param ScriptInterface $script |
||
299 | * @param BufferInterface[] $signatures |
||
300 | * @param BufferInterface[] $publicKeys |
||
301 | * @param int $sigVersion |
||
302 | * @return \SplObjectStorage |
||
303 | * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException |
||
304 | */ |
||
305 | 30 | private function sortMultisigs(ScriptInterface $script, array $signatures, array $publicKeys, int $sigVersion): \SplObjectStorage |
|
337 | |||
338 | /** |
||
339 | * @param array $decoded |
||
340 | * @param null $solution |
||
341 | * @return null|TimeLock|Checksig |
||
342 | */ |
||
343 | 118 | private function classifySignStep(array $decoded, &$solution = null) |
|
380 | |||
381 | /** |
||
382 | * @param Operation[] $scriptOps |
||
383 | * @return Checksig[] |
||
384 | */ |
||
385 | 118 | public function parseSequence(array $scriptOps) |
|
413 | |||
414 | /** |
||
415 | * @param Operation $operation |
||
416 | * @param Stack $mainStack |
||
417 | * @param bool[] $pathData |
||
418 | * @return Conditional |
||
419 | */ |
||
420 | 11 | public function extractConditionalOp(Operation $operation, Stack $mainStack, array &$pathData): Conditional |
|
454 | |||
455 | /** |
||
456 | * @param int $idx |
||
457 | * @return Checksig|Conditional |
||
458 | */ |
||
459 | 18 | public function step(int $idx) |
|
467 | |||
468 | /** |
||
469 | * @param OutputData $signScript |
||
470 | * @param Stack $stack |
||
471 | * @param SignData $signData |
||
472 | * @return array |
||
473 | * @throws ScriptRuntimeException |
||
474 | * @throws SignerException |
||
475 | * @throws \Exception |
||
476 | */ |
||
477 | 118 | public function extractScript(OutputData $signScript, Stack $stack, SignData $signData): array |
|
478 | { |
||
479 | 118 | $logicInterpreter = new BranchInterpreter(); |
|
480 | 118 | $tree = $logicInterpreter->getScriptTree($signScript->getScript()); |
|
481 | |||
482 | 118 | if ($tree->hasMultipleBranches()) { |
|
483 | 11 | $logicalPath = $signData->getLogicalPath(); |
|
484 | // we need a function like findWitnessScript to 'check' |
||
485 | // partial signatures against _our_ path |
||
486 | } else { |
||
487 | 107 | $logicalPath = []; |
|
488 | } |
||
489 | |||
490 | $scriptSections = $tree |
||
491 | 118 | ->getBranchByPath($logicalPath) |
|
492 | 118 | ->getScriptSections(); |
|
493 | |||
494 | 118 | $vfStack = new Stack(); |
|
495 | |||
496 | 118 | $pathCopy = $logicalPath; |
|
497 | 118 | $steps = []; |
|
498 | 118 | foreach ($scriptSections as $i => $scriptSection) { |
|
499 | /** @var Operation[] $scriptSection */ |
||
500 | 118 | $fExec = !$this->interpreter->checkExec($vfStack, false); |
|
501 | 118 | if (count($scriptSection) === 1 && $scriptSection[0]->isLogical()) { |
|
502 | 11 | $op = $scriptSection[0]; |
|
503 | 11 | switch ($op->getOp()) { |
|
504 | case Opcodes::OP_IF: |
||
505 | case Opcodes::OP_NOTIF: |
||
506 | 11 | $value = false; |
|
507 | 11 | if ($fExec) { |
|
508 | // Pop from mainStack if $fExec |
||
509 | 11 | $step = $this->extractConditionalOp($op, $stack, $pathCopy); |
|
510 | |||
511 | // the Conditional has a value in this case: |
||
512 | 11 | $value = $step->getValue(); |
|
513 | |||
514 | // Connect the last operation (if there is one) |
||
515 | // with the last step with isRequired==$value |
||
516 | // todo: check this part out.. |
||
517 | 11 | for ($j = count($steps) - 1; $j >= 0; $j--) { |
|
518 | 4 | if ($steps[$j] instanceof Checksig && $value === $steps[$j]->isRequired()) { |
|
519 | 4 | $step->providedBy($steps[$j]); |
|
520 | 4 | break; |
|
521 | } |
||
522 | } |
||
523 | } else { |
||
524 | 1 | $step = new Conditional($op->getOp()); |
|
525 | } |
||
526 | |||
527 | 11 | $steps[] = $step; |
|
528 | |||
529 | 11 | if ($op->getOp() === Opcodes::OP_NOTIF) { |
|
530 | 5 | $value = !$value; |
|
531 | } |
||
532 | |||
533 | 11 | $vfStack->push($value); |
|
534 | 11 | break; |
|
535 | case Opcodes::OP_ENDIF: |
||
536 | 11 | $vfStack->pop(); |
|
537 | 11 | break; |
|
538 | case Opcodes::OP_ELSE: |
||
539 | 9 | $vfStack->push(!$vfStack->pop()); |
|
540 | 11 | break; |
|
541 | } |
||
542 | } else { |
||
543 | 118 | $templateTypes = $this->parseSequence($scriptSection); |
|
544 | |||
545 | // Detect if effect on mainStack is `false` |
||
546 | 118 | $resolvesFalse = count($pathCopy) > 0 && !$pathCopy[0]; |
|
547 | 118 | if ($resolvesFalse) { |
|
548 | 2 | if (count($templateTypes) > 1) { |
|
549 | throw new UnsupportedScript("Unsupported script, multiple steps to segment which is negated"); |
||
550 | } |
||
551 | } |
||
552 | |||
553 | 118 | foreach ($templateTypes as $k => $checksig) { |
|
554 | 118 | if ($fExec) { |
|
555 | 118 | if ($checksig instanceof Checksig) { |
|
556 | 108 | $this->extractChecksig($signScript->getScript(), $checksig, $stack, $this->fqs->sigVersion(), $resolvesFalse); |
|
557 | |||
558 | // If this statement results is later consumed |
||
559 | // by a conditional which would be false, mark |
||
560 | // this operation as not required |
||
561 | 103 | if ($resolvesFalse) { |
|
562 | 103 | $checksig->setRequired(false); |
|
563 | } |
||
564 | 15 | } else if ($checksig instanceof TimeLock) { |
|
565 | 15 | $this->checkTimeLock($checksig); |
|
566 | } |
||
567 | |||
568 | 103 | $steps[] = $checksig; |
|
569 | } |
||
570 | } |
||
571 | } |
||
572 | } |
||
573 | |||
574 | 103 | return $steps; |
|
575 | } |
||
576 | |||
577 | /** |
||
578 | * @param int $verify |
||
579 | * @param int $input |
||
580 | * @param int $threshold |
||
581 | * @return int |
||
582 | */ |
||
583 | 5 | private function compareRangeAgainstThreshold($verify, $input, $threshold) |
|
595 | |||
596 | /** |
||
597 | * @param TimeLock $timelock |
||
598 | */ |
||
599 | 15 | public function checkTimeLock(TimeLock $timelock) |
|
652 | |||
653 | /** |
||
654 | * @param ScriptInterface $script |
||
655 | * @param BufferInterface $vchSig |
||
656 | * @param BufferInterface $vchKey |
||
657 | * @return bool |
||
658 | */ |
||
659 | 35 | private function checkSignature(ScriptInterface $script, BufferInterface $vchSig, BufferInterface $vchKey) |
|
667 | |||
668 | /** |
||
669 | * This function is strictly for $canSign types. |
||
670 | * It will extract signatures/publicKeys when given $outputData, and $stack. |
||
671 | * $stack is the result of decompiling a scriptSig, or taking the witness data. |
||
672 | * |
||
673 | * @param ScriptInterface $script |
||
674 | * @param Checksig $checksig |
||
675 | * @param Stack $stack |
||
676 | * @param int $sigVersion |
||
677 | * @param bool $expectFalse |
||
678 | * @throws ScriptRuntimeException |
||
679 | * @throws SignerException |
||
680 | * @throws \Exception |
||
681 | */ |
||
682 | 108 | public function extractChecksig(ScriptInterface $script, Checksig $checksig, Stack $stack, int $sigVersion, bool $expectFalse) |
|
823 | |||
824 | /** |
||
825 | * Pure function to produce a signature hash for a given $scriptCode, $sigHashType, $sigVersion. |
||
826 | * |
||
827 | * @param ScriptInterface $scriptCode |
||
828 | * @param int $sigHashType |
||
829 | * @param int $sigVersion |
||
830 | * @throws SignerException |
||
831 | * @return BufferInterface |
||
832 | */ |
||
833 | 96 | public function calculateSigHashUnsafe(ScriptInterface $scriptCode, int $sigHashType, int $sigVersion): BufferInterface |
|
841 | |||
842 | /** |
||
843 | * Calculates the signature hash for the input for the given $sigHashType. |
||
844 | * |
||
845 | * @param int $sigHashType |
||
846 | * @return BufferInterface |
||
847 | * @throws SignerException |
||
848 | */ |
||
849 | 1 | public function getSigHash(int $sigHashType): BufferInterface |
|
853 | |||
854 | /** |
||
855 | * Pure function to produce a signature for a given $key, $scriptCode, $sigHashType, $sigVersion. |
||
856 | * |
||
857 | * @param PrivateKeyInterface $key |
||
858 | * @param ScriptInterface $scriptCode |
||
859 | * @param int $sigHashType |
||
860 | * @param int $sigVersion |
||
861 | * @return TransactionSignatureInterface |
||
862 | * @throws SignerException |
||
863 | */ |
||
864 | 95 | private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, int $sigHashType, int $sigVersion) |
|
869 | |||
870 | /** |
||
871 | * Returns whether all required signatures have been provided. |
||
872 | * |
||
873 | * @return bool |
||
874 | */ |
||
875 | 62 | public function isFullySigned(): bool |
|
891 | |||
892 | /** |
||
893 | * Returns the required number of signatures for this input. |
||
894 | * |
||
895 | * @return int |
||
896 | */ |
||
897 | 62 | public function getRequiredSigs(): int |
|
907 | |||
908 | /** |
||
909 | * Returns an array where the values are either null, |
||
910 | * or a TransactionSignatureInterface. |
||
911 | * |
||
912 | * @return TransactionSignatureInterface[] |
||
913 | */ |
||
914 | 62 | public function getSignatures(): array |
|
918 | |||
919 | /** |
||
920 | * Returns an array where the values are either null, |
||
921 | * or a PublicKeyInterface. |
||
922 | * |
||
923 | * @return PublicKeyInterface[] |
||
924 | */ |
||
925 | 51 | public function getPublicKeys(): array |
|
929 | |||
930 | /** |
||
931 | * Returns a FullyQualifiedScript since we |
||
932 | * have solved all scripts to do with this input |
||
933 | * |
||
934 | * @return FullyQualifiedScript |
||
935 | */ |
||
936 | 50 | public function getInputScripts(): FullyQualifiedScript |
|
940 | |||
941 | /** |
||
942 | * @param int $stepIdx |
||
943 | * @param PrivateKeyInterface $privateKey |
||
944 | * @param int $sigHashType |
||
945 | * @return $this |
||
946 | * @throws SignerException |
||
947 | */ |
||
948 | 101 | public function signStep(int $stepIdx, PrivateKeyInterface $privateKey, int $sigHashType = SigHash::ALL) |
|
1012 | |||
1013 | /** |
||
1014 | * Sign the input using $key and $sigHashTypes |
||
1015 | * |
||
1016 | * @param PrivateKeyInterface $privateKey |
||
1017 | * @param int $sigHashType |
||
1018 | * @return $this |
||
1019 | * @throws SignerException |
||
1020 | */ |
||
1021 | 78 | public function sign(PrivateKeyInterface $privateKey, int $sigHashType = SigHash::ALL) |
|
1025 | |||
1026 | /** |
||
1027 | * Verifies the input using $flags for script verification |
||
1028 | * |
||
1029 | * @param int $flags |
||
1030 | * @return bool |
||
1031 | */ |
||
1032 | 79 | public function verify(int $flags = null): bool |
|
1066 | |||
1067 | /** |
||
1068 | * @return Stack |
||
1069 | */ |
||
1070 | 95 | private function serializeSteps(): Stack |
|
1102 | |||
1103 | /** |
||
1104 | * Produces a SigValues instance containing the scriptSig & script witness |
||
1105 | * |
||
1106 | * @return SigValues |
||
1107 | */ |
||
1108 | 95 | public function serializeSignatures(): SigValues |
|
1112 | |||
1113 | /** |
||
1114 | * @return Checksig[]|Conditional[]|mixed |
||
1115 | */ |
||
1116 | 50 | public function getSteps() |
|
1120 | } |
||
1121 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.
Either this assignment is in error or an instanceof check should be added for that assignment.