子地址
从一对密钥生成任意多个无法关联的地址
索引为 (i, j) 的子地址是一对点 D = B + m·G 和 C = a·D,其中 m = Hs("SubAddr\0" ‖ a ‖ i ‖ j)。没有查看私钥 a,就无法判断这些子地址属于同一个钱包。
为什么需要子地址
如果你把同一个地址给了两个不同的人,他们一对比就会发现自己付款给了同一个收款人。子地址解决了这个问题:每个付款人一个地址,而钱包仍然只有一个,扫描速度也不会变慢。
子地址构成两级结构:账户 i(主索引)和账户内索引 j(次索引)。子地址 (0, 0) 就是主地址。
一个钱包只有一个主地址——(0,0),它就是带有前缀 18 的 B 和 A。任何其他索引,包括第二个账户的“第一个”地址 (1,0),都是子地址:它的密钥通过 m = Hs("SubAddr\0" ‖ a ‖ 1 ‖ 0) 派生,并以前缀 42 编码,因此以 8… 开头。账户只是对子地址进行分组的一种方式;它没有属于自己的“主”地址。
如何计算
D = B + m·G C = a·D i 和 j 是 32 位小端序整数;"SubAddr\0" 共 8 字节,末尾是一个零字节
子地址的编码方式与普通地址相同,只是前缀为 42(在主网上以 8 开头):Base58(42 ‖ D ‖ C ‖ checksum)。
注意,子地址的公开“查看公钥” C 不是 a·G,而是 a·D。这个技巧是发送时所必需的。
向子地址发送
向普通地址发送时,交易公钥是 R = r·G。向子地址发送时,发送方使用 R = r·D。这样接收方照常计算共享秘密:
发送方知道 r 和 C,接收方知道 a 和 R,双方得到的是同一个点。
钱包如何识别自己的子地址
钱包会为几百个索引预先计算 D,并保存一张 D → (i, j) 的表。对于每个输出,它计算一个候选值
然后在表中查找 D'。找到了——这个输出就是我们的,而且我们还知道它是发到哪个子地址的。这就是子地址不会拖慢扫描的原因:只需一次减法和一次哈希表查询。
如果一笔交易中有发往子地址的输出,发送方钱包会为每个输出使用单独的交易公钥(即 tx_extra 中的附加公钥)。否则,接收方就可以检测发送方的找零是否属于某个地址。