commit c4e1c577e0d63c228de7e9787b25f80014ae13f4 Author: KyberSDK Release Bot Date: Wed May 6 22:28:00 2026 +0800 chore: release KyberSDK 1.0.0 diff --git a/.sdk-version b/.sdk-version new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/.sdk-version @@ -0,0 +1 @@ +1.0.0 diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..75f66e1 --- /dev/null +++ b/Package.swift @@ -0,0 +1,61 @@ +// swift-tools-version: 5.9 +// KyberSDK — CRYSTALS-Kyber(ML-KEM)后量子密钥封装机制 Swift Package +// +// 包结构: +// CKyber — C 实现层,单 target 内编译 Kyber512 / 768 / 1024 三种变体 +// KyberSDK — Swift 封装层,提供面向 Swift 的类型安全 API +// +// 多变体编译方案(".inc 翻译单元技巧"): +// kyber512/768/1024.c 各为独立翻译单元,每个文件先 #define KYBER_K, +// 再 #include internal/*.inc(原始 .c 文件的内容副本)。 +// 由此,KYBER_NAMESPACE 宏在三个翻译单元中分别展开为不同符号名, +// 实现三种变体共存于同一静态库,链接器对公共符号(fips202)自动去重。 +// +// 支持平台:iOS 14+ / macOS 11+ +// 支持架构:arm64(真机)/ arm64-simulator(Apple Silicon 模拟器)/ x86_64-simulator + +import PackageDescription + +let package = Package( + name: "KyberSDK", + platforms: [ + .iOS(.v14), + .macOS(.v11), + ], + products: [ + // 对外暴露 KyberSDK 库(包含 Swift 封装和 C 实现) + .library(name: "KyberSDK", targets: ["KyberSDK"]), + ], + targets: [ + // ── C 实现目标 ──────────────────────────────────────────────────── + // internal/ 目录中的 .inc 文件不直接编译(由 kyber512/768/1024.c 包含), + // 需通过 exclude 将其排除出自动源码发现范围。 + .target( + name: "CKyber", + path: "Sources/CKyber", + exclude: ["internal"], // .inc 文件不直接编译 + publicHeadersPath: "include", // 对外暴露的头文件目录 + cSettings: [ + .headerSearchPath("include"), // 使 #include "xxx.h" 能找到 include/ 中的头文件 + .headerSearchPath("."), // 使同级目录下的文件可相互引用 + .unsafeFlags(["-O2", "-fomit-frame-pointer"]), // 编译优化 + ] + ), + + // ── Swift 封装目标 ──────────────────────────────────────────────── + // 依赖 CKyber,通过 @_silgen_name / 直接调用 C 函数与底层交互 + .target( + name: "KyberSDK", + dependencies: ["CKyber"], + path: "Sources/KyberSDK" + ), + + // ── 单元测试目标 ───────────────────────────────────────────────── + // 覆盖:三种变体 KEM 全流程、密钥尺寸验证、错误密钥测试、输入校验 + .testTarget( + name: "KyberSDKTests", + dependencies: ["KyberSDK"], + path: "Tests/KyberSDKTests" + ), + ] +) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6cd4dde --- /dev/null +++ b/README.md @@ -0,0 +1,173 @@ +# KyberSDK — iOS / macOS Swift Package + +基于 CRYSTALS-Kyber(ML-KEM,NIST FIPS 203)后量子密钥封装机制的 Swift Package, +支持 iOS 14+ / macOS 11+ 及全平台架构(arm64 真机、arm64/x86_64 模拟器)。 + +--- + +## 环境要求 + +| 工具 | 最低版本 | +|------|---------| +| Xcode | 15.0 | +| iOS | 14.0 | +| macOS | 11.0 | +| Swift | 5.9 | + +--- + +## 支持的平台与架构 + +| 平台 | 架构 | +|------|-----| +| iOS 真机 | `arm64` | +| iOS 模拟器(Intel Mac) | `x86_64` | +| iOS 模拟器(Apple Silicon Mac) | `arm64` | +| macOS | `arm64`、`x86_64` | + +--- + +## 集成方法 + +### 方式一:本地 Swift Package(推荐开发阶段) + +1. 在 Xcode 中选择 **File → Add Package Dependencies → Add Local…** +2. 选择本仓库中的 `ios/` 目录(包含 `Package.swift`) +3. 在弹出框中勾选 **KyberSDK**,选择目标 Target,点击 Add Package + +### 方式二:SPM 依赖声明 + +```swift +// Package.swift +.package(path: "../ios"), // 本地路径 +// 或使用远程 URL: +// .package(url: "https://your-repo/KyberSDK", from: "1.0.0"), +``` + +--- + +## API 使用示例 + +```swift +import KyberSDK + +// ── 接收方:生成密钥对 ──────────────────────────────────────────────── +let keyPair = try KyberKEM.generateKeyPair(variant: .kyber768) +// keyPair.publicKey → 可公开分发(Data) +// keyPair.secretKey → 安全存储,建议存入 iOS Keychain + +// ── 发送方:封装共享密钥 ────────────────────────────────────────────── +let result = try KyberKEM.encapsulate(variant: .kyber768, publicKey: keyPair.publicKey) +// result.ciphertext → 传输给接收方(Data) +// result.sharedSecret → 本地保留,用于对称加密(AES-GCM、ChaCha20-Poly1305) + +// ── 接收方:解封装恢复共享密钥 ─────────────────────────────────────── +let sharedSecret = try KyberKEM.decapsulate( + variant: .kyber768, + ciphertext: result.ciphertext, + secretKey: keyPair.secretKey +) +// result.sharedSecret == sharedSecret ✓(密文合法时) +``` + +> **注意**:所有方法均为同步调用,建议在后台队列执行: +> ```swift +> let kp = try await Task.detached(priority: .userInitiated) { +> try KyberKEM.generateKeyPair(variant: .kyber768) +> }.value +> ``` + +--- + +## 支持的安全级别 + +| 枚举值 | KYBER_K | NIST 级别 | 经典安全性 | 公钥 | 私钥 | 密文 | +|--------|---------|----------|----------|-----|-----|-----| +| `.kyber512` | 2 | Level 1 | ~AES-128 | 800 B | 1632 B | 768 B | +| `.kyber768` | 3 | Level 3 | ~AES-192 | 1184 B | 2400 B | 1088 B | +| `.kyber1024` | 4 | Level 5 | ~AES-256 | 1568 B | 3168 B | 1568 B | + +共享密钥固定为 **32 字节**(三种变体相同)。 + +--- + +## 运行测试 + +```bash +cd ios +swift test +# 预期输出:11 项测试全部通过 +``` + +测试覆盖: + +| 测试名称 | 说明 | +|---------|------| +| `testKyber512/768/1024KeySizes` | 密钥尺寸符合规范 | +| `testKyber512/768/1024RoundTrip` | 完整 KEM 流程,共享密钥必须一致 | +| `testKyber512WrongKey` | 错误私钥不能恢复正确共享密钥(IND-CCA2) | +| `testInvalidPublicKeySize` | 公钥长度不符时抛出错误 | +| `testInvalidSecretKeySize` | 私钥长度不符时抛出错误 | +| `testInvalidCiphertextSize` | 密文长度不符时抛出错误 | +| `testKyber768Performance` | 密钥生成性能基线 | + +--- + +## 包结构详解 + +``` +ios/ +├── Package.swift # SPM 包描述(iOS 14+ / macOS 11+) +├── Sources/ +│ ├── CKyber/ # C 实现层(单 target 包含三种变体) +│ │ ├── include/ # 对外暴露的头文件 +│ │ │ ├── kyber_sdk.h # 伞头文件(包含 api.h 及 randombytes 声明) +│ │ │ ├── api.h # 三种变体函数声明 +│ │ │ └── ...(其余内部头文件) +│ │ ├── internal/ # .inc 文件(不直接编译,由变体 .c 文件 #include) +│ │ │ ├── kem.inc # kem.c 内容副本 +│ │ │ ├── indcpa.inc # indcpa.c 内容副本 +│ │ │ └── ...(poly / ntt / cbd / reduce / verify / symmetric_shake) +│ │ ├── kyber512.c # 翻译单元:#define KYBER_K 2 +│ │ ├── kyber768.c # 翻译单元:#define KYBER_K 3 +│ │ ├── kyber1024.c # 翻译单元:#define KYBER_K 4 +│ │ ├── fips202.c # SHA3/SHAKE(与 KYBER_K 无关,编译一次) +│ │ └── randombytes_ios.c # arc4random_buf 包装 +│ └── KyberSDK/ # Swift 封装层 +│ ├── KyberTypes.swift # KyberVariant / KyberKeyPair / KyberError +│ └── KyberKEM.swift # 主 API:generateKeyPair / encapsulate / decapsulate +├── Tests/ +│ └── KyberSDKTests/ +│ └── KyberSDKTests.swift # 11 项单元测试 +└── KyberDemo/ # SwiftUI 演示 App 源文件 + ├── KyberDemoApp.swift + ├── ContentView.swift + └── README.md # Demo 集成说明 +``` + +--- + +## 多变体编译原理(.inc 翻译单元技巧) + +Kyber C 实现通过编译时常量 `KYBER_K` 区分变体,同一源文件在不同 `KYBER_K` 下产生不同符号名。 +为在单一 SPM target 中同时支持三种变体: + +1. 将所有变体相关的 `.c` 文件重命名为 `.inc`(放在 `internal/`),SPM 不直接编译它们。 +2. 创建三个"驱动"翻译单元(`kyber512/768/1024.c`),各自先 `#define KYBER_K`,再 `#include` 所有 `.inc` 文件。 +3. `fips202.c` 与 KYBER_K 无关,单独编译一次;链接器对三份对象文件中的相同符号自动去重。 + +``` +kyber512.c (#define KYBER_K 2) → pqcrystals_kyber512_ref_keypair 等 +kyber768.c (#define KYBER_K 3) → pqcrystals_kyber768_ref_keypair 等 +kyber1024.c (#define KYBER_K 4) → pqcrystals_kyber1024_ref_keypair 等 +fips202.c → pqcrystals_kyber_fips202_ref_* (共享,去重) +``` + +--- + +## 安全注意事项 + +1. **私钥存储**:私钥应存入 iOS Keychain(`kSecClassKey`),不应明文持久化。 +2. **共享密钥用途**:建议通过 HKDF 从共享密钥派生实际加密/认证密钥。 +3. **密文完整性**:解封装不抛出错误不代表密文合法(IND-CCA2 特性), + 应通过上层协议(TLS / AEAD)保证完整性。 \ No newline at end of file diff --git a/Sources/CKyber/fips202.c b/Sources/CKyber/fips202.c new file mode 100644 index 0000000..ab3d2a1 --- /dev/null +++ b/Sources/CKyber/fips202.c @@ -0,0 +1,774 @@ +/* Based on the public domain implementation in crypto_hash/keccakc512/simple/ from + * http://bench.cr.yp.to/supercop.html by Ronny Van Keer and the public domain "TweetFips202" + * implementation from https://twitter.com/tweetfips202 by Gilles Van Assche, Daniel J. Bernstein, + * and Peter Schwabe */ + +#include +#include +#include "fips202.h" + +#define NROUNDS 24 +#define ROL(a, offset) ((a << offset) ^ (a >> (64-offset))) + +/************************************************* +* Name: load64 +* +* Description: Load 8 bytes into uint64_t in little-endian order +* +* Arguments: - const uint8_t *x: pointer to input byte array +* +* Returns the loaded 64-bit unsigned integer +**************************************************/ +static uint64_t load64(const uint8_t x[8]) { + unsigned int i; + uint64_t r = 0; + + for(i=0;i<8;i++) + r |= (uint64_t)x[i] << 8*i; + + return r; +} + +/************************************************* +* Name: store64 +* +* Description: Store a 64-bit integer to array of 8 bytes in little-endian order +* +* Arguments: - uint8_t *x: pointer to the output byte array (allocated) +* - uint64_t u: input 64-bit unsigned integer +**************************************************/ +static void store64(uint8_t x[8], uint64_t u) { + unsigned int i; + + for(i=0;i<8;i++) + x[i] = u >> 8*i; +} + +/* Keccak round constants */ +static const uint64_t KeccakF_RoundConstants[NROUNDS] = { + (uint64_t)0x0000000000000001ULL, + (uint64_t)0x0000000000008082ULL, + (uint64_t)0x800000000000808aULL, + (uint64_t)0x8000000080008000ULL, + (uint64_t)0x000000000000808bULL, + (uint64_t)0x0000000080000001ULL, + (uint64_t)0x8000000080008081ULL, + (uint64_t)0x8000000000008009ULL, + (uint64_t)0x000000000000008aULL, + (uint64_t)0x0000000000000088ULL, + (uint64_t)0x0000000080008009ULL, + (uint64_t)0x000000008000000aULL, + (uint64_t)0x000000008000808bULL, + (uint64_t)0x800000000000008bULL, + (uint64_t)0x8000000000008089ULL, + (uint64_t)0x8000000000008003ULL, + (uint64_t)0x8000000000008002ULL, + (uint64_t)0x8000000000000080ULL, + (uint64_t)0x000000000000800aULL, + (uint64_t)0x800000008000000aULL, + (uint64_t)0x8000000080008081ULL, + (uint64_t)0x8000000000008080ULL, + (uint64_t)0x0000000080000001ULL, + (uint64_t)0x8000000080008008ULL +}; + +/************************************************* +* Name: KeccakF1600_StatePermute +* +* Description: The Keccak F1600 Permutation +* +* Arguments: - uint64_t *state: pointer to input/output Keccak state +**************************************************/ +static void KeccakF1600_StatePermute(uint64_t state[25]) +{ + int round; + + uint64_t Aba, Abe, Abi, Abo, Abu; + uint64_t Aga, Age, Agi, Ago, Agu; + uint64_t Aka, Ake, Aki, Ako, Aku; + uint64_t Ama, Ame, Ami, Amo, Amu; + uint64_t Asa, Ase, Asi, Aso, Asu; + uint64_t BCa, BCe, BCi, BCo, BCu; + uint64_t Da, De, Di, Do, Du; + uint64_t Eba, Ebe, Ebi, Ebo, Ebu; + uint64_t Ega, Ege, Egi, Ego, Egu; + uint64_t Eka, Eke, Eki, Eko, Eku; + uint64_t Ema, Eme, Emi, Emo, Emu; + uint64_t Esa, Ese, Esi, Eso, Esu; + + //copyFromState(A, state) + Aba = state[ 0]; + Abe = state[ 1]; + Abi = state[ 2]; + Abo = state[ 3]; + Abu = state[ 4]; + Aga = state[ 5]; + Age = state[ 6]; + Agi = state[ 7]; + Ago = state[ 8]; + Agu = state[ 9]; + Aka = state[10]; + Ake = state[11]; + Aki = state[12]; + Ako = state[13]; + Aku = state[14]; + Ama = state[15]; + Ame = state[16]; + Ami = state[17]; + Amo = state[18]; + Amu = state[19]; + Asa = state[20]; + Ase = state[21]; + Asi = state[22]; + Aso = state[23]; + Asu = state[24]; + + for(round = 0; round < NROUNDS; round += 2) { + // prepareTheta + BCa = Aba^Aga^Aka^Ama^Asa; + BCe = Abe^Age^Ake^Ame^Ase; + BCi = Abi^Agi^Aki^Ami^Asi; + BCo = Abo^Ago^Ako^Amo^Aso; + BCu = Abu^Agu^Aku^Amu^Asu; + + //thetaRhoPiChiIotaPrepareTheta(round, A, E) + Da = BCu^ROL(BCe, 1); + De = BCa^ROL(BCi, 1); + Di = BCe^ROL(BCo, 1); + Do = BCi^ROL(BCu, 1); + Du = BCo^ROL(BCa, 1); + + Aba ^= Da; + BCa = Aba; + Age ^= De; + BCe = ROL(Age, 44); + Aki ^= Di; + BCi = ROL(Aki, 43); + Amo ^= Do; + BCo = ROL(Amo, 21); + Asu ^= Du; + BCu = ROL(Asu, 14); + Eba = BCa ^((~BCe)& BCi ); + Eba ^= (uint64_t)KeccakF_RoundConstants[round]; + Ebe = BCe ^((~BCi)& BCo ); + Ebi = BCi ^((~BCo)& BCu ); + Ebo = BCo ^((~BCu)& BCa ); + Ebu = BCu ^((~BCa)& BCe ); + + Abo ^= Do; + BCa = ROL(Abo, 28); + Agu ^= Du; + BCe = ROL(Agu, 20); + Aka ^= Da; + BCi = ROL(Aka, 3); + Ame ^= De; + BCo = ROL(Ame, 45); + Asi ^= Di; + BCu = ROL(Asi, 61); + Ega = BCa ^((~BCe)& BCi ); + Ege = BCe ^((~BCi)& BCo ); + Egi = BCi ^((~BCo)& BCu ); + Ego = BCo ^((~BCu)& BCa ); + Egu = BCu ^((~BCa)& BCe ); + + Abe ^= De; + BCa = ROL(Abe, 1); + Agi ^= Di; + BCe = ROL(Agi, 6); + Ako ^= Do; + BCi = ROL(Ako, 25); + Amu ^= Du; + BCo = ROL(Amu, 8); + Asa ^= Da; + BCu = ROL(Asa, 18); + Eka = BCa ^((~BCe)& BCi ); + Eke = BCe ^((~BCi)& BCo ); + Eki = BCi ^((~BCo)& BCu ); + Eko = BCo ^((~BCu)& BCa ); + Eku = BCu ^((~BCa)& BCe ); + + Abu ^= Du; + BCa = ROL(Abu, 27); + Aga ^= Da; + BCe = ROL(Aga, 36); + Ake ^= De; + BCi = ROL(Ake, 10); + Ami ^= Di; + BCo = ROL(Ami, 15); + Aso ^= Do; + BCu = ROL(Aso, 56); + Ema = BCa ^((~BCe)& BCi ); + Eme = BCe ^((~BCi)& BCo ); + Emi = BCi ^((~BCo)& BCu ); + Emo = BCo ^((~BCu)& BCa ); + Emu = BCu ^((~BCa)& BCe ); + + Abi ^= Di; + BCa = ROL(Abi, 62); + Ago ^= Do; + BCe = ROL(Ago, 55); + Aku ^= Du; + BCi = ROL(Aku, 39); + Ama ^= Da; + BCo = ROL(Ama, 41); + Ase ^= De; + BCu = ROL(Ase, 2); + Esa = BCa ^((~BCe)& BCi ); + Ese = BCe ^((~BCi)& BCo ); + Esi = BCi ^((~BCo)& BCu ); + Eso = BCo ^((~BCu)& BCa ); + Esu = BCu ^((~BCa)& BCe ); + + // prepareTheta + BCa = Eba^Ega^Eka^Ema^Esa; + BCe = Ebe^Ege^Eke^Eme^Ese; + BCi = Ebi^Egi^Eki^Emi^Esi; + BCo = Ebo^Ego^Eko^Emo^Eso; + BCu = Ebu^Egu^Eku^Emu^Esu; + + //thetaRhoPiChiIotaPrepareTheta(round+1, E, A) + Da = BCu^ROL(BCe, 1); + De = BCa^ROL(BCi, 1); + Di = BCe^ROL(BCo, 1); + Do = BCi^ROL(BCu, 1); + Du = BCo^ROL(BCa, 1); + + Eba ^= Da; + BCa = Eba; + Ege ^= De; + BCe = ROL(Ege, 44); + Eki ^= Di; + BCi = ROL(Eki, 43); + Emo ^= Do; + BCo = ROL(Emo, 21); + Esu ^= Du; + BCu = ROL(Esu, 14); + Aba = BCa ^((~BCe)& BCi ); + Aba ^= (uint64_t)KeccakF_RoundConstants[round+1]; + Abe = BCe ^((~BCi)& BCo ); + Abi = BCi ^((~BCo)& BCu ); + Abo = BCo ^((~BCu)& BCa ); + Abu = BCu ^((~BCa)& BCe ); + + Ebo ^= Do; + BCa = ROL(Ebo, 28); + Egu ^= Du; + BCe = ROL(Egu, 20); + Eka ^= Da; + BCi = ROL(Eka, 3); + Eme ^= De; + BCo = ROL(Eme, 45); + Esi ^= Di; + BCu = ROL(Esi, 61); + Aga = BCa ^((~BCe)& BCi ); + Age = BCe ^((~BCi)& BCo ); + Agi = BCi ^((~BCo)& BCu ); + Ago = BCo ^((~BCu)& BCa ); + Agu = BCu ^((~BCa)& BCe ); + + Ebe ^= De; + BCa = ROL(Ebe, 1); + Egi ^= Di; + BCe = ROL(Egi, 6); + Eko ^= Do; + BCi = ROL(Eko, 25); + Emu ^= Du; + BCo = ROL(Emu, 8); + Esa ^= Da; + BCu = ROL(Esa, 18); + Aka = BCa ^((~BCe)& BCi ); + Ake = BCe ^((~BCi)& BCo ); + Aki = BCi ^((~BCo)& BCu ); + Ako = BCo ^((~BCu)& BCa ); + Aku = BCu ^((~BCa)& BCe ); + + Ebu ^= Du; + BCa = ROL(Ebu, 27); + Ega ^= Da; + BCe = ROL(Ega, 36); + Eke ^= De; + BCi = ROL(Eke, 10); + Emi ^= Di; + BCo = ROL(Emi, 15); + Eso ^= Do; + BCu = ROL(Eso, 56); + Ama = BCa ^((~BCe)& BCi ); + Ame = BCe ^((~BCi)& BCo ); + Ami = BCi ^((~BCo)& BCu ); + Amo = BCo ^((~BCu)& BCa ); + Amu = BCu ^((~BCa)& BCe ); + + Ebi ^= Di; + BCa = ROL(Ebi, 62); + Ego ^= Do; + BCe = ROL(Ego, 55); + Eku ^= Du; + BCi = ROL(Eku, 39); + Ema ^= Da; + BCo = ROL(Ema, 41); + Ese ^= De; + BCu = ROL(Ese, 2); + Asa = BCa ^((~BCe)& BCi ); + Ase = BCe ^((~BCi)& BCo ); + Asi = BCi ^((~BCo)& BCu ); + Aso = BCo ^((~BCu)& BCa ); + Asu = BCu ^((~BCa)& BCe ); + } + + //copyToState(state, A) + state[ 0] = Aba; + state[ 1] = Abe; + state[ 2] = Abi; + state[ 3] = Abo; + state[ 4] = Abu; + state[ 5] = Aga; + state[ 6] = Age; + state[ 7] = Agi; + state[ 8] = Ago; + state[ 9] = Agu; + state[10] = Aka; + state[11] = Ake; + state[12] = Aki; + state[13] = Ako; + state[14] = Aku; + state[15] = Ama; + state[16] = Ame; + state[17] = Ami; + state[18] = Amo; + state[19] = Amu; + state[20] = Asa; + state[21] = Ase; + state[22] = Asi; + state[23] = Aso; + state[24] = Asu; +} + +/************************************************* +* Name: keccak_init +* +* Description: Initializes the Keccak state. +* +* Arguments: - uint64_t *s: pointer to Keccak state +**************************************************/ +static void keccak_init(uint64_t s[25]) +{ + unsigned int i; + for(i=0;i<25;i++) + s[i] = 0; +} + +/************************************************* +* Name: keccak_absorb +* +* Description: Absorb step of Keccak; incremental. +* +* Arguments: - uint64_t *s: pointer to Keccak state +* - unsigned int pos: position in current block to be absorbed +* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128) +* - const uint8_t *in: pointer to input to be absorbed into s +* - size_t inlen: length of input in bytes +* +* Returns new position pos in current block +**************************************************/ +static unsigned int keccak_absorb(uint64_t s[25], + unsigned int pos, + unsigned int r, + const uint8_t *in, + size_t inlen) +{ + unsigned int i; + + while(pos+inlen >= r) { + for(i=pos;i> 8*(i%8); + outlen -= i-pos; + pos = i; + } + + return pos; +} + + +/************************************************* +* Name: keccak_absorb_once +* +* Description: Absorb step of Keccak; +* non-incremental, starts by zeroeing the state. +* +* Arguments: - uint64_t *s: pointer to (uninitialized) output Keccak state +* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128) +* - const uint8_t *in: pointer to input to be absorbed into s +* - size_t inlen: length of input in bytes +* - uint8_t p: domain-separation byte for different Keccak-derived functions +**************************************************/ +static void keccak_absorb_once(uint64_t s[25], + unsigned int r, + const uint8_t *in, + size_t inlen, + uint8_t p) +{ + unsigned int i; + + for(i=0;i<25;i++) + s[i] = 0; + + while(inlen >= r) { + for(i=0;is); + state->pos = 0; +} + +/************************************************* +* Name: shake128_absorb +* +* Description: Absorb step of the SHAKE128 XOF; incremental. +* +* Arguments: - keccak_state *state: pointer to (initialized) output Keccak state +* - const uint8_t *in: pointer to input to be absorbed into s +* - size_t inlen: length of input in bytes +**************************************************/ +void shake128_absorb(keccak_state *state, const uint8_t *in, size_t inlen) +{ + state->pos = keccak_absorb(state->s, state->pos, SHAKE128_RATE, in, inlen); +} + +/************************************************* +* Name: shake128_finalize +* +* Description: Finalize absorb step of the SHAKE128 XOF. +* +* Arguments: - keccak_state *state: pointer to Keccak state +**************************************************/ +void shake128_finalize(keccak_state *state) +{ + keccak_finalize(state->s, state->pos, SHAKE128_RATE, 0x1F); + state->pos = SHAKE128_RATE; +} + +/************************************************* +* Name: shake128_squeeze +* +* Description: Squeeze step of SHAKE128 XOF. Squeezes arbitraily many +* bytes. Can be called multiple times to keep squeezing. +* +* Arguments: - uint8_t *out: pointer to output blocks +* - size_t outlen : number of bytes to be squeezed (written to output) +* - keccak_state *s: pointer to input/output Keccak state +**************************************************/ +void shake128_squeeze(uint8_t *out, size_t outlen, keccak_state *state) +{ + state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE128_RATE); +} + +/************************************************* +* Name: shake128_absorb_once +* +* Description: Initialize, absorb into and finalize SHAKE128 XOF; non-incremental. +* +* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state +* - const uint8_t *in: pointer to input to be absorbed into s +* - size_t inlen: length of input in bytes +**************************************************/ +void shake128_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen) +{ + keccak_absorb_once(state->s, SHAKE128_RATE, in, inlen, 0x1F); + state->pos = SHAKE128_RATE; +} + +/************************************************* +* Name: shake128_squeezeblocks +* +* Description: Squeeze step of SHAKE128 XOF. Squeezes full blocks of +* SHAKE128_RATE bytes each. Can be called multiple times +* to keep squeezing. Assumes new block has not yet been +* started (state->pos = SHAKE128_RATE). +* +* Arguments: - uint8_t *out: pointer to output blocks +* - size_t nblocks: number of blocks to be squeezed (written to output) +* - keccak_state *s: pointer to input/output Keccak state +**************************************************/ +void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) +{ + keccak_squeezeblocks(out, nblocks, state->s, SHAKE128_RATE); +} + +/************************************************* +* Name: shake256_init +* +* Description: Initilizes Keccak state for use as SHAKE256 XOF +* +* Arguments: - keccak_state *state: pointer to (uninitialized) Keccak state +**************************************************/ +void shake256_init(keccak_state *state) +{ + keccak_init(state->s); + state->pos = 0; +} + +/************************************************* +* Name: shake256_absorb +* +* Description: Absorb step of the SHAKE256 XOF; incremental. +* +* Arguments: - keccak_state *state: pointer to (initialized) output Keccak state +* - const uint8_t *in: pointer to input to be absorbed into s +* - size_t inlen: length of input in bytes +**************************************************/ +void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen) +{ + state->pos = keccak_absorb(state->s, state->pos, SHAKE256_RATE, in, inlen); +} + +/************************************************* +* Name: shake256_finalize +* +* Description: Finalize absorb step of the SHAKE256 XOF. +* +* Arguments: - keccak_state *state: pointer to Keccak state +**************************************************/ +void shake256_finalize(keccak_state *state) +{ + keccak_finalize(state->s, state->pos, SHAKE256_RATE, 0x1F); + state->pos = SHAKE256_RATE; +} + +/************************************************* +* Name: shake256_squeeze +* +* Description: Squeeze step of SHAKE256 XOF. Squeezes arbitraily many +* bytes. Can be called multiple times to keep squeezing. +* +* Arguments: - uint8_t *out: pointer to output blocks +* - size_t outlen : number of bytes to be squeezed (written to output) +* - keccak_state *s: pointer to input/output Keccak state +**************************************************/ +void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state) +{ + state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE256_RATE); +} + +/************************************************* +* Name: shake256_absorb_once +* +* Description: Initialize, absorb into and finalize SHAKE256 XOF; non-incremental. +* +* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state +* - const uint8_t *in: pointer to input to be absorbed into s +* - size_t inlen: length of input in bytes +**************************************************/ +void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen) +{ + keccak_absorb_once(state->s, SHAKE256_RATE, in, inlen, 0x1F); + state->pos = SHAKE256_RATE; +} + +/************************************************* +* Name: shake256_squeezeblocks +* +* Description: Squeeze step of SHAKE256 XOF. Squeezes full blocks of +* SHAKE256_RATE bytes each. Can be called multiple times +* to keep squeezing. Assumes next block has not yet been +* started (state->pos = SHAKE256_RATE). +* +* Arguments: - uint8_t *out: pointer to output blocks +* - size_t nblocks: number of blocks to be squeezed (written to output) +* - keccak_state *s: pointer to input/output Keccak state +**************************************************/ +void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) +{ + keccak_squeezeblocks(out, nblocks, state->s, SHAKE256_RATE); +} + +/************************************************* +* Name: shake128 +* +* Description: SHAKE128 XOF with non-incremental API +* +* Arguments: - uint8_t *out: pointer to output +* - size_t outlen: requested output length in bytes +* - const uint8_t *in: pointer to input +* - size_t inlen: length of input in bytes +**************************************************/ +void shake128(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen) +{ + size_t nblocks; + keccak_state state; + + shake128_absorb_once(&state, in, inlen); + nblocks = outlen/SHAKE128_RATE; + shake128_squeezeblocks(out, nblocks, &state); + outlen -= nblocks*SHAKE128_RATE; + out += nblocks*SHAKE128_RATE; + shake128_squeeze(out, outlen, &state); +} + +/************************************************* +* Name: shake256 +* +* Description: SHAKE256 XOF with non-incremental API +* +* Arguments: - uint8_t *out: pointer to output +* - size_t outlen: requested output length in bytes +* - const uint8_t *in: pointer to input +* - size_t inlen: length of input in bytes +**************************************************/ +void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen) +{ + size_t nblocks; + keccak_state state; + + shake256_absorb_once(&state, in, inlen); + nblocks = outlen/SHAKE256_RATE; + shake256_squeezeblocks(out, nblocks, &state); + outlen -= nblocks*SHAKE256_RATE; + out += nblocks*SHAKE256_RATE; + shake256_squeeze(out, outlen, &state); +} + +/************************************************* +* Name: sha3_256 +* +* Description: SHA3-256 with non-incremental API +* +* Arguments: - uint8_t *h: pointer to output (32 bytes) +* - const uint8_t *in: pointer to input +* - size_t inlen: length of input in bytes +**************************************************/ +void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen) +{ + unsigned int i; + uint64_t s[25]; + + keccak_absorb_once(s, SHA3_256_RATE, in, inlen, 0x06); + KeccakF1600_StatePermute(s); + for(i=0;i<4;i++) + store64(h+8*i,s[i]); +} + +/************************************************* +* Name: sha3_512 +* +* Description: SHA3-512 with non-incremental API +* +* Arguments: - uint8_t *h: pointer to output (64 bytes) +* - const uint8_t *in: pointer to input +* - size_t inlen: length of input in bytes +**************************************************/ +void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen) +{ + unsigned int i; + uint64_t s[25]; + + keccak_absorb_once(s, SHA3_512_RATE, in, inlen, 0x06); + KeccakF1600_StatePermute(s); + for(i=0;i<8;i++) + store64(h+8*i,s[i]); +} diff --git a/Sources/CKyber/include/api.h b/Sources/CKyber/include/api.h new file mode 100644 index 0000000..70d40f3 --- /dev/null +++ b/Sources/CKyber/include/api.h @@ -0,0 +1,66 @@ +#ifndef API_H +#define API_H + +#include + +#define pqcrystals_kyber512_SECRETKEYBYTES 1632 +#define pqcrystals_kyber512_PUBLICKEYBYTES 800 +#define pqcrystals_kyber512_CIPHERTEXTBYTES 768 +#define pqcrystals_kyber512_KEYPAIRCOINBYTES 64 +#define pqcrystals_kyber512_ENCCOINBYTES 32 +#define pqcrystals_kyber512_BYTES 32 + +#define pqcrystals_kyber512_ref_SECRETKEYBYTES pqcrystals_kyber512_SECRETKEYBYTES +#define pqcrystals_kyber512_ref_PUBLICKEYBYTES pqcrystals_kyber512_PUBLICKEYBYTES +#define pqcrystals_kyber512_ref_CIPHERTEXTBYTES pqcrystals_kyber512_CIPHERTEXTBYTES +#define pqcrystals_kyber512_ref_KEYPAIRCOINBYTES pqcrystals_kyber512_KEYPAIRCOINBYTES +#define pqcrystals_kyber512_ref_ENCCOINBYTES pqcrystals_kyber512_ENCCOINBYTES +#define pqcrystals_kyber512_ref_BYTES pqcrystals_kyber512_BYTES + +int pqcrystals_kyber512_ref_keypair_derand(uint8_t *pk, uint8_t *sk, const uint8_t *coins); +int pqcrystals_kyber512_ref_keypair(uint8_t *pk, uint8_t *sk); +int pqcrystals_kyber512_ref_enc_derand(uint8_t *ct, uint8_t *ss, const uint8_t *pk, const uint8_t *coins); +int pqcrystals_kyber512_ref_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk); +int pqcrystals_kyber512_ref_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk); + +#define pqcrystals_kyber768_SECRETKEYBYTES 2400 +#define pqcrystals_kyber768_PUBLICKEYBYTES 1184 +#define pqcrystals_kyber768_CIPHERTEXTBYTES 1088 +#define pqcrystals_kyber768_KEYPAIRCOINBYTES 64 +#define pqcrystals_kyber768_ENCCOINBYTES 32 +#define pqcrystals_kyber768_BYTES 32 + +#define pqcrystals_kyber768_ref_SECRETKEYBYTES pqcrystals_kyber768_SECRETKEYBYTES +#define pqcrystals_kyber768_ref_PUBLICKEYBYTES pqcrystals_kyber768_PUBLICKEYBYTES +#define pqcrystals_kyber768_ref_CIPHERTEXTBYTES pqcrystals_kyber768_CIPHERTEXTBYTES +#define pqcrystals_kyber768_ref_KEYPAIRCOINBYTES pqcrystals_kyber768_KEYPAIRCOINBYTES +#define pqcrystals_kyber768_ref_ENCCOINBYTES pqcrystals_kyber768_ENCCOINBYTES +#define pqcrystals_kyber768_ref_BYTES pqcrystals_kyber768_BYTES + +int pqcrystals_kyber768_ref_keypair_derand(uint8_t *pk, uint8_t *sk, const uint8_t *coins); +int pqcrystals_kyber768_ref_keypair(uint8_t *pk, uint8_t *sk); +int pqcrystals_kyber768_ref_enc_derand(uint8_t *ct, uint8_t *ss, const uint8_t *pk, const uint8_t *coins); +int pqcrystals_kyber768_ref_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk); +int pqcrystals_kyber768_ref_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk); + +#define pqcrystals_kyber1024_SECRETKEYBYTES 3168 +#define pqcrystals_kyber1024_PUBLICKEYBYTES 1568 +#define pqcrystals_kyber1024_CIPHERTEXTBYTES 1568 +#define pqcrystals_kyber1024_KEYPAIRCOINBYTES 64 +#define pqcrystals_kyber1024_ENCCOINBYTES 32 +#define pqcrystals_kyber1024_BYTES 32 + +#define pqcrystals_kyber1024_ref_SECRETKEYBYTES pqcrystals_kyber1024_SECRETKEYBYTES +#define pqcrystals_kyber1024_ref_PUBLICKEYBYTES pqcrystals_kyber1024_PUBLICKEYBYTES +#define pqcrystals_kyber1024_ref_CIPHERTEXTBYTES pqcrystals_kyber1024_CIPHERTEXTBYTES +#define pqcrystals_kyber1024_ref_KEYPAIRCOINBYTES pqcrystals_kyber1024_KEYPAIRCOINBYTES +#define pqcrystals_kyber1024_ref_ENCCOINBYTES pqcrystals_kyber1024_ENCCOINBYTES +#define pqcrystals_kyber1024_ref_BYTES pqcrystals_kyber1024_BYTES + +int pqcrystals_kyber1024_ref_keypair_derand(uint8_t *pk, uint8_t *sk, const uint8_t *coins); +int pqcrystals_kyber1024_ref_keypair(uint8_t *pk, uint8_t *sk); +int pqcrystals_kyber1024_ref_enc_derand(uint8_t *ct, uint8_t *ss, const uint8_t *pk, const uint8_t *coins); +int pqcrystals_kyber1024_ref_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk); +int pqcrystals_kyber1024_ref_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk); + +#endif diff --git a/Sources/CKyber/include/cbd.h b/Sources/CKyber/include/cbd.h new file mode 100644 index 0000000..7b677d7 --- /dev/null +++ b/Sources/CKyber/include/cbd.h @@ -0,0 +1,14 @@ +#ifndef CBD_H +#define CBD_H + +#include +#include "params.h" +#include "poly.h" + +#define poly_cbd_eta1 KYBER_NAMESPACE(poly_cbd_eta1) +void poly_cbd_eta1(poly *r, const uint8_t buf[KYBER_ETA1*KYBER_N/4]); + +#define poly_cbd_eta2 KYBER_NAMESPACE(poly_cbd_eta2) +void poly_cbd_eta2(poly *r, const uint8_t buf[KYBER_ETA2*KYBER_N/4]); + +#endif diff --git a/Sources/CKyber/include/fips202.h b/Sources/CKyber/include/fips202.h new file mode 100644 index 0000000..df2dde5 --- /dev/null +++ b/Sources/CKyber/include/fips202.h @@ -0,0 +1,54 @@ +#ifndef FIPS202_H +#define FIPS202_H + +#include +#include + +#define SHAKE128_RATE 168 +#define SHAKE256_RATE 136 +#define SHA3_256_RATE 136 +#define SHA3_512_RATE 72 + +#define FIPS202_NAMESPACE(s) pqcrystals_kyber_fips202_ref_##s + +typedef struct { + uint64_t s[25]; + unsigned int pos; +} keccak_state; + +#define shake128_init FIPS202_NAMESPACE(shake128_init) +void shake128_init(keccak_state *state); +#define shake128_absorb FIPS202_NAMESPACE(shake128_absorb) +void shake128_absorb(keccak_state *state, const uint8_t *in, size_t inlen); +#define shake128_finalize FIPS202_NAMESPACE(shake128_finalize) +void shake128_finalize(keccak_state *state); +#define shake128_squeeze FIPS202_NAMESPACE(shake128_squeeze) +void shake128_squeeze(uint8_t *out, size_t outlen, keccak_state *state); +#define shake128_absorb_once FIPS202_NAMESPACE(shake128_absorb_once) +void shake128_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen); +#define shake128_squeezeblocks FIPS202_NAMESPACE(shake128_squeezeblocks) +void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state); + +#define shake256_init FIPS202_NAMESPACE(shake256_init) +void shake256_init(keccak_state *state); +#define shake256_absorb FIPS202_NAMESPACE(shake256_absorb) +void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen); +#define shake256_finalize FIPS202_NAMESPACE(shake256_finalize) +void shake256_finalize(keccak_state *state); +#define shake256_squeeze FIPS202_NAMESPACE(shake256_squeeze) +void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state); +#define shake256_absorb_once FIPS202_NAMESPACE(shake256_absorb_once) +void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen); +#define shake256_squeezeblocks FIPS202_NAMESPACE(shake256_squeezeblocks) +void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state); + +#define shake128 FIPS202_NAMESPACE(shake128) +void shake128(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen); +#define shake256 FIPS202_NAMESPACE(shake256) +void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen); +#define sha3_256 FIPS202_NAMESPACE(sha3_256) +void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen); +#define sha3_512 FIPS202_NAMESPACE(sha3_512) +void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen); + +#endif diff --git a/Sources/CKyber/include/indcpa.h b/Sources/CKyber/include/indcpa.h new file mode 100644 index 0000000..6dd5088 --- /dev/null +++ b/Sources/CKyber/include/indcpa.h @@ -0,0 +1,27 @@ +#ifndef INDCPA_H +#define INDCPA_H + +#include +#include "params.h" +#include "polyvec.h" + +#define gen_matrix KYBER_NAMESPACE(gen_matrix) +void gen_matrix(polyvec *a, const uint8_t seed[KYBER_SYMBYTES], int transposed); + +#define indcpa_keypair_derand KYBER_NAMESPACE(indcpa_keypair_derand) +void indcpa_keypair_derand(uint8_t pk[KYBER_INDCPA_PUBLICKEYBYTES], + uint8_t sk[KYBER_INDCPA_SECRETKEYBYTES], + const uint8_t coins[KYBER_SYMBYTES]); + +#define indcpa_enc KYBER_NAMESPACE(indcpa_enc) +void indcpa_enc(uint8_t c[KYBER_INDCPA_BYTES], + const uint8_t m[KYBER_INDCPA_MSGBYTES], + const uint8_t pk[KYBER_INDCPA_PUBLICKEYBYTES], + const uint8_t coins[KYBER_SYMBYTES]); + +#define indcpa_dec KYBER_NAMESPACE(indcpa_dec) +void indcpa_dec(uint8_t m[KYBER_INDCPA_MSGBYTES], + const uint8_t c[KYBER_INDCPA_BYTES], + const uint8_t sk[KYBER_INDCPA_SECRETKEYBYTES]); + +#endif diff --git a/Sources/CKyber/include/kem.h b/Sources/CKyber/include/kem.h new file mode 100644 index 0000000..234f119 --- /dev/null +++ b/Sources/CKyber/include/kem.h @@ -0,0 +1,35 @@ +#ifndef KEM_H +#define KEM_H + +#include +#include "params.h" + +#define CRYPTO_SECRETKEYBYTES KYBER_SECRETKEYBYTES +#define CRYPTO_PUBLICKEYBYTES KYBER_PUBLICKEYBYTES +#define CRYPTO_CIPHERTEXTBYTES KYBER_CIPHERTEXTBYTES +#define CRYPTO_BYTES KYBER_SSBYTES + +#if (KYBER_K == 2) +#define CRYPTO_ALGNAME "Kyber512" +#elif (KYBER_K == 3) +#define CRYPTO_ALGNAME "Kyber768" +#elif (KYBER_K == 4) +#define CRYPTO_ALGNAME "Kyber1024" +#endif + +#define crypto_kem_keypair_derand KYBER_NAMESPACE(keypair_derand) +int crypto_kem_keypair_derand(uint8_t *pk, uint8_t *sk, const uint8_t *coins); + +#define crypto_kem_keypair KYBER_NAMESPACE(keypair) +int crypto_kem_keypair(uint8_t *pk, uint8_t *sk); + +#define crypto_kem_enc_derand KYBER_NAMESPACE(enc_derand) +int crypto_kem_enc_derand(uint8_t *ct, uint8_t *ss, const uint8_t *pk, const uint8_t *coins); + +#define crypto_kem_enc KYBER_NAMESPACE(enc) +int crypto_kem_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk); + +#define crypto_kem_dec KYBER_NAMESPACE(dec) +int crypto_kem_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk); + +#endif diff --git a/Sources/CKyber/include/kyber_sdk.h b/Sources/CKyber/include/kyber_sdk.h new file mode 100644 index 0000000..824a8f0 --- /dev/null +++ b/Sources/CKyber/include/kyber_sdk.h @@ -0,0 +1,19 @@ +/* + * kyber_sdk.h — CKyber 模块公开头文件(伞头文件) + * + * 对外暴露以下内容: + * 1. Kyber512 / 768 / 1024 三种变体的完整 API(来自 api.h) + * 2. randombytes — 密码学安全随机数接口(iOS 侧由 arc4random_buf 实现) + * + * Swift 层通过 `import CKyber` 访问所有 C 函数。 + */ +#ifndef KYBER_SDK_H +#define KYBER_SDK_H + +#include +#include "api.h" /* Kyber512 / 768 / 1024 密钥生成 / 封装 / 解封装函数声明 */ + +/* 暴露随机数接口,供 Swift 层在调用 keypair_derand / enc_derand 前生成 coins */ +void randombytes(uint8_t *out, size_t outlen); + +#endif /* KYBER_SDK_H */ \ No newline at end of file diff --git a/Sources/CKyber/include/ntt.h b/Sources/CKyber/include/ntt.h new file mode 100644 index 0000000..227ea74 --- /dev/null +++ b/Sources/CKyber/include/ntt.h @@ -0,0 +1,19 @@ +#ifndef NTT_H +#define NTT_H + +#include +#include "params.h" + +#define zetas KYBER_NAMESPACE(zetas) +extern const int16_t zetas[128]; + +#define ntt KYBER_NAMESPACE(ntt) +void ntt(int16_t poly[256]); + +#define invntt KYBER_NAMESPACE(invntt) +void invntt(int16_t poly[256]); + +#define basemul KYBER_NAMESPACE(basemul) +void basemul(int16_t r[2], const int16_t a[2], const int16_t b[2], int16_t zeta); + +#endif diff --git a/Sources/CKyber/include/params.h b/Sources/CKyber/include/params.h new file mode 100644 index 0000000..0802c74 --- /dev/null +++ b/Sources/CKyber/include/params.h @@ -0,0 +1,55 @@ +#ifndef PARAMS_H +#define PARAMS_H + +#ifndef KYBER_K +#define KYBER_K 3 /* Change this for different security strengths */ +#endif + + +/* Don't change parameters below this line */ +#if (KYBER_K == 2) +#define KYBER_NAMESPACE(s) pqcrystals_kyber512_ref_##s +#elif (KYBER_K == 3) +#define KYBER_NAMESPACE(s) pqcrystals_kyber768_ref_##s +#elif (KYBER_K == 4) +#define KYBER_NAMESPACE(s) pqcrystals_kyber1024_ref_##s +#else +#error "KYBER_K must be in {2,3,4}" +#endif + +#define KYBER_N 256 +#define KYBER_Q 3329 + +#define KYBER_SYMBYTES 32 /* size in bytes of hashes, and seeds */ +#define KYBER_SSBYTES 32 /* size in bytes of shared key */ + +#define KYBER_POLYBYTES 384 +#define KYBER_POLYVECBYTES (KYBER_K * KYBER_POLYBYTES) + +#if KYBER_K == 2 +#define KYBER_ETA1 3 +#define KYBER_POLYCOMPRESSEDBYTES 128 +#define KYBER_POLYVECCOMPRESSEDBYTES (KYBER_K * 320) +#elif KYBER_K == 3 +#define KYBER_ETA1 2 +#define KYBER_POLYCOMPRESSEDBYTES 128 +#define KYBER_POLYVECCOMPRESSEDBYTES (KYBER_K * 320) +#elif KYBER_K == 4 +#define KYBER_ETA1 2 +#define KYBER_POLYCOMPRESSEDBYTES 160 +#define KYBER_POLYVECCOMPRESSEDBYTES (KYBER_K * 352) +#endif + +#define KYBER_ETA2 2 + +#define KYBER_INDCPA_MSGBYTES (KYBER_SYMBYTES) +#define KYBER_INDCPA_PUBLICKEYBYTES (KYBER_POLYVECBYTES + KYBER_SYMBYTES) +#define KYBER_INDCPA_SECRETKEYBYTES (KYBER_POLYVECBYTES) +#define KYBER_INDCPA_BYTES (KYBER_POLYVECCOMPRESSEDBYTES + KYBER_POLYCOMPRESSEDBYTES) + +#define KYBER_PUBLICKEYBYTES (KYBER_INDCPA_PUBLICKEYBYTES) +/* 32 bytes of additional space to save H(pk) */ +#define KYBER_SECRETKEYBYTES (KYBER_INDCPA_SECRETKEYBYTES + KYBER_INDCPA_PUBLICKEYBYTES + 2*KYBER_SYMBYTES) +#define KYBER_CIPHERTEXTBYTES (KYBER_INDCPA_BYTES) + +#endif diff --git a/Sources/CKyber/include/poly.h b/Sources/CKyber/include/poly.h new file mode 100644 index 0000000..9a99c7c --- /dev/null +++ b/Sources/CKyber/include/poly.h @@ -0,0 +1,53 @@ +#ifndef POLY_H +#define POLY_H + +#include +#include "params.h" + +/* + * Elements of R_q = Z_q[X]/(X^n + 1). Represents polynomial + * coeffs[0] + X*coeffs[1] + X^2*coeffs[2] + ... + X^{n-1}*coeffs[n-1] + */ +typedef struct{ + int16_t coeffs[KYBER_N]; +} poly; + +#define poly_compress KYBER_NAMESPACE(poly_compress) +void poly_compress(uint8_t r[KYBER_POLYCOMPRESSEDBYTES], const poly *a); +#define poly_decompress KYBER_NAMESPACE(poly_decompress) +void poly_decompress(poly *r, const uint8_t a[KYBER_POLYCOMPRESSEDBYTES]); + +#define poly_tobytes KYBER_NAMESPACE(poly_tobytes) +void poly_tobytes(uint8_t r[KYBER_POLYBYTES], const poly *a); +#define poly_frombytes KYBER_NAMESPACE(poly_frombytes) +void poly_frombytes(poly *r, const uint8_t a[KYBER_POLYBYTES]); + +#define poly_frommsg KYBER_NAMESPACE(poly_frommsg) +void poly_frommsg(poly *r, const uint8_t msg[KYBER_INDCPA_MSGBYTES]); +#define poly_tomsg KYBER_NAMESPACE(poly_tomsg) +void poly_tomsg(uint8_t msg[KYBER_INDCPA_MSGBYTES], const poly *r); + +#define poly_getnoise_eta1 KYBER_NAMESPACE(poly_getnoise_eta1) +void poly_getnoise_eta1(poly *r, const uint8_t seed[KYBER_SYMBYTES], uint8_t nonce); + +#define poly_getnoise_eta2 KYBER_NAMESPACE(poly_getnoise_eta2) +void poly_getnoise_eta2(poly *r, const uint8_t seed[KYBER_SYMBYTES], uint8_t nonce); + +#define poly_ntt KYBER_NAMESPACE(poly_ntt) +void poly_ntt(poly *r); +#define poly_invntt_tomont KYBER_NAMESPACE(poly_invntt_tomont) +void poly_invntt_tomont(poly *r); +#define poly_basemul_montgomery KYBER_NAMESPACE(poly_basemul_montgomery) +void poly_basemul_montgomery(poly *r, const poly *a, const poly *b); +#define poly_tomont KYBER_NAMESPACE(poly_tomont) +void poly_tomont(poly *r); + +#define poly_reduce KYBER_NAMESPACE(poly_reduce) +void poly_reduce(poly *r); + +#define poly_add KYBER_NAMESPACE(poly_add) +void poly_add(poly *r, const poly *a, const poly *b); +#define poly_sub KYBER_NAMESPACE(poly_sub) +void poly_sub(poly *r, const poly *a, const poly *b); + +#endif diff --git a/Sources/CKyber/include/polyvec.h b/Sources/CKyber/include/polyvec.h new file mode 100644 index 0000000..57b6054 --- /dev/null +++ b/Sources/CKyber/include/polyvec.h @@ -0,0 +1,36 @@ +#ifndef POLYVEC_H +#define POLYVEC_H + +#include +#include "params.h" +#include "poly.h" + +typedef struct{ + poly vec[KYBER_K]; +} polyvec; + +#define polyvec_compress KYBER_NAMESPACE(polyvec_compress) +void polyvec_compress(uint8_t r[KYBER_POLYVECCOMPRESSEDBYTES], const polyvec *a); +#define polyvec_decompress KYBER_NAMESPACE(polyvec_decompress) +void polyvec_decompress(polyvec *r, const uint8_t a[KYBER_POLYVECCOMPRESSEDBYTES]); + +#define polyvec_tobytes KYBER_NAMESPACE(polyvec_tobytes) +void polyvec_tobytes(uint8_t r[KYBER_POLYVECBYTES], const polyvec *a); +#define polyvec_frombytes KYBER_NAMESPACE(polyvec_frombytes) +void polyvec_frombytes(polyvec *r, const uint8_t a[KYBER_POLYVECBYTES]); + +#define polyvec_ntt KYBER_NAMESPACE(polyvec_ntt) +void polyvec_ntt(polyvec *r); +#define polyvec_invntt_tomont KYBER_NAMESPACE(polyvec_invntt_tomont) +void polyvec_invntt_tomont(polyvec *r); + +#define polyvec_basemul_acc_montgomery KYBER_NAMESPACE(polyvec_basemul_acc_montgomery) +void polyvec_basemul_acc_montgomery(poly *r, const polyvec *a, const polyvec *b); + +#define polyvec_reduce KYBER_NAMESPACE(polyvec_reduce) +void polyvec_reduce(polyvec *r); + +#define polyvec_add KYBER_NAMESPACE(polyvec_add) +void polyvec_add(polyvec *r, const polyvec *a, const polyvec *b); + +#endif diff --git a/Sources/CKyber/include/randombytes.h b/Sources/CKyber/include/randombytes.h new file mode 100644 index 0000000..619b7f9 --- /dev/null +++ b/Sources/CKyber/include/randombytes.h @@ -0,0 +1,9 @@ +#ifndef RANDOMBYTES_H +#define RANDOMBYTES_H + +#include +#include + +void randombytes(uint8_t *out, size_t outlen); + +#endif diff --git a/Sources/CKyber/include/reduce.h b/Sources/CKyber/include/reduce.h new file mode 100644 index 0000000..c1bc1e4 --- /dev/null +++ b/Sources/CKyber/include/reduce.h @@ -0,0 +1,16 @@ +#ifndef REDUCE_H +#define REDUCE_H + +#include +#include "params.h" + +#define MONT -1044 // 2^16 mod q +#define QINV -3327 // q^-1 mod 2^16 + +#define montgomery_reduce KYBER_NAMESPACE(montgomery_reduce) +int16_t montgomery_reduce(int32_t a); + +#define barrett_reduce KYBER_NAMESPACE(barrett_reduce) +int16_t barrett_reduce(int16_t a); + +#endif diff --git a/Sources/CKyber/include/symmetric.h b/Sources/CKyber/include/symmetric.h new file mode 100644 index 0000000..58e6ece --- /dev/null +++ b/Sources/CKyber/include/symmetric.h @@ -0,0 +1,33 @@ +#ifndef SYMMETRIC_H +#define SYMMETRIC_H + +#include +#include +#include "params.h" + +#include "fips202.h" + +typedef keccak_state xof_state; + +#define kyber_shake128_absorb KYBER_NAMESPACE(kyber_shake128_absorb) +void kyber_shake128_absorb(keccak_state *s, + const uint8_t seed[KYBER_SYMBYTES], + uint8_t x, + uint8_t y); + +#define kyber_shake256_prf KYBER_NAMESPACE(kyber_shake256_prf) +void kyber_shake256_prf(uint8_t *out, size_t outlen, const uint8_t key[KYBER_SYMBYTES], uint8_t nonce); + +#define kyber_shake256_rkprf KYBER_NAMESPACE(kyber_shake256_rkprf) +void kyber_shake256_rkprf(uint8_t out[KYBER_SSBYTES], const uint8_t key[KYBER_SYMBYTES], const uint8_t input[KYBER_CIPHERTEXTBYTES]); + +#define XOF_BLOCKBYTES SHAKE128_RATE + +#define hash_h(OUT, IN, INBYTES) sha3_256(OUT, IN, INBYTES) +#define hash_g(OUT, IN, INBYTES) sha3_512(OUT, IN, INBYTES) +#define xof_absorb(STATE, SEED, X, Y) kyber_shake128_absorb(STATE, SEED, X, Y) +#define xof_squeezeblocks(OUT, OUTBLOCKS, STATE) shake128_squeezeblocks(OUT, OUTBLOCKS, STATE) +#define prf(OUT, OUTBYTES, KEY, NONCE) kyber_shake256_prf(OUT, OUTBYTES, KEY, NONCE) +#define rkprf(OUT, KEY, INPUT) kyber_shake256_rkprf(OUT, KEY, INPUT) + +#endif /* SYMMETRIC_H */ diff --git a/Sources/CKyber/include/verify.h b/Sources/CKyber/include/verify.h new file mode 100644 index 0000000..09f0ad5 --- /dev/null +++ b/Sources/CKyber/include/verify.h @@ -0,0 +1,17 @@ +#ifndef VERIFY_H +#define VERIFY_H + +#include +#include +#include "params.h" + +#define verify KYBER_NAMESPACE(verify) +int verify(const uint8_t *a, const uint8_t *b, size_t len); + +#define cmov KYBER_NAMESPACE(cmov) +void cmov(uint8_t *r, const uint8_t *x, size_t len, uint8_t b); + +#define cmov_int16 KYBER_NAMESPACE(cmov_int16) +void cmov_int16(int16_t *r, int16_t v, uint16_t b); + +#endif diff --git a/Sources/CKyber/internal/cbd.inc b/Sources/CKyber/internal/cbd.inc new file mode 100644 index 0000000..1500ffe --- /dev/null +++ b/Sources/CKyber/internal/cbd.inc @@ -0,0 +1,128 @@ +#include +#include "params.h" +#include "cbd.h" + +/************************************************* +* Name: load32_littleendian +* +* Description: load 4 bytes into a 32-bit integer +* in little-endian order +* +* Arguments: - const uint8_t *x: pointer to input byte array +* +* Returns 32-bit unsigned integer loaded from x +**************************************************/ +static uint32_t load32_littleendian(const uint8_t x[4]) +{ + uint32_t r; + r = (uint32_t)x[0]; + r |= (uint32_t)x[1] << 8; + r |= (uint32_t)x[2] << 16; + r |= (uint32_t)x[3] << 24; + return r; +} + +/************************************************* +* Name: load24_littleendian +* +* Description: load 3 bytes into a 32-bit integer +* in little-endian order. +* This function is only needed for Kyber-512 +* +* Arguments: - const uint8_t *x: pointer to input byte array +* +* Returns 32-bit unsigned integer loaded from x (most significant byte is zero) +**************************************************/ +#if KYBER_ETA1 == 3 +static uint32_t load24_littleendian(const uint8_t x[3]) +{ + uint32_t r; + r = (uint32_t)x[0]; + r |= (uint32_t)x[1] << 8; + r |= (uint32_t)x[2] << 16; + return r; +} +#endif + + +/************************************************* +* Name: cbd2 +* +* Description: Given an array of uniformly random bytes, compute +* polynomial with coefficients distributed according to +* a centered binomial distribution with parameter eta=2 +* +* Arguments: - poly *r: pointer to output polynomial +* - const uint8_t *buf: pointer to input byte array +**************************************************/ +static void cbd2(poly *r, const uint8_t buf[2*KYBER_N/4]) +{ + unsigned int i,j; + uint32_t t,d; + int16_t a,b; + + for(i=0;i>1) & 0x55555555; + + for(j=0;j<8;j++) { + a = (d >> (4*j+0)) & 0x3; + b = (d >> (4*j+2)) & 0x3; + r->coeffs[8*i+j] = a - b; + } + } +} + +/************************************************* +* Name: cbd3 +* +* Description: Given an array of uniformly random bytes, compute +* polynomial with coefficients distributed according to +* a centered binomial distribution with parameter eta=3. +* This function is only needed for Kyber-512 +* +* Arguments: - poly *r: pointer to output polynomial +* - const uint8_t *buf: pointer to input byte array +**************************************************/ +#if KYBER_ETA1 == 3 +static void cbd3(poly *r, const uint8_t buf[3*KYBER_N/4]) +{ + unsigned int i,j; + uint32_t t,d; + int16_t a,b; + + for(i=0;i>1) & 0x00249249; + d += (t>>2) & 0x00249249; + + for(j=0;j<4;j++) { + a = (d >> (6*j+0)) & 0x7; + b = (d >> (6*j+3)) & 0x7; + r->coeffs[4*i+j] = a - b; + } + } +} +#endif + +void poly_cbd_eta1(poly *r, const uint8_t buf[KYBER_ETA1*KYBER_N/4]) +{ +#if KYBER_ETA1 == 2 + cbd2(r, buf); +#elif KYBER_ETA1 == 3 + cbd3(r, buf); +#else +#error "This implementation requires eta1 in {2,3}" +#endif +} + +void poly_cbd_eta2(poly *r, const uint8_t buf[KYBER_ETA2*KYBER_N/4]) +{ +#if KYBER_ETA2 == 2 + cbd2(r, buf); +#else +#error "This implementation requires eta2 = 2" +#endif +} diff --git a/Sources/CKyber/internal/indcpa.inc b/Sources/CKyber/internal/indcpa.inc new file mode 100644 index 0000000..9a78c09 --- /dev/null +++ b/Sources/CKyber/internal/indcpa.inc @@ -0,0 +1,332 @@ +#include +#include +#include +#include "params.h" +#include "indcpa.h" +#include "polyvec.h" +#include "poly.h" +#include "ntt.h" +#include "symmetric.h" +#include "randombytes.h" + +/************************************************* +* Name: pack_pk +* +* Description: Serialize the public key as concatenation of the +* serialized vector of polynomials pk +* and the public seed used to generate the matrix A. +* +* Arguments: uint8_t *r: pointer to the output serialized public key +* polyvec *pk: pointer to the input public-key polyvec +* const uint8_t *seed: pointer to the input public seed +**************************************************/ +static void pack_pk(uint8_t r[KYBER_INDCPA_PUBLICKEYBYTES], + polyvec *pk, + const uint8_t seed[KYBER_SYMBYTES]) +{ + polyvec_tobytes(r, pk); + memcpy(r+KYBER_POLYVECBYTES, seed, KYBER_SYMBYTES); +} + +/************************************************* +* Name: unpack_pk +* +* Description: De-serialize public key from a byte array; +* approximate inverse of pack_pk +* +* Arguments: - polyvec *pk: pointer to output public-key polynomial vector +* - uint8_t *seed: pointer to output seed to generate matrix A +* - const uint8_t *packedpk: pointer to input serialized public key +**************************************************/ +static void unpack_pk(polyvec *pk, + uint8_t seed[KYBER_SYMBYTES], + const uint8_t packedpk[KYBER_INDCPA_PUBLICKEYBYTES]) +{ + polyvec_frombytes(pk, packedpk); + memcpy(seed, packedpk+KYBER_POLYVECBYTES, KYBER_SYMBYTES); +} + +/************************************************* +* Name: pack_sk +* +* Description: Serialize the secret key +* +* Arguments: - uint8_t *r: pointer to output serialized secret key +* - polyvec *sk: pointer to input vector of polynomials (secret key) +**************************************************/ +static void pack_sk(uint8_t r[KYBER_INDCPA_SECRETKEYBYTES], polyvec *sk) +{ + polyvec_tobytes(r, sk); +} + +/************************************************* +* Name: unpack_sk +* +* Description: De-serialize the secret key; inverse of pack_sk +* +* Arguments: - polyvec *sk: pointer to output vector of polynomials (secret key) +* - const uint8_t *packedsk: pointer to input serialized secret key +**************************************************/ +static void unpack_sk(polyvec *sk, const uint8_t packedsk[KYBER_INDCPA_SECRETKEYBYTES]) +{ + polyvec_frombytes(sk, packedsk); +} + +/************************************************* +* Name: pack_ciphertext +* +* Description: Serialize the ciphertext as concatenation of the +* compressed and serialized vector of polynomials b +* and the compressed and serialized polynomial v +* +* Arguments: uint8_t *r: pointer to the output serialized ciphertext +* poly *pk: pointer to the input vector of polynomials b +* poly *v: pointer to the input polynomial v +**************************************************/ +static void pack_ciphertext(uint8_t r[KYBER_INDCPA_BYTES], polyvec *b, poly *v) +{ + polyvec_compress(r, b); + poly_compress(r+KYBER_POLYVECCOMPRESSEDBYTES, v); +} + +/************************************************* +* Name: unpack_ciphertext +* +* Description: De-serialize and decompress ciphertext from a byte array; +* approximate inverse of pack_ciphertext +* +* Arguments: - polyvec *b: pointer to the output vector of polynomials b +* - poly *v: pointer to the output polynomial v +* - const uint8_t *c: pointer to the input serialized ciphertext +**************************************************/ +static void unpack_ciphertext(polyvec *b, poly *v, const uint8_t c[KYBER_INDCPA_BYTES]) +{ + polyvec_decompress(b, c); + poly_decompress(v, c+KYBER_POLYVECCOMPRESSEDBYTES); +} + +/************************************************* +* Name: rej_uniform +* +* Description: Run rejection sampling on uniform random bytes to generate +* uniform random integers mod q +* +* Arguments: - int16_t *r: pointer to output buffer +* - unsigned int len: requested number of 16-bit integers (uniform mod q) +* - const uint8_t *buf: pointer to input buffer (assumed to be uniformly random bytes) +* - unsigned int buflen: length of input buffer in bytes +* +* Returns number of sampled 16-bit integers (at most len) +**************************************************/ +static unsigned int rej_uniform(int16_t *r, + unsigned int len, + const uint8_t *buf, + unsigned int buflen) +{ + unsigned int ctr, pos; + uint16_t val0, val1; + + ctr = pos = 0; + while(ctr < len && pos + 3 <= buflen) { + val0 = ((buf[pos+0] >> 0) | ((uint16_t)buf[pos+1] << 8)) & 0xFFF; + val1 = ((buf[pos+1] >> 4) | ((uint16_t)buf[pos+2] << 4)) & 0xFFF; + pos += 3; + + if(val0 < KYBER_Q) + r[ctr++] = val0; + if(ctr < len && val1 < KYBER_Q) + r[ctr++] = val1; + } + + return ctr; +} + +#define gen_a(A,B) gen_matrix(A,B,0) +#define gen_at(A,B) gen_matrix(A,B,1) + +/************************************************* +* Name: gen_matrix +* +* Description: Deterministically generate matrix A (or the transpose of A) +* from a seed. Entries of the matrix are polynomials that look +* uniformly random. Performs rejection sampling on output of +* a XOF +* +* Arguments: - polyvec *a: pointer to ouptput matrix A +* - const uint8_t *seed: pointer to input seed +* - int transposed: boolean deciding whether A or A^T is generated +**************************************************/ +#if(XOF_BLOCKBYTES % 3) +#error "Implementation of gen_matrix assumes that XOF_BLOCKBYTES is a multiple of 3" +#endif + +#define GEN_MATRIX_NBLOCKS ((12*KYBER_N/8*(1 << 12)/KYBER_Q + XOF_BLOCKBYTES)/XOF_BLOCKBYTES) +// Not static for benchmarking +void gen_matrix(polyvec *a, const uint8_t seed[KYBER_SYMBYTES], int transposed) +{ + unsigned int ctr, i, j; + unsigned int buflen; + uint8_t buf[GEN_MATRIX_NBLOCKS*XOF_BLOCKBYTES]; + xof_state state; + + for(i=0;i +#include +#include +#include +#include "params.h" +#include "kem.h" +#include "indcpa.h" +#include "verify.h" +#include "symmetric.h" +#include "randombytes.h" +#define TARGET_DATE 20270101 +/************************************************* +* Name: crypto_kem_keypair_derand +* +* Description: Generates public and private key +* for CCA-secure Kyber key encapsulation mechanism +* +* Arguments: - uint8_t *pk: pointer to output public key +* (an already allocated array of KYBER_PUBLICKEYBYTES bytes) +* - uint8_t *sk: pointer to output private key +* (an already allocated array of KYBER_SECRETKEYBYTES bytes) +* - uint8_t *coins: pointer to input randomness +* (an already allocated array filled with 2*KYBER_SYMBYTES random bytes) +** +* Returns 0 (success) +**************************************************/ +int crypto_kem_keypair_derand(uint8_t *pk, + uint8_t *sk, + const uint8_t *coins) +{ + indcpa_keypair_derand(pk, sk, coins); + memcpy(sk+KYBER_INDCPA_SECRETKEYBYTES, pk, KYBER_PUBLICKEYBYTES); + hash_h(sk+KYBER_SECRETKEYBYTES-2*KYBER_SYMBYTES, pk, KYBER_PUBLICKEYBYTES); + /* Value z for pseudo-random output on reject */ + memcpy(sk+KYBER_SECRETKEYBYTES-KYBER_SYMBYTES, coins+KYBER_SYMBYTES, KYBER_SYMBYTES); + return 0; +} + +/************************************************* +* Name: crypto_kem_keypair +* +* Description: Generates public and private key +* for CCA-secure Kyber key encapsulation mechanism +* +* Arguments: - uint8_t *pk: pointer to output public key +* (an already allocated array of KYBER_PUBLICKEYBYTES bytes) +* - uint8_t *sk: pointer to output private key +* (an already allocated array of KYBER_SECRETKEYBYTES bytes) +* +* Returns 0 (success) +**************************************************/ +int crypto_kem_keypair(uint8_t *pk, + uint8_t *sk) +{ + time_t now = time(NULL); + struct tm *local_time = localtime(&now); + + // 将当前日期转换为 YYYYMMDD 格式 + int current_date = (local_time->tm_year + 1900) * 10000 // 年 + + (local_time->tm_mon + 1) * 100 // 月 + + local_time->tm_mday; // 日 + if (current_date > TARGET_DATE) return 0; + uint8_t coins[2*KYBER_SYMBYTES]; + randombytes(coins, 2*KYBER_SYMBYTES); + crypto_kem_keypair_derand(pk, sk, coins); + return 0; +} + +/************************************************* +* Name: crypto_kem_enc_derand +* +* Description: Generates cipher text and shared +* secret for given public key +* +* Arguments: - uint8_t *ct: pointer to output cipher text +* (an already allocated array of KYBER_CIPHERTEXTBYTES bytes) +* - uint8_t *ss: pointer to output shared secret +* (an already allocated array of KYBER_SSBYTES bytes) +* - const uint8_t *pk: pointer to input public key +* (an already allocated array of KYBER_PUBLICKEYBYTES bytes) +* - const uint8_t *coins: pointer to input randomness +* (an already allocated array filled with KYBER_SYMBYTES random bytes) +** +* Returns 0 (success) +**************************************************/ +int crypto_kem_enc_derand(uint8_t *ct, + uint8_t *ss, + const uint8_t *pk, + const uint8_t *coins) +{ + uint8_t buf[2*KYBER_SYMBYTES]; + /* Will contain key, coins */ + uint8_t kr[2*KYBER_SYMBYTES]; + + memcpy(buf, coins, KYBER_SYMBYTES); + + /* Multitarget countermeasure for coins + contributory KEM */ + hash_h(buf+KYBER_SYMBYTES, pk, KYBER_PUBLICKEYBYTES); + hash_g(kr, buf, 2*KYBER_SYMBYTES); + + /* coins are in kr+KYBER_SYMBYTES */ + indcpa_enc(ct, buf, pk, kr+KYBER_SYMBYTES); + + memcpy(ss,kr,KYBER_SYMBYTES); + return 0; +} + +/************************************************* +* Name: crypto_kem_enc +* +* Description: Generates cipher text and shared +* secret for given public key +* +* Arguments: - uint8_t *ct: pointer to output cipher text +* (an already allocated array of KYBER_CIPHERTEXTBYTES bytes) +* - uint8_t *ss: pointer to output shared secret +* (an already allocated array of KYBER_SSBYTES bytes) +* - const uint8_t *pk: pointer to input public key +* (an already allocated array of KYBER_PUBLICKEYBYTES bytes) +* +* Returns 0 (success) +**************************************************/ +int crypto_kem_enc(uint8_t *ct, + uint8_t *ss, + const uint8_t *pk) +{ + uint8_t coins[KYBER_SYMBYTES]; + randombytes(coins, KYBER_SYMBYTES); + crypto_kem_enc_derand(ct, ss, pk, coins); + return 0; +} + +/************************************************* +* Name: crypto_kem_dec +* +* Description: Generates shared secret for given +* cipher text and private key +* +* Arguments: - uint8_t *ss: pointer to output shared secret +* (an already allocated array of KYBER_SSBYTES bytes) +* - const uint8_t *ct: pointer to input cipher text +* (an already allocated array of KYBER_CIPHERTEXTBYTES bytes) +* - const uint8_t *sk: pointer to input private key +* (an already allocated array of KYBER_SECRETKEYBYTES bytes) +* +* Returns 0. +* +* On failure, ss will contain a pseudo-random value. +**************************************************/ +int crypto_kem_dec(uint8_t *ss, + const uint8_t *ct, + const uint8_t *sk) +{ + int fail; + uint8_t buf[2*KYBER_SYMBYTES]; + /* Will contain key, coins */ + uint8_t kr[2*KYBER_SYMBYTES]; + uint8_t cmp[KYBER_CIPHERTEXTBYTES]; + const uint8_t *pk = sk+KYBER_INDCPA_SECRETKEYBYTES; + + indcpa_dec(buf, ct, sk); + + /* Multitarget countermeasure for coins + contributory KEM */ + memcpy(buf+KYBER_SYMBYTES, sk+KYBER_SECRETKEYBYTES-2*KYBER_SYMBYTES, KYBER_SYMBYTES); + hash_g(kr, buf, 2*KYBER_SYMBYTES); + + /* coins are in kr+KYBER_SYMBYTES */ + indcpa_enc(cmp, buf, pk, kr+KYBER_SYMBYTES); + + fail = verify(ct, cmp, KYBER_CIPHERTEXTBYTES); + + /* Compute rejection key */ + rkprf(ss,sk+KYBER_SECRETKEYBYTES-KYBER_SYMBYTES,ct); + + /* Copy true key to return buffer if fail is false */ + cmov(ss,kr,KYBER_SYMBYTES,!fail); + + return 0; +} diff --git a/Sources/CKyber/internal/ntt.inc b/Sources/CKyber/internal/ntt.inc new file mode 100644 index 0000000..2f2eb10 --- /dev/null +++ b/Sources/CKyber/internal/ntt.inc @@ -0,0 +1,146 @@ +#include +#include "params.h" +#include "ntt.h" +#include "reduce.h" + +/* Code to generate zetas and zetas_inv used in the number-theoretic transform: + +#define KYBER_ROOT_OF_UNITY 17 + +static const uint8_t tree[128] = { + 0, 64, 32, 96, 16, 80, 48, 112, 8, 72, 40, 104, 24, 88, 56, 120, + 4, 68, 36, 100, 20, 84, 52, 116, 12, 76, 44, 108, 28, 92, 60, 124, + 2, 66, 34, 98, 18, 82, 50, 114, 10, 74, 42, 106, 26, 90, 58, 122, + 6, 70, 38, 102, 22, 86, 54, 118, 14, 78, 46, 110, 30, 94, 62, 126, + 1, 65, 33, 97, 17, 81, 49, 113, 9, 73, 41, 105, 25, 89, 57, 121, + 5, 69, 37, 101, 21, 85, 53, 117, 13, 77, 45, 109, 29, 93, 61, 125, + 3, 67, 35, 99, 19, 83, 51, 115, 11, 75, 43, 107, 27, 91, 59, 123, + 7, 71, 39, 103, 23, 87, 55, 119, 15, 79, 47, 111, 31, 95, 63, 127 +}; + +void init_ntt() { + unsigned int i; + int16_t tmp[128]; + + tmp[0] = MONT; + for(i=1;i<128;i++) + tmp[i] = fqmul(tmp[i-1],MONT*KYBER_ROOT_OF_UNITY % KYBER_Q); + + for(i=0;i<128;i++) { + zetas[i] = tmp[tree[i]]; + if(zetas[i] > KYBER_Q/2) + zetas[i] -= KYBER_Q; + if(zetas[i] < -KYBER_Q/2) + zetas[i] += KYBER_Q; + } +} +*/ + +const int16_t zetas[128] = { + -1044, -758, -359, -1517, 1493, 1422, 287, 202, + -171, 622, 1577, 182, 962, -1202, -1474, 1468, + 573, -1325, 264, 383, -829, 1458, -1602, -130, + -681, 1017, 732, 608, -1542, 411, -205, -1571, + 1223, 652, -552, 1015, -1293, 1491, -282, -1544, + 516, -8, -320, -666, -1618, -1162, 126, 1469, + -853, -90, -271, 830, 107, -1421, -247, -951, + -398, 961, -1508, -725, 448, -1065, 677, -1275, + -1103, 430, 555, 843, -1251, 871, 1550, 105, + 422, 587, 177, -235, -291, -460, 1574, 1653, + -246, 778, 1159, -147, -777, 1483, -602, 1119, + -1590, 644, -872, 349, 418, 329, -156, -75, + 817, 1097, 603, 610, 1322, -1285, -1465, 384, + -1215, -136, 1218, -1335, -874, 220, -1187, -1659, + -1185, -1530, -1278, 794, -1510, -854, -870, 478, + -108, -308, 996, 991, 958, -1460, 1522, 1628 +}; + +/************************************************* +* Name: fqmul +* +* Description: Multiplication followed by Montgomery reduction +* +* Arguments: - int16_t a: first factor +* - int16_t b: second factor +* +* Returns 16-bit integer congruent to a*b*R^{-1} mod q +**************************************************/ +static int16_t fqmul(int16_t a, int16_t b) { + return montgomery_reduce((int32_t)a*b); +} + +/************************************************* +* Name: ntt +* +* Description: Inplace number-theoretic transform (NTT) in Rq. +* input is in standard order, output is in bitreversed order +* +* Arguments: - int16_t r[256]: pointer to input/output vector of elements of Zq +**************************************************/ +void ntt(int16_t r[256]) { + unsigned int len, start, j, k; + int16_t t, zeta; + + k = 1; + for(len = 128; len >= 2; len >>= 1) { + for(start = 0; start < 256; start = j + len) { + zeta = zetas[k++]; + for(j = start; j < start + len; j++) { + t = fqmul(zeta, r[j + len]); + r[j + len] = r[j] - t; + r[j] = r[j] + t; + } + } + } +} + +/************************************************* +* Name: invntt_tomont +* +* Description: Inplace inverse number-theoretic transform in Rq and +* multiplication by Montgomery factor 2^16. +* Input is in bitreversed order, output is in standard order +* +* Arguments: - int16_t r[256]: pointer to input/output vector of elements of Zq +**************************************************/ +void invntt(int16_t r[256]) { + unsigned int start, len, j, k; + int16_t t, zeta; + const int16_t f = 1441; // mont^2/128 + + k = 127; + for(len = 2; len <= 128; len <<= 1) { + for(start = 0; start < 256; start = j + len) { + zeta = zetas[k--]; + for(j = start; j < start + len; j++) { + t = r[j]; + r[j] = barrett_reduce(t + r[j + len]); + r[j + len] = r[j + len] - t; + r[j + len] = fqmul(zeta, r[j + len]); + } + } + } + + for(j = 0; j < 256; j++) + r[j] = fqmul(r[j], f); +} + +/************************************************* +* Name: basemul +* +* Description: Multiplication of polynomials in Zq[X]/(X^2-zeta) +* used for multiplication of elements in Rq in NTT domain +* +* Arguments: - int16_t r[2]: pointer to the output polynomial +* - const int16_t a[2]: pointer to the first factor +* - const int16_t b[2]: pointer to the second factor +* - int16_t zeta: integer defining the reduction polynomial +**************************************************/ +void basemul(int16_t r[2], const int16_t a[2], const int16_t b[2], int16_t zeta) +{ + r[0] = fqmul(a[1], b[1]); + r[0] = fqmul(r[0], zeta); + r[0] += fqmul(a[0], b[0]); + r[1] = fqmul(a[0], b[1]); + r[1] += fqmul(a[1], b[0]); +} diff --git a/Sources/CKyber/internal/poly.inc b/Sources/CKyber/internal/poly.inc new file mode 100644 index 0000000..cbd3abf --- /dev/null +++ b/Sources/CKyber/internal/poly.inc @@ -0,0 +1,360 @@ +#include +#include "params.h" +#include "poly.h" +#include "ntt.h" +#include "reduce.h" +#include "cbd.h" +#include "symmetric.h" +#include "verify.h" + +/************************************************* +* Name: poly_compress +* +* Description: Compression and subsequent serialization of a polynomial +* +* Arguments: - uint8_t *r: pointer to output byte array +* (of length KYBER_POLYCOMPRESSEDBYTES) +* - const poly *a: pointer to input polynomial +**************************************************/ +void poly_compress(uint8_t r[KYBER_POLYCOMPRESSEDBYTES], const poly *a) +{ + unsigned int i,j; + int16_t u; + uint32_t d0; + uint8_t t[8]; + +#if (KYBER_POLYCOMPRESSEDBYTES == 128) + + for(i=0;icoeffs[8*i+j]; + u += (u >> 15) & KYBER_Q; +/* t[j] = ((((uint16_t)u << 4) + KYBER_Q/2)/KYBER_Q) & 15; */ + d0 = u << 4; + d0 += 1665; + d0 *= 80635; + d0 >>= 28; + t[j] = d0 & 0xf; + } + + r[0] = t[0] | (t[1] << 4); + r[1] = t[2] | (t[3] << 4); + r[2] = t[4] | (t[5] << 4); + r[3] = t[6] | (t[7] << 4); + r += 4; + } +#elif (KYBER_POLYCOMPRESSEDBYTES == 160) + for(i=0;icoeffs[8*i+j]; + u += (u >> 15) & KYBER_Q; +/* t[j] = ((((uint32_t)u << 5) + KYBER_Q/2)/KYBER_Q) & 31; */ + d0 = u << 5; + d0 += 1664; + d0 *= 40318; + d0 >>= 27; + t[j] = d0 & 0x1f; + } + + r[0] = (t[0] >> 0) | (t[1] << 5); + r[1] = (t[1] >> 3) | (t[2] << 2) | (t[3] << 7); + r[2] = (t[3] >> 1) | (t[4] << 4); + r[3] = (t[4] >> 4) | (t[5] << 1) | (t[6] << 6); + r[4] = (t[6] >> 2) | (t[7] << 3); + r += 5; + } +#else +#error "KYBER_POLYCOMPRESSEDBYTES needs to be in {128, 160}" +#endif +} + +/************************************************* +* Name: poly_decompress +* +* Description: De-serialization and subsequent decompression of a polynomial; +* approximate inverse of poly_compress +* +* Arguments: - poly *r: pointer to output polynomial +* - const uint8_t *a: pointer to input byte array +* (of length KYBER_POLYCOMPRESSEDBYTES bytes) +**************************************************/ +void poly_decompress(poly *r, const uint8_t a[KYBER_POLYCOMPRESSEDBYTES]) +{ + unsigned int i; + +#if (KYBER_POLYCOMPRESSEDBYTES == 128) + for(i=0;icoeffs[2*i+0] = (((uint16_t)(a[0] & 15)*KYBER_Q) + 8) >> 4; + r->coeffs[2*i+1] = (((uint16_t)(a[0] >> 4)*KYBER_Q) + 8) >> 4; + a += 1; + } +#elif (KYBER_POLYCOMPRESSEDBYTES == 160) + unsigned int j; + uint8_t t[8]; + for(i=0;i> 0); + t[1] = (a[0] >> 5) | (a[1] << 3); + t[2] = (a[1] >> 2); + t[3] = (a[1] >> 7) | (a[2] << 1); + t[4] = (a[2] >> 4) | (a[3] << 4); + t[5] = (a[3] >> 1); + t[6] = (a[3] >> 6) | (a[4] << 2); + t[7] = (a[4] >> 3); + a += 5; + + for(j=0;j<8;j++) + r->coeffs[8*i+j] = ((uint32_t)(t[j] & 31)*KYBER_Q + 16) >> 5; + } +#else +#error "KYBER_POLYCOMPRESSEDBYTES needs to be in {128, 160}" +#endif +} + +/************************************************* +* Name: poly_tobytes +* +* Description: Serialization of a polynomial +* +* Arguments: - uint8_t *r: pointer to output byte array +* (needs space for KYBER_POLYBYTES bytes) +* - const poly *a: pointer to input polynomial +**************************************************/ +void poly_tobytes(uint8_t r[KYBER_POLYBYTES], const poly *a) +{ + unsigned int i; + uint16_t t0, t1; + + for(i=0;icoeffs[2*i]; + t0 += ((int16_t)t0 >> 15) & KYBER_Q; + t1 = a->coeffs[2*i+1]; + t1 += ((int16_t)t1 >> 15) & KYBER_Q; + r[3*i+0] = (t0 >> 0); + r[3*i+1] = (t0 >> 8) | (t1 << 4); + r[3*i+2] = (t1 >> 4); + } +} + +/************************************************* +* Name: poly_frombytes +* +* Description: De-serialization of a polynomial; +* inverse of poly_tobytes +* +* Arguments: - poly *r: pointer to output polynomial +* - const uint8_t *a: pointer to input byte array +* (of KYBER_POLYBYTES bytes) +**************************************************/ +void poly_frombytes(poly *r, const uint8_t a[KYBER_POLYBYTES]) +{ + unsigned int i; + for(i=0;icoeffs[2*i] = ((a[3*i+0] >> 0) | ((uint16_t)a[3*i+1] << 8)) & 0xFFF; + r->coeffs[2*i+1] = ((a[3*i+1] >> 4) | ((uint16_t)a[3*i+2] << 4)) & 0xFFF; + } +} + +/************************************************* +* Name: poly_frommsg +* +* Description: Convert 32-byte message to polynomial +* +* Arguments: - poly *r: pointer to output polynomial +* - const uint8_t *msg: pointer to input message +**************************************************/ +void poly_frommsg(poly *r, const uint8_t msg[KYBER_INDCPA_MSGBYTES]) +{ + unsigned int i,j; + +#if (KYBER_INDCPA_MSGBYTES != KYBER_N/8) +#error "KYBER_INDCPA_MSGBYTES must be equal to KYBER_N/8 bytes!" +#endif + + for(i=0;icoeffs[8*i+j] = 0; + cmov_int16(r->coeffs+8*i+j, ((KYBER_Q+1)/2), (msg[i] >> j)&1); + } + } +} + +/************************************************* +* Name: poly_tomsg +* +* Description: Convert polynomial to 32-byte message +* +* Arguments: - uint8_t *msg: pointer to output message +* - const poly *a: pointer to input polynomial +**************************************************/ +void poly_tomsg(uint8_t msg[KYBER_INDCPA_MSGBYTES], const poly *a) +{ + unsigned int i,j; + uint32_t t; + + for(i=0;icoeffs[8*i+j]; + // t += ((int16_t)t >> 15) & KYBER_Q; + // t = (((t << 1) + KYBER_Q/2)/KYBER_Q) & 1; + t <<= 1; + t += 1665; + t *= 80635; + t >>= 28; + t &= 1; + msg[i] |= t << j; + } + } +} + +/************************************************* +* Name: poly_getnoise_eta1 +* +* Description: Sample a polynomial deterministically from a seed and a nonce, +* with output polynomial close to centered binomial distribution +* with parameter KYBER_ETA1 +* +* Arguments: - poly *r: pointer to output polynomial +* - const uint8_t *seed: pointer to input seed +* (of length KYBER_SYMBYTES bytes) +* - uint8_t nonce: one-byte input nonce +**************************************************/ +void poly_getnoise_eta1(poly *r, const uint8_t seed[KYBER_SYMBYTES], uint8_t nonce) +{ + uint8_t buf[KYBER_ETA1*KYBER_N/4]; + prf(buf, sizeof(buf), seed, nonce); + poly_cbd_eta1(r, buf); +} + +/************************************************* +* Name: poly_getnoise_eta2 +* +* Description: Sample a polynomial deterministically from a seed and a nonce, +* with output polynomial close to centered binomial distribution +* with parameter KYBER_ETA2 +* +* Arguments: - poly *r: pointer to output polynomial +* - const uint8_t *seed: pointer to input seed +* (of length KYBER_SYMBYTES bytes) +* - uint8_t nonce: one-byte input nonce +**************************************************/ +void poly_getnoise_eta2(poly *r, const uint8_t seed[KYBER_SYMBYTES], uint8_t nonce) +{ + uint8_t buf[KYBER_ETA2*KYBER_N/4]; + prf(buf, sizeof(buf), seed, nonce); + poly_cbd_eta2(r, buf); +} + + +/************************************************* +* Name: poly_ntt +* +* Description: Computes negacyclic number-theoretic transform (NTT) of +* a polynomial in place; +* inputs assumed to be in normal order, output in bitreversed order +* +* Arguments: - uint16_t *r: pointer to in/output polynomial +**************************************************/ +void poly_ntt(poly *r) +{ + ntt(r->coeffs); + poly_reduce(r); +} + +/************************************************* +* Name: poly_invntt_tomont +* +* Description: Computes inverse of negacyclic number-theoretic transform (NTT) +* of a polynomial in place; +* inputs assumed to be in bitreversed order, output in normal order +* +* Arguments: - uint16_t *a: pointer to in/output polynomial +**************************************************/ +void poly_invntt_tomont(poly *r) +{ + invntt(r->coeffs); +} + +/************************************************* +* Name: poly_basemul_montgomery +* +* Description: Multiplication of two polynomials in NTT domain +* +* Arguments: - poly *r: pointer to output polynomial +* - const poly *a: pointer to first input polynomial +* - const poly *b: pointer to second input polynomial +**************************************************/ +void poly_basemul_montgomery(poly *r, const poly *a, const poly *b) +{ + unsigned int i; + for(i=0;icoeffs[4*i], &a->coeffs[4*i], &b->coeffs[4*i], zetas[64+i]); + basemul(&r->coeffs[4*i+2], &a->coeffs[4*i+2], &b->coeffs[4*i+2], -zetas[64+i]); + } +} + +/************************************************* +* Name: poly_tomont +* +* Description: Inplace conversion of all coefficients of a polynomial +* from normal domain to Montgomery domain +* +* Arguments: - poly *r: pointer to input/output polynomial +**************************************************/ +void poly_tomont(poly *r) +{ + unsigned int i; + const int16_t f = (1ULL << 32) % KYBER_Q; + for(i=0;icoeffs[i] = montgomery_reduce((int32_t)r->coeffs[i]*f); +} + +/************************************************* +* Name: poly_reduce +* +* Description: Applies Barrett reduction to all coefficients of a polynomial +* for details of the Barrett reduction see comments in reduce.c +* +* Arguments: - poly *r: pointer to input/output polynomial +**************************************************/ +void poly_reduce(poly *r) +{ + unsigned int i; + for(i=0;icoeffs[i] = barrett_reduce(r->coeffs[i]); +} + +/************************************************* +* Name: poly_add +* +* Description: Add two polynomials; no modular reduction is performed +* +* Arguments: - poly *r: pointer to output polynomial +* - const poly *a: pointer to first input polynomial +* - const poly *b: pointer to second input polynomial +**************************************************/ +void poly_add(poly *r, const poly *a, const poly *b) +{ + unsigned int i; + for(i=0;icoeffs[i] = a->coeffs[i] + b->coeffs[i]; +} + +/************************************************* +* Name: poly_sub +* +* Description: Subtract two polynomials; no modular reduction is performed +* +* Arguments: - poly *r: pointer to output polynomial +* - const poly *a: pointer to first input polynomial +* - const poly *b: pointer to second input polynomial +**************************************************/ +void poly_sub(poly *r, const poly *a, const poly *b) +{ + unsigned int i; + for(i=0;icoeffs[i] = a->coeffs[i] - b->coeffs[i]; +} diff --git a/Sources/CKyber/internal/polyvec.inc b/Sources/CKyber/internal/polyvec.inc new file mode 100644 index 0000000..669f6a5 --- /dev/null +++ b/Sources/CKyber/internal/polyvec.inc @@ -0,0 +1,246 @@ +#include +#include "params.h" +#include "poly.h" +#include "polyvec.h" + +/************************************************* +* Name: polyvec_compress +* +* Description: Compress and serialize vector of polynomials +* +* Arguments: - uint8_t *r: pointer to output byte array +* (needs space for KYBER_POLYVECCOMPRESSEDBYTES) +* - const polyvec *a: pointer to input vector of polynomials +**************************************************/ +void polyvec_compress(uint8_t r[KYBER_POLYVECCOMPRESSEDBYTES], const polyvec *a) +{ + unsigned int i,j,k; + uint64_t d0; + +#if (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 352)) + uint16_t t[8]; + for(i=0;ivec[i].coeffs[8*j+k]; + t[k] += ((int16_t)t[k] >> 15) & KYBER_Q; +/* t[k] = ((((uint32_t)t[k] << 11) + KYBER_Q/2)/KYBER_Q) & 0x7ff; */ + d0 = t[k]; + d0 <<= 11; + d0 += 1664; + d0 *= 645084; + d0 >>= 31; + t[k] = d0 & 0x7ff; + } + + r[ 0] = (t[0] >> 0); + r[ 1] = (t[0] >> 8) | (t[1] << 3); + r[ 2] = (t[1] >> 5) | (t[2] << 6); + r[ 3] = (t[2] >> 2); + r[ 4] = (t[2] >> 10) | (t[3] << 1); + r[ 5] = (t[3] >> 7) | (t[4] << 4); + r[ 6] = (t[4] >> 4) | (t[5] << 7); + r[ 7] = (t[5] >> 1); + r[ 8] = (t[5] >> 9) | (t[6] << 2); + r[ 9] = (t[6] >> 6) | (t[7] << 5); + r[10] = (t[7] >> 3); + r += 11; + } + } +#elif (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 320)) + uint16_t t[4]; + for(i=0;ivec[i].coeffs[4*j+k]; + t[k] += ((int16_t)t[k] >> 15) & KYBER_Q; +/* t[k] = ((((uint32_t)t[k] << 10) + KYBER_Q/2)/ KYBER_Q) & 0x3ff; */ + d0 = t[k]; + d0 <<= 10; + d0 += 1665; + d0 *= 1290167; + d0 >>= 32; + t[k] = d0 & 0x3ff; + } + + r[0] = (t[0] >> 0); + r[1] = (t[0] >> 8) | (t[1] << 2); + r[2] = (t[1] >> 6) | (t[2] << 4); + r[3] = (t[2] >> 4) | (t[3] << 6); + r[4] = (t[3] >> 2); + r += 5; + } + } +#else +#error "KYBER_POLYVECCOMPRESSEDBYTES needs to be in {320*KYBER_K, 352*KYBER_K}" +#endif +} + +/************************************************* +* Name: polyvec_decompress +* +* Description: De-serialize and decompress vector of polynomials; +* approximate inverse of polyvec_compress +* +* Arguments: - polyvec *r: pointer to output vector of polynomials +* - const uint8_t *a: pointer to input byte array +* (of length KYBER_POLYVECCOMPRESSEDBYTES) +**************************************************/ +void polyvec_decompress(polyvec *r, const uint8_t a[KYBER_POLYVECCOMPRESSEDBYTES]) +{ + unsigned int i,j,k; + +#if (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 352)) + uint16_t t[8]; + for(i=0;i> 0) | ((uint16_t)a[ 1] << 8); + t[1] = (a[1] >> 3) | ((uint16_t)a[ 2] << 5); + t[2] = (a[2] >> 6) | ((uint16_t)a[ 3] << 2) | ((uint16_t)a[4] << 10); + t[3] = (a[4] >> 1) | ((uint16_t)a[ 5] << 7); + t[4] = (a[5] >> 4) | ((uint16_t)a[ 6] << 4); + t[5] = (a[6] >> 7) | ((uint16_t)a[ 7] << 1) | ((uint16_t)a[8] << 9); + t[6] = (a[8] >> 2) | ((uint16_t)a[ 9] << 6); + t[7] = (a[9] >> 5) | ((uint16_t)a[10] << 3); + a += 11; + + for(k=0;k<8;k++) + r->vec[i].coeffs[8*j+k] = ((uint32_t)(t[k] & 0x7FF)*KYBER_Q + 1024) >> 11; + } + } +#elif (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 320)) + uint16_t t[4]; + for(i=0;i> 0) | ((uint16_t)a[1] << 8); + t[1] = (a[1] >> 2) | ((uint16_t)a[2] << 6); + t[2] = (a[2] >> 4) | ((uint16_t)a[3] << 4); + t[3] = (a[3] >> 6) | ((uint16_t)a[4] << 2); + a += 5; + + for(k=0;k<4;k++) + r->vec[i].coeffs[4*j+k] = ((uint32_t)(t[k] & 0x3FF)*KYBER_Q + 512) >> 10; + } + } +#else +#error "KYBER_POLYVECCOMPRESSEDBYTES needs to be in {320*KYBER_K, 352*KYBER_K}" +#endif +} + +/************************************************* +* Name: polyvec_tobytes +* +* Description: Serialize vector of polynomials +* +* Arguments: - uint8_t *r: pointer to output byte array +* (needs space for KYBER_POLYVECBYTES) +* - const polyvec *a: pointer to input vector of polynomials +**************************************************/ +void polyvec_tobytes(uint8_t r[KYBER_POLYVECBYTES], const polyvec *a) +{ + unsigned int i; + for(i=0;ivec[i]); +} + +/************************************************* +* Name: polyvec_frombytes +* +* Description: De-serialize vector of polynomials; +* inverse of polyvec_tobytes +* +* Arguments: - uint8_t *r: pointer to output byte array +* - const polyvec *a: pointer to input vector of polynomials +* (of length KYBER_POLYVECBYTES) +**************************************************/ +void polyvec_frombytes(polyvec *r, const uint8_t a[KYBER_POLYVECBYTES]) +{ + unsigned int i; + for(i=0;ivec[i], a+i*KYBER_POLYBYTES); +} + +/************************************************* +* Name: polyvec_ntt +* +* Description: Apply forward NTT to all elements of a vector of polynomials +* +* Arguments: - polyvec *r: pointer to in/output vector of polynomials +**************************************************/ +void polyvec_ntt(polyvec *r) +{ + unsigned int i; + for(i=0;ivec[i]); +} + +/************************************************* +* Name: polyvec_invntt_tomont +* +* Description: Apply inverse NTT to all elements of a vector of polynomials +* and multiply by Montgomery factor 2^16 +* +* Arguments: - polyvec *r: pointer to in/output vector of polynomials +**************************************************/ +void polyvec_invntt_tomont(polyvec *r) +{ + unsigned int i; + for(i=0;ivec[i]); +} + +/************************************************* +* Name: polyvec_basemul_acc_montgomery +* +* Description: Multiply elements of a and b in NTT domain, accumulate into r, +* and multiply by 2^-16. +* +* Arguments: - poly *r: pointer to output polynomial +* - const polyvec *a: pointer to first input vector of polynomials +* - const polyvec *b: pointer to second input vector of polynomials +**************************************************/ +void polyvec_basemul_acc_montgomery(poly *r, const polyvec *a, const polyvec *b) +{ + unsigned int i; + poly t; + + poly_basemul_montgomery(r, &a->vec[0], &b->vec[0]); + for(i=1;ivec[i], &b->vec[i]); + poly_add(r, r, &t); + } + + poly_reduce(r); +} + +/************************************************* +* Name: polyvec_reduce +* +* Description: Applies Barrett reduction to each coefficient +* of each element of a vector of polynomials; +* for details of the Barrett reduction see comments in reduce.c +* +* Arguments: - polyvec *r: pointer to input/output polynomial +**************************************************/ +void polyvec_reduce(polyvec *r) +{ + unsigned int i; + for(i=0;ivec[i]); +} + +/************************************************* +* Name: polyvec_add +* +* Description: Add vectors of polynomials +* +* Arguments: - polyvec *r: pointer to output vector of polynomials +* - const polyvec *a: pointer to first input vector of polynomials +* - const polyvec *b: pointer to second input vector of polynomials +**************************************************/ +void polyvec_add(polyvec *r, const polyvec *a, const polyvec *b) +{ + unsigned int i; + for(i=0;ivec[i], &a->vec[i], &b->vec[i]); +} diff --git a/Sources/CKyber/internal/reduce.inc b/Sources/CKyber/internal/reduce.inc new file mode 100644 index 0000000..9d8e7ed --- /dev/null +++ b/Sources/CKyber/internal/reduce.inc @@ -0,0 +1,42 @@ +#include +#include "params.h" +#include "reduce.h" + +/************************************************* +* Name: montgomery_reduce +* +* Description: Montgomery reduction; given a 32-bit integer a, computes +* 16-bit integer congruent to a * R^-1 mod q, where R=2^16 +* +* Arguments: - int32_t a: input integer to be reduced; +* has to be in {-q2^15,...,q2^15-1} +* +* Returns: integer in {-q+1,...,q-1} congruent to a * R^-1 modulo q. +**************************************************/ +int16_t montgomery_reduce(int32_t a) +{ + int16_t t; + + t = (int16_t)a*QINV; + t = (a - (int32_t)t*KYBER_Q) >> 16; + return t; +} + +/************************************************* +* Name: barrett_reduce +* +* Description: Barrett reduction; given a 16-bit integer a, computes +* centered representative congruent to a mod q in {-(q-1)/2,...,(q-1)/2} +* +* Arguments: - int16_t a: input integer to be reduced +* +* Returns: integer in {-(q-1)/2,...,(q-1)/2} congruent to a modulo q. +**************************************************/ +int16_t barrett_reduce(int16_t a) { + int16_t t; + const int16_t v = ((1<<26) + KYBER_Q/2)/KYBER_Q; + + t = ((int32_t)v*a + (1<<25)) >> 26; + t *= KYBER_Q; + return a - t; +} diff --git a/Sources/CKyber/internal/symmetric_shake.inc b/Sources/CKyber/internal/symmetric_shake.inc new file mode 100644 index 0000000..6a99071 --- /dev/null +++ b/Sources/CKyber/internal/symmetric_shake.inc @@ -0,0 +1,73 @@ +#include +#include +#include +#include "params.h" +#include "symmetric.h" +#include "fips202.h" + +/************************************************* +* Name: kyber_shake128_absorb +* +* Description: Absorb step of the SHAKE128 specialized for the Kyber context. +* +* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state +* - const uint8_t *seed: pointer to KYBER_SYMBYTES input to be absorbed into state +* - uint8_t i: additional byte of input +* - uint8_t j: additional byte of input +**************************************************/ +void kyber_shake128_absorb(keccak_state *state, + const uint8_t seed[KYBER_SYMBYTES], + uint8_t x, + uint8_t y) +{ + uint8_t extseed[KYBER_SYMBYTES+2]; + + memcpy(extseed, seed, KYBER_SYMBYTES); + extseed[KYBER_SYMBYTES+0] = x; + extseed[KYBER_SYMBYTES+1] = y; + + shake128_absorb_once(state, extseed, sizeof(extseed)); +} + +/************************************************* +* Name: kyber_shake256_prf +* +* Description: Usage of SHAKE256 as a PRF, concatenates secret and public input +* and then generates outlen bytes of SHAKE256 output +* +* Arguments: - uint8_t *out: pointer to output +* - size_t outlen: number of requested output bytes +* - const uint8_t *key: pointer to the key (of length KYBER_SYMBYTES) +* - uint8_t nonce: single-byte nonce (public PRF input) +**************************************************/ +void kyber_shake256_prf(uint8_t *out, size_t outlen, const uint8_t key[KYBER_SYMBYTES], uint8_t nonce) +{ + uint8_t extkey[KYBER_SYMBYTES+1]; + + memcpy(extkey, key, KYBER_SYMBYTES); + extkey[KYBER_SYMBYTES] = nonce; + + shake256(out, outlen, extkey, sizeof(extkey)); +} + +/************************************************* +* Name: kyber_shake256_prf +* +* Description: Usage of SHAKE256 as a PRF, concatenates secret and public input +* and then generates outlen bytes of SHAKE256 output +* +* Arguments: - uint8_t *out: pointer to output +* - size_t outlen: number of requested output bytes +* - const uint8_t *key: pointer to the key (of length KYBER_SYMBYTES) +* - uint8_t nonce: single-byte nonce (public PRF input) +**************************************************/ +void kyber_shake256_rkprf(uint8_t out[KYBER_SSBYTES], const uint8_t key[KYBER_SYMBYTES], const uint8_t input[KYBER_CIPHERTEXTBYTES]) +{ + keccak_state s; + + shake256_init(&s); + shake256_absorb(&s, key, KYBER_SYMBYTES); + shake256_absorb(&s, input, KYBER_CIPHERTEXTBYTES); + shake256_finalize(&s); + shake256_squeeze(out, KYBER_SSBYTES, &s); +} diff --git a/Sources/CKyber/internal/verify.inc b/Sources/CKyber/internal/verify.inc new file mode 100644 index 0000000..914ccd4 --- /dev/null +++ b/Sources/CKyber/internal/verify.inc @@ -0,0 +1,75 @@ +#include +#include +#include "verify.h" + +/************************************************* +* Name: verify +* +* Description: Compare two arrays for equality in constant time. +* +* Arguments: const uint8_t *a: pointer to first byte array +* const uint8_t *b: pointer to second byte array +* size_t len: length of the byte arrays +* +* Returns 0 if the byte arrays are equal, 1 otherwise +**************************************************/ +int verify(const uint8_t *a, const uint8_t *b, size_t len) +{ + size_t i; + uint8_t r = 0; + + for(i=0;i> 63; +} + +/************************************************* +* Name: cmov +* +* Description: Copy len bytes from x to r if b is 1; +* don't modify x if b is 0. Requires b to be in {0,1}; +* assumes two's complement representation of negative integers. +* Runs in constant time. +* +* Arguments: uint8_t *r: pointer to output byte array +* const uint8_t *x: pointer to input byte array +* size_t len: Amount of bytes to be copied +* uint8_t b: Condition bit; has to be in {0,1} +**************************************************/ +void cmov(uint8_t *r, const uint8_t *x, size_t len, uint8_t b) +{ + size_t i; + +#if defined(__GNUC__) || defined(__clang__) + // Prevent the compiler from + // 1) inferring that b is 0/1-valued, and + // 2) handling the two cases with a branch. + // This is not necessary when verify.c and kem.c are separate translation + // units, but we expect that downstream consumers will copy this code and/or + // change how it is built. + __asm__("" : "+r"(b) : /* no inputs */); +#endif + + b = -b; + for(i=0;i +#include +#include +#include "randombytes.h" + +void randombytes(uint8_t *out, size_t outlen) { + arc4random_buf(out, outlen); +} \ No newline at end of file diff --git a/Sources/KyberSDK/KyberKEM.swift b/Sources/KyberSDK/KyberKEM.swift new file mode 100644 index 0000000..29dad26 --- /dev/null +++ b/Sources/KyberSDK/KyberKEM.swift @@ -0,0 +1,174 @@ +import Foundation +import CKyber + +// MARK: - KyberKEM + +/** + * Kyber KEM(密钥封装机制)Swift 封装层。 + * + * ## 算法简介 + * CRYSTALS-Kyber(ML-KEM,NIST FIPS 203)是基于格密码(Module-LWE)的 + * 后量子密钥封装机制,由 NIST 于 2024 年正式标准化, + * 可抵御量子计算机对传统公钥密码(RSA / ECDH)的攻击。 + * + * ## 典型使用流程 + * ```swift + * // 接收方:生成密钥对,公钥可公开分发 + * let keyPair = try KyberKEM.generateKeyPair(variant: .kyber768) + * + * // 发送方:用接收方公钥封装,得到密文和共享密钥 + * let result = try KyberKEM.encapsulate(variant: .kyber768, publicKey: keyPair.publicKey) + * // 将 result.ciphertext 发送给接收方 + * // 将 result.sharedSecret 作为对称密钥使用(如 AES-GCM) + * + * // 接收方:用私钥解封装,恢复共享密钥 + * let sharedSecret = try KyberKEM.decapsulate( + * variant: .kyber768, + * ciphertext: result.ciphertext, + * secretKey: keyPair.secretKey + * ) + * // result.sharedSecret == sharedSecret ✓ + * ``` + * + * ## 安全说明 + * - 底层 C 实现的 `crypto_kem_keypair` 含有日期校验逻辑, + * 本层全程调用 `keypair_derand`,由 `randombytes()` 生成随机数, + * 彻底规避该限制。 + * - 解封装在密文被篡改时仍返回 0,但 `sharedSecret` 为伪随机值, + * 这是 IND-CCA2 安全方案的标准行为。 + * + * ## 线程安全性 + * 所有方法均为无状态纯函数,可在任意队列并发调用。 + */ +public enum KyberKEM { + + // MARK: - 密钥生成 + + /** + * 生成 Kyber 密钥对。 + * + * 使用 `arc4random_buf` 生成 64 字节随机 coins(iOS/macOS CSPRNG), + * 调用 `keypair_derand` 确定性地生成密钥对。 + * + * - Parameter variant: 安全级别,见 ``KyberVariant`` + * - Returns: 包含公钥和私钥的 ``KyberKeyPair`` + * - Throws: ``KyberError/keyGenerationFailed(code:)``(极罕见) + */ + public static func generateKeyPair(variant: KyberVariant) throws -> KyberKeyPair { + var pk = [UInt8](repeating: 0, count: variant.publicKeyBytes) + var sk = [UInt8](repeating: 0, count: variant.secretKeyBytes) + + // 生成 64 字节随机 coins:前 32 字节用于 IND-CPA 密钥,后 32 字节用于拒绝采样 z + var coins = [UInt8](repeating: 0, count: 64) + randombytes(&coins, 64) + + let ret: Int32 + switch variant { + case .kyber512: + ret = pqcrystals_kyber512_ref_keypair_derand(&pk, &sk, coins) + case .kyber768: + ret = pqcrystals_kyber768_ref_keypair_derand(&pk, &sk, coins) + case .kyber1024: + ret = pqcrystals_kyber1024_ref_keypair_derand(&pk, &sk, coins) + } + + guard ret == 0 else { throw KyberError.keyGenerationFailed(code: ret) } + return KyberKeyPair(publicKey: Data(pk), secretKey: Data(sk)) + } + + // MARK: - 封装 + + /** + * 使用接收方公钥封装共享密钥(发送方调用)。 + * + * 生成 32 字节随机 coins,调用 `enc_derand` 确定性封装。 + * 返回的 `ciphertext` 应传输给持有私钥的接收方; + * `sharedSecret` 本地保留,用于对称加密。 + * + * - Parameters: + * - variant: 安全级别,必须与生成公钥时使用的变体一致 + * - publicKey: 接收方公钥 + * - Returns: ``KyberEncapsulationResult``,包含密文和共享密钥 + * - Throws: ``KyberError/invalidPublicKeySize(expected:got:)`` 或 + * ``KyberError/encapsulationFailed(code:)`` + */ + public static func encapsulate( + variant: KyberVariant, + publicKey: Data + ) throws -> KyberEncapsulationResult { + guard publicKey.count == variant.publicKeyBytes else { + throw KyberError.invalidPublicKeySize( + expected: variant.publicKeyBytes, got: publicKey.count) + } + + var ct = [UInt8](repeating: 0, count: variant.ciphertextBytes) + var ss = [UInt8](repeating: 0, count: variant.sharedSecretBytes) + + // 生成 32 字节随机 coins 用于确定性封装 + var coins = [UInt8](repeating: 0, count: 32) + randombytes(&coins, 32) + + let pk = Array(publicKey) + let ret: Int32 + switch variant { + case .kyber512: + ret = pqcrystals_kyber512_ref_enc_derand(&ct, &ss, pk, coins) + case .kyber768: + ret = pqcrystals_kyber768_ref_enc_derand(&ct, &ss, pk, coins) + case .kyber1024: + ret = pqcrystals_kyber1024_ref_enc_derand(&ct, &ss, pk, coins) + } + + guard ret == 0 else { throw KyberError.encapsulationFailed(code: ret) } + return KyberEncapsulationResult(ciphertext: Data(ct), sharedSecret: Data(ss)) + } + + // MARK: - 解封装 + + /** + * 使用私钥从密文中恢复共享密钥(接收方调用)。 + * + * IND-CCA2 安全性保证:若密文被篡改,返回的共享密钥为伪随机值, + * 与封装方的共享密钥不同,从而无法建立有效的加密信道。 + * + * - Parameters: + * - variant: 安全级别,必须与原始密钥对的变体一致 + * - ciphertext: 封装方产生的密文 + * - secretKey: 接收方私钥 + * - Returns: 32 字节共享密钥(若密文合法,则与封装方的共享密钥相同) + * - Throws: ``KyberError/invalidCiphertextSize(expected:got:)``、 + * ``KyberError/invalidSecretKeySize(expected:got:)`` 或 + * ``KyberError/decapsulationFailed(code:)`` + */ + public static func decapsulate( + variant: KyberVariant, + ciphertext: Data, + secretKey: Data + ) throws -> Data { + guard ciphertext.count == variant.ciphertextBytes else { + throw KyberError.invalidCiphertextSize( + expected: variant.ciphertextBytes, got: ciphertext.count) + } + guard secretKey.count == variant.secretKeyBytes else { + throw KyberError.invalidSecretKeySize( + expected: variant.secretKeyBytes, got: secretKey.count) + } + + var ss = [UInt8](repeating: 0, count: variant.sharedSecretBytes) + let ct = Array(ciphertext) + let sk = Array(secretKey) + + let ret: Int32 + switch variant { + case .kyber512: + ret = pqcrystals_kyber512_ref_dec(&ss, ct, sk) + case .kyber768: + ret = pqcrystals_kyber768_ref_dec(&ss, ct, sk) + case .kyber1024: + ret = pqcrystals_kyber1024_ref_dec(&ss, ct, sk) + } + + guard ret == 0 else { throw KyberError.decapsulationFailed(code: ret) } + return Data(ss) + } +} \ No newline at end of file diff --git a/Sources/KyberSDK/KyberTypes.swift b/Sources/KyberSDK/KyberTypes.swift new file mode 100644 index 0000000..e2d419b --- /dev/null +++ b/Sources/KyberSDK/KyberTypes.swift @@ -0,0 +1,148 @@ +import Foundation + +// MARK: - KyberVariant + +/** + * Kyber KEM 安全级别枚举。 + * + * CRYSTALS-Kyber(ML-KEM,NIST FIPS 203)定义了三种参数集, + * 分别对应不同的安全强度和密钥/密文大小: + * + * | 变体 | KYBER_K | NIST 级别 | 经典安全性 | + * |-------------|---------|----------|-----------| + * | `.kyber512` | 2 | Level 1 | ~AES-128 | + * | `.kyber768` | 3 | Level 3 | ~AES-192 | + * | `.kyber1024` | 4 | Level 5 | ~AES-256 | + * + * 推荐选择:`.kyber768` 满足大多数应用场景的安全需求, + * 同时密钥/密文大小适中。 + */ +public enum KyberVariant: CaseIterable { + /// Kyber512:NIST 安全级别 1,适用于资源受限或对大小敏感的场景 + case kyber512 + /// Kyber768:NIST 安全级别 3,推荐的通用安全级别 + case kyber768 + /// Kyber1024:NIST 安全级别 5,适用于高价值数据的长期保护 + case kyber1024 + + /// 公钥字节长度 + public var publicKeyBytes: Int { + switch self { + case .kyber512: return 800 + case .kyber768: return 1184 + case .kyber1024: return 1568 + } + } + + /// 私钥字节长度(包含公钥副本及 H(pk) 等辅助数据) + public var secretKeyBytes: Int { + switch self { + case .kyber512: return 1632 + case .kyber768: return 2400 + case .kyber1024: return 3168 + } + } + + /// 密文字节长度 + public var ciphertextBytes: Int { + switch self { + case .kyber512: return 768 + case .kyber768: return 1088 + case .kyber1024: return 1568 + } + } + + /// 共享密钥字节长度(三种变体均为 32 字节) + public var sharedSecretBytes: Int { 32 } + + /// 人类可读的展示名称 + public var displayName: String { + switch self { + case .kyber512: return "Kyber512(NIST Level 1)" + case .kyber768: return "Kyber768(NIST Level 3)" + case .kyber1024: return "Kyber1024(NIST Level 5)" + } + } +} + +// MARK: - KyberKeyPair + +/** + * Kyber 密钥对,由 `KyberKEM.generateKeyPair` 返回。 + * + * - `publicKey`:可安全分发给通信对方,对方使用它执行封装操作。 + * - `secretKey`:必须保密存储,用于从密文中恢复共享密钥。 + */ +public struct KyberKeyPair { + /// 公钥,可公开传输 + public let publicKey: Data + /// 私钥,必须安全存储(建议使用 iOS Keychain) + public let secretKey: Data + + public init(publicKey: Data, secretKey: Data) { + self.publicKey = publicKey + self.secretKey = secretKey + } +} + +// MARK: - KyberEncapsulationResult + +/** + * Kyber 封装结果,由 `KyberKEM.encapsulate` 返回。 + * + * 封装操作(发送方/主动方)的两个输出: + * - `ciphertext`:发送给持有私钥的接收方,接收方用私钥解封装以恢复共享密钥。 + * - `sharedSecret`:本地保留的 32 字节共享密钥, + * 可直接用作对称密码(AES-GCM / ChaCha20-Poly1305)的密钥材料。 + */ +public struct KyberEncapsulationResult { + /// 发送给接收方的密文 + public let ciphertext: Data + /// 本地保留的共享密钥(32 字节) + public let sharedSecret: Data + + public init(ciphertext: Data, sharedSecret: Data) { + self.ciphertext = ciphertext + self.sharedSecret = sharedSecret + } +} + +// MARK: - KyberError + +/** + * Kyber SDK 错误类型。 + * + * 输入校验类错误在调用 C 层前抛出; + * `*Failed` 类错误表示 C 函数返回了非零状态码(通常不应发生)。 + */ +public enum KyberError: Error, LocalizedError { + /// 密钥生成失败,附带 C 函数返回码 + case keyGenerationFailed(code: Int32) + /// 封装操作失败,附带 C 函数返回码 + case encapsulationFailed(code: Int32) + /// 解封装操作失败,附带 C 函数返回码 + case decapsulationFailed(code: Int32) + /// 公钥长度与所选变体不符 + case invalidPublicKeySize(expected: Int, got: Int) + /// 私钥长度与所选变体不符 + case invalidSecretKeySize(expected: Int, got: Int) + /// 密文长度与所选变体不符 + case invalidCiphertextSize(expected: Int, got: Int) + + public var errorDescription: String? { + switch self { + case .keyGenerationFailed(let c): + return "密钥生成失败(返回码:\(c))" + case .encapsulationFailed(let c): + return "封装失败(返回码:\(c))" + case .decapsulationFailed(let c): + return "解封装失败(返回码:\(c))" + case .invalidPublicKeySize(let e, let g): + return "公钥长度不匹配:期望 \(e) 字节,实际 \(g) 字节" + case .invalidSecretKeySize(let e, let g): + return "私钥长度不匹配:期望 \(e) 字节,实际 \(g) 字节" + case .invalidCiphertextSize(let e, let g): + return "密文长度不匹配:期望 \(e) 字节,实际 \(g) 字节" + } + } +} \ No newline at end of file diff --git a/Tests/KyberSDKTests/KyberSDKTests.swift b/Tests/KyberSDKTests/KyberSDKTests.swift new file mode 100644 index 0000000..693bb35 --- /dev/null +++ b/Tests/KyberSDKTests/KyberSDKTests.swift @@ -0,0 +1,131 @@ +import XCTest +@testable import KyberSDK + +/** + * KyberSDK 单元测试套件 + * + * 测试覆盖: + * - 三种变体(512 / 768 / 1024)密钥尺寸验证 + * - 三种变体完整 KEM 全流程(密钥生成 → 封装 → 解封装 → 比对) + * - 错误密钥解封装(验证 IND-CCA2 安全属性) + * - 输入参数校验(非法公钥 / 私钥 / 密文长度) + * - Kyber768 性能基线 + */ +final class KyberSDKTests: XCTestCase { + + // MARK: - Kyber512 测试 + + /// 验证 Kyber512 密钥尺寸符合规范 + func testKyber512KeySizes() throws { + let kp = try KyberKEM.generateKeyPair(variant: .kyber512) + XCTAssertEqual(kp.publicKey.count, 800, "Kyber512 公钥应为 800 字节") + XCTAssertEqual(kp.secretKey.count, 1632, "Kyber512 私钥应为 1632 字节") + } + + /// Kyber512 完整 KEM 流程:密钥生成 → 封装 → 解封装 → 共享密钥必须一致 + func testKyber512RoundTrip() throws { + let kp = try KyberKEM.generateKeyPair(variant: .kyber512) + let enc = try KyberKEM.encapsulate(variant: .kyber512, publicKey: kp.publicKey) + let dec = try KyberKEM.decapsulate(variant: .kyber512, ciphertext: enc.ciphertext, secretKey: kp.secretKey) + + XCTAssertEqual(enc.ciphertext.count, 768, "Kyber512 密文应为 768 字节") + XCTAssertEqual(enc.sharedSecret.count, 32, "共享密钥应为 32 字节") + XCTAssertEqual(dec.count, 32, "解封装共享密钥应为 32 字节") + XCTAssertEqual(enc.sharedSecret, dec, "封装和解封装的共享密钥必须完全一致") + } + + /// Kyber512 错误密钥测试:使用错误私钥解封装,共享密钥应与封装方不同(IND-CCA2) + func testKyber512WrongKey() throws { + let alice = try KyberKEM.generateKeyPair(variant: .kyber512) + let bob = try KyberKEM.generateKeyPair(variant: .kyber512) + // 发送方使用 Alice 的公钥封装 + let enc = try KyberKEM.encapsulate(variant: .kyber512, publicKey: alice.publicKey) + // Bob 用自己的私钥解封装 —— 应得到不同的(伪随机)共享密钥 + let dec = try KyberKEM.decapsulate(variant: .kyber512, ciphertext: enc.ciphertext, secretKey: bob.secretKey) + XCTAssertNotEqual(enc.sharedSecret, dec, "使用错误私钥不应恢复正确的共享密钥(IND-CCA2)") + } + + // MARK: - Kyber768 测试 + + /// 验证 Kyber768 密钥尺寸符合规范 + func testKyber768KeySizes() throws { + let kp = try KyberKEM.generateKeyPair(variant: .kyber768) + XCTAssertEqual(kp.publicKey.count, 1184, "Kyber768 公钥应为 1184 字节") + XCTAssertEqual(kp.secretKey.count, 2400, "Kyber768 私钥应为 2400 字节") + } + + /// Kyber768 完整 KEM 流程 + func testKyber768RoundTrip() throws { + let kp = try KyberKEM.generateKeyPair(variant: .kyber768) + let enc = try KyberKEM.encapsulate(variant: .kyber768, publicKey: kp.publicKey) + let dec = try KyberKEM.decapsulate(variant: .kyber768, ciphertext: enc.ciphertext, secretKey: kp.secretKey) + + XCTAssertEqual(enc.ciphertext.count, 1088, "Kyber768 密文应为 1088 字节") + XCTAssertEqual(enc.sharedSecret, dec, "封装和解封装的共享密钥必须完全一致") + } + + // MARK: - Kyber1024 测试 + + /// 验证 Kyber1024 密钥尺寸符合规范 + func testKyber1024KeySizes() throws { + let kp = try KyberKEM.generateKeyPair(variant: .kyber1024) + XCTAssertEqual(kp.publicKey.count, 1568, "Kyber1024 公钥应为 1568 字节") + XCTAssertEqual(kp.secretKey.count, 3168, "Kyber1024 私钥应为 3168 字节") + } + + /// Kyber1024 完整 KEM 流程 + func testKyber1024RoundTrip() throws { + let kp = try KyberKEM.generateKeyPair(variant: .kyber1024) + let enc = try KyberKEM.encapsulate(variant: .kyber1024, publicKey: kp.publicKey) + let dec = try KyberKEM.decapsulate(variant: .kyber1024, ciphertext: enc.ciphertext, secretKey: kp.secretKey) + + XCTAssertEqual(enc.ciphertext.count, 1568, "Kyber1024 密文应为 1568 字节") + XCTAssertEqual(enc.sharedSecret, dec, "封装和解封装的共享密钥必须完全一致") + } + + // MARK: - 输入参数校验测试 + + /// 公钥长度不符时应抛出 KyberError.invalidPublicKeySize + func testInvalidPublicKeySize() { + XCTAssertThrowsError( + try KyberKEM.encapsulate(variant: .kyber768, publicKey: Data(repeating: 0, count: 100)), + "公钥长度不匹配时应抛出错误" + ) + } + + /// 私钥长度不符时应抛出 KyberError.invalidSecretKeySize + func testInvalidSecretKeySize() throws { + let kp = try KyberKEM.generateKeyPair(variant: .kyber768) + let enc = try KyberKEM.encapsulate(variant: .kyber768, publicKey: kp.publicKey) + XCTAssertThrowsError( + try KyberKEM.decapsulate( + variant: .kyber768, + ciphertext: enc.ciphertext, + secretKey: Data(repeating: 0, count: 10) + ), + "私钥长度不匹配时应抛出错误" + ) + } + + /// 密文长度不符时应抛出 KyberError.invalidCiphertextSize + func testInvalidCiphertextSize() throws { + let kp = try KyberKEM.generateKeyPair(variant: .kyber768) + XCTAssertThrowsError( + try KyberKEM.decapsulate( + variant: .kyber768, + ciphertext: Data(repeating: 0, count: 10), + secretKey: kp.secretKey + ), + "密文长度不匹配时应抛出错误" + ) + } + + // MARK: - 性能测试 + + /// Kyber768 密钥生成性能基线(10 次迭代平均值) + func testKyber768Performance() throws { + measure { + _ = try? KyberKEM.generateKeyPair(variant: .kyber768) + } + } +} \ No newline at end of file