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.