• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bernardladenthin / BitcoinAddressFinder / #287

12 Apr 2025 09:07AM UTC coverage: 67.536% (+3.4%) from 64.134%
#287

push

bernardladenthin
Add assumeOpenCLLibraryLoadable.

1165 of 1725 relevant lines covered (67.54%)

0.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.92
/src/main/java/net/ladenthin/bitcoinaddressfinder/PublicKeyBytes.java
1
// @formatter:off
2
/**
3
 * Copyright 2020 Bernard Ladenthin bernard.ladenthin@gmail.com
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *    http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
// @formatter:on
19
package net.ladenthin.bitcoinaddressfinder;
20

21
import com.google.common.hash.Hashing;
22
import java.math.BigInteger;
23
import java.util.Objects;
24
import org.apache.commons.codec.digest.DigestUtils;
25
import org.bitcoinj.core.Utils;
26
import org.bitcoinj.crypto.ECKey;
27
import org.bitcoinj.crypto.internal.CryptoUtils;
28
import org.bouncycastle.crypto.digests.RIPEMD160Digest;
29

30
public class PublicKeyBytes {
31
    
32
    /**
33
     * Use {@link com.​google.​common.​hash.Hashing} and
34
     * {@link org.bouncycastle.crypto.digests.RIPEMD160Digest} instead
35
     * {@link org.bitcoinj.core.Utils#sha256hash160(byte[])}.
36
     */
37
    public static final boolean USE_SHA256_RIPEMD160_FAST = true;
38

39
    public static final BigInteger MAX_TECHNICALLY_PRIVATE_KEY = BigInteger.valueOf(2).pow(PublicKeyBytes.PRIVATE_KEY_MAX_NUM_BITS).subtract(BigInteger.ONE);
1✔
40

41
    public static final BigInteger MIN_PRIVATE_KEY = BigInteger.ONE;
1✔
42
    
43
    /**
44
     * Specifically, any 256-bit number between {@code 0x1} and {@code 0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4141} is a valid
45
     * private key.
46
     */
47
    public static final BigInteger MAX_PRIVATE_KEY = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);
1✔
48
    
49
    /**
50
     * I choose a random value for a replacement.
51
     */
52
    public static final BigInteger INVALID_PRIVATE_KEY_REPLACEMENT = BigInteger.valueOf(2);
1✔
53

54
    public static final int PRIVATE_KEY_MAX_NUM_BITS = 256;
55
    public static final int BITS_PER_BYTE = 8;
56
    public static final int PRIVATE_KEY_MAX_NUM_BYTES = PRIVATE_KEY_MAX_NUM_BITS / BITS_PER_BYTE;
57

58
    public static final int ONE_COORDINATE_NUM_BYTES = 32;
59
    public static final int TWO_COORDINATES_NUM_BYTES = ONE_COORDINATE_NUM_BYTES * 2;
60
    
61
    /**
62
     * Computes the maximum permissible length for an array intended to store pairs of coordinates within a 32-bit system.
63
     * This constant represents the upper limit on array length, factoring in the memory constraint imposed by the maximum
64
     * integer value addressable in Java ({@link Integer#MAX_VALUE}) and the storage requirement for two coordinates.
65
     * <p>
66
     * The calculation divides {@link Integer#MAX_VALUE} by the number of bytes needed to store two coordinates,
67
     * as defined by {@link PublicKeyBytes#TWO_COORDINATES_NUM_BYTES}, ensuring the array's indexing does not surpass
68
     * Java's maximum allowable array length.
69
     * </p>
70
     */
71
    public static final int MAXIMUM_TWO_COORDINATES_ARRAY_LENGTH = (int)(Integer.MAX_VALUE / PublicKeyBytes.TWO_COORDINATES_NUM_BYTES);
72

73
    /**
74
     * Determines the minimum number of bits required to address the maximum array length for storing coordinate pairs.
75
     * This value is crucial for efficiently allocating memory without exceeding the 32-bit system's limitations.
76
     * <p>
77
     * The calculation employs a bit manipulation strategy to find the exponent of the nearest superior power of 2
78
     * capable of accommodating the maximum array length. By decrementing the maximum array length by 1 and
79
     * calculating 32 minus the count of leading zeros in the decremented value, we obtain the closest superior power of 2.
80
     * This technique, derived from a common bit manipulation trick (source: https://stackoverflow.com/questions/5242533/fast-way-to-find-exponent-of-nearest-superior-power-of-2),
81
     * ensures the calculated bit count represents the smallest possible number that can address all potential array indices
82
     * without breaching the 32-bit address space limitation.
83
     * </p>
84
     */
85
    public static final int BIT_COUNT_FOR_MAX_COORDINATE_PAIRS_ARRAY = PublicKeyBytes.MAXIMUM_TWO_COORDINATES_ARRAY_LENGTH == 0 ? 0 : 32 - Integer.numberOfLeadingZeros(PublicKeyBytes.MAXIMUM_TWO_COORDINATES_ARRAY_LENGTH - 1) - 1;
1✔
86

87
    public static final int PARITY_BYTES_LENGTH = 1;
88

