- 입력값 준비
- Mnemonic: 예)
"abandon abandon ... about"
- Salt:
"mnemonic"
+passphrase
(passphrase가 없다면 빈 문자열)
- Mnemonic: 예)
- PBKDF2-HMAC-SHA512 적용
- 알고리즘: PBKDF2
- 해시 함수: HMAC-SHA512
- 반복 횟수: 2,048회
- 출력 길이: 64바이트 (512비트) github.com+2medium.com+2github.com+2github.com
- 알고리즘: PBKDF2
- 출력값 →
seed
- 반환된 64바이트 데이터는 HD 지갑의 마스터 시드로 사용되며, 이 시드를 통해 BIP-32 계층적 키 유도가 진행됩니다.
📦 코드 상 구현 (trezor‑crypto 기준)
C 인터페이스는 일반적으로 다음과 같은 구조입니다:
cCopyEditvoid mnemonic_to_seed(const char* mnemonic, const char* passphrase, unsigned char* seed, void* ctx);
- 내부적으로
PBKDF2(HMAC-SHA512, password=mnemonic, salt="mnemonic"+passphrase, iter=2048, dkLen=64)
호출 seed
버퍼에 64바이트 결과를 채워줍니다.
이 함수는 Trust Wallet의 HDWallet.cpp
에서 다음과 같이 래핑되어 사용됩니다:
cppCopyEditmnemonic_to_seed(mnemonic.c_str(), passphrase.c_str(), seed, nullptr);
⏱️ 성능 메트릭
- 2,048회 반복 → 약 4,096회의 SHA-512 계산을 수행
- 이 연산량은 브루트 포스 공격 방어 및 메모리 보호에 충분히 설계되어 있습니다.
✅ 요약
mnemonicToSeed
= PBKDF2(HMAC-SHA512) 기반의 안전한 key stretching- 입력값: 니모닉 문구 +
"mnemonic"
+ (선택적) 패스프레이즈 - 설정: 2,048회 반복 → 64바이트 시드 생성
- 생성된 시드는 이후 BIP‑32 키 유도와 지갑 전체적인 보안의 기반이 됩니다.
🔍 1. 구현 위치 & 함수 시그니처
파일: bip39.c
(trezor‑crypto)
cCopyEditvoid mnemonic_to_seed(const char* mnemonic, const char* passphrase, unsigned char* seed, void* ctx);
이 함수는 BIP‑39 표준에 따라 니모닉 + 패스프레이즈를 입력받아 64바이트(512비트) 시드를 생성합니다 stackoverflow.com+13github.com+13medium.com+13cs.utexas.edu+6bips.dev+6github.com+6.
🧠 2. 내부 알고리즘 흐름 (BIP-39 표준)
salt = "mnemonic" + passphrase
(NFKD normalization 적용)PBKDF2
를 통해:- PRF: HMAC‑SHA512
- iterations: 2048
- dkLen (출력길이): 64바이트
즉:
cCopyEditPBKDF2-HMAC-SHA512(password=mnemonic, salt="mnemonic"+passphrase, iter=2048, dkLen=64)
→ seed[64]
이 과정은 니모닉 문장을 시드로 안전하게 확장(key stretching) 합니다 crypto.stackexchange.com+8bips.dev+8en.wikipedia.org+8codeandlife.com+4stackoverflow.com+4reddit.com+4en.wikipedia.org+1zh.wikipedia.org+1.
⚙️ 3. PBKDF2-블럭 처리 구현 분석 (pbkdf2.c
)
파일: pbkdf2.c
pbkdf2_hmac_sha512
함수는 PRF HMAC‑SHA512를 기반으로 각 블록(U₁, U₂, … Uₙ)의 XOR 형식 반복 계산을 수행합니다 (RFC 8018 규격) docs.pingidentity.com+15github.com+15codeandlife.com+15stackoverflow.com+3en.wikipedia.org+3cs.utexas.edu+3.- 단순 구조 예시:
cCopyEditfor (i = 1; i <= blocks; i++) {
U = HMAC(password, salt || INT(i))
T = U
for (j = 2; j <= iterations; j++) {
U = HMAC(password, U)
T ^= U
}
append T to derived key
}
- iterations = 2,048, dkLen = 64, HMAC‑SHA512 블록 길이에 따라 블록 수는 1개(U₁) 또는 2개(U₁,U₂)가 생성됩니다.
🔒 4. 보안 고려 사항
2048
번 반복 수행: 단순 HMAC보다 2K 배 느린 연산으로 brute-force 공격 저항 강화 reddit.com+1zh.wikipedia.org+1.- 출력 길이 512비트는 BIP‑32 마스터 시드 생성에 적합합니다.
- salt 값이
"mnemonic"+passphrase"
이므로, 패스프레이즈 사용 시 동일 니모닉이라도 다른 seed 생성 → 추가 보안 계층 제공 crypto.stackexchange.com+10bips.dev+10github.com+10.
📊 5. 전체 요약 테이블
단계 | 동작 내용 |
---|---|
1. 입력값 준비 | mnemonic + passphrase (UTF‑8 NFKD) |
2. salt 생성 | "mnemonic" + passphrase 문자열 조합 |
3. PBKDF2 호출 | PRF = HMAC-SHA512, iter = 2048, dkLen = 64 |
4. seed 반환 | 생성된 64바이트 시드에 채워져 HDWallet 의 기반으로 사용됨 |
🧩 6. HDWallet.cpp
에서 활용 예시
Trust Wallet은 아래와 같이 해당 시드 생성 함수를 래핑하여 사용합니다:
cppCopyEditmnemonic_to_seed(mnemonic.c_str(), passphrase.c_str(), seed, nullptr);
이후 생성된 seed
를 기반으로 BIP-32 마스터 키와 체계적 HD 파생 키 생성으로 이어지는 구조입니다.
🔡 BIP-39에서의 NFKD 정규화 과정 (Mnemonic & Passphrase)
BIP-39 표준은 **니모닉(mnemonic phrase)**과 패스프레이즈(passphrase) 모두에 대해 Unicode NFKD (Normalization Form KD) 정규화를 요구합니다. 이는 사용자가 입력한 한글, 일본어, 이모지 등의 시각적으로 동일하지만 이진적으로 다른 문자열을 일관되게 처리하기 위함입니다.
📚 1. NFKD란?
NFKD = Normalization Form Compatibility Decomposition
- 호환 분해(KD): visually 동일하지만 의미가 호환되는 글자들을 구성요소로 분해.
- 예:
é
(U+00E9) →e
+́
(U+0065 + U+0301)
📌 2. BIP-39 정규화 요구 사항
“Both the mnemonic sentence and the passphrase must be encoded in UTF-8 using NFKD normalization.”
즉:
- 니모닉:
"abandon abandon abandon ... about"
- 패스프레이즈: 사용자가 입력한 자유 문자열 (예:
"my pass"
)
둘 다 NFKD로 정규화된 후 UTF-8 바이트로 변환되어 PBKDF2 함수에 전달됩니다.
🔧 3. Trust Wallet / trezor-crypto 적용 여부
trezor-crypto에서는 명시적으로 NFKD
정규화를 수행하지 않습니다. 이유는 다음과 같습니다:
- C언어 표준 라이브러리에는 NFKD 처리가 없음
- 대신 상위 애플리케이션(예: Trust Wallet App, Python, JS 등)에서 처리하도록 합니다.
📌 예시: Trust Wallet에서 Swift 또는 Java 앱 상단에서 NFKD 적용 후 mnemonic_to_seed()
로 전달
🧪 4. 예제 (Python)
pythonCopyEditimport unicodedata
mnemonic = "가가가가가가가가가가가" # 조합형 한글
normalized = unicodedata.normalize("NFKD", mnemonic)
print([hex(ord(c)) for c in normalized])
결과:
plaintextCopyEdit['0x1100', '0x1161', '0x1100', '0x1161', ...] # 분리된 자모
⚠️ 5. 주의사항
항목 | 설명 |
---|---|
❗ 필수 단계 | NFKD는 시드 생성의 핵심 전처리로, 생략 시 완전히 다른 시드 생성 |
🌐 국제화 문제 | 한글, 일본어, 아랍어, 악센트가 있는 유럽 문자 등에서 큰 차이 발생 |
🧪 테스트 도구 | Python unicodedata.normalize() , Rust unicode-normalization , Java Normalizer 클래스 등 사용 |
✅ 요약
항목 | 내용 |
---|---|
정규화 방식 | Unicode NFKD (호환 분해) |
적용 대상 | 니모닉, 패스프레이즈 |
인코딩 | UTF-8 바이트로 변환 후 PBKDF2에 입력 |
라이브러리 구현 | 대부분 애플리케이션 레벨에서 처리 (Trezor-Crypto는 미포함) |
실수시 영향 | 다른 시드 생성 → 완전 다른 지갑 생성 |