目标
为什么以太坊地址有大小写混杂?
带着问题
- 为什么以太坊地址有大小写混合的情况?
- 为什么将大小写地址其中一位改成小写metamask就无法识别呢?
- 如何校验地址的正确性?
EIP-55
Ethereum Improvement Proposal 55:混合大小写的校验和地址编码,是以太坊改进提案的编号,旨在解决以太坊地址在人类输入和显示时可能导致的错误和混淆问题。
原理
通过修改十六进制地址的大小写,EIP-55为以太坊地址提供了向后兼容的校验和。这个想法是,以太坊地址不区分大小写,所有钱包都应该接受以大写字母或小写字母表示的以太坊地址,在解释上没有任何区别。
通过修改地址中字母字符的大小写,我们可以传达一个校验和,可以用来保护地址完整性,防止输入或读取错误。 不支持EIP-55校验和的钱包简单地忽略地址包含混合大写的事实。但那些支持它的人可以验证它并以99.986%的准确度检测错误。
什么是校验和?
校验和(Checksum)是一种在数据传输或存储过程中用来验证数据完整性的方法。它通过在原始数据中添加一些校验信息(通常是一些校验位或校验码),使得接收端可以通过对收到的数据进行计算,验证数据是否在传输或存储过程中被修改或损坏。
规范
采用小写十六进制地址的Keccak-256哈希,这个哈希作为地址的数字指纹,给我们一个方便的校验和。输入(地址)中的任何小改动都会导致哈希结果(校验和)发生很大变化,从而使我们能够有效地检测错误。然后我们的地址的哈希被编码为地址本身的大写字母。
const createKeccakHash = require('keccak');
function toChecksumAddress(address) {
address = address.toLowerCase().replace('0x', '');
const hash = createKeccakHash('keccak256').update(address).digest('hex');
let ret = '0x';
for (let i = 0; i < address.length; i++) {
if (parseInt(hash[i], 16) >= 8) {
ret += address[i].toUpperCase();
} else {
ret += address[i];
}
}
return ret;
}
// 只改变大小写
toChecksumAddress('0x02d1e4A4A35914a3b5dE03b3864e113c4fb6Db56') === '0x02d1e4A4A35914a3b5dE03b3864e113c4fb6Db56' // false
输出:0x02d1e4A4A35914a3b5dE03b3864e113c4fb6DB56
输入:0x02d1e4A4A35914a3b5dE03b3864e113c4fb6Db56
// 改变其中一个字符
toChecksumAddress('0x02d1e4A4A35914a3b5dE03b3864e113c4fb6Db57') === '0x02d1e4A4A35914a3b5dE03b3864e113c4fb6DB57' // false
hash: cb5541d2d4302d4f69dfb38fe3f1273bf441cfd6e4f698ed9be263d76be031b4
输出: 0x02d1e4A4A35914a3b5DE03B3864e113C4fb6DB57
输入: 0x02d1e4A4A35914a3b5dE03b3864e113c4fb6Db57
优势
-
向后兼容许多接受混合大小写的十六进制解析器,使其能够随着时间的推移轻松引入
-
将长度保持在 40 个字符
-
平均每个地址将有15个校验位,如果输入错误,随机生成的地因抄写错误意外通过检查的净概率为0.0247%。 这比ICAP提高了约50倍。
现状
-
Metamask 转账时,如果to地址是大小写混合地址,会进行EIP55的校验
-
Okx 转账时,只做地址合法性的校验(长度和16进制的判断)
-
已反馈okx钱包开发,会反馈产品考虑添加EIP55的地址校验。
比特币的地址校验
比特币地址生成:
address = base58encode(Version+Publickeyhash+Checksum)
// bc1p2d3a05pvdlpnzyzl44fhvd8jyhq5av5p4zgraywuprlzv2zpq5aq7wlscw
// bc|1|3 hash xxxx
- 合约地址能不能支持EIP55的校验?
- Btc 地址校验细节?
function isBitcoinAddress(address) {
try {
// 第10步:base58解码
const arr = bs58.decode(address);
const buf = Buffer.from(arr);
console.log(arr);
// 第9步:分成两个字节数组
const checksum = buf.slice(-4);
const bytes = buf.slice(0, buf.length - 4);
console.log(checksum.toString(`hex`));
console.log(bytes.toString(`hex`));
// 第6步(SHA256)
const shax1 = crypto.createHash(`sha256`).update(bytes).digest();
console.log('shax1', shax1.toString(`hex`));
// 第7步(SHA256)
const shax2 = crypto.createHash(`sha256`).update(shax1).digest();
console.log(shax2.toString(`hex`));
// 第8步.取校验和
const newChecksum = shax2.slice(0, 4);
// 校验和跟字节数组[checksum]比较。如果相同校验通过。
if (checksum.toString('hex') !== newChecksum.toString('hex')) {
throw new Error('Invalid checksum');
}
// 第5步.比特币主网版本号
const version = buf.toString('hex').slice(0, 2);
console.log(version);
// 检验版本号的合法性(主网00)00 为普通地址,05为脚本地址,注意大小写。
if (version !== '00' && version !== '05') {
throw new Error('Invalid version');
}
} catch (e) {
console.log('catch', e)
return false;
}
return true;
}
console.log('isBitcoinAddress', isBitcoinAddress('3N1hLk7Re66PTSjS7RTn8c4s3ru7J4WSCR'))