itlgl / note

使用github的issues功能记录日常的笔记
http://itlgl.com/note
5 stars 2 forks source link

以太坊的iban格式地址生成 #23

Open itlgl opened 6 years ago

itlgl commented 6 years ago

简介

imToken钱包转账的二维码扫描后信息为iban开头的一段字符串: iban:XE86G29C8IV34UOJMYWHGDSGME33YKEC3QO?account=100&type=ETH 这种格式是ICAP: 互换客户端地址协议定义的,有关ICAP协议的简介如下:

Interexchange Client Address Protocol, an IBAN-compatible system for referencing and transacting to client accounts aimed to streamline the process of transferring funds, worry-free between exchanges and, ultimately, making KYC and AML concerns a thing of the past. ICAP 互换客户端地址协议,一种IBAN兼容系统,用于引用和处理客户帐户,旨在简化资金转移流程,在交易所之间无忧无虑,并最终使KYC和AML成为过去。

以太坊有关ICAP的wiki:ICAP: Inter exchange Client Address Protocol

生成过程

比如在以太坊的wiki中,提到了一个例子:地址0x00c5496aee77c1ba1f0854206a26dda82a81d6d8对应的ICAP格式地址为XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS。 (ps:wiki中的例子本来是错误的,我给修改了,但是也没看到审核的过程,直接修改成功了)

步骤1:将地址转为36进制编码

将地址0x00c5496aee77c1ba1f0854206a26dda82a81d6d8看做是16进制编码的一个大数,把这个大数按照36进制编码输出并前补0到30个字符。 示例代码:

BigInteger value = new BigInteger(address, 16);
StringBuilder bban = new StringBuilder(value.toString(36).toUpperCase());
while (bban.length() < 15 * 2) {
    bban.insert(0, '0');
}

输出的字符串为:38O073KYGTWWZN0F2WZ0R8PX5ZPPZS

其实这也可以理解为base36编码的过程,编码对应的字符串范围为0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ

步骤2:计算校验位

1、 将36进制编码的字符串38O073KYGTWWZN0F2WZ0R8PX5ZPPZS前面补上XE00(数字0不是字母O),其中00的位置就是需要填写校验位的位置。 2、 将补上的XE00放到编码字符串的末尾(为啥不直接补在末尾?),此时字符串为38O073KYGTWWZN0F2WZ0R8PX5ZPPZSXE00 3、 对上面的字符串,取出每一位,按照0=0,1=1, ... 9=9,A=10,B=11 ... Z=35的规则,将单个字符转换以后的结果拼接成一个新的字符串:

3 8 O  0 7 3 K  Y  G  T  W  W  Z  N  0 F  2 W  Z  0 R  8 P  X  5 Z  P  P  Z  S  X  E  0 0
3 8 24 0 7 3 20 34 16 29 32 32 35 23 0 15 2 32 35 0 27 8 25 33 5 35 25 25 35 28 33 14 0 0

结果字符串为:38240732034162932323523015232350278253353525253528331400 4、 将上面的字符串看做是一个10进制的大数,除以97取余: 38240732034162932323523015232350278253353525253528331400 % 97=25 取余结果为25 5、 校验位=(97 + 1) - 25=73 6、 将73转为2位的字符串替换开头的校验位位置,ICAP结果就是:XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS

java代码实现

代码实现参见:IBAN.java

代码优化

代码中取余的操作是不断截取字符串换成int值取余做的,换成java的BigInteger类进行取余操作效率会更高 这个类代码也不多,把修改后的代码复制到这里保存:

import java.math.BigInteger;

public class IBAN {

    public static class Iban {
        private String address;
        private String token;
        private String amount;

        public Iban(String address, String token, String amount) {
            this.address = address;
            this.token = token;
            this.amount = amount;
        }

        public String getAmount() {
            return amount;
        }

        public String getAddress() {
            return address;
        }

        public String getToken() {
            return token;
        }

        public void setAmount(String amount) {
            this.amount = amount;
        }

        public void setAddress(String address) {
            this.address = address;
        }

        public void setToken(String token) {
            this.token = token;
        }

        @Override
        public String toString() {
            return "Iban [address=" + address + ", token=" + token + ", amount=" + amount + "]";
        }
    }

    private static boolean validateIBAN(String iban) {
        int len = iban.length();
        if (len < 4 || !iban.matches("[0-9A-Z]+"))
            return false;

        iban = iban.substring(4) + iban.substring(0, 4);

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < len; i++)
            sb.append(Character.digit(iban.charAt(i), 36));

        BigInteger bigInt = new BigInteger(sb.toString());

