简介
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
文档信息
- 本文作者:itlgl
- 本文链接:https://itlgl.com/note/2018/07/11/issues-23/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)