区块链Base58格式的地址生成

2018-03-27  王佳亮

一、背景

前段时间无意中了解到了Base58,分析后发现比Base64更适合做为UUID主键的编码方式。

二、Base58简介

Base58采用的字符集合为“123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ”,从这不难看出,Base58是纯数字与字母组成而且去掉了容易引起视觉混淆的字符(0:数字零,O:大写O,I:大写i,l:小写L)。9个数字+49个字母=58个。由于没有特殊字符所以在采用鼠标双击或移动设备选择时可以自动识别全选。

Base58本身就是URLSafe。Base64的URFSafe模式虽然已经对URL支持的比较好,但UUID中还是包含“-或_”。

目前流行的比特币,采用的就是Base58Check编码,是在Base58基础上又增加了安全效验机制。

Base58采用的字符集合为“123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ”,从这不难看出,Base58是纯数字与字母组成而且去掉了容易引起视觉混淆的字符(0:数字零,O:大写O,I:大写i,l:小写L)。9个数字+49个字母=58个。由于没有特殊字符所以在采用鼠标双击或移动设备选择时可以自动识别全选。

三、Base58编码器程序

[java] view plain copy
  1. package org.noahx.uuid.utils;  
  2.   
  3. import java.io.UnsupportedEncodingException;  
  4. import java.math.BigInteger;  
  5.   
  6. /** 
  7.  * Created with IntelliJ IDEA. 
  8.  * User: noah 
  9.  * Date: 8/2/13 
  10.  * Time: 10:36 AM 
  11.  * To change this template use File | Settings | File Templates. 
  12.  */  
  13. public class Base58 {  
  14.   
  15.     public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();  
  16.     private static final int[] INDEXES = new int[128];  
  17.   
  18.     static {  
  19.         for (int i = 0; i < INDEXES.length; i++) {  
  20.             INDEXES[i] = -1;  
  21.         }  
  22.         for (int i = 0; i < ALPHABET.length; i++) {  
  23.             INDEXES[ALPHABET[i]] = i;  
  24.         }  
  25.     }  
  26.   
  27.     /** 
  28.      * Encodes the given bytes in base58. No checksum is appended. 
  29.      */  
  30.     public static String encode(byte[] input) {  
  31.         if (input.length == 0) {  
  32.             return "";  
  33.         }  
  34.         input = copyOfRange(input, 0, input.length);  
  35.         // Count leading zeroes.  
  36.         int zeroCount = 0;  
  37.         while (zeroCount < input.length && input[zeroCount] == 0) {  
  38.             ++zeroCount;  
  39.         }  
  40.         // The actual encoding.  
  41.         byte[] temp = new byte[input.length * 2];  
  42.         int j = temp.length;  
  43.   
  44.         int startAt = zeroCount;  
  45.         while (startAt < input.length) {  
  46.             byte mod = divmod58(input, startAt);  
  47.             if (input[startAt] == 0) {  
  48.                 ++startAt;  
  49.             }  
  50.             temp[--j] = (byte) ALPHABET[mod];  
  51.         }  
  52.   
  53.         // Strip extra '1' if there are some after decoding.  
  54.         while (j < temp.length && temp[j] == ALPHABET[0]) {  
  55.             ++j;  
  56.         }  
  57.         // Add as many leading '1' as there were leading zeros.  
  58.         while (--zeroCount >= 0) {  
  59.             temp[--j] = (byte) ALPHABET[0];  
  60.         }  
  61.   
  62.         byte[] output = copyOfRange(temp, j, temp.length);  
  63.         try {  
  64.             return new String(output, "US-ASCII");  
  65.         } catch (UnsupportedEncodingException e) {  
  66.             throw new RuntimeException(e);  // Cannot happen.  
  67.         }  
  68.     }  
  69.   
  70.     public static byte[] decode(String input) throws IllegalArgumentException {  
  71.         if (input.length() == 0) {  
  72.             return new byte[0];  
  73.         }  
  74.         byte[] input58 = new byte[input.length()];  
  75.         // Transform the String to a base58 byte sequence  
  76.         for (int i = 0; i < input.length(); ++i) {  
  77.             char c = input.charAt(i);  
  78.   
  79.             int digit58 = -1;  
  80.             if (c >= 0 && c < 128) {  
  81.                 digit58 = INDEXES[c];  
  82.             }  
  83.             if (digit58 < 0) {  
  84.                 throw new IllegalArgumentException("Illegal character " + c + " at " + i);  
  85.             }  
  86.   
  87.             input58[i] = (byte) digit58;  
  88.         }  
  89.         // Count leading zeroes  
  90.         int zeroCount = 0;  
  91.         while (zeroCount < input58.length && input58[zeroCount] == 0) {  
  92.             ++zeroCount;  
  93.         }  
  94.         // The encoding  
  95.         byte[] temp = new byte[input.length()];  
  96.         int j = temp.length;  
  97.   
  98.         int startAt = zeroCount;  
  99.         while (startAt < input58.length) {  
  100.             byte mod = divmod256(input58, startAt);  
  101.             if (input58[startAt] == 0) {  
  102.                 ++startAt;  
  103.             }  
  104.   
  105.             temp[--j] = mod;  
  106.         }  
  107.         // Do no add extra leading zeroes, move j to first non null byte.  
  108.         while (j < temp.length && temp[j] == 0) {  
  109.             ++j;  
  110.         }  
  111.   
  112.         return copyOfRange(temp, j - zeroCount, temp.length);  
  113.     }  
  114.   
  115.     public static BigInteger decodeToBigInteger(String input) throws IllegalArgumentException {  
  116.         return new BigInteger(1, decode(input));  
  117.     }  
  118.   
  119.     //  
  120.     // number -> number / 58, returns number % 58  
  121.     //  
  122.     private static byte divmod58(byte[] number, int startAt) {  
  123.         int remainder = 0;  
  124.         for (int i = startAt; i < number.length; i++) {  
  125.             int digit256 = (int) number[i] & 0xFF;  
  126.             int temp = remainder * 256 + digit256;  
  127.   
  128.             number[i] = (byte) (temp / 58);  
  129.   
  130.             remainder = temp % 58;  
  131.         }  
  132.   
  133.         return (byte) remainder;  
  134.     }  
  135.   
  136.     //  
  137.     // number -> number / 256, returns number % 256  
  138.     //  
  139.     private static byte divmod256(byte[] number58, int startAt) {  
  140.         int remainder = 0;  
  141.         for (int i = startAt; i < number58.length; i++) {  
  142.             int digit58 = (int) number58[i] & 0xFF;  
  143.             int temp = remainder * 58 + digit58;  
  144.   
  145.             number58[i] = (byte) (temp / 256);  
  146.   
  147.             remainder = temp % 256;  
  148.         }  
  149.   
  150.         return (byte) remainder;  
  151.     }  
  152.   
  153.     private static byte[] copyOfRange(byte[] source, int from, int to) {  
  154.         byte[] range = new byte[to - from];  
  155.         System.arraycopy(source, from, range, 0, range.length);  
  156.   
  157.         return range;  
  158.     }  
  159.   
  160.   
  161. }  