        return bigInt.mod(BigInteger.valueOf(97)).intValue() == 1;
    }

    public IBAN() {
        // TODO Auto-generated constructor stub
    }

    public Iban decode(String result) {
        int ibanEndpoint = result.indexOf("?");
        String iban = result.substring(5, ibanEndpoint < 0 ? result.length() : ibanEndpoint);
        String address = this.toAddress(iban);
        String query = result.substring(ibanEndpoint + 1, result.length());
        String[] params = query.split("&");
        String token = null;
        String amount = null;
        for (String param : params) {
            if (param.startsWith("token=")) {
                token = param.substring(6);
                continue;
            }
            if (param.startsWith("amount=")) {
                amount = param.substring(7);
            }
        }
        return new Iban(address, token, amount);
    }

    public String encode(String address, String token, String amount) throws Exception {
        return String.format("iban:%s?token=%s&amount=%s", this.toIban(address), token, amount);
    }

    private String toAddress(String iban) {
        String base36 = iban.substring(4);
        StringBuilder base16 = new StringBuilder(new BigInteger(base36, 36).toString(16));
        while (base16.length() < 20) {
            base16.insert(0, "0");
        }
        return "0x" + base16.toString().toLowerCase();
    }

    public String toIban(String address) throws Exception {
        if(address.length() != 42) {
            throw new Exception("The length of address is 42.");
        }
        address = address.toLowerCase().substring(2);
        BigInteger value = new BigInteger(address, 16);
        StringBuilder bban = new StringBuilder(value.toString(36).toUpperCase());
        while (bban.length() < 15 * 2) {
            bban.insert(0, '0');
        }
        String iban = "XE00" + bban;

        iban = iban.substring(4) + iban.substring(0, 4);
        StringBuilder code = new StringBuilder();
        for (int i = 0; i < iban.length(); i++) {
            char chr = iban.charAt(i);
            if (chr >= 'A' && chr <= 'Z') {
                int temp = chr - 'A' + 10;
                code.append(String.valueOf(temp));
            } else {
                code.append(String.valueOf((chr - '0')));
            }
        }

//        String remainder = code.toString();
//        String block;
//        while (remainder.length() > 2) {
//            int endPoint = remainder.length() >= 9 ? 9 : remainder.length();
//            block = remainder.substring(0, endPoint);
//            remainder = parseInt(block, 10) % 97 + remainder.substring(block.length());
//        }
//
//        int checkNum = parseInt(remainder, 10) % 97;
//        String checkDigit = ("0" + (98 - checkNum));
//        checkDigit = checkDigit.substring(checkDigit.length() - 2);

        // 把上面代码换成大数的取余操作
        BigInteger biTem = new BigInteger(code.toString(), 10);
        BigInteger biRemainder = biTem.remainder(BigInteger.valueOf(97));
        String checkDigit = BigInteger.valueOf(97 + 1).subtract(biRemainder).toString(10);
        checkDigit = "0" + checkDigit;
        checkDigit = checkDigit.substring(checkDigit.length() - 2);

        String ibanAddress = "XE" + checkDigit + bban;
        if (validateIBAN(ibanAddress)) {
            return ibanAddress;
        }
        return null;
    }

    public static void main(String[] args) throws Exception {
        IBAN iban = new IBAN();
        // TODO Auto-generated method stub
        String address = iban.toAddress("XE039RBH0XKV9FZMTH2701Q37FLX10NTWXU");
        System.out.println("IBAN to Address: " + address);

        String ibanAddress = iban.toIban("0x00c5496aee77c1ba1f0854206a26dda82a81d6d8");
        System.out.println("Address to IBAN: " + ibanAddress);
        String ibanAddress2 = iban.toIban("0xc94770007dda54cF92009BFF0dE90c06F603a09f");
        System.out.println("Address to IBAN: " + ibanAddress2);

        Iban ibanObj = iban.decode("iban:XE039RBH0XKV9FZMTH2701Q37FLX10NTWXU?token=ETH&amount=5");
        System.out.println("IBAN decode: " + ibanObj.toString());

        String ibanString = iban.encode("0x538b392D57d867A57eE8Eed05737cB08B4691302", "NBRC", "5");
        System.out.println("IBAN encode: " + ibanString);
        System.out.println("0x538b392d57d867a57ee8eed05737cb08b4691302".equals("0x538b392d57d867a57ee8eed05737cb08b4691302"));
    }
}

代码执行的log:

IBAN to Address: 0x538b392d57d867a57ee8eed05737cb08b4691302
Address to IBAN: XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS
Address to IBAN: XE15NIF30EOWCXHSNK5SW4QJRGJQ7NS3AN3
IBAN decode: Iban [address=0x538b392d57d867a57ee8eed05737cb08b4691302, token=ETH, amount=5]
IBAN encode: iban:XE039RBH0XKV9FZMTH2701Q37FLX10NTWXU?token=NBRC&amount=5
true