Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ _안녕하세요. 저희는 팀 퀀트(Quant)이며, 저는 Quant Theodore Felix
- 공통
- **올바른 오류 전파 방법**: 많은 크레이트의 핵심 기능은 `Result` 열거형을 통해 `SecureBuffer` 구조체와 문자열 참조를 반환합니다. 이는 오류 전파에 부적절합니다.
- **컴플라이언스 문제**: 암호 모듈 구현에 있어 국제적 인증 및 규정을 준수하지 않은 부분을 발견했다면, 즉시 연락주세요.
- **오류 메시지**: 오류 메시지는 기본적으로 모호해야 하지만 알아차리기 애-매한 정도로 진실성이 있어야 합니다. 현재 오류 메시지는 어때 보이시나요?
- 보안 버퍼 크레이트 `entlib-native-secure-buffer`
- **베어메탈 캐시 플러시 문제**: `zeroizer.rs` 내 no_std 폐쇄 환경을 위한 Fall-back 시, 해당 환경의 하드웨어(CPU) 특성에 따라 캐시 라인 플러시가 보장되지 않을 수 있다고 합니다. 이 부분에 대해 섬세한 평가검증이 필요합니다.
- **이중 잠금**: JO(Java-Owned) 패턴을 통해 상호 작용 시 메모리 lock 수행 후 전달됩니다. Rust 측 `SecureMemoryBlock` 구조체는 이 데이터에 대해 한 번 더 lock을 수행합니다. 이 작업에 대해 어떻게 생각하시나요?
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTION_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Contributions corresponding to the following items for this project are classifi
- Common
- **Correct error propagation method**: The core function of many crates returns a `SecureBuffer` struct and a string reference through a `Result` enum. This is inappropriate for error propagation.
- **Compliance issues**: If you find any parts that do not comply with international certifications and regulations in the implementation of the cryptographic module, please contact us immediately.
- **Error messages**: Error messages should be ambiguous by default, but they must be truthful enough to be subtly recognizable. What do you think of the current error messages?
- Secure buffer crate `entlib-native-secure-buffer`
- **Bare-metal cache flush issue**: When falling back for a no_std closed environment in `zeroizer.rs`, it is said that cache line flushing may not be guaranteed depending on the hardware (CPU) characteristics of the environment. Delicate evaluation and verification are needed for this part.
- **Double lock**: When interacting through the JO (Java-Owned) pattern, the memory is locked and then transmitted. The `SecureMemoryBlock` struct on the Rust side performs another lock on this data. What do you think about this operation?
Expand Down
12 changes: 12 additions & 0 deletions crypto/mldsa/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "entlib-native-mldsa"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true

