跳到主要内容
ecdsa.com
C

libsodium

Frank Denis 的 libsodium,NaCl 的可移植分支:提供验证严格的 Ed25519 签名、X25519、认证加密,以及 ML-KEM-768 和 X-Wing 混合 KEM——但完全不支持 ECDSA。
许可证
ISC
检查的版本
1.0.22 ·
源代码
github.com/jedisct1/libsodium
文档
doc.libsodium.org/

兼容性概览

EdDSA后量子

EdDSA
Ed25519Ed25519ph
后量子算法
ML-KEMX-Wing
私钥格式
seed-32expanded-64

libsodium 通过难以误用的函数,提供一小组经过精心取舍的原语;PHP 内置了它,许多语言绑定在使用它,即时通讯应用也嵌入了它。签名只支持 Ed25519:1.0.22 的头文件(基于 Alpine 3.24.2 的软件包检查)没有声明任何 ECDSA 函数,因此凡是使用 ES256 或 secp256k1 的场景都需要另找一个库。

Ed25519 签名按 RFC 8032 的规定是确定性的——同一消息的两个签名完全相同——crypto_sign_*_ph 函数实现 Ed25519ph,它的签名与普通 Ed25519 签名不能互换:在检查中,普通验证器拒绝了这样的签名。私钥为 64 字节,即 32 字节的种子后接公钥,而其他大多数库只保存种子;如示例所示,crypto_sign_ed25519_sk_to_seed 和 crypto_sign_seed_keypair 可在两者之间转换。

在各实现行为不一的地方,libsodium 的验证很严格。把 S 替换为 S + L 的签名——它与原值模群的阶同余,但 RFC 8032 禁止这种形式——被拒绝了;以小阶的单位元点作为公钥来验证的签名也被拒绝。接受这些输入的库会在边界情况上与 libsodium 得出不同结论,这对必须就有效性达成一致的共识系统很重要。crypto_sign_verify_detached 不接收签名长度参数,而是恰好读取 64 字节,因此调用前要先检查长度。

1.0.22 的后量子支持仅限于密钥封装:crypto_kem_mlkem768_*(公钥 1184 字节,密文 1088 字节),以及混合方案 X-Wing(crypto_kem_xwing_*,分别为 1216 字节和 1120 字节),后者也是通用 crypto_kem_* 函数的默认方案。不支持 ML-DSA 或其他后量子签名。密钥始终是原始字节——32 字节的公钥和种子——不支持 PEM、DER 或 JWK。可以在后量子 KEM 工具中比较各 KEM 的数据大小。

事实及其依据的检查

