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

bernardladenthin / BitcoinAddressFinder / #315

23 May 2025 03:19PM UTC coverage: 66.005% (-1.9%) from 67.925%
#315

push

bernardladenthin
Refactor OpenCL resource handling and improve consistency across API

- Introduced AutoCloseable for OpenCL resources (OpenClTask, OpenCLContext)
- Replaced manual release() methods with try-with-resources using close()
- Added isClosed() for defensive resource lifecycle checks
- Refactored buffer handling to separate host and device pointers
- Renamed createSecrets() param for clarity (overallWorkSize instead of batchSizeInBits)
- Updated all tests and affected logic to match new API and lifecycle
- Enhanced OpenCLGridResult to avoid unnecessary buffer duplication
- Improved benchmark documentation in README with CPU column
- Added optional LMDB in-memory caching in configuration

11 of 118 new or added lines in 10 files covered. (9.32%)

3 existing lines in 2 files now uncovered.

1231 of 1865 relevant lines covered (66.01%)

0.66 hits per line

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

0.0
/src/main/java/net/ladenthin/bitcoinaddressfinder/OpenCLGridResult.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 java.util.Arrays;
22
import java.math.BigInteger;
23
import java.nio.ByteBuffer;
24
import net.ladenthin.bitcoinaddressfinder.configuration.CConsumerJava;
25

