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 ≤ 57 + 58 × (2³²−1) < 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}