In diesem Artikel stelle ich Euch ein kleines Programm vor, welches Euch detailliert die drei Schritte für eine Bitcoin-Adress-Erzeugung erläutert.
In Schritt 1 wird ein Elliptisches Kurvenpaar („Elliptic curve keypair“) oder kurz ECKey erzeugt.
Die Umwandlung des privaten Schlüssels in das Base58- oder auf Wallet Import Format („WIF) zeige ich im Schritt 2.
Der letzte Schritt 3 zeigt Euch, wie aus dem öffentlichen Schlüssel die öffentlich nutzbare Adresse wird. Das Programm kann die drei Schritte sowohl für das „richtige“ Main Netzwerk als auch für das Test Netzwerk durchführen, die Umschaltung erfolgt durch die Boolean-Variable „testnetwork“. Steht hier ein „true“ werden die Werte für das Test Netzwerk errechnet, ein „false“ erzeugt Daten für das Main Netzwerk.
Zum Programm selber: neben der Main-Klasse nutze ich drei Java-Klassen, die aber nicht von mir stammen:
Base58NotBitcoinJ.java: dient der Erzeugung von Base58-String, die Klasse stammt von https://gist.github.com/vrotaru/1753908.
Ripemd160.java: erzeugt RipeMd160-Hashwerte, die Klasse stammt von https://github.com/nayuki/Bitcoin-Cryptography-Library
TeePrintStream.java: die Routine ermöglicht es, die Ausgabe auf die Konsole zusätzlich in eine Textdatei zu schreiben. Da Ihr später die erzeugten Schlüssel in anderen Programmen oder Webservices nutzen wollt ist es extrem nützlich, diese Werte in einer kopierbaren Textdatei vorzuhalten. Die Klasse stammt von https://github.com/oreillymedia/java_cookbook_3e/blob/master/javacooksrc/javacooksrc/main/java/io/TeePrintStream.java.
Damit Ihr die Schritte und insbesondere die Unterschritte versteht, gibt das Programm auch jeden Zwischenschritt aus. Das Programm folgt hinsichtlich der Umwandlung des Private Keys (im Byte Array-Format) in das WIF-Format (Schritt 2) der Anleitung unter https://en.bitcoin.it/wiki/Wallet_import_format. Die Umwandlung des öffentlichen Schlüssels (ebenfalls im Byte Array Format) in das WIF-Format (Schritt 3) folgt den Schritten in https://www.novixys.com/blog/generate-bitcoin-addresses-java. Bitte beachtet dazu auch die Hinweise in den Kommentaren zu diesem Artikel, ansonsten klappt das Programm nicht mit dem Sourcecode auf der Webseite (mein Code funktioniert !).
Bevor wir uns die Quellcodes und Konsolenausgaben anschauen: Ihr könnt die Richtigkeit der Berechnungen für das Mainnet über 2 Webservices überprüfen (http://gobittest.appspot.com/PrivateKey und http://gobittest.appspot.com/Address). In meinem Artikel http://java-crypto.bplaced.net/bitcoin-erzeugung-eigener-schluessel-und-adressen-ueberpruefung/ zeige ich Euch detailliert die Überprüfung für das Testnetz.
Falls Ihr Euch fragen solltet „warum wird wieder eine neue Kodierung namens Base58 benutzt ?“ zeige ich Euch das klassische Oo0Ii1lL-Beispiel. Die Zeichenkette besteht aus diesen Buchstaben und Ziffern [buchstabiert]: grosses Otto, kleines Otto, Null, grosses Ida, kleines Ida, Eins, kleines Ludwig, grosses Ludwig. Konntet Ihr das so auch lesen? Ich vermute mal nein und damit sollte klar sein, das bestimmte Buchstaben und Zahlen je nach Zeichensatz kaum unterscheidbar sind. Wenn nun z.B. auf einem Papierbasierten Wallet die obige Kombination (rein zufällig) auftaucht werdet Ihr später große Schwierigkeiten haben, Euren privaten Schlüssel richtig einzugeben. Die Base58-Kodierung vermeidet solche kritischen Buchstaben und Zahlen komplett und macht damit die Kryptographie wieder ein bischen besser und sicherer.
Hier nun die Quellcodes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
/* * Herkunft/Origin: http://java-crypto.bplaced.net/ * Programmierer/Programmer: Michael Fehr * Copyright/Copyright: verschiedene Copyrights / different copyrights * Lizenttext/Licence: verschiedene Lizenzen / different licences * getestet mit/tested with: Java Runtime Environment 11.0.5 x64 * verwendete IDE/used IDE: intelliJ IDEA 2019.3.1 * Datum/Date (dd.mm.jjjj): 22.02.2020 * Funktion: Bitcoin Erzeugung von Schluesseln und Adressen * Function: Bitcoin Generate Keys and Addresses * * Hinweis/Notice * Sie benoetigen diese externen Klassen / you need these external classes: * TeePrintStream.java https://github.com/oreillymedia/java_cookbook_3e/blob/master/javacooksrc/javacooksrc/main/java/io/TeePrintStream.java * Base58NotBitcoinJ https://gist.github.com/vrotaru/1753908 * Ripe1md160 https://github.com/nayuki/Bitcoin-Cryptography-Library/blob/master/java/io/nayuki/bitcoin/crypto/Ripemd160.java */ import java.io.IOException; import java.security.*; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECPoint; public class BitcoinGenerateKeysAndAddresses { public static void main(String[] args) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, IOException { boolean testnetwork = true; // for mainnetwork false, for testnetwork true TeePrintStream ts = new TeePrintStream(System.out, "Bitcoin manuelle Erzeugung von Schluesseln und Adressen.txt", true); System.setOut(ts); System.out.println("Bitcoin manuelle Erzeugung von Schluesseln und Adressen"); // 1 erzeuge ein schlüsselpaar System.out.println("\nSchritt 1: Erzeugung eines Schluesselpaares"); if (testnetwork == true) { System.out.println("Erzeugte Schluessel sind fuer das Bitcoin Test Network"); } else { System.out.println("Erzeugte Schluessel sind fuer das Bitcoin Main Network"); } String eccCurvenameString = "secp256k1"; // curve for bitcoin KeyPairGenerator keypairGenerator = KeyPairGenerator.getInstance("EC", "SunEC"); ECGenParameterSpec ecSpec = new ECGenParameterSpec(eccCurvenameString); keypairGenerator.initialize(ecSpec, new SecureRandom()); KeyPair ecKey = keypairGenerator.genKeyPair(); System.out.println("EC-Keypair:" + ecKey.getPrivate().getAlgorithm()); System.out.println("EC Private Key:" + "Laenge: " + ecKey.getPrivate().getEncoded().length + " Key:\n" + byteArrayPrint(ecKey.getPrivate().getEncoded(), 32)); System.out.println("EC Public Key:" + "Laenge: " + ecKey.getPublic().getEncoded().length + " Key:\n" + byteArrayPrint(ecKey.getPublic().getEncoded(), 32)); //System.out.println("EC Public Key:" + printHexBinary(ecKey.getPublic().getEncoded())); System.out.println("EC Public Key:" + ecKey.getPublic().toString()); PublicKey publicKey = ecKey.getPublic(); PrivateKey privateKey = ecKey.getPrivate(); // 2 erzeuge die private key adresse System.out.println("Der Private Key in voller Laenge (vollstaendig) und den letzten 32 Byte (gekuerzt)"); String ecPrivateKeyFullString = bytesToHex(privateKey.getEncoded()); System.out.println("Private Key vollstaendig:" + ecPrivateKeyFullString); String ecPrivateKeyString = ecPrivateKeyFullString.substring(64, 128); System.out.println("Private Key gekuerzt :" + ecPrivateKeyString); System.out.println("\nSchritt 2: Umwandlung des Private Keys in das Base58- bzw. WIF-Format"); System.out.println("Die einzelnen Schritte folgen dem Artikel https://en.bitcoin.it/wiki/Wallet_import_format"); // testvectors for bitcoin main and testnet // // https://walletgenerator.net/?currency=Bitcoin# // testnet: testnetwork = true // String privateKeyAddressWif = generatePrivateKeyAddress(testnetwork, "F93FECB8F617ABE291BE83300DBB2D0EDCB1CBB3F0CB5E502CEAFF6AB5F868F5"); // result in wif uncompressed format: 93UgusbzSkSUbUk4N9T7eHdU2H7AsqvpoFY9b5FCwcMGmLkdwkH // // mainnet: testnetwork = false // String privateKeyAddressWif = generatePrivateKeyAddress(testnetwork, "0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D"); // result in wif uncompressed format: 5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ // String privateKeyAddressWif = generatePrivateKeyAddress(testnetwork, ecPrivateKeyString); System.out.println("\nZusammenfassung von Schritt 2"); if (testnetwork == true) { System.out.println("Erzeugte Schluessel sind fuer das Bitcoin Test Network"); } else { System.out.println("Erzeugte Schluessel sind fuer das Bitcoin Main Network"); } System.out.println("Private Key im 32 Byte Format: " + ecPrivateKeyString); System.out.println("Private Key im WIF-Format : " + privateKeyAddressWif); // https://en.bitcoin.it/wiki/Wallet_import_format // http://gobittest.appspot.com/PrivateKey // https://bitcoin.stackexchange.com/questions/63949/java-way-to-convert-a-256-bit-private-key-to-wif System.out.println("\nSchritt 3: Oeffentliche Adresse des Wallets"); System.out.println("Die einzelnen Schritte folgen dem Artikel https://www.novixys.com/blog/generate-bitcoin-addresses-java"); String publicKeyAddressWif = generatePublicKeyAddress(testnetwork, publicKey); System.out.println("\nZusammenfassung von Schritt 3"); if (testnetwork == true) { System.out.println("Erzeugte Schluessel sind fuer das Bitcoin Test Network"); } else { System.out.println("Erzeugte Schluessel sind fuer das Bitcoin Main Network"); } System.out.println("Public Key im 32 Byte Format: " + bytesToHex(publicKey.getEncoded())); System.out.println("Public Key im WIF-Format : " + publicKeyAddressWif); System.out.println("\n* * * Zusammenfassung aller Schluessel und Adressen * * *"); if (testnetwork == true) { System.out.println("Erzeugte Schluessel sind fuer das Bitcoin Test Network"); } else { System.out.println("Erzeugte Schluessel sind fuer das Bitcoin Main Network"); } System.out.println("EC Private Key im Format :" + ecPrivateKeyString); System.out.println("EC Private Key im WIF-Format:" + privateKeyAddressWif); System.out.println("EC Public Key im WIF-Format :" + publicKeyAddressWif); System.out.println("\nDie korrekte Umwandlung fuer das Main Network kann ueber diese beiden Services ueberprueft werden:"); System.out.println("Private Key im WIF-Format : http://gobittest.appspot.com/PrivateKey"); System.out.println("Oeffentliche Adresse im WIF-Format: http://gobittest.appspot.com/Address"); System.out.println(""); // close TeePrintStream ts.close(); } public static String generatePrivateKeyAddress(boolean testnetwork, String privateKeyString) throws NoSuchAlgorithmException { // code: https://en.bitcoin.it/wiki/Wallet_import_format // 2 - Add a 0x80 byte in front of it for mainnet addresses or 0xef for testnet addresses. Also add a 0x01 byte at the end if the private key will correspond to a compressed public key // // 800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D System.out.println("\n2 - Add a 0x80 byte in front of it for mainnet addresses or 0xef for testnet addresses."); String step2; if (testnetwork == true) { step2 = "EF" + privateKeyString; } else { step2 = "80" + privateKeyString; } System.out.println("Step 2 Extended Private Key:" + step2); // 3 - Perform SHA-256 hash on the extended key // // 8147786C4D15106333BF278D71DADAF1079EF2D2440A4DDE37D747DED5403592 System.out.println("\n3 - Perform SHA-256 hash on the extended key"); byte[] data = hexStringToByteArray(step2); byte[] digest = MessageDigest.getInstance("SHA-256").digest(data); //byte[] digest = MessageDigest.getInstance("SHA-256").digest(hexStringToByteArray(privateKeyStringStep2)); String step3 = bytesToHex(digest); System.out.println("Step 3 SHA256:" + step3); // 4 - Perform SHA-256 hash on result of SHA-256 hash // // 507A5B8DFED0FC6FE8801743720CEDEC06AA5C6FCA72B07C49964492FB98A714 System.out.println("\n4 - Perform SHA-256 hash on result of SHA-256 hash"); byte[] digest2 = MessageDigest.getInstance("SHA-256").digest(digest); //byte[] digest2 = MessageDigest.getInstance("SHA-256").digest(hexStringToByteArray(result)); String step4 = bytesToHex(digest2); System.out.println("Step 4 SHA256:" + step4); // 5 - Take the first 4 bytes of the second SHA-256 hash, this is the checksum // // 507A5B8D System.out.println("\n5 - Take the first 4 bytes of the second SHA-256 hash, this is the checksum"); String step5 = step4.substring(0, 8); System.out.println("Step 5 First 4 Bytes from Step 4:" + step5); // 6 - Add the 4 checksum bytes from point 5 at the end of the extended key from point 2 // // 800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D507A5B8D System.out.println("\n6 - Add the 4 checksum bytes from point 5 at the end of the extended key from point 2"); String step6 = step2 + step5; System.out.println("Step 6 complete String:" + step6); // 7 - Convert the result from a byte string into a base58 string using Base58Check encoding. This is the Wallet Import Format // Link: https://en.bitcoin.it/wiki/Base58Check_encoding // 5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ System.out.println("\n7 - Convert the result from a byte string into a base58 string"); byte[] step6ByteArray = hexStringToByteArray(step6); //String step7String = Base58.encode(step6ByteArray); String step7 = Base58NotBitcoinJ.encode(step6ByteArray); System.out.println("Step 7 Base58-String:" + step7); System.out.println("Step 7 Base58-String:" + "93UgusbzSkSUbUk4N9T7eHdU2H7AsqvpoFY9b5FCwcMGmLkdwkH" + " walletgenerator.net"); return step7; } public static String generatePublicKeyAddress(boolean testnetwork, PublicKey publicKey) throws NoSuchAlgorithmException { ECPublicKey epub = (ECPublicKey) publicKey; // routinen von https://www.novixys.com/blog/generate-bitcoin-addresses-java/ ECPoint pt = epub.getW(); String sx = adjustTo64(pt.getAffineX().toString(16)).toUpperCase(); String sy = adjustTo64(pt.getAffineY().toString(16)).toUpperCase(); String bcPub = "04" + sx + sy; System.out.println("\nbcPub: " + bcPub + " Length:" + bcPub.length()); MessageDigest sha = MessageDigest.getInstance("SHA-256"); byte[] s1 = sha.digest(hexStringToByteArray(bcPub)); System.out.println("\n sha: " + bytesToHex(s1).toUpperCase()); // prints sha: 7524DC35AEB4B62A0F1C90425ADC6732A7C5DF51A72E8B90983629A7AEC656A0 byte[] r1 = Ripemd160.getHash(s1); System.out.println("\nr1:" + bytesToHex(r1) + " Length:" + r1.length); // mainnet = 0x00, testnet = 111 //Next we need to add a version byte of 0x00 at the beginning of the hash. byte[] r2 = new byte[r1.length + 1]; if (testnetwork == true) { r2[0] = 111; // testnet } else { r2[0] = 0; // mainnet } for (int i = 0; i < r1.length; i++) r2[i + 1] = r1[i]; System.out.println("\n rmd: " + bytesToHex(r2).toUpperCase()); // prints rmd: 00C5FAE41AB21FA56CFBAFA3AE7FB5784441D11CEC System.out.println("\nexp: " + "00C5FAE41AB21FA56CFBAFA3AE7FB5784441D11CEC"); // 6. Repeat the SHA-256 Hashing Twice // We now need to perform a SHA-256 hash twice on the result above. byte[] s2 = sha.digest(r2); System.out.println("\n sha: " + bytesToHex(s2).toUpperCase()); byte[] s3 = sha.digest(s2); System.out.println("\n sha: " + bytesToHex(s3).toUpperCase()); // The first 4 bytes of the result of the second hashing is used as the address checksum. It is appended to the RIPEMD160 hash above. This is the 25-byte bitcoin address. byte[] a1 = new byte[25]; for (int i = 0; i < r2.length; i++) a1[i] = r2[i]; for (int i = 0; i < 4; i++) a1[21 + i] = s3[i]; System.out.println("\n25 Byte Bitcoin Address:" + bytesToHex(a1) + " Länge:" + a1.length); // 7. Encode the Address Using Base58 // We now use the Base58.encode() method from the bitcoinj library to arrive at the final bitcoin address. //System.out.println(" adr: " + Base58.encode(a1)); System.out.println("\n adr: " + Base58NotBitcoinJ.encode(a1)); // This is the address to which the bitcoin should be sent to in a transaction. // test addres with http://gobittest.appspot.com/Address return Base58NotBitcoinJ.encode(a1); } // helper public static String byteArrayPrint(byte[] byteData, int numberPerRow) { String returnString = ""; String rawString = printHexBinary(byteData); int rawLength = rawString.length(); int i = 0; int j = 1; int z = 0; for (i = 0; i < rawLength; i++) { z++; returnString = returnString + rawString.charAt(i); if (j == 2) { returnString = returnString + " "; j = 0; } j++; if (z == (numberPerRow * 2)) { returnString = returnString + "\n"; z = 0; } } return returnString; } // diese routinen in java 11 an stelle von datatypeconverter.xxx nutzen public static String printHexBinary(byte[] bytes) { final char[] hexArray = "0123456789ABCDEF".toCharArray(); char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } private static String bytesToHex(byte[] bytes) { StringBuffer result = new StringBuffer(); for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1)); return result.toString(); } static private String adjustTo64(String s) { switch (s.length()) { case 62: return "00" + s; case 63: return "0" + s; case 64: return s; default: throw new IllegalArgumentException("not a valid key: " + s); } } public static byte[] hexStringToByteArray(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); } return data; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
// source: https://gist.github.com/vrotaru/1753908 public class Base58NotBitcoinJ { private static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" .toCharArray(); private static final int BASE_58 = ALPHABET.length; private static final int BASE_256 = 256; private static final int[] INDEXES = new int[128]; static { for (int i = 0; i < INDEXES.length; i++) { INDEXES[i] = -1; } for (int i = 0; i < ALPHABET.length; i++) { INDEXES[ALPHABET[i]] = i; } } public static String encode(byte[] input) { if (input.length == 0) { // paying with the same coin return ""; } // // Make a copy of the input since we are going to modify it. // input = copyOfRange(input, 0, input.length); // // Count leading zeroes // int zeroCount = 0; while (zeroCount < input.length && input[zeroCount] == 0) { ++zeroCount; } // // The actual encoding // byte[] temp = new byte[input.length * 2]; int j = temp.length; int startAt = zeroCount; while (startAt < input.length) { byte mod = divmod58(input, startAt); if (input[startAt] == 0) { ++startAt; } temp[--j] = (byte) ALPHABET[mod]; } // // Strip extra '1' if any // while (j < temp.length && temp[j] == ALPHABET[0]) { ++j; } // // Add as many leading '1' as there were leading zeros. // while (--zeroCount >= 0) { temp[--j] = (byte) ALPHABET[0]; } byte[] output = copyOfRange(temp, j, temp.length); return new String(output); } public static byte[] decode(String input) { if (input.length() == 0) { // paying with the same coin return new byte[0]; } byte[] input58 = new byte[input.length()]; // // Transform the String to a base58 byte sequence // for (int i = 0; i < input.length(); ++i) { char c = input.charAt(i); int digit58 = -1; if (c >= 0 && c < 128) { digit58 = INDEXES[c]; } if (digit58 < 0) { throw new RuntimeException("Not a Base58 input: " + input); } input58[i] = (byte) digit58; } // // Count leading zeroes // int zeroCount = 0; while (zeroCount < input58.length && input58[zeroCount] == 0) { ++zeroCount; } // // The encoding // byte[] temp = new byte[input.length()]; int j = temp.length; int startAt = zeroCount; while (startAt < input58.length) { byte mod = divmod256(input58, startAt); if (input58[startAt] == 0) { ++startAt; } temp[--j] = mod; } // // Do no add extra leading zeroes, move j to first non null byte. // while (j < temp.length && temp[j] == 0) { ++j; } return copyOfRange(temp, j - zeroCount, temp.length); } private static byte divmod58(byte[] number, int startAt) { int remainder = 0; for (int i = startAt; i < number.length; i++) { int digit256 = (int) number[i] & 0xFF; int temp = remainder * BASE_256 + digit256; number[i] = (byte) (temp / BASE_58); remainder = temp % BASE_58; } return (byte) remainder; } private static byte divmod256(byte[] number58, int startAt) { int remainder = 0; for (int i = startAt; i < number58.length; i++) { int digit58 = (int) number58[i] & 0xFF; int temp = remainder * BASE_58 + digit58; number58[i] = (byte) (temp / BASE_256); remainder = temp % BASE_256; } return (byte) remainder; } private static byte[] copyOfRange(byte[] source, int from, int to) { byte[] range = new byte[to - from]; System.arraycopy(source, from, range, 0, range.length); return range; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
/* * Bitcoin cryptography library * Copyright (c) Project Nayuki * * https://www.nayuki.io/page/bitcoin-cryptography-library * https://github.com/nayuki/Bitcoin-Cryptography-Library */ import java.util.Arrays; import java.util.Objects; import static java.lang.Integer.rotateLeft; /** * Computes the RIPEMD-160 hash of an array of bytes. Not instantiable. */ public final class Ripemd160 { private static final int BLOCK_LEN = 64; // In bytes /*---- Static functions ----*/ /** * Computes and returns a 20-byte (160-bit) hash of the specified binary message. * Each call will return a new byte array object instance. * @param msg the message to compute the hash of * @return a 20-byte array representing the message's RIPEMD-160 hash * @throws NullPointerException if the message is {@code null} */ public static byte[] getHash(byte[] msg) { // Compress whole message blocks Objects.requireNonNull(msg); int[] state = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0}; int off = msg.length / BLOCK_LEN * BLOCK_LEN; compress(state, msg, off); // Final blocks, padding, and length byte[] block = new byte[BLOCK_LEN]; System.arraycopy(msg, off, block, 0, msg.length - off); off = msg.length % block.length; block[off] = (byte)0x80; off++; if (off + 8 > block.length) { compress(state, block, block.length); Arrays.fill(block, (byte)0); } long len = (long)msg.length << 3; for (int i = 0; i < 8; i++) block[block.length - 8 + i] = (byte)(len >>> (i * 8)); compress(state, block, block.length); // Int32 array to bytes in little endian byte[] result = new byte[state.length * 4]; for (int i = 0; i < result.length; i++) result[i] = (byte)(state[i / 4] >>> (i % 4 * 8)); return result; } /*---- Private functions ----*/ private static void compress(int[] state, byte[] blocks, int len) { if (len % BLOCK_LEN != 0) throw new IllegalArgumentException(); for (int i = 0; i < len; i += BLOCK_LEN) { // Message schedule int[] schedule = new int[16]; for (int j = 0; j < BLOCK_LEN; j++) schedule[j / 4] |= (blocks[i + j] & 0xFF) << (j % 4 * 8); // The 80 rounds int al = state[0], ar = state[0]; int bl = state[1], br = state[1]; int cl = state[2], cr = state[2]; int dl = state[3], dr = state[3]; int el = state[4], er = state[4]; for (int j = 0; j < 80; j++) { int temp; temp = rotateLeft(al + f(j, bl, cl, dl) + schedule[RL[j]] + KL[j / 16], SL[j]) + el; al = el; el = dl; dl = rotateLeft(cl, 10); cl = bl; bl = temp; temp = rotateLeft(ar + f(79 - j, br, cr, dr) + schedule[RR[j]] + KR[j / 16], SR[j]) + er; ar = er; er = dr; dr = rotateLeft(cr, 10); cr = br; br = temp; } int temp = state[1] + cl + dr; state[1] = state[2] + dl + er; state[2] = state[3] + el + ar; state[3] = state[4] + al + br; state[4] = state[0] + bl + cr; state[0] = temp; } } private static int f(int i, int x, int y, int z) { assert 0 <= i && i < 80; if (i < 16) return x ^ y ^ z; if (i < 32) return (x & y) | (~x & z); if (i < 48) return (x | ~y) ^ z; if (i < 64) return (x & z) | (y & ~z); return x ^ (y | ~z); } /*---- Class constants ----*/ private static final int[] KL = {0x00000000, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E}; // Round constants for left line private static final int[] KR = {0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000}; // Round constants for right line private static final int[] RL = { // Message schedule for left line 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13}; private static final int[] RR = { // Message schedule for right line 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11}; private static final int[] SL = { // Left-rotation for left line 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6}; private static final int[] SR = { // Left-rotation for right line 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11}; /*---- Miscellaneous ----*/ private Ripemd160() {} // Not instantiable } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
// source: https://github.com/oreillymedia/java_cookbook_3e/blob/master/javacooksrc/javacooksrc/main/java/io/TeePrintStream.java import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; /** TeePrintStream tees all PrintStream operations into a file, rather * like the UNIX tee(1) command. It is a PrintStream subclass. The * expected usage would be something like the following: * <PRE> * ... * TeePrintStream ts = new TeePrintStream(System.err, "err.log"); * System.setErr(ts); * // ...lots of code that occasionally writes to System.err... * ts.close(); * ... * <PRE> * <P>I only override Constructors, the write(), check() and close() methods, * since any of the print() or println() methods must go through these. * Thanks to Svante Karlsson for help formulating this. * <br/> * Note: there is another way of doing this, using a FilterStream; * see the example at http://www.javaspecialists.eu/archive/Issue003.html * (written a year after the initial import of my version). * @author Ian F. Darwin, http://www.darwinsys.com/ */ // BEGIN main class TeePrintStream extends PrintStream { /** The original/direct print stream */ /** A simple test case. public static void main(String[] args) throws IOException { TeePrintStream ts = new TeePrintStream(System.err, "err.log", true); System.setErr(ts); System.err.println("An imitation error message"); ts.close(); } */ protected PrintStream parent; /** The filename we are tee-ing too, if known; * intended for use in future error reporting. */ protected String fileName; /** The name for when the input filename is not known */ private static final String UNKNOWN_NAME = "(opened Stream)"; /** Construct a TeePrintStream given an existing PrintStream, * an opened OutputStream, and a boolean to control auto-flush. * This is the main constructor, to which others delegate via "this". */ public TeePrintStream(PrintStream orig, OutputStream os, boolean flush) throws IOException { super(os, true); fileName = UNKNOWN_NAME; parent = orig; } /** Construct a TeePrintStream given an existing PrintStream and * an opened OutputStream. */ public TeePrintStream(PrintStream orig, OutputStream os) throws IOException { this(orig, os, true); } /* Construct a TeePrintStream given an existing Stream and a filename. */ public TeePrintStream(PrintStream os, String fn) throws IOException { this(os, fn, true); } /* Construct a TeePrintStream given an existing Stream, a filename, * and a boolean to control the flush operation. */ public TeePrintStream(PrintStream orig, String fn, boolean flush) throws IOException { this(orig, new FileOutputStream(fn), flush); fileName = fn; } /** Return true if either stream has an error. */ public boolean checkError() { return parent.checkError() || super.checkError(); } /** override write(). This is the actual "tee" operation. */ public void write(int x) { parent.write(x); // "write once; super.write(x); // write somewhere else." } /** override write(). This is the actual "tee" operation. */ public void write(byte[] x, int o, int l) { parent.write(x, o, l); // "write once; super.write(x, o, l); // write somewhere else." } /** Close both streams. */ public void close() { parent.close(); super.close(); } /** Flush both streams. */ public void flush() { parent.flush(); super.flush(); } } // END main |
Hier die Ausgabe auf der Konsole (Eure Werte werden unterschiedlich sein, da das jeweilige Schlüsselpaar bei jedem Lauf neu erzeugt wird):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
Bitcoin manuelle Erzeugung von Schluesseln und Adressen Schritt 1: Erzeugung eines Schluesselpaares Erzeugte Schluessel sind fuer das Bitcoin Test Network EC-Keypair:EC EC Private Key:Laenge: 64 Key: 30 3E 02 01 00 30 10 06 07 2A 86 48 CE 3D 02 01 06 05 2B 81 04 00 0A 04 27 30 25 02 01 01 04 20 1C 25 19 05 CD E6 E4 CC D0 55 A0 0B 4D BB B2 C6 8C 7E DC BA 67 20 E1 54 93 12 82 4D C0 50 AC 2B EC Public Key:Laenge: 88 Key: 30 56 30 10 06 07 2A 86 48 CE 3D 02 01 06 05 2B 81 04 00 0A 03 42 00 04 03 1E B7 65 C1 C1 B2 AA CE 0A 8B D7 A8 C8 B3 56 43 14 95 E2 2D 3C E1 0A 72 3B 8A F5 0F 8A 5C DE 1E DF 46 60 3E 6A 8D 8F FB 6F C5 B3 B4 06 46 3F 02 84 33 0D 10 AC 8D 94 51 D7 63 67 F2 81 90 01 EC Public Key:Sun EC public key, 256 bits public x coord: 1411209720634181744448765001188814275284149220389351390666392761381044247774 public y coord: 13963878069915922178897894831646609797119273110760406486775323411398048976897 parameters: secp256k1 (1.3.132.0.10) Der Private Key in voller Laenge (vollstaendig) und den letzten 32 Byte (gekuerzt) Private Key vollstaendig:303e020100301006072a8648ce3d020106052b8104000a0427302502010104201c251905cde6e4ccd055a00b4dbbb2c68c7edcba6720e1549312824dc050ac2b Private Key gekuerzt :1c251905cde6e4ccd055a00b4dbbb2c68c7edcba6720e1549312824dc050ac2b Schritt 2: Umwandlung des Private Keys in das Base58- bzw. WIF-Format Die einzelnen Schritte folgen dem Artikel https://en.bitcoin.it/wiki/Wallet_import_format 2 - Add a 0x80 byte in front of it for mainnet addresses or 0xef for testnet addresses. Step 2 Extended Private Key:EF1c251905cde6e4ccd055a00b4dbbb2c68c7edcba6720e1549312824dc050ac2b 3 - Perform SHA-256 hash on the extended key Step 3 SHA256:5435c672a6fd138025ebf2bb89303fb1b5907ec6a56373aafe9ea13915e461fd 4 - Perform SHA-256 hash on result of SHA-256 hash Step 4 SHA256:a57cc2c4c65ed688b0c3743dce36865e2559f7d0a5f4caf44b83e9ffe61af49f 5 - Take the first 4 bytes of the second SHA-256 hash, this is the checksum Step 5 First 4 Bytes from Step 4:a57cc2c4 6 - Add the 4 checksum bytes from point 5 at the end of the extended key from point 2 Step 6 complete String:EF1c251905cde6e4ccd055a00b4dbbb2c68c7edcba6720e1549312824dc050ac2ba57cc2c4 7 - Convert the result from a byte string into a base58 string Step 7 Base58-String:91oK61hw9ABiKWqfLqWLEV69usmbeGw2gc7RUTp1ZpXhxQ6Ho71 Step 7 Base58-String:93UgusbzSkSUbUk4N9T7eHdU2H7AsqvpoFY9b5FCwcMGmLkdwkH walletgenerator.net Zusammenfassung von Schritt 2 Erzeugte Schluessel sind fuer das Bitcoin Test Network Private Key im 32 Byte Format: 1c251905cde6e4ccd055a00b4dbbb2c68c7edcba6720e1549312824dc050ac2b Private Key im WIF-Format : 91oK61hw9ABiKWqfLqWLEV69usmbeGw2gc7RUTp1ZpXhxQ6Ho71 Schritt 3: Oeffentliche Adresse des Wallets Die einzelnen Schritte folgen dem Artikel https://www.novixys.com/blog/generate-bitcoin-addresses-java bcPub: 04031EB765C1C1B2AACE0A8BD7A8C8B356431495E22D3CE10A723B8AF50F8A5CDE1EDF46603E6A8D8FFB6FC5B3B406463F0284330D10AC8D9451D76367F2819001 Length:130 sha: 14CF1091C72B24CEE4B713530E8DEF3CDA47A12AD2388CEC6CB897ACFE261FA7 r1:c745f75e6e93fd09724dfadf060a994106bfc5f8 Length:20 rmd: 6FC745F75E6E93FD09724DFADF060A994106BFC5F8 exp: 00C5FAE41AB21FA56CFBAFA3AE7FB5784441D11CEC sha: D19679B3377BAA7BB44AFC6FE3F06FBB55E2EC8B21C9E444AFC4A3EF335BB2CA sha: CE1F2157FC09420A130706B9CEAB36510D4BA78BDA7E9983CF9BEA589B3759A3 25 Byte Bitcoin Address:6fc745f75e6e93fd09724dfadf060a994106bfc5f8ce1f2157 Länge:25 adr: mygcaa4xUGAPLdhuYHggWV7tt6RcZqGeTg Zusammenfassung von Schritt 3 Erzeugte Schluessel sind fuer das Bitcoin Test Network Public Key im 32 Byte Format: 3056301006072a8648ce3d020106052b8104000a03420004031eb765c1c1b2aace0a8bd7a8c8b356431495e22d3ce10a723b8af50f8a5cde1edf46603e6a8d8ffb6fc5b3b406463f0284330d10ac8d9451d76367f2819001 Public Key im WIF-Format : mygcaa4xUGAPLdhuYHggWV7tt6RcZqGeTg * * * Zusammenfassung aller Schluessel und Adressen * * * Erzeugte Schluessel sind fuer das Bitcoin Test Network EC Private Key im Format :1c251905cde6e4ccd055a00b4dbbb2c68c7edcba6720e1549312824dc050ac2b EC Private Key im WIF-Format:91oK61hw9ABiKWqfLqWLEV69usmbeGw2gc7RUTp1ZpXhxQ6Ho71 EC Public Key im WIF-Format :mygcaa4xUGAPLdhuYHggWV7tt6RcZqGeTg Die korrekte Umwandlung fuer das Main Network kann ueber diese beiden Services ueberprueft werden: Private Key im WIF-Format : http://gobittest.appspot.com/PrivateKey Oeffentliche Adresse im WIF-Format: http://gobittest.appspot.com/Address |
Alle Quellcodes zum Bitcoin findet Ihr zum Download in meinem Github-Repository, welches Ihr über diesen Link erreicht: https://github.com/java-crypto/Bitcoin. Alle Programme sind unter Java 11 lauffähig (vermutlich auch unter Java 8) und wurden mit intelliJ IDEA entwickelt, welches für dieses Programm aber nicht notwendig ist.
Letzte Bearbeitung: 28.02.2020