26
public class OpenCLGridResult {
27
    
28
    /**
29
     * Enable additional validation to check if generated uncompressed keys are not all zero.
30
     * <p>
31
     * This should remain <b>disabled in production</b> to avoid unnecessary performance overhead.
32
     * Useful only during debugging or when validating OpenCL/GPU kernel correctness.
33
     * </p>
34
     * <p>
35
     * Superseded by {@link PublicKeyBytes#runtimePublicKeyCalculationCheck(org.slf4j.Logger)}
36
     * and its activation via {@link CConsumerJava#runtimePublicKeyCalculationCheck}.
37
     * </p>
38
     */
39
    private static final boolean ENABLE_UNCOMPRESSED_KEY_VALIDATION = false;
40

41
    private final ByteBufferUtility byteBufferUtility = new ByteBufferUtility(true);
×
42
    
43
    private final BigInteger secretKeyBase;
44
    private final int workSize;
45
    private ByteBuffer result;
46
    
47
    OpenCLGridResult(BigInteger secretKeyBase, int workSize, ByteBuffer result) {
×
48
        this.secretKeyBase = secretKeyBase;
×
49
        this.workSize = workSize;
×
50
        this.result = result;
×
51
    }
×
52

53
    public BigInteger getSecretKeyBase() {
54
        return secretKeyBase;
×
55
    }
56

57
    public int getWorkSize() {
58
        return workSize;
×
59
    }
60

61
    public ByteBuffer getResult() {
62
        return result;
×
63
    }
64
    
65
    public void freeResult() {
66
        // free and do not use anymore
67
        byteBufferUtility.freeByteBuffer(result);
×
68
        result = null;
×
69
    }
×
70
    
71
    /**
72
     * Reads the computed public keys from the OpenCL result buffer and converts them into the correct format.
73
     * <p>
74
     * OpenCL writes 32-bit integers (u32) into memory using the device's native endianness (typically Little-Endian).
75
     * However, Bitcoin/ECC standards expect public keys in Big-Endian (MSB-first) byte order.
76
     * <p>
77
     * Therefore, after reading X and Y coordinates for each public key, the bytes of each coordinate
78
     * must be reversed if the device endianness differs from the target Big-Endian format.
79
     * <p>
80
     * The resulting format matches the uncompressed SEC (Standards for Efficient Cryptography) format:
81
     * <ul>
82
     *   <li>Prefix byte 0x04</li>
83
     *   <li>Followed by 32 bytes for X coordinate (Big-Endian)</li>
84
     *   <li>Followed by 32 bytes for Y coordinate (Big-Endian)</li>
85
     * </ul>
86
     * <p>
87
     * <b>Note:</b> This operation is relatively time-consuming because it involves memory copying and per-key byte order correction.
88
     *
89
     * @return an array of {@link PublicKeyBytes} containing the reconstructed public keys.
90
     */
91
    public PublicKeyBytes[] getPublicKeyBytes() {
NEW
92
        ByteBuffer readOnlyResult = result.asReadOnlyBuffer();
×
93
        PublicKeyBytes[] publicKeys = new PublicKeyBytes[workSize];
×
94
        
95
        for (int i = 0; i < workSize; i++) {
×
NEW
96
            PublicKeyBytes publicKeyBytes = getPublicKeyFromByteBufferXY(readOnlyResult, i, secretKeyBase);
×
97
            publicKeys[i] = publicKeyBytes;
×
98
        }
99
        return publicKeys;
×
100
    }
101
    
102
    public static byte[] trimU32PrefixBytes(byte[] fullArray) {
103
        final int PREFIX_BYTES_TO_SKIP = 3;
×
104
        return Arrays.copyOfRange(fullArray, PREFIX_BYTES_TO_SKIP, fullArray.length);
×
105
    }
106

107
    /**
108
     * Reconstructs a {@link PublicKeyBytes} object from the OpenCL kernel output.
109
     * <p>
110
     * This method extracts a block of bytes for one key from the given {@link ByteBuffer},
111
     * based on the work-item index ({@code keyNumber}). The layout of each chunk is defined
112
     * by constants in {@link PublicKeyBytes}:
113
     * <ul>
114
     *   <li>{@code CHUNK_SIZE_00_NUM_BYTES_BIG_ENDIAN_X}: X coordinate (Big-Endian)</li>
115
     *   <li>{@code CHUNK_SIZE_01_NUM_BYTES_BIG_ENDIAN_Y}: Y coordinate (Big-Endian)</li>
116
     *   <li>{@code CHUNK_SIZE_10_NUM_BYTES_RIPEMD160_UNCOMPRESSED}: RIPEMD-160 hash of the uncompressed key</li>
117
     *   <li>{@code CHUNK_SIZE_11_NUM_BYTES_RIPEMD160_COMPRESSED}: RIPEMD-160 hash of the compressed key</li>
118
     * </ul>
119
     * <p>
120
     * The method reads and assembles the uncompressed public key in SEC format
121
     * ({@code 04 || X || Y}) using the provided X and Y coordinates. It also
122
     * extracts the precomputed RIPEMD-160 hashes for both uncompressed and
123
     * compressed formats from the buffer.
124
     * <p>
125
     * If the reconstructed secret key is zero, a predefined fallback key is returned.
126
     *
127
     * @param resultBuffer the buffer containing OpenCL results for all keys
128
     * @param keyNumber the zero-based index of the key to extract
129
     * @param secretKeyBase the base secret key used to derive the current key
130
     * @return the reconstructed {@link PublicKeyBytes} object
131
     * @throws RuntimeException if the key bytes are invalid (e.g. all coordinate bytes are zero)
132
     */
133
    private static final PublicKeyBytes getPublicKeyFromByteBufferXY(ByteBuffer resultBuffer, int keyNumber, BigInteger secretKeyBase) {
134
        BigInteger secret = AbstractProducer.calculateSecretKey(secretKeyBase, keyNumber);
×
135
        if(BigInteger.ZERO.equals(secret)) {
×
136
            // the calculated key is invalid, return a fallback
137
            return PublicKeyBytes.INVALID_KEY_ONE;
×
138
        }
139
        
NEW
140
        final int keyOffsetInByteBuffer = PublicKeyBytes.CHUNK_SIZE_NUM_BYTES * keyNumber;
×
141
        
142
        // Get X
NEW
143
        byte[] xFromBigEndian = new byte[PublicKeyBytes.CHUNK_SIZE_00_NUM_BYTES_BIG_ENDIAN_X];
×
NEW
144
        resultBuffer.get(keyOffsetInByteBuffer + PublicKeyBytes.CHUNK_OFFSET_00_NUM_BYTES_BIG_ENDIAN_X, xFromBigEndian, 0, xFromBigEndian.length);
×
145
        
146
        // Get Y
NEW
147
        byte[] yFromBigEndian = new byte[PublicKeyBytes.CHUNK_SIZE_01_NUM_BYTES_BIG_ENDIAN_Y];
×
NEW
148
        resultBuffer.get(keyOffsetInByteBuffer + PublicKeyBytes.CHUNK_OFFSET_01_NUM_BYTES_BIG_ENDIAN_Y, yFromBigEndian, 0, yFromBigEndian.length);
×
149
        
150
        // Assemble uncompressed key
151
        byte[] uncompressedFromBigEndian = PublicKeyBytes.assembleUncompressedPublicKey(xFromBigEndian, yFromBigEndian);
×
152
        
153
        // Get RIPEMD160 for uncompressed key
NEW
154
        byte[] ripemd160Uncompressed = new byte[PublicKeyBytes.CHUNK_SIZE_10_NUM_BYTES_RIPEMD160_UNCOMPRESSED];
×
NEW
155
        resultBuffer.get(keyOffsetInByteBuffer + PublicKeyBytes.CHUNK_OFFSET_10_NUM_BYTES_RIPEMD160_UNCOMPRESSED, ripemd160Uncompressed, 0, ripemd160Uncompressed.length);
×
156
        
157
        // Get RIPEMD160 for uncompressed key
NEW
158
        byte[] ripemd160Compressed = new byte[PublicKeyBytes.CHUNK_SIZE_11_NUM_BYTES_RIPEMD160_COMPRESSED];
×
NEW
159
        resultBuffer.get(keyOffsetInByteBuffer + PublicKeyBytes.CHUNK_OFFSET_11_NUM_BYTES_RIPEMD160_COMPRESSED, ripemd160Compressed, 0, ripemd160Compressed.length);
×
160

161
        if (ENABLE_UNCOMPRESSED_KEY_VALIDATION) {
162
            boolean allZero = PublicKeyBytes.isAllCoordinateBytesZero(uncompressedFromBigEndian);
163
            if (allZero) {
164
                throw new RuntimeException("Invalid GPU result: all coordinate bytes are zero in uncompressed public key.");
165
            }
166
        }
167
        
168
        PublicKeyBytes publicKeyBytes = new PublicKeyBytes(secret, uncompressedFromBigEndian,  ripemd160Uncompressed, ripemd160Compressed);
×
169
        
170
        return publicKeyBytes;
×
171
    }
172
}
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