cjdns 消息签名与验签完全指南:Sign_sign 与 Sign_checkSig RPC 实战 网络网络安全【免费下载链接】cjdnsAn encrypted IPv6 network using public-key cryptography for address allocation and a distributed hash table for routing.项目地址https://gitcode.com/gh_mirrors/cj/cjdns点击查看免费下载cjdns 通过管理 RPCAdmin RPC内置了一套基于 ed25519 的签名与验签接口节点可用自己的私钥对消息摘要签名Sign_sign()任何持有签名者都能通过Sign_checkSig()验签并还原签名者的 cjdns IPv6 地址与公钥实现无需可信第三方的身份证明。本文将围绕 doc/Sign.md 展开结合 crypto/Sign.c、crypto/Sign_admin.c 等源码完整覆盖两个 RPC 的请求格式、返回字段、边界条件与全部错误场景并深入剖析其密钥推导与签名算法细节让读者不仅能直接上手调用还能理解其底层原理。一、背景为什么 cjdns 需要一套自己的签名接口cjdns 网络的核心身份体系建立在 Curve25519 公钥之上——节点的 IPv6 地址由公钥经哈希计算得出crypto/AddressCalc.h地址即身份。但历史上 cjdns 只具备加密能力Curve25519 用于密钥交换不具备签名能力。当需要向网络中的其他节点证明这条消息确实由某个 cjdns 地址发出时就需要一套与现有身份体系无缝衔接的签名方案。因此 cjdns 实现了签名模块复用节点现有的 Curve25519 私钥即cjdroute.conf中配置的privateKey将其转换为 ed25519 签名密钥从而做到签名者身份直接可映射回 cjdns 公钥与 IPv6 地址。这正是 crypto/Sign.c 源码注释中明确说明的设计动机——为了不破坏所有已有cjdroute.conf的存量用户签名模块必须能直接使用既有加密密钥。二、签名与验签的 RPC 入口总览签名功能通过管理 RPC 对外暴露共两个端点均在节点启动时注册见 admin/angel/Core.c 中Sign_admin_register(privateKey, admin, rand, alloc)的调用RPC 端点功能是否需要认证注册源码位置Sign_sign()用节点私钥对消息摘要签名是needsAuth truecrypto/Sign_admin.cSign_checkSig()验证签名并返回签名者身份否needsAuth falsecrypto/Sign_admin.c需要特别说明两点Sign_sign()必须走认证通道。因为签名操作会动用节点私钥RPC 注册时needsAuth参数为truecrypto/Sign_admin.c意味着发起请求的客户端必须持有管理密码完成认证而Sign_checkSig()的needsAuth为false任何能连接 Admin 端口的一方都可以验签。签名使用节点的身份私钥即节点自身的privateKey该密钥在节点启动时被传入Sign_admin_register()并保存在内部Context中crypto/Sign_admin.c因此签名者身份天然与节点身份绑定。两个端点都用Admin_registerFunction()注册参数表声明了参数名、类型与是否必填crypto/Sign_admin.cAdmin 框架会据此完成参数校验。三、Sign_sign()对消息签名3.1 参数与返回Sign_sign()只有一个参数参数类型必填说明msgHashstring是待签名的消息必须是不超过 64 字节的短字符串返回结果字段类型说明errorstringnone或错误描述字符串signaturestring可选可打印格式的签名出错时该字段缺失3.2 为什么只签 64 字节以内的摘要源码 crypto/Sign_admin.c 中的sign()处理函数明确限制msgHash长度超过 64 字节时直接返回错误msgHash too long, max 64 bytes。这正是文档中强调的设计建议如果是大消息应当先对消息做哈希再对哈希值签名。64 字节上限与 ed25519 签名算法的工作方式相符——签名过程需要对消息随机数做 SHA-512 哈希见后文算法剖析把任意长消息压缩为定长摘要后参与签名既保证效率也保证安全性。3.3 签名示例原文实例假设节点已运行并通过 Admin 端口可访问在项目根目录使用./tools/cexec调用userunderscore cjdns % ./tools/cexec Sign_sign(test message) { error: none, signature: 0ytl2njc1hy86tlxtc2zc3449up47uqb0u04kcy233d7zrn2cwh1_y96duzwpvmslj8b7pnk2b32m0rhs738yujwtrtlcq81r0u114svygwn56phn9yncpyzhswpj3bd808lgd5bknlj8xwf7purl0r0hc30, }./tools/cexec是一个基于 Node.js 的管理 RPC 客户端tools/cexec它读取本地 Admin 配置、连接 Admin 端口并把命令行参数包装成一次 RPC 调用返回结果以 JSON 格式化输出。3.4 签名格式解析观察示例中的signature字段可以发现它由两部分用下划线_拼接而成。对照源码 crypto/Sign_admin.cAssert_true(Base32_encode(signB64, 128, Message_bytes(msg), 64) 0); String* sig String_printf(requestAlloc, %s_%s, ctx-pubSigningKey, signB64);即签名字符串的构成是签名者公钥的 Base32 编码_64 字节签名的 Base32 编码前半段签名者节点公钥的 Base32 文本32 字节公钥编码为 52 个 Base32 字符它由Sign_publicKeyFromKeyPair()从签名密钥对中取出并预编码crypto/Sign_admin.c。后半段ed25519 签名的 64 字节原始数据编码为 Base32。下划线_是两部分的分隔符验签时依赖它来切分公钥与签名。四、Sign_checkSig()验证签名并还原身份4.1 参数与返回Sign_checkSig()接收两个参数参数类型必填说明signaturestring是Sign_sign()产生的签名msgHashstring是被签名的原始消息摘要返回结果字段类型说明errorstringnone或错误描述字符串ipv6string可选验签通过时签名者的 cjdns IPv6 地址pubkeystring可选验签通过时签名者的 cjdns 公钥4.2 验签成功示例原文实例userunderscore cjdns % ./tools/cexec Sign_checkSig(0ytl2njc1hy86tlxtc2zc3449up47uqb0u04kcy233d7zrn2cwh1_y96duzwpvmslj8b7pnk2b32m0rhs738yujwtrtlcq81r0u114svygwn56phn9yncpyzhswpj3bd808lgd5bknlj8xwf7purl0r0hc30, test message) { error: none, ipv6: fca4:aa4c:3686:6a29:e301:89a5:942c:38d3, pubkey: hwnu9u7n8v9u7rjrflhsv45q16p103c1rfx9208hnzr2tq988z90.k, txid: 575360523 }验签通过时返回签名者的 cjdns IPv6 地址与公钥——这就是整个签名模块最有价值的地方验签即证明身份消息接收方可以从签名中直接还原出这条消息由哪个 cjdns 地址发出。4.3 身份还原的底层逻辑验签成功后crypto/Sign_admin.c 中的checkSig()处理函数做如下身份还原调用Sign_publicSigningKeyToCurve25519()把 ed25519 签名公钥转换回 Curve25519 公钥crypto/Sign.c失败则报错not a valid curve25519 key用Address_forKey()按 cjdns 规则从 Curve25519 公钥计算 IPv6 地址dht/Address.c 相关实现规则定义在 crypto/AddressCalc.h默认前缀0xfc用Key_stringify()把 32 字节公钥编码为以.k结尾的标准 cjdns 公钥文本crypto/Key.c。因此返回的pubkey是形如hwnu...z0.k的 54 字符标准格式ipv6则是形如fca4:...:38d3的 fc00::/8 网段地址。身份还原链路在测试 crypto/test/Sign_test.c 中也有验证从签名公钥还原出的 Curve25519 公钥与直接用 Curve25519 私钥计算出的公钥完全一致。五、错误场景全解析签名与验签接口对异常输入有严格的错误返回源码 crypto/Sign_admin.c 中按顺序完成一系列校验文档给出了四种典型错误示例。5.1 消息不匹配invalid signature对签名用错误的原文验签userunderscore cjdns % ./tools/cexec Sign_checkSig(0ytl2njc1hy86tlxtc2zc3449up47uqb0u04kcy233d7zrn2cwh1_y96duzwpvmslj8b7pnk2b32m0rhs738yujwtrtlcq81r0u114svygwn56phn9yncpyzhswpj3bd808lgd5bknlj8xwf7purl0r0hc30, not the right message) { error: invalid signature, txid: 828277386 }签名与消息内容不匹配时底层Sign_verifyMsg()调用 ed25519 的crypto_sign_ed25519_open()校验失败返回非零值crypto/Sign.c上层即返回invalid signature。5.2 签名被篡改malformed signature, failed to decode signature把签名末尾最后一个字符从0改为xBase32 字母表之外的字符userunderscore cjdns % ./tools/cexec Sign_checkSig(0ytl2njc1hy86tlxtc2zc3449up47uqb0u04kcy233d7zrn2cwh1_y96duzwpvmslj8b7pnk2b32m0rhs738yujwtrtlcq81r0u114svygwn56phn9yncpyzhswpj3bd808lgd5bknlj8xwf7purl0r0hc3x, test message) { error: malformed signature, failed to decode signature, txid: 1892499317 }原因是后半段 Base32 解码长度不是预期的 64 字节crypto/Sign_admin.c。5.3 分隔符被替换malformed signature, missing separator把公钥与签名之间的_替换为userunderscore cjdns % ./tools/cexec Sign_checkSig(0ytl2njc1hy86tlxtc2zc3449up47uqb0u04kcy233d7zrn2cwh1y96duzwpvmslj8b7pnk2b32m0rhs738yujwtrtlcq81r0u114svygwn56phn9yncpyzhswpj3bd808lgd5bknlj8xwf7purl0r0hc30, test message) { error: malformed signature, missing separator, txid: 1193071367 }checkSig()首先用CString_strchr(signature-bytes, _)查找分隔符找不到就立即报此错crypto/Sign_admin.c。5.4 公钥部分被破坏malformed signature, failed to decode pubkey把签名字符串第一个字符替换为_userunderscore cjdns % ./tools/cexec Sign_checkSig(_ytl2njc1hy86tlxtc2zc3449up47uqb0u04kcy233d7zrn2cwh1_y96duzwpvmslj8b7pnk2b32m0rhs738yujwtrtlcq81r0u114svygwn56phn9yncpyzhswpj3bd808lgd5bknlj8xwf7purl0r0hc30, test message) { error: malformed signature, failed to decode pubkey, txid: 2668454052 }此时分隔符位置前移前半段 Base32 解码后长度不等于 32 字节crypto/Sign_admin.c。5.5 完整的校验顺序将上述错误场景与源码校验逻辑对照checkSig()的校验顺序为msgHash长度 64 →msgHash too long, max 64 bytes找不到_分隔符 →malformed signature, missing separator分隔符前段 Base32 解码 ≠ 32 字节 →malformed signature, failed to decode pubkey分隔符后段 Base32 解码 ≠ 64 字节 →malformed signature, failed to decode signature签名验证失败 →invalid signature公钥无法转换回 Curve25519 →not a valid curve25519 key全部通过 →none并返回ipv6与pubkey。另外注意所有响应无论成功失败都带有txid字段这是 Admin RPC 的请求关联标识admin/Admin.h 中Admin_sendMessage()传递。六、底层实现从 Curve25519 到 ed25519 的密钥转换cjdns 的签名实现位于 crypto/Sign.c核心是把 Curve25519 私钥直接当作 ed25519 签名私钥使用并配套一个可逆的公钥转换函数。理解这一点是读懂整个签名模块的关键。6.1 私钥转换Sign_signingKeyPairFromCurve25519()函数 crypto/Sign.c 把 32 字节 Curve25519 私钥加工成 64 字节 ed25519 密钥对前 32 字节直接复制 Curve25519 私钥按 ed25519 规范做位掩码修正keypairOut[0] 248清除低 3 位、keypairOut[31] 63、keypairOut[31] | 64设置第 254 位。源码注释解释了 Curve25519 与 ed25519 在位掩码上的细微差异其实殊途同归crypto/Sign.c用ge_scalarmult_base()计算基点标量乘法得到 32 字节公钥存入keypairOut[32..63]。关键区别在于标准 ed25519 会先对私钥做 SHA-512 哈希再展开密钥而 cjdns 直接使用原始 Curve25519 私钥参与计算crypto/Sign.c 注释明确说明没有在计算前哈希私钥。这是为了保持与既有 Curve25519 加密密钥的兼容性。6.2 签名算法Sign_signMsg()与随机数处理函数 crypto/Sign.c 实现了签名主流程。与标准crypto_sign()最显著的差异在于随机数 r 的派生方式标准 ed25519nacl/libsodium把私钥哈希成 64 字节一半作签名密钥、一半作秘密随机种子用于为每条消息生成唯一的rcjdns 因为不哈希私钥改为取私钥32 字节 每次调用由Random_bytes()生成的 32 字节随机数一起做 SHA-512 哈希得到 64 字节az再用前 32 字节私钥与消息组合派生rcrypto/Sign.c。源码注释crypto/Sign.c明确指出这种腰带上再拴一根绳子的做法每次签名混入独立随机数避免两条不同消息使用相同r导致私钥被数学推导的风险。随后sc_reduce()、ge_scalarmult_base()、ge_p3_tobytes()、sc_muladd()等完成标准 ed25519 的 R 点计算与 S 分量签名最终在消息前压入 64 字节签名crypto/Sign.c。6.3 验签Sign_verifyMsg()验签直接复用标准 ed25519 的crypto_sign_ed25519_open()crypto/Sign.c没有特殊处理。消息长度小于 64 字节直接失败验证通过后把 64 字节签名从消息中弹出。6.4 公钥转换Sign_publicSigningKeyToCurve25519()函数 crypto/Sign.c 实现 ed25519 公钥 → Curve25519 公钥的转换把 ed25519 公钥解析为椭圆曲线点A利用公式x (1y)/(1-y)从点的 Y 坐标恢复 X 坐标fe_add/fe_sub/fe_invert/fe_mul等有限域运算输出 Curve25519 公钥。源码注释说明这是 libsodium 同款实现crypto/Sign.c。正是这个可逆转换让验签 → 还原 Curve25519 公钥 → 计算 cjdns IPv6 地址的完整身份还原链路得以成立。七、测试验证与身份一致性保证crypto/test/Sign_test.c 对签名模块做了完整的单元测试验证了核心属性随机生成 Curve25519 私钥计算其公钥用Sign_signingKeyPairFromCurve25519()生成签名密钥对对hello world调用Sign_signMsg()签名Sign_verifyMsg()验签成功断言返回 0Sign_publicSigningKeyToCurve25519()从签名公钥还原出 Curve25519 公钥与第 1 步直接计算的公钥逐字节一致crypto/test/Sign_test.c。这条测试从数学上证明了签名身份与 cjdns 加密身份是同一个身份用节点私钥签名的消息验签者得到的公钥和地址与节点在网络中实际使用的公钥和地址完全一致。因此签名模块可以天然地用于 cjdns 网络中的节点身份证明、消息来源认证等场景。八、调用前置条件与注意事项节点必须运行两个 RPC 由运行中的节点进程提供需要在节点启动后./cjdroute --nobg或后台运行通过 Admin 端口调用。管理认证Sign_sign()需要管理密码认证。RPC 调用的认证方式与其他管理操作一致通过 Admin 配置文件/etc/cjdroute.conf中的admin段建立连接。./tools/cexec依赖工具本身是 Node.js 脚本tools/cexec调用前需确保项目依赖已安装./do构建流程会处理且当前目录在项目根下能找到./tools/lib/cjdnsadmin。消息上限 64 字节大消息务必先哈希如 SHA-256/SHA-512再对摘要签名。签名格式不可变签名文本必须保持公钥_签名的 Base32 双段结构任何字符改动都会导致验签失败或格式错误。九、总结cjdns 的签名功能是一套身份即签名的轻量方案通过Sign_sign()用节点既有 Curve25519 私钥派生出的 ed25519 密钥对消息签名通过Sign_checkSig()验签并把签名者还原为可读的 cjdns 公钥.k格式与 IPv6 地址fc00::/8网段。其实现巧妙之处在于复用存量密钥体系——不新增密钥文件、不破坏现有cjdroute.conf且从测试中可确认签名公钥与加密公钥完全同源。无论是节点间消息鉴权、网络工具的身份标注还是对外证明某条消息出自某节点这套 RPC 都提供了开箱即用的能力。结合本文的源码对照读者可以放心地把签名与验签接入自己的管理脚本与网络应用。赞分享网络网络安全【免费下载链接】cjdnsAn encrypted IPv6 network using public-key cryptography for address allocation and a distributed hash table for routing.项目地址https://gitcode.com/gh_mirrors/cj/cjdns点击查看免费下载相关推荐FastStream消息签名数字签名与消息完整性验证FastStream消息签名数字签名与消息完整性验证 在分布式系统和微服务架构中消息的完整性和真实性验证是确保系统安全的关键环节。FastStream作为现后端消息队列微服务Fuel 钱包签名全指南在 fuels-ts 中完成消息签名、Personal Message 签名与交易签名Fuel 钱包签名全指南在 fuels ts 中完成消息签名、Personal Message 签名与交易签名 本文以 fuels ts https://li区块链Web3wagmi 的 useSignMessage在 React 中实现消息签名与验证的完整实战指南wagmi 的 useSignMessage在 React 中实现消息签名与验证的完整实战指南 在以太坊应用中「签名」是证明账户控制权、实现登录鉴权Sig区块链Web3前端上一篇Java泛型反射toBeBetterJavaer TypeVariable下一篇WebGL 2.0渲染管道优化Threepipe高性能图形处理技术揭秘创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考