ICAP: 互换客户端地址协议

什么是ICAP?从以太坊Homestead指南的词汇表中可以看出:

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的介绍:

在第三方账户之间(特别是交易所账户)之间转账资金给用户带来了相当大的负担,并且由于客户账户中的存款被识别的方式而容易出错。现有的银行业通过拥有一个被称为IBAN的通用代码解决了这个问题。该代码合并了机构和客户帐户以及错误检测机制,实际上消除了微不足道的错误并为用户提供了相当大的便利。不幸的是,这是一个严格监管和集中的服务,只有大型的,完善的机构才能使用。目前的议定书ICAP可被视为适用于以太坊系统中任何含有资金的机构的分散版本。

IBAN介绍

国际银行账户号码[1] (International Bank Account Number,简称IBAN)是各国各银行之间互相定立的标识号码,可降低国际间金融操作的失误。它最初是由欧洲银行标准委员会(ECBS)通过,后来被采纳为国际标准 ISO 13616:1997。目前的标准是ISO 13616:2007,表明SWIFT代码(ISO 9362)为正式的格式。最初开发是为了促进欧盟范围内的支付,但现在也已经实施到大多数欧洲国家和其他国家,尤其是在中东和加勒比海地区。IBAN最多包含34个字母和数字字符:首两个字母是ISO 3166-1α-2国家代码,然后两个校验位,校验位可检查完整性。最后一个是特定国家的基本银行账户号码(BBAN)。BBAN格式的决定是由每个国家的银行界的约束下,它必须是一个固定长度的不区分大小写的英数字。它包括国内银行账户号码,银行分行的号码,和潜在的路由信息。

基本银行账户号码

基本银行账户号码(The Basic Bank Account Number,简称BBAN)的格式是由国家中央银行或相应机关所订定,格式并没有强制性。一国的基本银行账户号码须为固定长度且由大小写不敏感的英数字组成。其包括本国账户号码,子分支辨识码与路径资讯。各国皆可拥有不同的编号系统,最多三十字。

IBAN结构

IBAN代码由多达34个不区分大小写的字母数字字符组成,其字符取值范围为0-9和A-Z。它包含三个信息:

  • 国家代码; 以下内容的顶级标识符(ISO 3166-1 alpha-2);
  • 错误检测代码; 使用mod-97-10校验和协议(ISO / IEC 7064:2003);
  • 基本银行账号(BBAN); 该机构,分支和客户账户的标识符,其组成取决于上述国家。

举例来说,从维基百科可以得知,英国的IBAN格式定义为:

国家 格式 说明
英国 GBkk bbbb ssss sscc cccc cc b = 银行代码 s = 银行分类代码 c = 账号

GBkk bbbb ssss sscc cccc cc 格式解读为:

[国家代码:GB][错误校验码:kk][基本银行账号:bbbb ssss sscc cccc cc]

对于英国来说,BBAN由以下部分组成:

  • 机构标识符,4个字符的字母,例如 MIDL 代表汇丰银行。
  • 分类代码(机构内的分行标识符),一个6位十进制数字,例如402702汇丰银行的Lancaster分行。
  • 帐号(分支机构内的客户标识符),一个8位十进制数字。

以太坊ICAP设计

以太坊引入了新的IBAN国家代码:XE,X前缀为扩展的意思,E表示为以太坊(Ethereum),制定为非法定货币(例如XBOX, XEOS)。以太坊在IBAN基础上设计了ICAP,其设计思路与IBAN兼容。目的是为了方便各大公链之间转账?它有三种BBAN代码类型:直接类型,基本类型和间接类型。

直接类型

此代码的直接BBAN为30个字符,将包含一个字段:

  • 帐户标识符,30个字母数字(<155位)。这将被解释为表示160位以太坊地址的最低有效位的大端编码的36进制整数。因此,这些以太坊地址通常以零字节开始。

例如XE 73 38O073KYGTWWZN0F2WZ0R8PX5ZPPZS,它的对应的地址00c5496aee77c1ba1f0854206a26dda82a81d6d8

基本类型

与直接编码相同,但代码为31个字符(与IBAN不兼容)并组成相同的单个字段:

  • 帐户标识符,31个字符字母数字(<161位)。这将被解释为一个代表160位以太坊地址的大端编码的36进制整数。

间接类型

该代码的BBAN在间接时将为16个字符,并将包含三个字段:

  • 资产标识符,3个字符的字母数字(<16位);
  • 机构标识符,4个字符的字母数字(<21位);
  • 机构客户标识符,9个字符的字母数字(<47位);

包括四个首字符,这导致最终的客户帐户地址长度为20个字符,格式为:

XE81ETHXREGGAVOFYORK

分成:

  • XE 以太坊的国家代码;
  • 81 校验和;
  • ETH 客户账户中的资产标识符 - 在这种情况下,“ETH”是唯一有效的资产标识符,因为以太坊的基础注册管理机构合同仅支持该资产;
  • XREG 账户的机构代码 - 在这种情况下,以太坊的基础注册管理机构合同;
  • GAVOFYORK 该机构内的客户标识符 - 在这种情况下,直接支付且无任何主要地址的额外数据与以太坊基础注册合同中的名称“GAVOFYORK”相关联;

URI表示形式

iban:XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS

具体实现:以太坊地址转换成ICAP格式

转换函数

//ConvertAddressToICAP 将以太坊地址转换为ICAP格式的地址
func ConvertAddressToICAP(a common.Address) (string, error) {
    enc := base36Encode(a.Big())
    if len(enc) < 30 {
        enc = join(strings.Repeat("0", 30-len(enc)), enc)
    }
    // 以太坊国家代码XE + 校验码 + 账号
    icap := join("XE", checkDigits(enc), enc)
    return icap, nil
}

