Skip to content

Support working with ed25519 seeds in addition to raw keypairs.#1055

Open
aqua wants to merge 3 commits intomeshcore-dev:devfrom
aqua:ed25519-seed-dev
Open

Support working with ed25519 seeds in addition to raw keypairs.#1055
aqua wants to merge 3 commits intomeshcore-dev:devfrom
aqua:ed25519-seed-dev

Conversation

@aqua
Copy link
Copy Markdown

@aqua aqua commented Nov 3, 2025

Replaces pull #1046, which I accidentally closed by being bad at github. Addresses #1044.

Our ed25519 library uses a representation of its key pair that is largely incompatible with modern implementations, which mostly work with the original 32-byte seed; Peters' impentation represents the private key as the clamped sha512 of the seed.

This change:

  • preserves the original seed when generating keys
  • adds CLI commands to obtain the seed via get prv.seed, under the same conditions as get prv.key is allowed
  • adds support for set prv.key to supply a seed, in which case the keypair will be re-generated from it. This is mostly to enable external key management using modern libraries, but could also be of use on devices where we don't have a trustworthy entropy source.

I split Identity::writeTo(uint8_t*,size_t) into explicit forms for the thing being written; the original implementation wrote a different thing depending on the length, which would be ambiguous between pubkey and seed and cumbersome if it tried to return all three in one long buffer. Identity::readFrom() did not have that ambiguity problem because keys can't be set from pubkey alone, though it might be preferable to split readFrom() up as well and not use magic length values.

Devin Carraway added 2 commits November 3, 2025 11:21
Our ed25519 library uses a representation of its key pair that
is largely incompatible with modern implementations, which mostly
work with the original 32-byte seed; Peters' impentation represents
the private key as the clamped sha512 of the seed.

This change:

- preserves the original seed when generating keys
- adds CLI commands to obtain the seed via `get prv.seed`,
  under the same conditions as `get prv.key` is allowed
- adds support for `set prv.key` to supply a seed, in which
  case the keypair will be re-generated from it.  This is
  mostly to enable external key management using modern
  libraries, but could also be of use on devices where
  we don't have a trustworthy entropy source.

I split Identity::writeTo(uint8_t*,size_t) into explicit forms for
the thing being written; the original implementation wrote a
different thing depending on the length, which would be ambiguous
between pubkey and seed and cumbersome if it tried to return all
three in one long buffer. Identity::readFrom() did not have that
ambiguity problem because keys can't be set from pubkey alone,
though it might be preferable to split readFrom() up as well
and not use magic length values.
Copy link
Copy Markdown
Contributor

@446564 446564 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

tested on xiao nrf repeater, getting and setting work as advertised. all other functions remain operational.

Comment thread src/helpers/CommonCLI.cpp Outdated
} else {
strcpy(reply, "Error, bad key");
}
} else if (sender_timestamp == 0 && memcmp(config, "prv.seed ", 9) == 0) { // from serial command line only
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not too long ago, we allowed updating the private key via LoRa with set prv.key <hex>, so we might as well allow setting the private key from the seed via LoRa with set prv.seed <hex> as well.

For now, I believe the fetching of the private key will still remain serial only.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, done in eb33284

Comment thread src/helpers/CommonCLI.cpp
}
} else if (sender_timestamp == 0 && memcmp(config, "prv.seed ", 9) == 0) { // from serial command line only
uint8_t seed[SEED_SIZE];
bool success = mesh::Utils::fromHex(seed, SEED_SIZE, &config[9]);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there's no length validation for the provided seed, is there any issue with providing a seed that's too short or too long? I guess the remainder would just be all zeros...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Utils::fromHex() does a check that strlen(src_hex)==dest_size*2, so it should give an error with a seed that's too short or too long (I'd test but I'm across an ocean from my nearest spare repeater until the weekend.)

Comment thread src/helpers/CommonCLI.cpp
sprintf(reply, "> %s", tmp);
} else if (sender_timestamp == 0 && memcmp(config, "prv.seed", 8) == 0) { // from serial command line only
uint8_t seed[SEED_SIZE];
auto len = _callbacks->getSelfId().writeSeedTo(seed, SEED_SIZE);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for devices that update firmware, but keep their existing identity, I'd assume this will just provide an output of full zeros. Probably fine, as you could check for full zeros to know there's no seed available. But wondering if it should return an error message instead...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming you mean here devices that were seeded before this code: yes, LocalIdentity::readFrom() zeroes the seed and then attempts to read over it if the seed is in the identity store, so if the identity were stored from an older revision it'll stay zeroed and this will output a zero string. I suppose returning an error would be more helpful but we should have something in the docs to say more than the firmware itself can.

@liamcottle
Copy link
Copy Markdown
Member

LGTM

tested on xiao nrf repeater, getting and setting work as advertised. all other functions remain operational.

@446564 thanks for the review. I've added some extra comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants