- 通信
- 游戏开发
【免费下载链接】GameNetworkingSockets
Reliable & unreliable messages over UDP. Robust message fragmentation & reassembly. P2P networking / NAT traversal. Encryption.
本文围绕 GameNetworkingSockets 仓库内置的 ed25519-donna 移植代码展开,系统讲解 Ed25519 椭圆曲线签名算法的高性能实现、编译选项、完整 API 用法与批量验证机制,并结合仓库源码揭示其在 P2P 加密链路中的实际调用方式。读完本文,你将掌握如何为 Ed25519 签名选择正确的哈希与随机数后端、如何在 32/64 位与 SSE2 平台编译,以及如何在项目中使用密钥生成、签名、验签、批量验证和 Curve25519 公钥派生。
Ed25519 与 ed25519-donna 是什么
Ed25519 是一种椭圆曲线数字签名算法(Elliptic Curve Digital Signature Algorithm),由 Dan Bernstein、Niels Duif、Tanja Lange、Peter Schwabe 和 Bo-Yin Yang 共同设计。它基于 25519 曲线,具有签名短(64 字节)、密钥短(公钥 32 字节、私钥 32 字节)、速度快且天然具备恒定时间执行等特性,因此在现代加密通信中被广泛采用。
ed25519-donna 是 Ed25519 算法的一套独立 C 实现,本仓库将其作为第三方代码内置于 src/external/ed25519-donna/,并在此基础上做了 Valve 风格的定制(见ed25519_VALVE.c)。该项目提供了高性能、可移植的 32 位与 64 位实现,且所有实现对机密数据的处理都是恒定时间的(constant time),这意味着执行时间不随密钥或消息内容变化,能有效抵抗时序侧信道攻击。
在 GameNetworkingSockets 中,这套代码被用于连接加密:src/common/crypto_25519.h明确注释了它的用途——“Common base for x25519 and ed25519 public keys on the 25519 curve”,即同时承担 Curve25519 密钥交换(ECDH)与 Ed25519 签名两类密码学原语,为 UDP 上的可靠/不可靠消息与 P2P 握手提供身份认证和密钥协商能力。
性能基准
ed25519-donna 的核心卖点是性能。README 中的基准数据基于 gcc 4.6.3、icc 13.1.1、clang 3.4-1~exp1 编译,单位是千周期(k)。批验证时间(括号内)是每 64 个签名一批时,单个验证的平均耗时。
E5200 @ 2.5GHz(march=core2)
| 实现 | Sign | gcc | icc | clang | Verify | gcc | icc | clang |
|---|---|---|---|---|---|---|---|---|
| ed25519-donna 64bit | 100k | 110k | 137k | 327k (144k) | 342k (163k) | 422k (194k) | ||
| amd64-64-24k | 102k | 355k (158k) | ||||||
| ed25519-donna-sse2 64bit | 108k | 111k | 116k | 353k (155k) | 345k (154k) | 360k (161k) | ||
| amd64-51-32k | 116k | 380k (175k) | ||||||
| ed25519-donna-sse2 32bit | 147k | 147k | 156k | 380k (178k) | 381k (173k) | 430k (192k) | ||
| ed25519-donna 32bit | 597k | 335k | 380k | 1693k (720k) | 1052k (453k) | 1141k (493k) |
E3-1270 @ 3.4GHz(march=corei7-avx)
| 实现 | Sign | gcc | icc | clang | Verify | gcc | icc | clang |
|---|---|---|---|---|---|---|---|---|
| amd64-64-24k | 68k | 225k (104k) | ||||||
| ed25519-donna 64bit | 71k | 75k | 90k | 226k (105k) | 226k (112k) | 277k (125k) | ||
| amd64-51-32k | 72k | 218k (107k) | ||||||
| ed25519-donna-sse2 64bit | 79k | 82k | 92k | 252k (122k) | 259k (124k) | 282k (131k) | ||
| ed25519-donna-sse2 32bit | 94k | 95k | 103k | 296k (146k) | 294k (137k) | 306k (147k) | ||
| ed25519-donna 32bit | 525k | 299k | 316k | 1502k (645k) | 959k (418k) | 954k (416k) |
几点结论值得注意(均为原 README 的说明):
- 64 位实现明显快于 32 位实现;SSE2 版本在 64 位下与纯 C 的 64 位版本接近,但在 32 位下提升显著。
- SSE2 性能在 AMD 及 SSE 指令较慢的老 CPU 上可能不那么亮眼。
- Visual Studio 下
ge25519_scalarmult_base_niels的性能会略有滞后,直到为其ge25519_scalarmult_base_choose_niels提供优化的汇编版本。 - README 亦注明:SSE2 代码与基准尚未更新完毕。
从源码看,这些性能差异的根源在算术层:仓库同时提供了 32 位与 64 位的 limb 运算实现(curve25519-donna-32bit.h、curve25519-donna-64bit.h),并在 ed25519-donna-32bit-sse2.h、ed25519-donna-64bit-sse2.h 等文件中以 SSE2 指令加速域运算。
编译:哈希与随机数后端的选择
ed25519-donna 的编译非常灵活。如果你针对 OpenSSL 编译,则无需任何配置(哈希与随机数都默认使用 OpenSSL)。
Hash 选项
Ed25519 内部需要 SHA-512 作为哈希原语。若不使用 OpenSSL,则必须自行提供哈希函数,选择逻辑见 ed25519-hash.h:
#if defined(ED25519_REFHASH) #include "ed25519-hash-ref.h" #elif defined(ED25519_CUSTOMHASH) #include "ed25519-hash-custom.h" #elif defined(ED25519_HASH_BCRYPT) #include "ed25519-hash-bcrypt.h" #else #include "ed25519-openssl.h" #endif-DED25519_REFHASH:使用 SHA-512 的简单但较慢的参考实现(ed25519-hash-ref.h)。README 明确警告:这只应在没有 OpenSSL 时用来验证代码能否工作,生产环境不应使用。-DED25519_CUSTOMHASH:接入自定义哈希。编译ed25519.c时加上该宏,并把自定义实现放入ed25519-hash-custom.h。哈希必须具有 512 位摘要,并实现以下接口:
struct ed25519_hash_context; void ed25519_hash_init(ed25519_hash_context *ctx); void ed25519_hash_update(ed25519_hash_context *ctx, const uint8_t *in, size_t inlen); void ed25519_hash_final(ed25519_hash_context *ctx, uint8_t *hash); void ed25519_hash(uint8_t *hash, const uint8_t *in, size_t inlen);-DED25519_HASH_BCRYPT:接入 ed25519-hash-bcrypt.h(Windows 上可利用 BCrypt 的 SHA-512)。
值得注意的是,GameNetworkingSockets 的定制文件 ed25519_VALVE.c 在无 OpenSSL 环境下定义了ED25519_CUSTOMHASH,并内联了一份来自公共领域 libTomCrypt 的 SHA-512 完整实现(含 80 轮常数K[80]、sha512_init/process/done等),通过宏包装成上述 ed25519 哈希接口,从而在没有 OpenSSL 的平台上也能完整编译。
Random 选项
随机数仅用于批量验证(为验证生成随机标量)。若不用 OpenSSL,则需要自行提供随机函数,选择逻辑见 ed25519-randombytes.h:
-DED25519_CUSTOMRANDOM:使用自定义随机函数。编译ed25519.c时加该宏,并把实现放入ed25519-randombytes-custom.h,必须实现:
void ED25519_FN(ed25519_randombytes_unsafe) (void *p, size_t len);-DED25519_TEST:使用基于 Bob Jenkins ISAAC 算法的变体——确定性播种、非线程安全的 CSPRNG,专用于确定性测试。- 默认情况:直接使用 OpenSSL 的
RAND_bytes(#include <openssl/rand.h>)。README 中也确认:默认实现现在使用 OpenSSL 的RAND_bytes。
GameNetworkingSockets 的定制版本在 ed25519_VALVE.c 顶部定义了ED25519_CUSTOMRNG,并注释掉了原先 Windows/非 Windows 的ed25519_randombytes_unsafe实现(含 Win32 的RtlGenRandom以及“其他平台直接令链接失败”的策略),最终随机函数由项目自身提供。
次要选项
-DED25519_INLINE_ASM:禁用自定义汇编例程,改用可移植 C 代码。-DED25519_FORCE_32BIT:即使在 64 位编译环境下,也强制使用 32 位例程。
32 位 / 64 位 / SSE2 编译命令
# 32 位 gcc ed25519.c -m32 -O3 -c # 64 位 gcc ed25519.c -m64 -O3 -c # SSE2(32 位) gcc ed25519.c -m32 -O3 -c -DED25519_SSE2 -msse2 # SSE2(64 位) gcc ed25519.c -m64 -O3 -c -DED25519_SSE2clang 与 icc 同样受支持。ED25519_SSE2宏会启用 ed25519-donna-sse2.h 中的 SSE2 加速路径。
API 使用指南
使用方式很简单:链接ed25519.o(按-mbits位宽生成),并在代码中:
#include "ed25519.h"若使用 OpenSSL,还需-lssl -lcrypto(README 提示:有些系统可能不需要-lcrypto,可能需要反复试验确认)。
密钥与签名数据结构
ed25519.h 定义了四个类型,全部是定长字节数组:
typedef unsigned char ed25519_signature[64]; /* 签名:R(32) + S(32) */ typedef unsigned char ed25519_public_key[32]; /* 公钥 */ typedef unsigned char ed25519_secret_key[32]; /* 私钥:仅 32 字节 */ typedef unsigned char curved25519_key[32]; /* Curve25519 密钥 */函数声明(均为extern "C",C++ 可直接使用):
void ed25519_publickey(const ed25519_secret_key sk, ed25519_public_key pk); int ed25519_sign_open(const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS); void ed25519_sign(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_public_key pk, ed25519_signature RS); int ed25519_sign_open_batch(const unsigned char **m, size_t *mlen, const unsigned char **pk, const unsigned char **RS, size_t num, int *valid); void ed25519_randombytes_unsafe(void *out, size_t count); void curved25519_scalarmult_basepoint(curved25519_key pk, const curved25519_key e);生成私钥
私钥就是来自安全密码学随机源的32 个随机字节:
ed25519_secret_key sk; randombytes(sk, sizeof(ed25519_secret_key));生成公钥
ed25519_public_key pk; ed25519_publickey(sk, pk);从 ed25519.c 的实现可以看出其数学本质:先对私钥做哈希扩展并钳制(ed25519_extsk:extsk[0] &= 248; extsk[31] &= 127; extsk[31] |= 64;),然后计算A = aB(基点多倍标量乘,ge25519_scalarmult_base_niels),最后打包为 32 字节公钥。
签名
签名需要同时提供私钥和公钥:
ed25519_signature sig; ed25519_sign(message, message_len, sk, pk, signature);对应实现流程(ed25519.c):计算r = H(aExt[32..64], m)、R = rB,再计算S = (r + H(R,A,m)·a) mod L,输出 64 字节R || S。
验证签名
int valid = ed25519_sign_open(message, message_len, pk, signature) == 0;验证实现(ed25519.c)会先做两个快速拒绝检查:RS[63] & 224非零则无效、公钥无法解包则无效;随后计算hram = H(R,A,m),用双标量乘ge25519_double_scalarmult_vartime验证R = SB - H(R,A,m)A是否成立。
批量验证
批量验证可以显著摊薄成本。README 给出的示例将消息、长度、公钥、签名组织成数组,并传入一个valid结果数组:
const unsigned char *mp[num] = {message1, message2..}; size_t ml[num] = {message_len1, message_len2..}; const unsigned char *pkp[num] = {pk1, pk2..}; const unsigned char *sigp[num] = {signature1, signature2..}; int valid[num]; /* valid[i] 将在单个签名有效时被置为 1,否则为 0 */ int all_valid = ed25519_sign_open_batch(mp, ml, pkp, sigp, num, valid) == 0;其实现位于 ed25519-donna-batchverify.h:单批最大 64 个签名(#define max_batch_size 64),使用 Bos–Carter 方法将多个验签合并为一次多点标量乘,过程中用堆(heap)管理标量减法,并通过ed25519_randombytes_unsafe为每个签名生成 128 位随机标量做随机组合。README 特别强调:批量验证使用ed25519_randombytes_unsafe(实现在ed25519-randombytes.h,默认基于 OpenSSL 的RAND_bytes)来生成随机标量。
需要说明的仓库差异:GameNetworkingSockets 的这份 ed25519-donna-batchverify.h 中,ed25519_sign_open_batch函数体被注释掉了,注释写着// @VALVE Delete this. We don't need it, and we don't want to hook up the random number generator.——即 Valve 出于“不需要它、也不想接入随机数生成器”的原因移除了批量验证入口。因此本仓库的实际可用验证 API 以单签名ed25519_sign_open为主。
与 SUPERCOP 版本的区别
README 明确对比了与 SUPERCOP 版本的差异,理解这些差异对正确使用 API 至关重要:
- 签名不追加在消息后面(不要求消息后有 64 字节签名空间);
- 消息前面不需要填充(no padding);
- 私钥不包含公钥副本,因此只有32 字节(SUPERCOP 版为 64 字节),签名时必须显式传入公钥。
顺带获得 Curve25519 公钥生成
ed25519-donna 还提供了 Curve25519 公钥生成能力——得益于 Adam Langley 的方案,直接复用 Ed25519 预计算的基点标量乘来实现:
curved25519_key sk, pk; randombytes(sk, sizeof(curved25519_key)); curved25519_scalarmult_basepoint(pk, sk);命名curved25519(curve 与 ed25519 的组合)是为了避免命名冲突。其性能略快于短消息的 ed25519 签名,因为两者共用同一套标量乘代码。实现见 ed25519.c:先对密钥钳制(ec[0] &= 248; ec[31] &= 127; ec[31] |= 64;),再计算基点标量乘,最后通过u = (y + z) / (z - y)从 Edwards 坐标转换为 Montgomery 形式的 x 坐标。
这一能力正是 GameNetworkingSockets 最关键的复用点之一:在 crypto_25519_donna.cpp 中可以看到,项目在 Curve25519 公钥生成路径上特意放弃了curve25519_donna_basepoint,转而使用curved25519_scalarmult_basepoint(注释说明“Ed25519 codebase provides a faster version of curve25519_donna_basepoint”),并断言“ed25519 方案中所有位都是有意义的”。也就是说,网络层密钥交换用的 Curve25519 公钥派生,实际跑在 ed25519-donna 的标量乘代码上。
测试与验证
基础测试与基准
用-DED25519_TEST编译ed25519.c,并与 test.c 链接,即可运行基础健全性测试并对每个函数做基准;test-batch.c已并入test.c。
内部数学单元测试
test-internals.c 是独立文件,与ed25519.c相同方式构建。它用极值测试数学原语(标量展开/收缩、模运算、基点乘等)以确保其正确性,并支持 SSE2。
模糊测试(fuzzing)
项目提供针对参考实现(ref10)的模糊测试,详见 fuzz/README.md。fuzz/目录包含fuzz-curve25519.c、fuzz-ed25519.c、curve25519-ref10.c、ed25519-ref10.c以及 SSE2 版本,用于将 ed25519-donna(可选含 SSE2)与 SUPERCOP 的 ref10 实现逐位对照。
*nix 下的构建脚本为 PHP 脚本:
php build-nix.php --bits=64 --function=ed25519 --with-sse2 --compiler=icc必选参数:--function=[curve25519,ed25519]、--bits=[32,64];可选参数:--with-sse2(同时模糊测试 SSE2 版)、--with-openssl(用 OpenSSL 的 SHA-512,默认是慢速参考实现)、--compiler=[gcc,clang,icc](默认 gcc)、--no-asm(禁用平台汇编)。
如果各实现结果一致,程序只间歇输出状态点(每 0x1000 次通过)与 64 位进度计数(每 0x20000 次通过);一旦 ed25519-donna 与 ref10 不一致,会转储随机输入数据、ref10 输出以及差异对比,便于定位回归。
在 GameNetworkingSockets 中的实际落地
最后把视角拉回项目本身。仓库的 25519 密码学封装集中在 src/common/crypto_25519.h 与 src/common/crypto_25519_donna.cpp:
crypto_25519.h定义了三类密钥对象:CCryptoKeyBase系列的 25519 曲线公钥/私钥基类、Curve25519 密钥交换公钥封装、Ed25519 签名私钥封装,并声明了Sign、VerifySignature、GenerateKeyPair、ComputeSharedSecret等接口。crypto_25519_donna.cpp直接包含 ed25519.h 与curve25519-donna/curve25519.h,并通过CHOOSE_25519_IMPL宏在普通实现与_sse2后缀实现(ed25519_publickey_sse2、ed25519_sign_sse2、ed25519_sign_open_sse2、curved25519_scalarmult_basepoint_sse2等)之间按平台选择。- 签名流程:
Sign()调用CHOOSE_25519_IMPL(ed25519_sign),传入数据、长度、私钥原始数据与公钥;VerifySignature()调用ed25519_sign_open并以返回 0 作为验证成功标志(crypto_25519_donna.cpp)。 - 密钥交换流程:
ComputeSharedSecret()调用curve25519_donna完成 ECDH;公钥派生则如前述复用curved25519_scalarmult_basepoint(crypto_25519_donna.cpp)。
除此之外,仓库还提供了 libsodium 与 OpenSSL 的等价实现(crypto_25519_libsodium.cpp、crypto_25519_openssl.cpp),它们与 donna 实现共享同一套上层接口,说明 ed25519-donna 在这里是一个可替换的密码学后端。定制文件 ed25519_VALVE.c 则以#define ED25519_CUSTOMRNG+ 内联 SHA-512 的方式,保证即便脱离 OpenSSL 也能完整构建 Ed25519 能力——这正是 UDP 加密与 P2P 握手场景下对可移植性与确定性最看重的部分。
延伸阅读
算法设计、安全性分析与安全实现建议的权威论文,可访问 Ed25519 官方网站的论文页(见原 README 的 Papers 一节)。若希望继续深入本仓库的密码学封装,建议阅读 crypto_25519.h、crypto_25519_donna.cpp 以及 test_pki.cpp(PKI/证书相关的签名验证测试)。
- 通信
- 游戏开发
【免费下载链接】GameNetworkingSockets
Reliable & unreliable messages over UDP. Robust message fragmentation & reassembly. P2P networking / NAT traversal. Encryption.
相关推荐
cryptography 库 Ed25519 签名与验签实战指南:基于 EdDSA 与 Curve25519 的 Python 实现
cryptography 库 Ed25519 签名与验签实战指南:基于 EdDSA 与 Curve25519 的 Python 实现 Ed25519 是 cry
密码学electron-builder 密钥轮换实战:Ed25519 更新清单签名与 Windows/macOS/Linux 代码签名证书的平滑过渡
electron builder 密钥轮换实战:Ed25519 更新清单签名与 Windows/macOS/Linux 代码签名证书的平滑过渡 导读 本文是一份
构建工具桌面应用开发工具Operator SDK 发布签名 GPG 密钥管理实战:密钥托管、轮换、签名与 CI 集成
Operator SDK 发布签名 GPG 密钥管理实战:密钥托管、轮换、签名与 CI 集成 Operator SDK 仓库使用 OpenPGP(GPG)密钥对
云原生后端开发工具微服务
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考