36进制编码

其规则如同十进制数转十六进制数,其算法如下:

十六进制数取值范围表示s="0123456789ABCDEF"。其字符串数组下标范围为0-15,其中对应的下标:A=11,B=12… F=15。

记余数数组a,

  1. 把10进制数除以16,获取整数商和余数,记下余数和整数商,并把余数放入余数数组a;
  2. 整数商不为0时,再次执行第一个步骤,整数商为0时停止,并记录下此时的余数;
  3. 倒排余数数组a,同时影射每个位置值到s字符串数组下标对应的值。

例如:十进制数505,500/16 得31余9,将9放入余数数组a,由于31不为0,再次用31/16 得1余15,将15放入a,1不为0,再次用1/16 得0余1,将1放入a,倒排a,得[s[1],s[15],s[9]] = ['1', 'F', '9'] = "0x1F9" = 505。

36进制编码也是采用了如上算法,只是s="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"。

var (
    Base36Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    Big36 = big.NewInt(36)
)
func base36Encode(i *big.Int) string {
    var chars []rune
    x := new(big.Int)
    for {
        x.Mod(i, Big36)
        chars = append(chars, rune(Base36Chars[x.Uint64()]))
        i.Div(i, Big36)
        if i.Cmp(Big0) == 0 {
            break
        }
    }
    // reverse slice
    for i, j := 0, len(chars)-1; i < j; i, j = i+1, j-1 {
        chars[i], chars[j] = chars[j], chars[i]
    }
    return string(chars)
}

校验码

校验码使用了mod-97-10校验和协议 (ISO / IEC 7064:2003),查看ISO具体协议需要8,800瑞士法郎。

举例,对于字符串794,其算法步骤为:

步骤1:追加两个零占据校验字符位置:79400;
步骤2:除以97,得到商818和整数余数54;
步骤3:将校验字符值确定为(97 + 1) - 54 = 44并将其附加到原始字符串以给出79444。

为了检查,将字符串除以97; 如果提醒是1,则校验通过。

计算国际银行帐号(IBAN)的校验位(xx yyy zzzzzzzz kk)

  • 余数不等于0的计算。

不包括支票号码的银行账号:06 000 01234567。

a:06 000 01234567 00
b:060000123456700:97 = 618557973780余数= 40
c:(97 + 1) - 40 = 58结果:06 000 01234567 58

校验和:06000123456758:97 = 618557973781其余= 1

  • 无余数计算或余数等于0。

不包括支票号码的银行账号:06 000 01234586。

a:06 000 01234586 00
b:060000123458600:97 = 618557973800其余= 00
c:(97 + 1) - 00 = 98结果:06 000 01234586 98

校验和:06000123458698:97 = 618557973801余数= 1。

func checkDigits(s string) string {
    expanded, _ := iso13616Expand(strings.Join([]string{s, "XE00"}, ""))
    num, _ := new(big.Int).SetString(expanded, 10)
    //mod-97-10
    num.Sub(Big98, num.Mod(num, Big97))

    checkDigits := num.String()
    // zero padd checksum
    if len(checkDigits) == 1 {
        checkDigits = join("0", checkDigits)
    }
    return checkDigits
}

// not base-36, but expansion to decimal literal: A = 10, B = 11, ... Z = 35
func iso13616Expand(s string) (string, error) {
    var parts []string
    if !validBase36(s) {
        return "", errICAPEncoding
    }
    for _, c := range s {
        i := uint64(c)
        if i >= 65 {
            //字符A-Z在ASCII码表中分别对应10进制值为65,66...
            //字符A-Z的36进制字符串索引分别是A=10,B=11...
            //字符的码表值-字符的索引值=55
            //字符码表值-55=字符的索引值
            parts = append(parts, strconv.FormatUint(uint64(c)-55, 10))
        } else {
            parts = append(parts, string(c))
        }
    }
    return join(parts...), nil
}

具体实现:ICAP格式转换成以太坊地址

校验

func validCheckSum(s string) error {
    s = join(s[4:], s[:4])
    expanded, err := iso13616Expand(s)
    if err != nil {
        return err
    }
    checkSumNum, _ := new(big.Int).SetString(expanded, 10)
    if checkSumNum.Mod(checkSumNum, Big97).Cmp(Big1) != 0 {
        return errICAPChecksum
    }
    return nil
}

转换

func parseICAP(s string) (common.Address, error) {
    if !strings.HasPrefix(s, "XE") {
        return common.Address{}, errICAPCountryCode
    }
    if err := validCheckSum(s); err != nil {
        return common.Address{}, err
    }
    // checksum is ISO13616, Ethereum address is base-36
    bigAddr, _ := new(big.Int).SetString(s[4:], 36)
    return common.BigToAddress(bigAddr), nil
}

用途

imToken钱包二维码使用了ICAP格式,扫码之后出现的格式为:

iban:XE86G29C8IV34UOJMYWHGDSGME33YKEC3QO?account=100&type=ETH

其中XE86G29C8IV34UOJMYWHGDSGME33YKEC3QO是转账的地址,account是转账的数额,type是转账的类型,这里代表转ETH。

BOX将兼容imToken格式。

完整的代码片段可以在这里找到:https://gist.github.com/alphaqiu/646d45fc5c2b1f42d8529d55b52f58d2

参考

1.) Wikipedia: International Bank Account Number

2.) ICAP: Inter exchange Client Address Protocol

3.) Open source: BOX a business wallet solution

你可能感兴趣的:(ICAP: 互换客户端地址协议)