mirror of
https://git.wownero.com/wowlet/wownero-seed.git
synced 2024-08-15 01:03:29 +00:00
3 -> 5 reserved bits
removed network type
This commit is contained in:
parent
41546fa019
commit
7609d2ee96
4 changed files with 25 additions and 71 deletions
38
README.md
38
README.md
|
@ -10,7 +10,7 @@ make
|
|||
## Features
|
||||
|
||||
* embedded wallet birthday to optimize restoring from the seed (only blocks after the wallet birthday have to be scanned for transactions)
|
||||
* embedded network type (mainnet/stagenet/testnet) to prevent accidental misuse of the seed on a different network
|
||||
* 5 bits reserved for future updates
|
||||
* advanced checksum based on Reed-Solomon linear code, which allows certain types of errors to be detected without false positives and provides limited error correction capability
|
||||
* built-in way to make seeds incompatible between different coins, e.g. a seed for Aeon cannot be accidentally used to restore a Monero wallet
|
||||
|
||||
|
@ -19,15 +19,14 @@ make
|
|||
### Create a new seed
|
||||
|
||||
```
|
||||
> ./monero-seed --create [--coin <monero|aeon>] [--net <MAIN|STAGE|TEST>] [--date <yyyy-MM-dd>]
|
||||
> ./monero-seed --create [--date <yyyy-MM-dd>] [--coin <monero|aeon>]
|
||||
```
|
||||
|
||||
Example:
|
||||
```
|
||||
> ./monero-seed --create --coin monero --net MAIN --date 2100/03/14
|
||||
> ./monero-seed --create --date 2100/03/14 --coin monero
|
||||
Mnemonic phrase: test park taste security oxygen decorate essence ridge ship fish vehicle dream fluid pattern
|
||||
- coin: monero
|
||||
- network: MAIN
|
||||
- private key: 7b816d8134e29393b0333eed4b6ed6edf97c156ad139055a706a6fb9599dcf8c
|
||||
- created on or after: 02/Mar/2100
|
||||
```
|
||||
|
@ -42,7 +41,6 @@ Example:
|
|||
```
|
||||
> ./monero-seed --restore "test park taste security oxygen decorate essence ridge ship fish vehicle dream fluid pattern" --coin monero
|
||||
- coin: monero
|
||||
- network: MAIN
|
||||
- private key: 7b816d8134e29393b0333eed4b6ed6edf97c156ad139055a706a6fb9599dcf8c
|
||||
- created on or after: 02/Mar/2100
|
||||
```
|
||||
|
@ -60,7 +58,6 @@ This can be tested by replacing a word with `xxxx`:
|
|||
> ./monero-seed --restore "test park xxxx security oxygen decorate essence ridge ship fish vehicle dream fluid pattern" --coin monero
|
||||
Warning: corrected erasure: xxxx -> taste
|
||||
- coin: monero
|
||||
- network: MAIN
|
||||
- private key: 7b816d8134e29393b0333eed4b6ed6edf97c156ad139055a706a6fb9599dcf8c
|
||||
- created on or after: 02/Mar/2100
|
||||
```
|
||||
|
@ -69,8 +66,7 @@ Warning: corrected erasure: xxxx -> taste
|
|||
|
||||
The mnemonic phrase contains 154 bits of data, which are used as follows:
|
||||
|
||||
* 2 bits for the network type
|
||||
* 3 bits reserved for future use
|
||||
* 5 bits reserved for future use
|
||||
* 10 bits for approximate wallet birthday
|
||||
* 128 bits for the private key seed
|
||||
* 11 bits for checksum
|
||||
|
@ -80,36 +76,28 @@ The mnemonic phrase contains 154 bits of data, which are used as follows:
|
|||
The mnemonic phrase uses the BIP-39 wordlist, which has 2048 words, allowing 11 bits to be stored in each word. It has some additional useful properties,
|
||||
for example each word can be uniquly identified by its first 4 characters. The wordlist is available for 9 languages (this repository only uses the English list).
|
||||
|
||||
### Network type
|
||||
|
||||
The network type is stored in 2 bits as follows:
|
||||
|
||||
* `00` = mainnet
|
||||
* `01` = stagenet
|
||||
* `10` = testnet
|
||||
* `11` = invalid
|
||||
|
||||
The "Iinvalid" value can be used to support future extensions of the mnemonic seed to more than 14 words. Setting these two bits to `11` will prevent the first 14-words of a longer seed from being a valid 14-word seed (the checksum alone cannot prevent this).
|
||||
|
||||
### Reserved bits
|
||||
|
||||
There are 3 reserved bits for future use. Since there is no dedicated "version" field, the current implementation requires all reserved bits to be set to `0` for backwards compatibility.
|
||||
|
||||
Possible use cases for the reserved bits include:
|
||||
There are 5 reserved bits for future use. Possible use cases for the reserved bits include:
|
||||
|
||||
* a flag to differentiate between normal and "short" address format (with view key equal to the spend key)
|
||||
* different KDF algorithms for generating the private key
|
||||
* seed encrypted with a passphrase
|
||||
|
||||
Backwards compatibility is achieved under these two conditions:
|
||||
|
||||
1. Reserved (unused) bits are required to be 0. The software should return an error otherwise.
|
||||
2. When defining a new feature bit, 0 should be the previous behavior.
|
||||
|
||||
### Wallet birthday
|
||||
|
||||
The mnemonic phrase doesn't store block height but the approximate date when the wallet was created. This allows the seed to be generated offline without access to the blockchain. Wallet software can easily convert a date to the corresponding block height when restoring a seed.
|
||||
The mnemonic phrase stores the approximate date when the wallet was created. This allows the seed to be generated offline without access to the blockchain. Wallet software can easily convert a date to the corresponding block height when restoring a seed.
|
||||
|
||||
The wallet creation date has a resolution of 2629746 seconds (1/12 of the average Gregorian year). All dates between June 2020 and September 2105 can be represented.
|
||||
The wallet birthday has a resolution of 2629746 seconds (1/12 of the average Gregorian year). All dates between June 2020 and September 2105 can be represented.
|
||||
|
||||
### Private key seed
|
||||
|
||||
The private key is derived from the 128-bit seed using PBKDF2-HMAC-SHA256 with 4096 iterations.The wallet birthday and network type are used as a salt. 128-bit seed provides the same level of security as the elliptic curve used by Monero.
|
||||
The private key is derived from the 128-bit seed using PBKDF2-HMAC-SHA256 with 4096 iterations.The wallet birthday and the 5 reserved/feature bits are used as a salt. 128-bit seed provides the same level of security as the elliptic curve used by Monero.
|
||||
|
||||
Future extensions may define other KDFs.
|
||||
|
||||
|
|
|
@ -59,7 +59,6 @@ void print_seed(const monero_seed& seed, const char* coin, bool phrase) {
|
|||
std::cout << "Mnemonic phrase: " << seed << std::endl;
|
||||
}
|
||||
std::cout << "- coin: " << coin << std::endl;
|
||||
std::cout << "- network: " << seed.net_name() << std::endl;
|
||||
std::cout << "- private key: " << seed.key() << std::endl;
|
||||
auto created_on = seed.date();
|
||||
std::tm tm = *std::localtime(&created_on);
|
||||
|
@ -69,12 +68,10 @@ void print_seed(const monero_seed& seed, const char* coin, bool phrase) {
|
|||
int main(int argc, const char* argv[]) {
|
||||
bool create;
|
||||
const char* create_date;
|
||||
const char* create_net;
|
||||
const char* coin;
|
||||
const char* restore;
|
||||
read_option("--create", argc, argv, create);
|
||||
read_string_option("--date", argc, argv, &create_date);
|
||||
read_string_option("--net", argc, argv, &create_net, "MAIN");
|
||||
read_string_option("--coin", argc, argv, &coin, "monero");
|
||||
read_string_option("--restore", argc, argv, &restore);
|
||||
|
||||
|
@ -87,7 +84,7 @@ int main(int argc, const char* argv[]) {
|
|||
else {
|
||||
time = std::time(nullptr);
|
||||
}
|
||||
monero_seed seed(time, coin, create_net);
|
||||
monero_seed seed(time, coin);
|
||||
print_seed(seed, coin, true);
|
||||
}
|
||||
else if (restore != nullptr) {
|
||||
|
@ -97,7 +94,7 @@ int main(int argc, const char* argv[]) {
|
|||
else {
|
||||
std::cout << "Monero 14-word mnemonic seed proof of concept" << std::endl;
|
||||
std::cout << "Usage: " << std::endl;
|
||||
std::cout << argv[0] << " --create [--coin <monero|aeon>] [--net <MAIN|STAGE|TEST>] [--date <yyyy-MM-dd>]" << std::endl;
|
||||
std::cout << argv[0] << " --create [--date <yyyy-MM-dd>] [--coin <monero|aeon>]" << std::endl;
|
||||
std::cout << argv[0] << " --restore \"<14-word seed>\" [--coin <monero|aeon>]" << std::endl;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,9 +47,7 @@ constexpr std::time_t time_step = 2629746; //30.436875 days = 1/12 of the Gregor
|
|||
|
||||
constexpr unsigned date_bits = 10;
|
||||
constexpr unsigned date_mask = (1u << date_bits) - 1;
|
||||
constexpr unsigned net_bits = 2;
|
||||
constexpr unsigned net_mask = (1u << net_bits) - 1;
|
||||
constexpr unsigned reserved_bits = 3;
|
||||
constexpr unsigned reserved_bits = 5;
|
||||
constexpr unsigned reserved_mask = (1u << reserved_bits) - 1;
|
||||
constexpr unsigned check_digits = 1;
|
||||
constexpr unsigned checksum_size = gf_elem::size() * check_digits;
|
||||
|
@ -64,16 +62,11 @@ static const std::string COIN_AEON = "aeon";
|
|||
|
||||
constexpr gf_elem monero_flag = gf_elem(0x539);
|
||||
constexpr gf_elem aeon_flag = gf_elem(0x201);
|
||||
constexpr int flag_word = 1;
|
||||
|
||||
static const char* net_types[] = {
|
||||
"MAIN", "STAGE", "TEST", nullptr
|
||||
};
|
||||
|
||||
static const char* KDF_PBKDF2 = "PBKDF2-HMAC-SHA256/4096";
|
||||
|
||||
static_assert(total_bits
|
||||
== net_bits + reserved_bits + date_bits + checksum_size +
|
||||
== reserved_bits + date_bits + checksum_size +
|
||||
sizeof(monero_seed::secret_seed) * CHAR_BIT,
|
||||
"Invalid mnemonic seed size");
|
||||
|
||||
|
@ -119,32 +112,21 @@ static gf_elem get_coin_flag(const std::string& coin) {
|
|||
|
||||
static const reed_solomon_code rs(check_digits);
|
||||
|
||||
monero_seed::monero_seed(std::time_t date_created, const std::string& coin, const std::string& net) {
|
||||
monero_seed::monero_seed(std::time_t date_created, const std::string& coin) {
|
||||
if (date_created < epoch) {
|
||||
THROW_EXCEPTION("date_created must not be before 1st June 2020");
|
||||
}
|
||||
unsigned quantized_date = ((date_created - epoch) / time_step) & date_mask;
|
||||
date_ = epoch + quantized_date * time_step;
|
||||
gf_elem coin_flag = get_coin_flag(coin);
|
||||
net_name_ = nullptr;
|
||||
for (int i = 0; i < net_mask; ++i) {
|
||||
if (net_types[i] == net) {
|
||||
net_type_ = i;
|
||||
net_name_ = net_types[i];
|
||||
}
|
||||
}
|
||||
if (net_name_ == nullptr) {
|
||||
THROW_EXCEPTION("invalid network type");
|
||||
}
|
||||
reserved_ = 0;
|
||||
secure_random::gen_bytes(seed_.data(), seed_.size());
|
||||
uint8_t salt[25] = "Monero 14-word seed";
|
||||
salt[20] = net_type_;
|
||||
salt[20] = reserved_;
|
||||
store32(salt + 21, quantized_date);
|
||||
//argon2id_hash_raw(argon_tcost, argon_mcost, 1, seed_.data(), seed_.size(), salt, sizeof(salt), key_.data(), key_.size());
|
||||
pbkdf2_hmac_sha256(seed_.data(), seed_.size(), salt, sizeof(salt), pbkdf2_iterations, key_.data(), key_.size());
|
||||
unsigned rem_bits = gf_elem::size();
|
||||
write_data(message_, rem_bits, net_type_, net_bits);
|
||||
write_data(message_, rem_bits, reserved_, reserved_bits);
|
||||
write_data(message_, rem_bits, quantized_date, date_bits);
|
||||
for (auto byte : seed_) {
|
||||
|
@ -152,7 +134,7 @@ monero_seed::monero_seed(std::time_t date_created, const std::string& coin, cons
|
|||
}
|
||||
assert(rem_bits == 0);
|
||||
rs.encode(message_);
|
||||
message_[flag_word] -= coin_flag;
|
||||
message_[check_digits] -= coin_flag;
|
||||
}
|
||||
|
||||
monero_seed::monero_seed(const std::string& phrase, const std::string& coin) {
|
||||
|
@ -191,17 +173,17 @@ monero_seed::monero_seed(const std::string& phrase, const std::string& coin) {
|
|||
if (error >= 0) {
|
||||
for (unsigned i = 0; i < gf_2048::elements(); ++i) {
|
||||
message_[error] = i;
|
||||
message_[flag_word] += coin_flag;
|
||||
message_[check_digits] += coin_flag;
|
||||
if (rs.check(message_)) {
|
||||
correction_ = wordlist::english.get_word(i);
|
||||
break;
|
||||
}
|
||||
message_[flag_word] -= coin_flag;
|
||||
message_[check_digits] -= coin_flag;
|
||||
}
|
||||
assert(!correction_.empty());
|
||||
}
|
||||
else {
|
||||
message_[flag_word] += coin_flag;
|
||||
message_[check_digits] += coin_flag;
|
||||
if (!rs.check(message_)) {
|
||||
THROW_EXCEPTION("phrase is invalid (checksum mismatch)");
|
||||
}
|
||||
|
@ -209,12 +191,10 @@ monero_seed::monero_seed(const std::string& phrase, const std::string& coin) {
|
|||
|
||||
unsigned used_bits = checksum_size;
|
||||
unsigned quantized_date;
|
||||
net_type_ = 0;
|
||||
reserved_ = 0;
|
||||
quantized_date = 0;
|
||||
memset(seed_.data(), 0, seed_.size());
|
||||
|
||||
read_data(message_, used_bits, net_type_, net_bits);
|
||||
read_data(message_, used_bits, reserved_, reserved_bits);
|
||||
read_data(message_, used_bits, quantized_date, date_bits);
|
||||
|
||||
|
@ -228,16 +208,10 @@ monero_seed::monero_seed(const std::string& phrase, const std::string& coin) {
|
|||
THROW_EXCEPTION("reserved bits must be zero");
|
||||
}
|
||||
|
||||
net_name_ = net_types[net_type_];
|
||||
|
||||
if (net_name_ == nullptr) {
|
||||
THROW_EXCEPTION("invalid network type");
|
||||
}
|
||||
|
||||
date_ = epoch + quantized_date * time_step;
|
||||
|
||||
uint8_t salt[25] = "Monero 14-word seed";
|
||||
salt[20] = net_type_;
|
||||
salt[20] = reserved_;
|
||||
store32(salt + 21, quantized_date);
|
||||
//argon2id_hash_raw(argon_tcost, argon_mcost, 1, seed_.data(), seed_.size(), salt, sizeof(salt), key_.data(), key_.size());
|
||||
pbkdf2_hmac_sha256(seed_.data(), seed_.size(), salt, sizeof(salt), pbkdf2_iterations, key_.data(), key_.size());
|
||||
|
|
|
@ -19,16 +19,13 @@ public:
|
|||
using secret_key = std::array<uint8_t, key_size>;
|
||||
using secret_seed = std::array<uint8_t, size>;
|
||||
monero_seed(const std::string& phrase, const std::string& coin);
|
||||
monero_seed(std::time_t date_created, const std::string& coin, const std::string& net);
|
||||
monero_seed(std::time_t date_created, const std::string& coin);
|
||||
std::time_t date() const {
|
||||
return date_;
|
||||
}
|
||||
const std::string& correction() const {
|
||||
return correction_;
|
||||
}
|
||||
const char* net_name() const {
|
||||
return net_name_;
|
||||
}
|
||||
const secret_key& key() const {
|
||||
return key_;
|
||||
}
|
||||
|
@ -37,11 +34,9 @@ private:
|
|||
secret_seed seed_;
|
||||
secret_key key_;
|
||||
std::time_t date_;
|
||||
unsigned net_type_;
|
||||
unsigned reserved_;
|
||||
std::string correction_;
|
||||
gf_poly message_;
|
||||
const char* net_name_;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const monero_seed::secret_key& key);
|
||||
|
|
Loading…
Reference in a new issue