技术 › 密钥与地址

地址

95 个字符:网络前缀、两把公钥和校验和,采用特殊的 Base58 编码

简而言之

标准门罗币地址 = Base58( prefix ‖ B ‖ A ‖ checksum )。前缀表明网络和地址类型,B 和 A 是花费公钥和查看公钥,校验和是对前面所有内容计算 Keccak-256 后的前 4 个字节。

结构

12 B —— 花费公钥 A —— 查看公钥 校验和 1 字节 32 字节 32 字节 4 字节 69 字节 → 门罗币 Base58 → 95 个字符

网络前缀

前缀以 varint 形式写入(较小的数只占一个字节)。由于 Base58 的工作方式,前缀决定了地址的第一个字符:

网络标准地址集成地址子地址
mainnet18 → 4…19 → 4…42 → 8…
stagenet24 → 5…25 → 5…36 → 7…
testnet53 → 9…/A…54 → A…63 → B…

校验和

checksum = Keccak256( prefix ‖ B ‖ A )[0..4]

如果地址中有输入错误,校验和就对不上,钱包会拒绝发送。

门罗币的 Base58 不同于比特币的

字母表相同(不含 0 O I l),编码方式不同。比特币把整条消息变成一个大数,再转换为 58 进制。门罗币把数据切成 8 字节的块,每个块单独编码为恰好 11 个字符(最后一个不完整的块按下表编码为更少的字符)。

块中字节数12345678
字符数2356791011

69 字节 = 8 个完整块(88 个字符)+ 5 字节(7 个字符)= 95 个字符。好处是:地址长度始终固定,编码耗时与长度成线性关系。

const ENC_BLOCK = [0, 2, 3, 5, 6, 7, 9, 10, 11];
function encodeBlock(block) {
  let n = 0n;
  for (const x of block) n = (n << 8n) | BigInt(x);       // 块内使用大端序
  let s = '';
  while (n > 0n) { s = ALPHABET[Number(n % 58n)] + s; n /= 58n; }
  return s.padStart(ENC_BLOCK[block.length], '1');
}

集成地址

集成地址(前缀 19)= 标准地址 + 8 字节的支付 ID(共 106 个字符)。支付 ID 在交易中被加密,帮助商店判断是哪个订单付了款。如今更推荐使用子地址:每个订单使用一个新的子地址。

地址中不包含什么

重要的是,地址永远不会被写入区块链。发送方用 A 和 B 构造一个一次性地址,只有它会进入区块链。