hiero-ledger / hiero-ledger/hiero-sdk-cpp

[Intermediate]: Normalize Mnemonic Passphrases to NFKD

Open
#130 0 comments 0 reactions 0 assignees View on GitHub
priority: medium scope: crypto skill: intermediate status: ready for dev
Dominant language
C++
Stars
42
Forks
108
Avg merge
11h 45m
Merged PRs (30d)
2

Description

## 🧩 Intermediate Friendly

This issue is a good fit for contributors who are already familiar with the Hiero C++ SDK and feel comfortable navigating the codebase.

Intermediate Issues often involve:
- Exploring existing implementations
- Understanding how different components work together
- Making thoughtful changes that follow established patterns

The goal is to support deeper problem-solving while keeping the task clear, focused, and enjoyable to work on.

---

## 🐞 Problem Description

Per the [BIP-39 specification](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki), both the mnemonic phrase and the passphrase should be normalized to **UTF-8 NFKD** (Unicode Normalization Form KD - Compatibility Decomposition) before being used in seed derivation.

**Current implementation in `MnemonicBIP39::toSeed()`:**
```cpp
const std::string mnemonicString = toString();
const std::string saltStr = std::string("mnemonic").append(passphrase);
// passphrase used directly without normalization
```

**Why this matters:**
- The same character can have multiple Unicode representations (e.g., "é" as U+00E9 vs "e" + U+0301 combining accent)
- Without NFKD normalization, different representations produce different seeds
- This causes interoperability issues with other BIP-39 compliant wallets
- Users could lose access to funds if they enter their passphrase on a different system

---

## 💡 Expected Outcome

Add NFKD normalization to both the mnemonic string and passphrase before seed derivation in `MnemonicBIP39::toSeed()`.

```cpp
std::vector MnemonicBIP39::toSeed(std::string_view passphrase) const
{
// Normalize mnemonic and passphrase to NFKD
const std::string normalizedMnemonic = normalizeToNFKD(toString());
const std::string normalizedPassphrase = normalizeToNFKD(std::string(passphrase));

const std::string saltStr = std::string("mnemonic").append(normalizedPassphrase);
// ... rest of implementation
}
```

---

## 🧠 Implementation Notes

**Unicode normalization library options:**

1. **ICU (International Components for Unicode)** - Comprehensive but heavy
- vcpkg: `vcpkg install icu`
- Well-tested, industry standard

2. **utf8proc** - Lightweight alternative
- vcpkg: `vcpkg install utf8proc`
- Smaller footprint, focused on normalization

3. **uni-algo** - Modern C++ header-only option
- vcpkg: `vcpkg install uni-algo`
- C++17 compatible, no runtime dependencies

**Example using utf8proc:**
```cpp
#include

std::string normalizeToNFKD(const std::string& input) {
utf8proc_uint8_t* result;
utf8proc_ssize_t length = utf8proc_map(
reinterpret_cast(input.c_str()),
static_cast(input.length()),
&result,
UTF8PROC_STABLE | UTF8PROC_DECOMPOSE | UTF8PROC_COMPAT
);

if (length < 0) {
throw std::runtime_error("NFKD normalization failed");
}

std::string normalized(reinterpret_cast(result), length);
free(result);
return normalized;
}
```

**Files to modify:**
- `vcpkg.json` - Add normalization library dependency
- `src/sdk/main/src/MnemonicBIP39.cc` - Add normalization to `toSeed()`
- Possibly create a utility function in `impl/` for reuse

**Testing:**
- Add test cases with non-ASCII passphrases
- Test with characters that have multiple Unicode representations
- Verify seed output matches other BIP-39 implementations

---

## ✅ Acceptance Criteria

- [ ] Unicode normalization library added as dependency
- [ ] `toSeed()` normalizes both mnemonic and passphrase to NFKD
- [ ] Unit tests added for non-ASCII passphrases
- [ ] Seeds match reference BIP-39 implementations for normalized inputs
- [ ] Documentation updated if needed

---

## 📋 Contribution Guide

- [ ] Comment `/assign` to request the issue
- [ ] Wait for assignment
- [ ] Fork the repository and create a branch
- [ ] Evaluate library options and choose one
- [ ] Implement NFKD normalization
- [ ] Add tests with Unicode edge cases
- [ ] Sign each commit using `-s -S`
- [ ] Push your branch and open a pull request

Read [Workflow Guide](docs/training/workflow.md) for step-by-step workflow guidance.
Read [README.md](README.md) for setup instructions.

**Pull requests cannot be merged without `S` and `s` signed commits.**
See the [Signing Guide](docs/training/signing.md).

---

## 📚 Additional Context or Resources

**References:**
- [BIP-39 Specification](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki)
- [Unicode NFKD Normalization](https://unicode.org/reports/tr15/)
- [utf8proc library](https://github.com/JuliaStrings/utf8proc)

**Files:**
- `src/sdk/main/src/MnemonicBIP39.cc` - Seed derivation implementation
- `src/sdk/main/include/MnemonicBIP39.h` - Public interface
- `vcpkg.json` - Dependencies

If you have questions, the community is happy to help:

https://discord.com/channels/905194001349627914/1337424839761465364

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.