以太坊的密码学(四)-以太坊地址

介绍以太坊地址的生成及其校验

Posted by HonorJoey on March 25, 2020

以太坊地址

以太坊地址是唯一标识符,从公钥或者合约通过单向哈希函数Keccak-256计算而来。

在之前的例子中,我们从私钥开始,通过椭圆曲线乘法运算获得了一个公钥。

私钥k:

k = f8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315

公钥K(x和y坐标组合后以十六进制的方式显示):

K = 6e145ccef1033dea239875dd00dfb4fee6e3348b84985c92f103444683bae07b83b5c38e5e...

在进行以太坊地址运算时,公钥的前缀必须是十六进制的04。

我们使用Keccak-256来计算公钥的哈希值:

Keccak256(K) = 2a5bc342ed616b5ba5732269001d3f1ef827552ae1114027bd3ecf1f086ba0f9

然后只保留后20位(大端序中最末的字节),这就是我们的以太坊地址:

001d3f1ef827552ae1114027bd3ecf1f086ba0f9

很多情况下,你会看到以太坊地址带有0x的前缀,这表示它采用十六进制的表示方式。

0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9

以太坊地址格式

以太坊地址是十六进制的数字,来自于公钥经过Keccak-256哈希运算之后得出结果的后20位。

不同于比特币,比特币地址中包含了一个内置的校验器用于防止可能的错误地址输入。以太坊的地址是原生的十六进制数据,没有任何校验信息。

这个决定背后的原因在于,以太坊地址通常都隐藏在类似域名服务等高层抽象之后,如果需要,对应的校验也应该在抽象层之上实现。

然而,由于抽象层的开发十分缓慢,这样的设计决策也导致了一些问题,比如在网络生态早期,由于输入错误地址导致的一些以太币丢失。以太坊域名服务的开发进度较预期更慢,而钱包的其他编码标准也并没有在开发者中得到广泛应用。我们会在下文中介绍几种编码选择。

ICAP协议

