๐Ÿ” BIPโ€‘39 mnemonicToSeed ๋‚ด๋ถ€ ํ๋ฆ„

  1. ์ž…๋ ฅ๊ฐ’ ์ค€๋น„
    • Mnemonic: ์˜ˆ) "abandon abandon ... about"
    • Salt: "mnemonic" + passphrase (passphrase๊ฐ€ ์—†๋‹ค๋ฉด ๋นˆ ๋ฌธ์ž์—ด)
  2. PBKDF2-HMAC-SHA512 ์ ์šฉ
    • ์•Œ๊ณ ๋ฆฌ์ฆ˜: PBKDF2
  3. ์ถœ๋ ฅ๊ฐ’ โ†’ 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 ํ‘œ์ค€)

  1. salt = "mnemonic" + passphrase (NFKD normalization ์ ์šฉ)
  2. 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

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๋Š” ๋ฏธํฌํ•จ)
์‹ค์ˆ˜์‹œ ์˜ํ–ฅ๋‹ค๋ฅธ ์‹œ๋“œ ์ƒ์„ฑ โ†’ ์™„์ „ ๋‹ค๋ฅธ ์ง€๊ฐ‘ ์ƒ์„ฑ

์ฝ”๋ฉ˜ํŠธ

๋‹ต๊ธ€ ๋‚จ๊ธฐ๊ธฐ

์ด๋ฉ”์ผ ์ฃผ์†Œ๋Š” ๊ณต๊ฐœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•„์ˆ˜ ํ•„๋“œ๋Š” *๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค