两个程序之间要加密通信,最经典的办法是各自的私钥永远不出门,却仍能算出同一把共享秘密——椭圆曲线上的 Diffie-Hellman(ECDH)。LND 把这个原语做成了一个单发接口 DeriveSharedKey:调用方递上一个临时公钥,节点用指定私钥乘出共享点,回传一个散列后的 32 字节密钥;节点自己的私钥从头到尾没离开过钱包。本文基于 0.19.0-beta 源码,讲清它的公式、默认键选择与消费端最容易踩的两个约定。
公式、默认值与返回物
proto 注释把数学写得直白:共享点 P 等于节点私钥乘上请求里的临时公钥,即 P_shared = privKeyNode * ephemeralPubkey。随后把这个共享的公共钥按压缩格式序列化,再做 sha256,得到最终 256 比特的密钥;响应字段只有一个 shared_key,注释确认它已经是哈希后的产物。私钥侧的选择由 key_desc 描述:可以填密钥定位器(指定钱包里的哪把钥匙),也可以直接填一把原始公钥;两者都不填时,默认用节点的身份私钥——也就是 getinfo 里那个 identity_pubkey 背后的钥匙。旧版单独的定位器字段已标记为弃用,统一收进了描述符。

约定一:默认键是身份私钥
“不填就用身份私钥”是个省事的默认,也是容易被误读的默认。身份私钥是节点的全局名片,用它派生的共享秘密意味着:凡是能拿到你 identity_pubkey 的对手方,都可以和你建立一对可复算的密钥。对”应用与自家节点之间”的通道加密来说这恰好合适——外部应用先查询节点公钥,再生成一对临时密钥,把临时公钥发来换取 shared_key,自己用临时私钥乘以节点公钥算出同一个点、同样 sha256,两边就此对上暗号,节点私钥全程不外露。但若你本意是”每次会话用不同的身份”,就必须显式指定 key 描述符,而不是顺手留空。
约定二:返回的是哈希后的密钥
消费端最常犯的错,是把 shared_key 当成”共享公共钥本身”去拼协议。0.19 的响应只有哈希版本:拿到的是 sha256(压缩共享点),不是未散列的曲线点。对端在自己一侧复算时,必须先做同样的压缩序列化、再套同一层 sha256,任何一步序列化方式不同(压缩与非压缩、有无前缀字节),两端就永远对不上。另一个实现细节藏在注释的推导里:乘法用的临时公钥是请求里传入的字节,调用方需保证它是曲线上合法点,接口不会替你验点。
什么场景用它,什么场景不该用
合适的场景:与外部应用建立加密通道(自定义消息内容的端到端加密)、给配对流程派生一次性会话密钥、在不暴露长期私钥的前提下与第三方协商对称密钥。不适合的场景:想拿它当随机数源——输出由特定输入决定,不是无源熵;想拿它替代签名——它不证明身份,只协商秘密;想导出公钥做验证——响应里没有公钥,只有散列。一句话,它是密钥协商的管道,不是身份系统,也不是存储。
再补一遍对称性账目,方便两端各自核对:外部应用生成一把临时密钥对,把临时公钥发进请求;节点用自己的私钥乘它得到共享点,散列后回传。应用这侧从不需要节点的私钥,只要拿节点的公钥乘自己的临时私钥,得到的曲线点是同一个,按同样的压缩格式序列化、同样做 sha256,两端字符串自然相等。整个往返里节点私钥只参与乘法、从不出门,这正是它对集成方最有价值的地方:把”知道私钥”与”使用私钥”拆成了两件事。
风险提示:使用 ECDH 派生密钥做加密时,序列化与散列约定必须两端严格一致,协议实现缺陷可能导致加密失效;密钥协商本身不提供抗重放能力,安全设计还需结合完整协议栈;本文不构成任何投资建议。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。