001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.codec.binary;
019
020import java.math.BigInteger;
021import java.util.Arrays;
022import java.util.function.BiConsumer;
023
024/**
025 * Provides Base58 encoding and decoding as commonly used in cryptocurrency and blockchain applications.
026 * <p>
027 * Base58 is a binary-to-text encoding scheme that uses a 58-character alphabet to encode data. It avoids characters that can be confused (0/O, I/l, +/) and is
028 * commonly used in Bitcoin and other blockchain systems.
029 * </p>
030 * <p>
031 * Encoding and decoding produce results when EOF is signaled.
032 * </p>
033 * <p>
034 * Decoding rejects input longer than a configurable maximum ({@link #DEFAULT_MAX_DECODE_LENGTH} encoded bytes by default, see
035 * {@link Builder#setMaxDecodeLength(int)}). Encoding rejects binary input longer than {@link #DEFAULT_MAX_ENCODE_LENGTH} bytes by default; configure it with
036 * {@link Builder#setMaxEncodeLength(int)}. These limits apply to the total input across all chunks in an operation. Memory usage is proportional to the
037 * accumulated input and conversion output. Encoded output can exceed the decode limit; configure both limits appropriately for larger trusted values.
038 * </p>
039 * <p>
040 * This class is thread-safe for read operations but the Context object used during encoding/decoding should not be shared between threads.
041 * </p>
042 * <p>
043 * The Base58 alphabet is:
044 * </p>
045 *
046 * <pre>
047 * 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
048 * </pre>
049 * <p>
050 * This excludes: {@code 0}, {@code I}, {@code O}, and {@code l}.
051 * </p>
052 *
053 * @see Base58InputStream
054 * @see Base58OutputStream
055 * @see <a href="https://datatracker.ietf.org/doc/html/draft-msporny-base58-03">The Base58 Encoding Scheme draft-msporny-base58-03</a>
056 * @since 1.22.0
057 */
058public class Base58 extends BaseNCodec {
059
060    /**
061     * Builds {@link Base58} instances with custom configuration.
062     */
063    public static class Builder extends AbstractBuilder<Base58, Builder> {
064
065        private int maxDecodeLength = DEFAULT_MAX_DECODE_LENGTH;
066        private int maxEncodeLength = DEFAULT_MAX_ENCODE_LENGTH;
067
068        /**
069         * Constructs a new Base58 builder.
070         */
071        public Builder() {
072            super(ENCODE_TABLE);
073            setDecodeTable(DECODE_TABLE);
074        }
075
076        /**
077         * Gets a new Base58 instance with the configured settings.
078         *
079         * @return A new Base58 codec.
080         */
081        @Override
082        public Base58 get() {
083            return new Base58(this);
084        }
085
086        int getMaxDecodeLength() {
087            return maxDecodeLength;
088        }
089
090        int getMaxEncodeLength() {
091            return maxEncodeLength;
092        }
093
094        /**
095         * Sets the encode table and derives the matching decode table.
096         *
097         * @param encodeTable The encode table with exactly 58 unique entries, null resets to the default.
098         * @return {@code this} instance.
099         * @throws IllegalArgumentException Thrown if the encode table does not contain exactly 58 unique entries.
100         */
101        @Override
102        public Base58.Builder setEncodeTable(final byte... encodeTable) {
103            super.setDecodeTableRaw(toDecodeTable(encodeTable));
104            return super.setEncodeTable(encodeTable);
105        }
106
107        /**
108         * Sets the line length to zero.
109         * <p>
110         * Base58 does not support line chunking. Zero or a negative value selects unchunked output.
111         * </p>
112         *
113         * @param lineLength The line length; must not be positive.
114         * @return {@code this} instance.
115         * @throws IllegalArgumentException Thrown if lineLength is positive.
116         * @since 1.23.0
117         */
118        @Override
119        public Builder setLineLength(final int lineLength) {
120            if (lineLength > 0) {
121                throw new IllegalArgumentException("Base58 does not support line chunking.");
122            }
123            return super.setLineLength(lineLength);
124        }
125
126        /**
127         * Sets the maximum number of encoded bytes accepted by a single decode operation.
128         * <p>
129         * Defaults to {@link Base58#DEFAULT_MAX_DECODE_LENGTH}. Pass {@link Integer#MAX_VALUE} to effectively disable the limit for trusted input.
130         * </p>
131         *
132         * @param maxDecodeLength The maximum accepted encoded input length; must be positive.
133         * @return {@code this} instance.
134         * @throws IllegalArgumentException Thrown if maxDecodeLength is not positive.
135         * @since 1.23.0
136         */
137        public Builder setMaxDecodeLength(final int maxDecodeLength) {
138            if (maxDecodeLength <= 0) {
139                throw new IllegalArgumentException("maxDecodeLength must be positive.");
140            }
141            this.maxDecodeLength = maxDecodeLength;
142            return this;
143        }
144
145        /**
146         * Sets the maximum number of binary bytes accepted by a single encode operation.
147         * <p>
148         * Defaults to {@link Base58#DEFAULT_MAX_ENCODE_LENGTH}. Pass {@link Integer#MAX_VALUE} to effectively disable the limit for trusted input.
149         * </p>
150         *
151         * @param maxEncodeLength The maximum accepted binary input length; must be positive.
152         * @return {@code this} instance.
153         * @throws IllegalArgumentException Thrown if maxEncodeLength is not positive.
154         * @since 1.23.0
155         */
156        public Builder setMaxEncodeLength(final int maxEncodeLength) {
157            if (maxEncodeLength <= 0) {
158                throw new IllegalArgumentException("maxEncodeLength must be positive.");
159            }
160            this.maxEncodeLength = maxEncodeLength;
161            return this;
162        }
163
164    }
165    private static final BigInteger BASE = BigInteger.valueOf(58);
166
167    private static final int DECODING_TABLE_LENGTH = 256;
168    private static final int ENCODING_TABLE_LENGTH = 58;
169
170    /**
171     * The default maximum number of encoded bytes accepted by a single decode operation: {@value}.
172     * <p>
173     * Use {@link Builder#setMaxDecodeLength(int)} to raise (or effectively disable) the limit for trusted input.
174     * </p>
175     *
176     * @since 1.23.0
177     */
178    public static final int DEFAULT_MAX_DECODE_LENGTH = 8192;
179
180    /**
181     * The default maximum number of binary bytes accepted by a single encode operation: {@value}.
182     * <p>
183     * Use {@link Builder#setMaxEncodeLength(int)} to raise (or effectively disable) the limit for trusted input.
184     * </p>
185     *
186     * @since 1.23.0
187     */
188    public static final int DEFAULT_MAX_ENCODE_LENGTH = 8192;
189
190    /**
191     * Base58 alphabet: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
192     * (excludes: 0, I, O, l).
193     */
194    private static final byte[] ENCODE_TABLE = {
195            '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
196            'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a',
197            'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm', 'n', 'o', 'p', 'q', 'r', 's',
198            't', 'u', 'v', 'w', 'x', 'y', 'z'
199    };
200    /**
201     * This array is a lookup table that translates Unicode characters drawn from the "Base58 Alphabet"
202     * into their numeric equivalents (0-57). Characters that are not in the Base58 alphabet are marked
203     * with -1.
204     */
205    // @formatter:off
206    private static final byte[] DECODE_TABLE = {
207         //  0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
208            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f
209            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
210            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-2f
211            -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, -1, -1, -1, -1, -1,          // 30-3f '1'-'9' -> 0-8
212            -1, 9, 10, 11, 12, 13, 14, 15, 16, -1, 17, 18, 19, 20, 21, -1,  // 40-4f 'A'-'N', 'P'-'Z' (skip 'I' and 'O')
213            22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,                     // 50-5a 'P'-'Z'
214            -1, -1, -1, -1, -1,                                             // 5b-5f
215            -1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46, // 60-6f 'a'-'k', 'm'-'o' (skip 'l')
216            47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,                     // 70-7a 'p'-'z'
217    };
218    // @formatter:on
219
220    /**
221     * Creates a new Builder.
222     *
223     * <p>
224     * To configure a new instance, use a {@link Builder}. For example:
225     * </p>
226     *
227     * <pre>
228     * Base58 base58 = Base58.builder()
229     *   .setMaxEncodeLength(4096)
230     *   .get()
231     * </pre>
232     *
233     * @return A new Builder.
234     */
235    public static Builder builder() {
236        return new Builder();
237    }
238
239    /**
240     * Calculates a decode table for a given encode table.
241     *
242     * @param encodeTable that is used to determine decode lookup table.
243     * @return A new decode table.
244     * @throws IllegalArgumentException Thrown if the encode table does not contain exactly 58 unique entries.
245     */
246    private static byte[] calculateDecodeTable(final byte[] encodeTable) {
247        if (encodeTable.length != ENCODING_TABLE_LENGTH) {
248            throw new IllegalArgumentException("encodeTable must have exactly 58 entries.");
249        }
250        final byte[] decodeTable = new byte[DECODING_TABLE_LENGTH];
251        Arrays.fill(decodeTable, (byte) -1);
252        for (int i = 0; i < encodeTable.length; i++) {
253            final int encodedByte = encodeTable[i] & 0xff;
254            if (decodeTable[encodedByte] != -1) {
255                throw new IllegalArgumentException("encodeTable must not contain duplicate entries.");
256            }
257            decodeTable[encodedByte] = (byte) i;
258        }
259        return decodeTable;
260    }
261
262    /**
263     * Gets the decode table that matches the given encode table.
264     *
265     * @param encodeTable that is used to determine decode lookup table.
266     * @return The matching decode table.
267     */
268    private static byte[] toDecodeTable(final byte[] encodeTable) {
269        final byte[] table = encodeTable != null ? encodeTable : ENCODE_TABLE;
270        if (Arrays.equals(table, ENCODE_TABLE)) {
271            return DECODE_TABLE;
272        }
273        return calculateDecodeTable(table);
274    }
275
276    /**
277     * The maximum number of encoded bytes accepted by a single decode operation.
278     */
279    private final int maxDecodeLength;
280
281    /**
282     * The maximum number of binary bytes accepted by a single encode operation.
283     */
284    private final int maxEncodeLength;
285
286    /**
287     * Constructs a Base58 codec used for encoding and decoding.
288     */
289    public Base58() {
290        this(new Builder());
291    }
292
293    /**
294     * Constructs a Base58 codec used for encoding and decoding with custom configuration.
295     *
296     * @param builder The builder with custom configuration.
297     */
298    public Base58(final Builder builder) {
299        super(builder);
300        this.maxDecodeLength = builder.getMaxDecodeLength();
301        this.maxEncodeLength = builder.getMaxEncodeLength();
302    }
303
304    private void checkLength(final int length, final int accumulatedLength, final int maximum, final String operation) {
305        if (length > maximum - accumulatedLength) {
306            throw new IllegalArgumentException("Base58 input exceeds the maximum " + operation + " length of " + maximum + " bytes.");
307        }
308    }
309
310    private void code(final byte[] array, final int offset, final int length, final Context context, final int maximum, final String operation,
311            final BiConsumer<byte[], Context> consumer) {
312        if (context.eof) {
313            return;
314        }
315        // Base58 needs the complete input before it can convert, so input is accumulated in context.buffer. The number of accumulated bytes
316        // is tracked in context.ibitWorkArea (otherwise unused by this codec) so the buffer can grow geometrically; reallocating an
317        // exact-size buffer per chunk would copy the whole accumulation on every chunk, making streaming quadratic in the input length.
318        if (length < 0) {
319            context.eof = true;
320            final byte[] accumulate = context.buffer = context.buffer == null ? EMPTY_BYTE_ARRAY :
321                    context.buffer.length == context.ibitWorkArea ? context.buffer : Arrays.copyOf(context.buffer, context.ibitWorkArea);
322            if (accumulate.length > 0) {
323                consumer.accept(accumulate, context);
324            }
325            return;
326        }
327        final int accumulated = context.ibitWorkArea;
328        checkLength(length, accumulated, maximum, operation);
329        if (length > Integer.MAX_VALUE - 8 - accumulated) {
330            throw new IllegalArgumentException("Base58 input too large to accumulate: " + ((long) accumulated + length) + " bytes.");
331        }
332        final int required = accumulated + length;
333        byte[] buffer = context.buffer != null ? context.buffer : EMPTY_BYTE_ARRAY;
334        if (required > buffer.length) {
335            // Grow geometrically to amortize copying across chunks.
336            buffer = Arrays.copyOf(buffer, (int) Math.min(Math.max((long) buffer.length * 2, required), Math.min(maximum, Integer.MAX_VALUE - 8L)));
337        }
338        System.arraycopy(array, offset, buffer, accumulated, length);
339        context.buffer = buffer;
340        context.ibitWorkArea = required;
341    }
342
343    /**
344     * Converts Base58 encoded data to binary.
345     * <p>
346     * Uses 32-bit word arithmetic ({@code int[]} with {@code long} carry) to convert the Base58 string to binary data, avoiding {@link BigInteger} and its
347     * per-digit object allocation. Each Base58 digit is processed left-to-right using Horner's scheme: {@code value = value * 58 + digit}. An
348     * {@code wordsStart} cursor tracks the leftmost word that contains data, so the inner loop only touches the active portion of the work buffer. The active
349     * range grows linearly with the number of digits, so total conversion work is quadratic in the input length.
350     * </p>
351     * <p>
352     * At each word position the carry satisfies {@code carry &le; 57 + 58 &times; (2³²&minus;1) &lt; 2⁴⁰}, which fits in a Java {@code long}.
353     * </p>
354     * <p>
355     * Leading characters that match the first Base58 alphabet entry each represent a leading zero byte in the output.
356     * </p>
357     *
358     * @param base58  The Base58 encoded data.
359     * @param context The context for this decoding operation.
360     * @throws IllegalArgumentException Thrown if the Base58 data contains invalid characters or is longer than the configured maximum decode length.
361     */
362    private void convertFromBase58(final byte[] base58, final Context context) {
363        checkLength(base58.length, 0, maxDecodeLength, "decode");
364        final int zero = encodeTable[0] & 0xff;
365        // Count leading Base58 "zero" characters; each represents a leading zero byte in the output.
366        int leadingZeros = 0;
367        for (final byte b : base58) {
368            if ((b & 0xff) != zero) {
369                break;
370            }
371            leadingZeros++;
372        }
373        // Horner's scheme uses 32-bit words and a long carry, avoiding per-digit BigInteger allocation.
374        // wordsStart tracks the active word range, which grows linearly with the number of digits.
375        // Traversing this range for each digit makes conversion quadratic in the input length.
376        // At each word, carry <= 57 + 58 * (2^32 - 1) < 2^40, which fits in a long.
377        //
378        // Work buffer of 32-bit words, big-endian, right-aligned.
379        // Upper bound on decoded bytes is base58.length, so (base58.length+3)/4 words suffice.
380        final int numWords = base58.length + 3 >>> 2;
381        final int[] words = new int[numWords];
382        int wordsStart = numWords; // grows leftward as the value increases
383        for (int i = leadingZeros; i < base58.length; i++) {
384            final int b = base58[i] & 0xff;
385            final int digit = b < decodeTable.length ? decodeTable[b] : -1;
386            if (digit < 0) {
387                throw new IllegalArgumentException(String.format("Invalid character in Base58 string: 0x%02x", b));
388            }
389            // value = value * 58 + digit (Horner's scheme over 32-bit words)
390            long carry = digit;
391            for (int j = numWords - 1; j >= wordsStart; j--) {
392                carry += 58L * (words[j] & 0xFFFFFFFFL);
393                words[j] = (int) carry;
394                carry >>>= 32;
395            }
396            while (carry != 0) {
397                words[--wordsStart] = (int) carry;
398                carry >>>= 32;
399            }
400        }
401        // Expand active words to bytes (big-endian), then skip leading zero bytes.
402        final int activeWords = numWords - wordsStart;
403        final byte[] raw = new byte[activeWords * 4];
404        for (int i = 0; i < activeWords; i++) {
405            final int w = words[wordsStart + i];
406            raw[i * 4] = (byte) (w >>> 24);
407            raw[i * 4 + 1] = (byte) (w >>> 16);
408            raw[i * 4 + 2] = (byte) (w >>> 8);
409            raw[i * 4 + 3] = (byte) w;
410        }
411        int rawStart = 0;
412        while (rawStart < raw.length && raw[rawStart] == 0) {
413            rawStart++;
414        }
415        // Assemble result: leadingZeros zero bytes followed by the decoded value.
416        final int decodedLength = raw.length - rawStart;
417        final byte[] result = new byte[leadingZeros + decodedLength];
418        System.arraycopy(raw, rawStart, result, leadingZeros, decodedLength);
419        final byte[] buffer = ensureBufferSize(result.length, context);
420        System.arraycopy(result, 0, buffer, context.pos, result.length);
421        context.pos += result.length;
422    }
423
424    /**
425     * Converts accumulated binary data to Base58 encoding.
426     * <p>
427     * Uses BigInteger arithmetic to convert the binary data to Base58. Leading zeros in the binary data are represented as the first character in the Base58
428     * alphabet.
429     * </p>
430     *
431     * @param accumulate The binary data to encode.
432     * @param context    The context for this encoding operation.
433     * @return The buffer containing the encoded data.
434     */
435    private byte[] convertToBase58(final byte[] accumulate, final Context context) {
436        final StringBuilder base58 = getStringBuilder(accumulate);
437        final byte[] encodedBytes = new byte[base58.length()];
438        for (int i = 0; i < encodedBytes.length; i++) {
439            encodedBytes[i] = (byte) base58.charAt(encodedBytes.length - 1 - i);
440        }
441        final byte[] buffer = ensureBufferSize(encodedBytes.length, context);
442        System.arraycopy(encodedBytes, 0, buffer, context.pos, encodedBytes.length);
443        context.pos += encodedBytes.length;
444        return buffer;
445    }
446
447    /**
448     * Decodes the given Base58 encoded data.
449     * <p>
450     * This implementation accumulates data internally. When length is less than 0 (EOF), the accumulated data is converted from Base58 to binary.
451     * </p>
452     *
453     * @param array   The byte array containing Base58 encoded data.
454     * @param offset  The offset in the array to start from.
455     * @param length  The number of bytes to decode, or negative to signal EOF.
456     * @param context The context for this decoding operation.
457     * @throws IllegalArgumentException Thrown when a problem is detected processing data.
458     */
459    @Override
460    void decode(final byte[] array, final int offset, final int length, final Context context) {
461        code(array, offset, length, context, maxDecodeLength, "decode", this::convertFromBase58);
462    }
463
464    /**
465     * Encodes the given binary data as Base58.
466     * <p>
467     * This implementation accumulates data internally. When length is less than 0 (EOF), the accumulated data is converted to Base58.
468     * </p>
469     *
470     * @param array   The byte array containing binary data to encode.
471     * @param offset  The offset in the array to start from.
472     * @param length  The number of bytes to encode, or negative to signal EOF.
473     * @param context The context for this encoding operation.
474     */
475    @Override
476    void encode(final byte[] array, final int offset, final int length, final Context context) {
477        code(array, offset, length, context, maxEncodeLength, "encode", this::convertToBase58);
478    }
479
480    /**
481     * Gets the number of Base58 characters needed to encode the supplied array.
482     * <p>
483     * The length depends on the input bytes, including leading zeros. This method observes the configured maximum encode length.
484     * </p>
485     *
486     * @param array The binary input to encode.
487     * @return The number of Base58 characters that encoding the array produces.
488     * @throws IllegalArgumentException Thrown if the input exceeds the configured maximum encode length.
489     * @since 1.23.0
490     */
491    @Override
492    public long getEncodedLength(final byte[] array) {
493        checkLength(array.length, 0, maxEncodeLength, "encode");
494        return getStringBuilder(array).length();
495    }
496
497    /**
498     * Gets the Base58 string representation of the given binary data.
499     * <p>
500     * Converts binary data to a BigInteger and divides by 58 repeatedly to get the Base58 digits. Handles leading zeros by counting them and appending the first
501     * character in the Base58 alphabet for each leading zero byte.
502     * </p>
503     *
504     * @param accumulate The binary data to convert.
505     * @return A StringBuilder with the Base58 representation (not yet reversed).
506     */
507    private StringBuilder getStringBuilder(final byte[] accumulate) {
508        BigInteger value = new BigInteger(1, accumulate);
509        int leadingZeros = 0;
510        for (final byte b : accumulate) {
511            if (b != 0) {
512                break;
513            }
514            leadingZeros++;
515        }
516        final StringBuilder base58 = new StringBuilder();
517        while (value.signum() > 0) {
518            final BigInteger[] divRem = value.divideAndRemainder(BASE);
519            base58.append((char) (encodeTable[divRem[1].intValue()] & 0xff));
520            value = divRem[0];
521        }
522        final char zero = (char) (encodeTable[0] & 0xff);
523        for (int i = 0; i < leadingZeros; i++) {
524            base58.append(zero);
525        }
526        return base58;
527    }
528
529    /**
530     * Tests whether the {@code octet} is in the Base58 alphabet.
531     *
532     * @param value The value to test.
533     * @return {@code true} if the value is defined in the Base58 alphabet {@code false} otherwise.
534     */
535    @Override
536    protected boolean isInAlphabet(final byte value) {
537        final int octet = value & 0xff;
538        return octet < decodeTable.length && decodeTable[octet] != -1;
539    }
540}