UUID生成程序

[java] view plain copy
  1. package org.noahx.uuid.util;  
  2.   
  3. import org.apache.commons.codec.binary.Base64;  
  4.   
  5. import java.nio.ByteBuffer;  
  6. import java.util.UUID;  
  7.   
  8. public abstract class UuidUtils {  
  9.   
  10.     public static String uuid() {  
  11.         UUID uuid = UUID.randomUUID();  
  12.         return uuid.toString();  
  13.     }  
  14.   
  15.     public static String base64Uuid() {  
  16.         UUID uuid = UUID.randomUUID();  
  17.         return base64Uuid(uuid);  
  18.     }  
  19.   
  20.     protected static String base64Uuid(UUID uuid) {  
  21.   
  22.         ByteBuffer bb = ByteBuffer.wrap(new byte[16]);  
  23.         bb.putLong(uuid.getMostSignificantBits());  
  24.         bb.putLong(uuid.getLeastSignificantBits());  
  25.   
  26.         return Base64.encodeBase64URLSafeString(bb.array());  
  27.     }  
  28.   
  29.     public static String encodeBase64Uuid(String uuidString) {  
  30.         UUID uuid = UUID.fromString(uuidString);  
  31.         return base64Uuid(uuid);  
  32.     }  
  33.   
  34.     public static String decodeBase64Uuid(String compressedUuid) {  
  35.   
  36.         byte[] byUuid = Base64.decodeBase64(compressedUuid);  
  37.   
  38.         ByteBuffer bb = ByteBuffer.wrap(byUuid);  
  39.         UUID uuid = new UUID(bb.getLong(), bb.getLong());  
  40.         return uuid.toString();  
  41.     }  
  42.   
  43.     public static String base58Uuid() {  
  44.         UUID uuid = UUID.randomUUID();  
  45.         return base58Uuid(uuid);  
  46.     }  
  47.   
  48.     protected static String base58Uuid(UUID uuid) {  
  49.   
  50.         ByteBuffer bb = ByteBuffer.wrap(new byte[16]);  
  51.         bb.putLong(uuid.getMostSignificantBits());  
  52.         bb.putLong(uuid.getLeastSignificantBits());  
  53.   
  54.         return Base58.encode(bb.array());  
  55.     }  
  56.   
  57.     public static String encodeBase58Uuid(String uuidString) {  
  58.         UUID uuid = UUID.fromString(uuidString);  
  59.         return base58Uuid(uuid);  
  60.     }  
  61.   
  62.     public static String decodeBase58Uuid(String base58uuid) {  
  63.         byte[] byUuid = Base58.decode(base58uuid);  
  64.         ByteBuffer bb = ByteBuffer.wrap(byUuid);  
  65.         UUID uuid = new UUID(bb.getLong(), bb.getLong());  
  66.         return uuid.toString();  
  67.     }  
  68. }  


1、Base64的效果

M0ISICCxQi6sP-KIq3kFOw
11YozyYYTvKmuUXpRDvoJA
KlZnS-MuT2m3d-the2chxg
8J3SC10AQzqZr6Im8V2xYA
ES1UiFTGTHqn6ADU5YW0aw
1usa208oT1q7FitKbQHH5Q
53aDQZxKTGyqmKCzDnBwYQ
SVVjViEoQXayWB9_JknKqQ
fP6znJIAT1uGMN9HW5o8cw
YR-2-kKmSOubhGr2LpFCgQ

可以看到有-与_字符。大家可以双击上面包含-的UUID,得到只选中部分的效果。

2、Base58的效果

MqJqC2rtZLkuHys6ed2Eai QrS5w2t5etpRY3zTR1BAEJ
Qd6wcFFVz2ZSQb3voGGj8P
75bJdWMcEh6NhT51D5Uyju
2L7kTgsktxMBKLkfAo2iWC
UX2Twhbt1kstRziqc7iwCR
9tZNKCeR93taLHU6PVy8hN
HSn6JMibca4nG9URWokpwg
8eL4SNz2a4puEW8fD4njsG
GThFxPsdVUoZMfmKoEHwQX

没有混淆字符一眼看上去更清晰更可读。