[dependencies]
entlib-native-constant-time.workspace = true
entlib-native-rng.workspace = true
entlib-native-secure-buffer.workspace = true
entlib-native-sha3.workspace = true
276 changes: 276 additions & 0 deletions crypto/mldsa/src/_mldsa_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
#[cfg(test)]
mod tests {
use crate::mldsa::{MLDSA, MLDSAParameter};
use crate::mldsa_keys::{
MLDSAPrivateKey, MLDSAPrivateKeyTrait, MLDSAPublicKey, MLDSAPublicKeyTrait, keygen_internal,
};
use crate::ntt::N;

// ML-DSA-44 파라미터
const K44: usize = 4;
const L44: usize = 4;
const ETA44: i32 = 2;
const PK44_LEN: usize = 1312;
const SK44_LEN: usize = 2560;

// ML-DSA-65 파라미터
const K65: usize = 6;
const L65: usize = 5;
const ETA65: i32 = 4;
const PK65_LEN: usize = 1952;
const SK65_LEN: usize = 4032;

//
// pkEncode / pkDecode 라운드트립 (ML-DSA-44)
//

#[test]
fn test_pk_encode_decode_roundtrip_44() {
let xi = [0u8; 32];
let (pk, _sk) = keygen_internal::<K44, L44, ETA44>(&xi).expect("keygen_internal failed");

// pkEncode
let pk_bytes: [u8; PK44_LEN] =
<MLDSAPublicKey<K44> as MLDSAPublicKeyTrait<K44, PK44_LEN>>::pk_encode(&pk);

// pkDecode
let pk2 = <MLDSAPublicKey<K44> as MLDSAPublicKeyTrait<K44, PK44_LEN>>::pk_decode(&pk_bytes);

// ρ 일치 검증
assert_eq!(pk.rho, pk2.rho, "pkDecode: ρ 불일치");

// t1 계수 일치 검증
for i in 0..K44 {
for j in 0..N {
assert_eq!(
pk.t1.vec[i].coeffs[j].0, pk2.t1.vec[i].coeffs[j].0,
"pkDecode: t1[{i}][{j}] 불일치"
);
}
}
}

//
// pkEncode / pkDecode 라운드트립 (ML-DSA-65)
//

#[test]
fn test_pk_encode_decode_roundtrip_65() {
let xi = [1u8; 32];
let (pk, _sk) = keygen_internal::<K65, L65, ETA65>(&xi).expect("keygen_internal failed");

let pk_bytes: [u8; PK65_LEN] =
<MLDSAPublicKey<K65> as MLDSAPublicKeyTrait<K65, PK65_LEN>>::pk_encode(&pk);
let pk2 = <MLDSAPublicKey<K65> as MLDSAPublicKeyTrait<K65, PK65_LEN>>::pk_decode(&pk_bytes);

assert_eq!(pk.rho, pk2.rho, "pkDecode: ρ 불일치");
for i in 0..K65 {
for j in 0..N {
assert_eq!(
pk.t1.vec[i].coeffs[j].0, pk2.t1.vec[i].coeffs[j].0,
"pkDecode: t1[{i}][{j}] 불일치"
);
}
}
}

//
// skEncode / skDecode 라운드트립 (ML-DSA-44)
//

#[test]
fn test_sk_encode_decode_roundtrip_44() {
type SK44 = MLDSAPrivateKey<K44, L44, ETA44>;

let xi = [2u8; 32];
let (_pk, sk) = keygen_internal::<K44, L44, ETA44>(&xi).expect("keygen_internal failed");

// skEncode → SecureBuffer
let sk_buf = <SK44 as MLDSAPrivateKeyTrait<K44, L44, SK44_LEN>>::sk_encode(&sk)
.expect("skEncode failed");

// SecureBuffer 길이 검증
assert_eq!(sk_buf.len(), SK44_LEN, "skEncode: 길이 불일치");

// skDecode
let sk2 = <SK44 as MLDSAPrivateKeyTrait<K44, L44, SK44_LEN>>::sk_decode(&sk_buf)
.expect("skDecode failed");

// 고정 필드 일치 검증
assert_eq!(sk.rho, sk2.rho, "skDecode: ρ 불일치");
assert_eq!(sk.k_seed, sk2.k_seed, "skDecode: K_seed 불일치");
assert_eq!(sk.tr, sk2.tr, "skDecode: tr 불일치");

// s1 계수 일치 검증
for i in 0..L44 {
for j in 0..N {
assert_eq!(
sk.s1.vec[i].coeffs[j].0, sk2.s1.vec[i].coeffs[j].0,
"skDecode: s1[{i}][{j}] 불일치"
);
}
}

// s2 계수 일치 검증
for i in 0..K44 {
for j in 0..N {
assert_eq!(
sk.s2.vec[i].coeffs[j].0, sk2.s2.vec[i].coeffs[j].0,
"skDecode: s2[{i}][{j}] 불일치"
);
}
}

// t0 계수 일치 검증
for i in 0..K44 {
for j in 0..N {
assert_eq!(
sk.t0.vec[i].coeffs[j].0, sk2.t0.vec[i].coeffs[j].0,
"skDecode: t0[{i}][{j}] 불일치"
);
}
}
}

//
// skEncode / skDecode 라운드트립 (ML-DSA-65)
//

#[test]
fn test_sk_encode_decode_roundtrip_65() {
type SK65 = MLDSAPrivateKey<K65, L65, ETA65>;

let xi = [3u8; 32];
let (_pk, sk) = keygen_internal::<K65, L65, ETA65>(&xi).expect("keygen_internal failed");

let sk_buf = <SK65 as MLDSAPrivateKeyTrait<K65, L65, SK65_LEN>>::sk_encode(&sk)
.expect("skEncode failed");

assert_eq!(sk_buf.len(), SK65_LEN, "skEncode: 길이 불일치");

let sk2 = <SK65 as MLDSAPrivateKeyTrait<K65, L65, SK65_LEN>>::sk_decode(&sk_buf)
.expect("skDecode failed");

assert_eq!(sk.rho, sk2.rho, "skDecode: ρ 불일치");
assert_eq!(sk.k_seed, sk2.k_seed, "skDecode: K_seed 불일치");
assert_eq!(sk.tr, sk2.tr, "skDecode: tr 불일치");
}

//
// 서명 + 검증 종단 간 테스트 (ML-DSA-44)
//

#[test]
fn test_sign_verify_roundtrip_44() {
let xi = [0xAAu8; 32];
let (pk_bytes, sk_buf) =
MLDSA::key_gen_internal(MLDSAParameter::MLDSA44, &xi).expect("key_gen_internal failed");

let message = b"Hello, ML-DSA-44!";
let m_prime = {
let mut v = Vec::new();
v.push(0x00u8); // domain_sep
v.push(0u8); // |ctx| = 0
v.extend_from_slice(message);
v
};
let rnd = [0u8; 32]; // 결정론적 서명

let sig = MLDSA::sign_internal(MLDSAParameter::MLDSA44, &sk_buf, &m_prime, &rnd)
.expect("sign_internal failed");

assert_eq!(sig.len(), 2420, "서명 길이 불일치");

let ok =
MLDSA::verify_internal(MLDSAParameter::MLDSA44, &pk_bytes, &m_prime, sig.as_slice())
.expect("verify_internal failed");

assert!(ok, "서명 검증 실패 (ML-DSA-44)");
}

//
// 서명 + 검증 종단 간 테스트 (ML-DSA-65)
//

#[test]
fn test_sign_verify_roundtrip_65() {
let xi = [0xBBu8; 32];
let (pk_bytes, sk_buf) =
MLDSA::key_gen_internal(MLDSAParameter::MLDSA65, &xi).expect("key_gen_internal failed");

let message = b"Hello, ML-DSA-65!";
let m_prime = {
let mut v = Vec::new();
v.push(0x00u8);
v.push(0u8);
v.extend_from_slice(message);
v
};
let rnd = [0u8; 32];

let sig = MLDSA::sign_internal(MLDSAParameter::MLDSA65, &sk_buf, &m_prime, &rnd)
.expect("sign_internal failed");

assert_eq!(sig.len(), 3309, "서명 길이 불일치");

let ok =
MLDSA::verify_internal(MLDSAParameter::MLDSA65, &pk_bytes, &m_prime, sig.as_slice())
.expect("verify_internal failed");

assert!(ok, "서명 검증 실패 (ML-DSA-65)");
}

//
// 변조된 메시지 검증 거부 테스트
//

#[test]
fn test_verify_rejects_tampered_message_44() {
let xi = [0xCCu8; 32];
let (pk_bytes, sk_buf) =
MLDSA::key_gen_internal(MLDSAParameter::MLDSA44, &xi).expect("key_gen_internal failed");

let m_prime_orig = b"\x00\x00Hello";
let rnd = [0u8; 32];

let sig = MLDSA::sign_internal(MLDSAParameter::MLDSA44, &sk_buf, m_prime_orig, &rnd)
.expect("sign_internal failed");

let m_prime_tampered = b"\x00\x00World";
let ok = MLDSA::verify_internal(
MLDSAParameter::MLDSA44,
&pk_bytes,
m_prime_tampered,
sig.as_slice(),
)
.expect("verify_internal error");

assert!(!ok, "변조된 메시지가 검증을 통과해서는 안 됩니다");
}

//
// 변조된 서명 검증 거부 테스트
//

#[test]
fn test_verify_rejects_tampered_signature_44() {
let xi = [0xDDu8; 32];
let (pk_bytes, sk_buf) =
MLDSA::key_gen_internal(MLDSAParameter::MLDSA44, &xi).expect("key_gen_internal failed");

let m_prime = b"\x00\x00TestMessage";
let rnd = [0u8; 32];

let mut sig = MLDSA::sign_internal(MLDSAParameter::MLDSA44, &sk_buf, m_prime, &rnd)
.expect("sign_internal failed");

// 서명 중간 바이트 비트 플립
sig.as_mut_slice()[100] ^= 0xFF;

let ok =
MLDSA::verify_internal(MLDSAParameter::MLDSA44, &pk_bytes, m_prime, sig.as_slice())
.expect("verify_internal error");

assert!(!ok, "변조된 서명이 검증을 통과해서는 안 됩니다");
}
}
25 changes: 25 additions & 0 deletions crypto/mldsa/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// mod 오 에러 정의하는것보단 enum이 괜찮을 듯