89
    public static final int LAST_Y_COORDINATE_BYTE_INDEX = PublicKeyBytes.PARITY_BYTES_LENGTH + PublicKeyBytes.TWO_COORDINATES_NUM_BYTES - 1;
90

91
    /**
92
     * The first byte (parity) is 4 to indicate a public key with x and y
93
     * coordinate (uncompressed).
94
     */
95
    public static final int PARITY_UNCOMPRESSED = 4;
96
    public static final int PARITY_COMPRESSED_EVEN = 2;
97
    public static final int PARITY_COMPRESSED_ODD = 3;
98

99
    public static final int HASH160_SIZE = 20;
100
    
101
    public final static int PUBLIC_KEY_UNCOMPRESSED_BYTES = PARITY_BYTES_LENGTH + TWO_COORDINATES_NUM_BYTES;
102
    public final static int PUBLIC_KEY_COMPRESSED_BYTES = PARITY_BYTES_LENGTH + ONE_COORDINATE_NUM_BYTES;
103

104
    private final byte[] uncompressed;
105
    private final byte[] compressed;
106
    
107
    /**
108
     * Lazy initialization.
109
     */
110
    private byte[] uncompressedKeyHash;
111
    
112
    /**
113
     * Lazy initialization.
114
     */
115
    private byte[] compressedKeyHash;
116
    
117
    /**
118
     * Lazy initialization.
119
     */
120
    private String uncompressedKeyHashBase58;
121
    
122
    /**
123
     * Lazy initialization.
124
     */
125
    private String compressedKeyHashBase58;
126
    
127
    private final BigInteger secretKey;
128
    
129
    // [4, 121, -66, 102, 126, -7, -36, -69, -84, 85, -96, 98, -107, -50, -121, 11, 7, 2, -101, -4, -37, 45, -50, 40, -39, 89, -14, -127, 91, 22, -8, 23, -104, 72, 58, -38, 119, 38, -93, -60, 101, 93, -92, -5, -4, 14, 17, 8, -88, -3, 23, -76, 72, -90, -123, 84, 25, -100, 71, -48, -113, -5, 16, -44, -72]
130
    // Hex.decodeHex("0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8")
131
    public static final PublicKeyBytes INVALID_KEY_ONE = new PublicKeyBytes(BigInteger.ONE, new byte[] {4, 121, -66, 102, 126, -7, -36, -69, -84, 85, -96, 98, -107, -50, -121, 11, 7, 2, -101, -4, -37, 45, -50, 40, -39, 89, -14, -127, 91, 22, -8, 23, -104, 72, 58, -38, 119, 38, -93, -60, 101, 93, -92, -5, -4, 14, 17, 8, -88, -3, 23, -76, 72, -90, -123, 84, 25, -100, 71, -48, -113, -5, 16, -44, -72});
1✔
132
    
133
    public BigInteger getSecretKey() {
134
        return secretKey;
1✔
135
    }
136

137
    public byte[] getCompressed() {
138
        return compressed;
1✔
139
    }
140

141
    public byte[] getUncompressed() {
142
        return uncompressed;
1✔
143
    }
144
    
145
    public boolean isInvalid() {
146
        return isInvalid(secretKey);
1✔
147
    }
148
    
149
    public static boolean isInvalid(BigInteger secret) {
150
        if (secret.compareTo(MIN_PRIVATE_KEY) < 1 || secret.compareTo(MAX_PRIVATE_KEY) > 1) {
1✔
151
            // prevent an IllegalArgumentException
152
            return true;
1✔
153
        }
154
        
155
        return false;
1✔
156
    }
157
    
158
    public static BigInteger returnValidPrivateKey(BigInteger secret) {
159
        if (isInvalid(secret)) {
1✔
160
            return INVALID_PRIVATE_KEY_REPLACEMENT;
1✔
161
        }
162
        return secret;
1✔
163
    }
164
    
165
    public static void replaceInvalidPrivateKeys(BigInteger[] secrets) {
166
        for (int i = 0; i < secrets.length; i++) {
1✔
167
            secrets[i] = returnValidPrivateKey(secrets[i]);
1✔
168
        }
169
    }
1✔
170
    
171
    public PublicKeyBytes(BigInteger secretKey, byte[] uncompressed) {
172
        this(secretKey, uncompressed, createCompressedBytes(uncompressed));
1✔
173
    }
1✔
174
    
175
    public PublicKeyBytes(BigInteger secretKey, byte[] uncompressed, byte[] compressed) {
1✔
176
        this.secretKey = secretKey;
1✔
177
        this.uncompressed = uncompressed;
1✔
178
        this.compressed = compressed;
1✔
179
    }
1✔
180
    
181
    public static PublicKeyBytes fromPrivate(BigInteger secretKey) {
182
        ECKey ecKey = ECKey.fromPrivate(secretKey, false);
1✔
183
        return new PublicKeyBytes(ecKey.getPrivKey(), ecKey.getPubKey());
1✔
184
    }
185
    