互换客户端地址协议(ICAP)是一种与国际银行账号(IBAN)编码部分兼容的以太坊地址编码形式,为以太坊地址提供通用、经校验且可互操作的编码。ICAP地址可以用于编码以太坊地址,也可以用于编码注册在以太坊域名注册服务之下的通用名称。关于ICAP的更多内容,可以在Ethereum Wiki(http://bit.ly/2JsZHKu)上看到。

IBAN是一个国际通用的用于识别银行账户的编码体系,通常用于电汇转账,在欧元区等地域广泛使用。IBAN是一个中心化的、受到高度监管的服务。ICAP是一个去中心化实现,但仍与以太坊地址相兼容。

IBAN是一个包括34个数字字符(不区分大小写)的字符串,含有国家代码、校验码和每个国家内的银行识别码。

ICAP使用了与IBAN相同的结构,以代表以太坊的非标准国家代码“XE”开头,后接两位校验码与三个可能的账户标识符变量:

直接式

最多30个字符的大端序base-36编码,代表以太坊地址的末155比特。因为该编码方式无法包含通用以太坊地址的全部160比特,所以只能用于由一个或者多个零字节起始的以太坊地址。其优势在于与IBAN的字段长度和校验码格式兼容,例如:XE60HAMICDXSV5QXVJA7TJW47Q9CHWKJD(33字符)。

基础式

与直接式类似的编码方式,唯一区别是有31个字符。该方式可以编码任何以太坊地址,但结果是这样的编码与IBAN字段的验证不兼容。例如:XE18CHDJBPLTBCJ03FE9 O2NS0BPOJVQCU2P(35字符)。

非直接式

在编码中加入一个可以通过以太坊域名注册服务解析出以太坊地址的标识符。非直接式使用16位字符,包含一个资产识别符(如ETH)、一个域名(如XREG)和9个字符的人类可读的名字(如KITTYCATS)。例如:“XE##ETHXREGKITTYCATS(20字符),##是两个经过计算得出的校验字符。

我们可以使用helpeth命令行工具创建ICAP地址。现在我们使用之前获得的那个私钥试验一下(给私钥添加0x前缀,然后将其作为helpeth命令的参数):

$ helpeth keyDetails \
    -p 0xf8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315
Address: 0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9
ICAP: XE60 HAMI CDXS V5QX VJA7 TJW4 7Q9C HWKJ D
Public key: 0x6e145ccef1033dea239875dd00dfb4fee6e3348b84985c92f103444683bae07b...

在提供ICAP地址的同时,helpeth命令也构建了一个十六进制的以太坊地址。在我们这个例子中生成的ICAP地址是:

XE60HAMICDXSV5QXVJA7TJW47Q9CHWKJD

因为我们的以太坊地址恰巧是从零开始的,所以可以使用ICAP编码方法中的“直接式”获得一个兼容IBAN格式的地址。可以看出它们是兼容的,因为这个ICAP地址的长度为33字符。

如果你的以太坊地址不是从零开始的,那么只能通过“基础式”编码,生成的地址是35字符长,与IBAN格式不兼容。

 获得从零开始的以太坊地址的概率是1/256。为了生成这样一个地址,需要平均对256个不同的私钥进行运算,才能获得一个能够产生IBAN兼容格式的公钥地址。

目前阶段,只有少量的钱包支持ICAP地址格式。

EIP-55:十六进制编码地址的大写校验

由于ICAP和域名服务推进的速度缓慢,有人提出了一个新的编码标准方案(EIP-55)(https://github.com/Ethereum/EIPs/blob/master/EIPS/eip-55.md)。EIP-55为以太坊地址提供了一种后向兼容的校验机制,这个机制是通过修改十六进制地址中的大写字母来实现的。以太坊地址并不区分大小写,对于钱包软件来说,不论是包含大写字母还是小写字母格式的地址都是一样的。

通过修改地址中字母的大小写,我们可以获得一种校验,用于保护地址的完整性,避免地址输入式的人为错误。不支持EIP-55的钱包软件只需要忽略这些地址中存在的大小写组合形式。那些支持EIP-55的钱包软件,可以通过这些大小写来验证地址的正确性,错误检出率高达99.986%。

混合大小写编码方式的改动非常微妙,你在最初可能无法注意到其中的变化。我们的示例地址是:

0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9 通过EIP-55混合大小写编码后,地址变为:

0x001d3F1ef827552Ae1114027BD3ECF1f086bA0F9

你能看出其中的区别吗?十六进制中的字符(A~F)有一部分已经变成大写了,有些却仍旧保持小写。

EIP-55的实现非常简单。我们对全小写的十六进制地址计算Keccak-256哈希。这个哈希被视为地址的数字指纹,为我们提供了一个方便的校验方式。任何地址中的微小变化,都会在对应的哈希中带来巨大的变化。这样我们就能发现可能存在的输入或者打字错误。接着,把这个哈希校验信息通过大写修改的方式融合到地址中。我们一步一步来看看是如何实现的:

  • 1.针对全小写的地址计算一次哈希,不包括0x前缀:
Keccak256("001d3f1ef827552ae1114027bd3ecf1f086ba0f9") =
23a69c1653e4ebbb619b0b2cb8a9bad49892a8b9695d9a19d8f673ca991deae1
  • 2.注意检查地址中的字母,如果在哈希中对应的十六进制大于或者等于8,就把这个字母改为大写。如果我们把地址和它的十六进制哈希值并列在一起,就很容易看出端倪:
Address: 001d3f1ef827552ae1114027bd3ecf1f086ba0f9
Hash   : 23a69c1653e4ebbb619b0b2cb8a9bad49892a8b9...

在我们地址中,第四位有一个小写的d,哈希中对应位数的数值是6,因为它小于8,所以我们保持d的小写格式不变。地址中下一个字母是f,位于第六位。哈希值中对应位置的数字是c,它比8要大,因此我们把地址中的f改为大写的F。以此类推。如你所见,我们只使用了哈希值中的前20字节(40个十六进制字符)作为校验,因为地址中只有20字节。

检查地址中剩余的字符,看看你是否可以判断哪一个字母应该改为大写,它对应在哈希中的内容是什么?

Address: 001d3F1ef827552Ae1114027BD3ECF1f086bA0F9
Hash   : 23a69c1653e4ebbb619b0b2cb8a9bad49892a8b9...

用EIP-55编码方式检测错误

现在,我们来看看EIP-55格式的地址如何帮助我们发现错误。假设我们打印出了一个以太坊地址,是用EIP-55格式编码的:

0x001d3F1ef827552Ae1114027BD3ECF1f086bA0F9

现在假设我们在使用这个地址的时候犯了一个小错误。最后一个字母是大写的F,假设不小心把这个字母看成了E。我们把这个错误的地址输入钱包:

0x001d3F1ef827552Ae1114027BD3ECF1f086bA0E9

幸运的是,我们的钱包兼容EIP-55格式。它注意到了地址中混合的大小写字母,并且会尝试验证这个地址是否正确。钱包会把地址改为全小写,然后计算它的哈希:

Keccak256("001d3f1ef827552ae1114027bd3ecf1f086ba0e9") =
5429b5d9460122fb4b11af9cb88b7bb76d8928862e0a57d46dd18dd8e08a6927

如你所见,尽管地址只有一个字母出错了(而且e和f之间其实只有一个比特的差异而已),但这个地址的哈希发生了彻底的变化。这就是哈希函数带来的功能,可以用来生成非常有效的校验数据!

现在,我们把地址和它的哈希对齐,来检查大小写是否满足校验:

001d3F1ef827552Ae1114027BD3ECF1f086bA0E9
5429b5d9460122fb4b11af9cb88b7bb76d892886...

全都乱套了!有几个字母被错误地设定成了大写。现在,记住,大写的位置应该跟哈希校验的值匹配对应。

我们输入的地址中的大小写并不满足刚才计算得出的结果,这就意味着地址发生了变化,输入过程中发生了错误。