#[derive(Debug)]
pub enum MLDSAError {
/// 입력 바이트 슬라이스의 길이가 요구사항과 일치하지 않습니다.
InvalidLength(&'static str),
/// 내부 연산 실패 (예: 해시 함수 오류, 메모리 할당 실패)
InternalError(&'static str),
/// 난수 생성기(RNG) 오류
RngError(&'static str),
/// ctx(컨텍스트) 길이가 FIPS 204 제한(255바이트)을 초과합니다.
ContextTooLong,
/// 서명 시도가 최대 반복 횟수를 초과하였습니다 (극히 희박한 경우).
SigningFailed,
/// 서명 검증 실패
InvalidSignature,
/// 아직 구현되지 않은 기능입니다.
NotImplemented(&'static str),
}

impl From<&'static str> for MLDSAError {
fn from(s: &'static str) -> Self {
MLDSAError::InternalError(s)
}
}
57 changes: 57 additions & 0 deletions crypto/mldsa/src/field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use crate::Q;
use crate::Q_INV;
use entlib_native_constant_time::traits::{ConstantTimeIsNegative, ConstantTimeSelect};

/// 유한체 Z_q의 원소를 나타내는 구조체
#[derive(Clone, Copy, Debug, Default)]
pub struct Fq(pub i32);

impl Fq {
/// 새로운 필드 요소를 생성합니다. (입력값은 [0, Q-1] 범위 내에 있어야 함)
#[inline(always)]
pub const fn new(val: i32) -> Self {
Self(val)
}

/// 상수-시간 모듈러 덧셈
pub fn add(self, other: Self) -> Self {
let sum = self.0 + other.0;
// sum은 최대 2Q - 2 값을 가질 수 있습니다. Q를 빼서 범위를 맞춥니다.
let sub = sum - Q;

// sub가 음수(즉, sum < Q)이면 sum을 선택하고, 그렇지 않으면 sub를 선택합니다.
let is_neg = sub.ct_is_negative();
Self(i32::ct_select(&sum, &sub, is_neg))
}

/// 상수-시간 모듈러 뺄셈
pub fn sub(self, other: Self) -> Self {
let diff = self.0 - other.0;
// diff는 음수가 될 수 있으므로 Q를 더한 값을 준비합니다.
let add = diff + Q;

// diff가 음수이면 Q를 더한 add를 선택하고, 그렇지 않으면 diff를 유지합니다.
let is_neg = diff.ct_is_negative();
Self(i32::ct_select(&add, &diff, is_neg))
}

/// 몽고메리 환원을 이용한 상수-시간 모듈러 곱셈
///
/// a * b * R^(-1) mod Q 연산을 수행합니다. (여기서 R = 2^32)
pub fn mul(self, other: Self) -> Self {
let prod = (self.0 as i64) * (other.0 as i64);

// t = (prod * Q_INV) mod 2^32
let t = (prod as i32).wrapping_mul(Q_INV);
// t_q = t * Q
let t_q = (t as i64) * (Q as i64);

// u = (prod - t_q) / 2^32
let u = ((prod - t_q) >> 32) as i32;

// u는 [-Q, Q-1] 범위에 있습니다. 음수인 경우 Q를 더해 보정합니다.
let is_neg = u.ct_is_negative();
let u_plus_q = u + Q;
Self(i32::ct_select(&u_plus_q, &u, is_neg))
}
}
Loading
Loading