以下内容均非抄自文档。检查脚本 content/libraries/verify/libsodium/run.sh 于2026年10月20日在容器镜像 alpine:3.24.2 中运行了 libsodium 1.0.22;本页内容必须与它的输出一致,每条事实都列出了对应的输出行。

  • ECDSA 曲线

    none

    不支持 ECDSA:crypto_sign_PRIMITIVE 为 ed25519,也没有任何头文件声明 ECDSA 函数。

    检查记录
    fact ecdsa-curves = none
    evidence ecdsa-curves: crypto_sign_PRIMITIVE is "ed25519"; the header set declares no ECDSA functions
  • EdDSA

    Ed25519Ed25519ph

    通过 crypto_sign_* 支持 Ed25519,通过 crypto_sign_init、_update 和 _final_* 支持 Ed25519ph;普通验证器会拒绝 Ed25519ph 签名。

    检查记录
    fact eddsa = Ed25519, Ed25519ph
    evidence eddsa: crypto_sign_final_verify (Ed25519ph) accepts its signature: yes; the plain verifier rejects it: yes
  • 确定性 Ed25519 签名

    yes

    用同一密钥对同一消息生成的两个签名完全相同。

    检查记录
    fact ed25519-deterministic = yes
  • Ed25519 私钥布局

    expanded-64

    crypto_sign_SECRETKEYBYTES 为 64:种子后接公钥。

    检查记录
    fact ed25519-private-key = expanded-64
    evidence ed25519-private-key: crypto_sign_SECRETKEYBYTES = 64: the 32-byte seed followed by the 32-byte public key (seed matches: yes)
  • S 值非规范的 Ed25519 签名

    rejected

    把 S 替换为 S + L 的签名被拒绝。

    检查记录
    fact ed25519-non-canonical-s = rejected
  • Ed25519 小阶公钥

    rejected

    以单位元点作为公钥验证时,签名被拒绝。

    检查记录
    fact ed25519-small-order-key = rejected
    evidence ed25519-small-order-key: the identity point as public key with R = identity, S = 0
  • 格式错误的 Ed25519 签名

    rejected

    传入 64 字节的垃圾数据时,crypto_sign_verify_detached 返回 -1。

    检查记录
    fact ed25519-malformed-signature = rejected
    evidence ed25519-malformed-signature: 64 bytes of 0xab: crypto_sign_verify_detached returned -1; the function takes no length and reads exactly crypto_sign_BYTES = 64 bytes
  • 签名模式

    detachedcombined

    分离签名(64 字节)和组合式签名消息(签名附加在消息之前)都可使用。

    检查记录
    fact signature-modes = detached, combined
    evidence signature-modes: crypto_sign prepends the 64-byte signature to the message: 82 bytes for a 18-byte message
  • 后量子算法

    ML-KEMX-Wing

    ML-KEM-768(crypto_kem_mlkem768_*)和 X-Wing(crypto_kem_xwing_*,通用 crypto_kem_* 的默认方案)的封装与解封装均成功;不支持后量子签名。

    检查记录
    fact pqc = ML-KEM, X-Wing
    evidence pqc: crypto_kem_mlkem768: public key 1184 bytes, ciphertext 1088 bytes
    evidence pqc: crypto_kem_xwing: public key 1216 bytes, ciphertext 1120 bytes; generic crypto_kem_primitive() is "xwing"
  • 公钥格式

    raw-32

    公钥是 32 字节的原始数据;不支持 PEM、DER 或 JWK。

    检查记录
    fact public-key-formats = raw-32
  • 私钥格式

    seed-32expanded-64

    密钥由 32 字节的种子派生(crypto_sign_seed_keypair),以 64 字节存储;crypto_sign_ed25519_sk_to_seed 可取回种子。

    检查记录
    fact private-key-formats = seed-32, expanded-64
    evidence private-key-formats: crypto_sign_seed_keypair reproduces the key from the seed: yes; crypto_sign_ed25519_sk_to_seed recovers it: yes

用法

生成 Ed25519 密钥,提取其他库所需的 32 字节种子,然后生成并验证分离签名。
#include <sodium.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    if (sodium_init() < 0) return 1;
    const unsigned char msg[] = "order #1042 approved";
    unsigned char pk[crypto_sign_PUBLICKEYBYTES], sk[crypto_sign_SECRETKEYBYTES];
    unsigned char sig[crypto_sign_BYTES], seed[32];

    crypto_sign_keypair(pk, sk);
    /* Other libraries store the 32-byte seed; libsodium's secret key is seed || public key. */
    crypto_sign_ed25519_sk_to_seed(seed, sk);

    /* Detached signature: 64 bytes, deterministic per RFC 8032. */
    crypto_sign_detached(sig, NULL, msg, sizeof msg - 1, sk);
    int ok = crypto_sign_verify_detached(sig, msg, sizeof msg - 1, pk) == 0;

    printf("secret key %d bytes, seed %zu bytes, signature %d bytes, verified: %d\n",
           crypto_sign_SECRETKEYBYTES, sizeof seed, crypto_sign_BYTES, ok);
    return 0;
}

代码片段测试工具会在容器镜像 alpine:3.24.2 中原样运行每个示例,只要输出不匹配,运行就会失败。

环境准备
apk add --no-cache -q build-base libsodium-dev=1.0.22-r0 >/dev/null
命令
cc -o usage usage.c -lsodium && ./usage
输出必须匹配(正则表达式)
secret key 64 bytes, seed 32 bytes, signature 64 bytes, verified: 1

库