Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like BitcoinECDSA 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 BitcoinECDSA, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
14 | class BitcoinECDSA |
||
15 | { |
||
16 | |||
17 | public $k; |
||
18 | public $a; |
||
19 | public $b; |
||
20 | public $p; |
||
21 | public $n; |
||
22 | public $G; |
||
23 | public $networkPrefix; |
||
24 | |||
25 | public function __construct() |
||
39 | |||
40 | /*** |
||
41 | * Convert a number to a compact Int |
||
42 | * taken from https://github.com/scintill/php-bitcoin-signature-routines/blob/master/verifymessage.php |
||
43 | * |
||
44 | * @param int $i |
||
45 | * @return string (bin) |
||
46 | * @throws \Exception |
||
47 | */ |
||
48 | public function numToVarIntString($i) { |
||
59 | |||
60 | /*** |
||
61 | * Set the network prefix, '00' = main network, '6f' = test network. |
||
62 | * |
||
63 | * @param string $prefix (hexa) |
||
64 | */ |
||
65 | public function setNetworkPrefix($prefix) |
||
69 | |||
70 | /** |
||
71 | * Returns the current network prefix, '00' = main network, '6f' = test network. |
||
72 | * |
||
73 | * @return string (hexa) |
||
74 | */ |
||
75 | public function getNetworkPrefix() |
||
79 | |||
80 | /** |
||
81 | * Returns the current network prefix for WIF, '80' = main network, 'ef' = test network. |
||
82 | * |
||
83 | * @return string (hexa) |
||
84 | */ |
||
85 | public function getPrivatePrefix(){ |
||
91 | |||
92 | /*** |
||
93 | * Permutation table used for Base58 encoding and decoding. |
||
94 | * |
||
95 | * @param string $char |
||
96 | * @param bool $reverse |
||
97 | * @return string|null |
||
98 | */ |
||
99 | public function base58_permutation($char, $reverse = false) |
||
127 | |||
128 | /*** |
||
129 | * Bitcoin standard 256 bit hash function : double sha256 |
||
130 | * |
||
131 | * @param string $data |
||
132 | * @return string (hexa) |
||
133 | */ |
||
134 | public function hash256($data) |
||
138 | |||
139 | /** |
||
140 | * @param string $data |
||
141 | * @return string (hexa) |
||
142 | */ |
||
143 | public function hash160($data) |
||
147 | |||
148 | /** |
||
149 | * Generates a random 256 bytes hexadecimal encoded string that is smaller than n |
||
150 | * |
||
151 | * @param string $extra |
||
152 | * @return string (hexa) |
||
153 | * @throws \Exception |
||
154 | */ |
||
155 | public function generateRandom256BitsHexaString($extra = 'FkejkzqesrfeifH3ioio9hb55sdssdsdfOO:ss') |
||
172 | |||
173 | /*** |
||
174 | * encode a hexadecimal string in Base58. |
||
175 | * |
||
176 | * @param string $data (hexa) |
||
177 | * @param bool $littleEndian |
||
178 | * @return string (base58) |
||
179 | * @throws \Exception |
||
180 | */ |
||
181 | public function base58_encode($data, $littleEndian = true) |
||
214 | |||
215 | /*** |
||
216 | * Decode a Base58 encoded string and returns it's value as a hexadecimal string |
||
217 | * |
||
218 | * @param string $encodedData (base58) |
||
219 | * @param bool $littleEndian |
||
220 | * @return string (hexa) |
||
221 | */ |
||
222 | public function base58_decode($encodedData, $littleEndian = true) |
||
257 | |||
258 | /*** |
||
259 | * Computes the result of a point addition and returns the resulting point as an Array. |
||
260 | * |
||
261 | * @param Array $pt |
||
262 | * @return Array Point |
||
263 | * @throws \Exception |
||
264 | */ |
||
265 | public function doublePoint(Array $pt) |
||
332 | |||
333 | /*** |
||
334 | * Computes the result of a point addition and returns the resulting point as an Array. |
||
335 | * |
||
336 | * @param Array $pt1 |
||
337 | * @param Array $pt2 |
||
338 | * @return Array Point |
||
339 | * @throws \Exception |
||
340 | */ |
||
341 | public function addPoints(Array $pt1, Array $pt2) |
||
404 | |||
405 | /*** |
||
406 | * Computes the result of a point multiplication and returns the resulting point as an Array. |
||
407 | * |
||
408 | * @param string|resource $k (hexa|GMP|Other bases definded in base) |
||
409 | * @param Array $pG |
||
410 | * @param $base |
||
411 | * @throws \Exception |
||
412 | * @return Array Point |
||
413 | */ |
||
414 | public function mulPoint($k, Array $pG, $base = null) |
||
440 | |||
441 | /*** |
||
442 | * Calculates the square root of $a mod p and returns the 2 solutions as an array. |
||
443 | * |
||
444 | * @param resource $a (GMP) |
||
445 | * @return array|null |
||
446 | * @throws \Exception |
||
447 | */ |
||
448 | public function sqrt($a) |
||
479 | |||
480 | /*** |
||
481 | * Calculate the Y coordinates for a given X coordinate. |
||
482 | * |
||
483 | * @param string $x (hexa) |
||
484 | * @param null $derEvenOrOddCode |
||
485 | * @return array|null|String |
||
486 | */ |
||
487 | public function calculateYWithX($x, $derEvenOrOddCode = null) |
||
552 | |||
553 | /*** |
||
554 | * returns the public key coordinates as an array. |
||
555 | * |
||
556 | * @param string $derPubKey (hexa) |
||
557 | * @return array |
||
558 | * @throws \Exception |
||
559 | */ |
||
560 | public function getPubKeyPointsWithDerPubKey($derPubKey) |
||
581 | |||
582 | |||
583 | /** |
||
584 | * @param array $pubKey (array <x:string, y:string>) |
||
585 | * @param bool $compressed |
||
586 | * @return string |
||
587 | */ |
||
588 | public function getDerPubKeyWithPubKeyPoints($pubKey, $compressed = true) |
||
604 | |||
605 | /*** |
||
606 | * Returns true if the point is on the curve and false if it isn't. |
||
607 | * |
||
608 | * @param string $x (hexa) |
||
609 | * @param string $y (hexa) |
||
610 | * @return bool |
||
611 | */ |
||
612 | public function validatePoint($x, $y) |
||
636 | |||
637 | /*** |
||
638 | * returns the X and Y point coordinates of the public key. |
||
639 | * |
||
640 | * @return Array Point |
||
641 | * @throws \Exception |
||
642 | */ |
||
643 | public function getPubKeyPoints() |
||
673 | |||
674 | /*** |
||
675 | * returns the uncompressed DER encoded public key. |
||
676 | * |
||
677 | * @param array $pubKeyPts (array <x:string, y:string>) |
||
678 | * @return string (hexa) |
||
679 | * @throws \Exception |
||
680 | */ |
||
681 | public function getUncompressedPubKey(array $pubKeyPts = []) |
||
689 | |||
690 | /*** |
||
691 | * returns the compressed DER encoded public key. |
||
692 | * |
||
693 | * @param array $pubKeyPts (array <x:string, y:string>) |
||
694 | * @return array|string |
||
695 | * @throws \Exception |
||
696 | */ |
||
697 | public function getPubKey(array $pubKeyPts = []) |
||
709 | |||
710 | /*** |
||
711 | * returns the uncompressed Bitcoin address generated from the private key if $compressed is false and |
||
712 | * the compressed if $compressed is true. |
||
713 | * |
||
714 | * @param bool $compressed |
||
715 | * @param string $derPubKey (hexa) |
||
716 | * @throws \Exception |
||
717 | * @return String Base58 |
||
718 | */ |
||
719 | public function getUncompressedAddress($compressed = false, $derPubKey = null) |
||
751 | |||
752 | /*** |
||
753 | * returns the compressed Bitcoin address generated from the private key. |
||
754 | * |
||
755 | * @param string $derPubKey (hexa) |
||
756 | * @return String (base58) |
||
757 | */ |
||
758 | public function getAddress($derPubKey = null) |
||
762 | |||
763 | /*** |
||
764 | * set a private key. |
||
765 | * |
||
766 | * @param string $k (hexa) |
||
767 | * @throws \Exception |
||
768 | */ |
||
769 | public function setPrivateKey($k) |
||
778 | |||
779 | /*** |
||
780 | * return the private key. |
||
781 | * |
||
782 | * @return string (hexa) |
||
783 | */ |
||
784 | public function getPrivateKey() |
||
788 | |||
789 | |||
790 | /*** |
||
791 | * Generate a new random private key. |
||
792 | * The extra parameter can be some random data typed down by the user or mouse movements to add randomness. |
||
793 | * |
||
794 | * @param string $extra |
||
795 | * @throws \Exception |
||
796 | */ |
||
797 | public function generateRandomPrivateKey($extra = 'FSQF5356dsdsqdfEFEQ3fq4q6dq4s5d') |
||
801 | |||
802 | /*** |
||
803 | * Tests if the address is valid or not. |
||
804 | * |
||
805 | * @param string $address (base58) |
||
806 | * @return bool |
||
807 | */ |
||
808 | public function validateAddress($address) |
||
821 | |||
822 | /*** |
||
823 | * returns the private key under the Wallet Import Format |
||
824 | * |
||
825 | * @return string (base58) |
||
826 | * @throws \Exception |
||
827 | */ |
||
828 | public function getWif($compressed = true) |
||
850 | |||
851 | /*** |
||
852 | * returns the private key under the Wallet Import Format for an uncompressed address |
||
853 | * |
||
854 | * @return string (base58) |
||
855 | * @throws \Exception |
||
856 | */ |
||
857 | public function getUncompressedWif() |
||
861 | |||
862 | /*** |
||
863 | * Tests if the Wif key (Wallet Import Format) is valid or not. |
||
864 | * |
||
865 | * @param string $wif (base58) |
||
866 | * @return bool |
||
867 | */ |
||
868 | public function validateWifKey($wif) |
||
878 | |||
879 | /** |
||
880 | * @param string $wif (base58) |
||
881 | * @return bool |
||
882 | */ |
||
883 | public function setPrivateKeyWithWif($wif) |
||
893 | |||
894 | /*** |
||
895 | * Sign a hash with the private key that was set and returns signatures as an array (R,S) |
||
896 | * |
||
897 | * @param string $hash (hexa) |
||
898 | * @param null $nonce |
||
899 | * @throws \Exception |
||
900 | * @return Array |
||
901 | */ |
||
902 | public function getSignatureHashPoints($hash, $nonce = null) |
||
973 | |||
974 | /*** |
||
975 | * Sign a hash with the private key that was set and returns a DER encoded signature |
||
976 | * |
||
977 | * @param string $hash (hexa) |
||
978 | * @param null $nonce |
||
979 | * @return string |
||
980 | */ |
||
981 | public function signHash($hash, $nonce = null) |
||
990 | |||
991 | /*** |
||
992 | * Satoshi client's standard message signature implementation. |
||
993 | * |
||
994 | * @param string $message |
||
995 | * @param bool $onlySignature |
||
996 | * @param bool $compressed |
||
997 | * @param null $nonce |
||
998 | * @return string |
||
999 | * @throws \Exception |
||
1000 | */ |
||
1001 | public function signMessage($message, $onlySignature = false ,$compressed = true, $nonce = null) |
||
1062 | |||
1063 | /*** |
||
1064 | * extract the public key from the signature and using the recovery flag. |
||
1065 | * see http://crypto.stackexchange.com/a/18106/10927 |
||
1066 | * based on https://github.com/brainwallet/brainwallet.github.io/blob/master/js/bitcoinsig.js |
||
1067 | * possible public keys are r−1(sR−zG) and r−1(sR′−zG) |
||
1068 | * Recovery flag rules are : |
||
1069 | * binary number between 28 and 35 inclusive |
||
1070 | * if the flag is > 30 then the address is compressed. |
||
1071 | * |
||
1072 | * @param int $flag |
||
1073 | * @param string $R (hexa) |
||
1074 | * @param string $S (hexa) |
||
1075 | * @param string $hash (hexa) |
||
1076 | * @return array |
||
1077 | */ |
||
1078 | public function getPubKeyWithRS($flag, $R, $S, $hash) |
||
1161 | |||
1162 | /*** |
||
1163 | * Check signature with public key R & S values of the signature and the message hash. |
||
1164 | * |
||
1165 | * @param string $pubKey (hexa) |
||
1166 | * @param string $R (hexa) |
||
1167 | * @param string $S (hexa) |
||
1168 | * @param string $hash (hexa) |
||
1169 | * @return bool |
||
1170 | */ |
||
1171 | public function checkSignaturePoints($pubKey, $R, $S, $hash) |
||
1225 | |||
1226 | /*** |
||
1227 | * checkSignaturePoints wrapper for DER signatures |
||
1228 | * |
||
1229 | * @param string $pubKey (hexa) |
||
1230 | * @param string $signature (hexa) |
||
1231 | * @param string $hash (hexa) |
||
1232 | * @return bool |
||
1233 | */ |
||
1234 | public function checkDerSignature($pubKey, $signature, $hash) |
||
1256 | |||
1257 | /*** |
||
1258 | * checks the signature of a bitcoin signed message. |
||
1259 | * |
||
1260 | * @param string $rawMessage |
||
1261 | * @return bool |
||
1262 | */ |
||
1263 | public function checkSignatureForRawMessage($rawMessage) |
||
1275 | |||
1276 | /*** |
||
1277 | * checks the signature of a bitcoin signed message. |
||
1278 | * |
||
1279 | * @param string $address (base58) |
||
1280 | * @param string $encodedSignature (base64) |
||
1281 | * @param string $message |
||
1282 | * @return bool |
||
1283 | */ |
||
1284 | public function checkSignatureForMessage($address, $encodedSignature, $message) |
||
1314 | } |
||
1315 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.