186
    public static byte[] createCompressedBytes(byte[] uncompressed) {
187
        // add one byte for format sign
188
        byte[] compressed = new byte[PUBLIC_KEY_COMPRESSED_BYTES];
1✔
189
        
190
        // copy x
191
        System.arraycopy(uncompressed, PARITY_BYTES_LENGTH, compressed, PublicKeyBytes.PARITY_BYTES_LENGTH, PublicKeyBytes.ONE_COORDINATE_NUM_BYTES);
1✔
192
        
193
        boolean even = uncompressed[LAST_Y_COORDINATE_BYTE_INDEX] % 2 == 0;
1✔
194
        
195
        if (even) {
1✔
196
            compressed[0] = PARITY_COMPRESSED_EVEN;
1✔
197
        } else {
198
            compressed[0] = PARITY_COMPRESSED_ODD;
1✔
199
        }
200
        return compressed;
1✔
201
    }
202

203
    public byte[] getUncompressedKeyHash() {
204
        if (uncompressedKeyHash == null) {
1✔
205
            if (USE_SHA256_RIPEMD160_FAST) {
206
                uncompressedKeyHash = sha256hash160Fast(uncompressed);
1✔
207
            } else {
208
                uncompressedKeyHash = CryptoUtils.sha256hash160(uncompressed);
209
            }
210
        }
211
        return uncompressedKeyHash;
1✔
212
    }
213

214
    public byte[] getCompressedKeyHash() {
215
        if (compressedKeyHash == null) {
1✔
216
            if (USE_SHA256_RIPEMD160_FAST) {
217
                compressedKeyHash = CryptoUtils.sha256hash160(compressed);
1✔
218
            } else {
219
                compressedKeyHash = sha256hash160Fast(compressed);
220
            }
221
        }
222
        return compressedKeyHash;
1✔
223
    }
224

225
    /**
226
     * Calculates RIPEMD160(SHA256(input)). This is used in Address
227
     * calculations. Same as {@link Utils#sha256hash160(byte[])} but using
228
     * {@link DigestUtils}.
229
     */
230
    public static byte[] sha256hash160Fast(byte[] input) {
231
        byte[] sha256 = Hashing.sha256().hashBytes(input).asBytes();
1✔
232
        RIPEMD160Digest digest = new RIPEMD160Digest();
1✔
233
        digest.update(sha256, 0, sha256.length);
1✔
234
        byte[] out = new byte[HASH160_SIZE];
1✔
235
        digest.doFinal(out, 0);
1✔
236
        return out;
1✔
237
    }
238

239
    public String getCompressedKeyHashAsBase58(KeyUtility keyUtility) {
240
        if (uncompressedKeyHashBase58 == null) {
1✔
241
            uncompressedKeyHashBase58 = keyUtility.toBase58(getCompressedKeyHash());
1✔
242
        }
243
        return uncompressedKeyHashBase58;
1✔
244
    }
245

246
    public String getUncompressedKeyHashAsBase58(KeyUtility keyUtility) {
247
        if (compressedKeyHashBase58 == null) {
1✔
248
            compressedKeyHashBase58 = keyUtility.toBase58(getUncompressedKeyHash());
1✔
249
        }
250
        return compressedKeyHashBase58;
1✔
251
    }
252

253
    // <editor-fold defaultstate="collapsed" desc="Overrides: hashCode, equals, toString">
254
    /*
255
     * Overrides for {@code hashCode()}, {@code equals(Object)}, and {@code toString()}.
256
     * <p>
257
     * These methods are implemented based **only** on the {@code secretKey} field, 
258
     * which uniquely identifies the {@code PublicKeyBytes} instance.
259
     * <ul>
260
     *     <li>{@code hashCode()} – Generated using a prime multiplier and {@code secretKey} hash.</li>
261
     *     <li>{@code equals(Object)} – Considers two instances equal if their {@code secretKey} values are equal.</li>
262
     *     <li>{@code toString()} – Returns a string including the {@code secretKey} for debugging/logging.</li>
263
     * </ul>
264
     * <p>
265
     * This design ensures that objects with the same {@code secretKey} are treated as equal,
266
     * regardless of other internal state (e.g., precomputed hash representations or compressed keys).
267
     */
268
    
269
    // generated, based on secretKey only!
270
    @Override
271
    public int hashCode() {
272
        int hash = 3;
1✔
273
        hash = 73 * hash + Objects.hashCode(this.secretKey);
1✔
274
        return hash;
1✔
275
    }
276

277
    // generated, based on secretKey only!
278
    @Override
279
    public boolean equals(Object obj) {
280
        if (this == obj) {
1✔
281
            return true;
1✔
282
        }
283
        if (obj == null) {
1✔
284
            return false;
×
285
        }
286
        if (getClass() != obj.getClass()) {
1✔
287
            return false;
×
288
        }
289
        final PublicKeyBytes other = (PublicKeyBytes) obj;
1✔
290
        return Objects.equals(this.secretKey, other.secretKey);
1✔
291
    }
292

293
    // generated, based on secretKey only!
294
    @Override
295
    public String toString() {
296
        return "PublicKeyBytes{" + "secretKey=" + secretKey + '}';
1✔
297
    }
298
    // </editor-fold>
299
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc