技术 › 密钥与地址

子地址

从一对密钥生成任意多个无法关联的地址

简而言之

索引为 (i, j) 的子地址是一对点 D = B + m·G 和 C = a·D,其中 m = Hs("SubAddr\0" ‖ a ‖ i ‖ j)。没有查看私钥 a,就无法判断这些子地址属于同一个钱包。

为什么需要子地址

如果你把同一个地址给了两个不同的人,他们一对比就会发现自己付款给了同一个收款人。子地址解决了这个问题:每个付款人一个地址,而钱包仍然只有一个,扫描速度也不会变慢。

子地址构成两级结构:账户 i(主索引)和账户内索引 j(次索引)。子地址 (0, 0) 就是主地址。

为什么地址 (1,0) 不以 4 开头?

一个钱包只有一个主地址——(0,0),它就是带有前缀 18 的 B 和 A。任何其他索引,包括第二个账户的“第一个”地址 (1,0),都是子地址:它的密钥通过 m = Hs("SubAddr\0" ‖ a ‖ 1 ‖ 0) 派生,并以前缀 42 编码,因此以 8… 开头。账户只是对子地址进行分组的一种方式;它没有属于自己的“主”地址。

如何计算

m = Hs( "SubAddr\0" ‖ a ‖ i ‖ j )
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。这样接收方照常计算共享秘密:

a·R = a·r·D = r·(a·D) = r·C

发送方知道 r 和 C,接收方知道 a 和 R,双方得到的是同一个点。

钱包如何识别自己的子地址

钱包会为几百个索引预先计算 D,并保存一张 D → (i, j) 的表。对于每个输出,它计算一个候选值

D' = P − Hs(8·a·R ‖ n)·G

然后在表中查找 D'。找到了——这个输出就是我们的,而且我们还知道它是发到哪个子地址的。这就是子地址不会拖慢扫描的原因:只需一次减法和一次哈希表查询。

细节:子地址与多个输出

如果一笔交易中有发往子地址的输出,发送方钱包会为每个输出使用单独的交易公钥(即 tx_extra 中的附加公钥)。否则,接收方就可以检测发送方的找零是否属于某个地址。