Skip to content

feat: add Mnemonic XOR (SeedXOR)#727

Merged
odudex merged 5 commits intoselfcustody:developfrom
qlrd:feat/seedxor
Oct 11, 2025
Merged

feat: add Mnemonic XOR (SeedXOR)#727
odudex merged 5 commits intoselfcustody:developfrom
qlrd:feat/seedxor

Conversation

@qlrd
Copy link
Copy Markdown
Member

@qlrd qlrd commented Sep 21, 2025

What is this PR for?

This PR add an entry to Backup Mnemonics > Other formats menu as well add a new page class to src/krux/pages/seedxor_ui called SeedXOR. This class allow to choose between 2 up to 4 shares, add entropies from camera to the new shares as well show the mnemonic shares.

Close #384.

Changes made to:

  • Code
  • Tests
  • Docs
  • CHANGELOG

Did you build the code and tested on device?

  • Yes, build and tested on

What is the purpose of this pull request?

  • Bug fix
  • New feature
  • Docs update
  • Other

@qlrd qlrd force-pushed the feat/seedxor branch 2 times, most recently from 39cbcfd to 80224ef Compare September 21, 2025 22:58
@codecov
Copy link
Copy Markdown

codecov Bot commented Sep 21, 2025

Codecov Report

❌ Patch coverage is 92.85714% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.06%. Comparing base (88aa221) to head (1aff195).
⚠️ Report is 1 commits behind head on develop.

Files with missing lines Patch % Lines
src/krux/pages/mnemonic_loader.py 90.90% 24 Missing ⚠️
src/krux/pages/home_pages/mnemonic_xor.py 98.68% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #727      +/-   ##
===========================================
+ Coverage    97.02%   97.06%   +0.03%     
===========================================
  Files           80       82       +2     
  Lines        10299    10395      +96     
===========================================
+ Hits          9993    10090      +97     
+ Misses         306      305       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@qlrd qlrd changed the title feat: add seedxor as option to backup mnemonics feat: add Mnemonic XOR (SeedXOR) Sep 21, 2025
@qlrd qlrd force-pushed the feat/seedxor branch 5 times, most recently from 518302c to 13be4fa Compare September 24, 2025 12:53
@jdlcdl
Copy link
Copy Markdown
Collaborator

jdlcdl commented Sep 24, 2025

Played with this today, I like the flow. Used 3 XORed mnemonics to get to a my expected 4th, and was able to remove one or more to get back to a prior mix(es).

@qlrd qlrd force-pushed the feat/seedxor branch 13 times, most recently from 769fc17 to 05b6742 Compare September 30, 2025 17:16
@qlrd qlrd marked this pull request as ready for review September 30, 2025 17:30
@jdlcdl
Copy link
Copy Markdown
Collaborator

jdlcdl commented Oct 1, 2025

I'm not sure I understand why MaixPy has changes. Please verify that this is intended, I suspect that this branch is pointing to an older MaixPy.

I like that there is a check for the result of another XOR not altering the mnemonic (as if someone used all 0x00s "abandon " * 11 + "about"). Shows error ValueError('XORed mnemonic didn't changed')

I'm wondering if it could also be useful (to avoid confusion and silly errors) to also check for:

  • does the result of another XOR produce all 0xFFs like "zoo " * 11 + "wrong", which would happen if someone inverted their mnemonic via that key.
  • does the result of another XOR produce all 0x00s like "abandon " * 11 + "about", which would happen if someone used the existing mnemonic.

Attentive users could notice that the new mnemonic is low entropy, but those with hide-mnemonic turned on couldn't.

Similarly, these 3 (or 4) checks could be done earlier, instead of waiting to see what the result of the xor will be, simply by using the arguments passed into xor and failing early.

  • Is either of the mnemonic entropies all 0x00 bytes (then this will result: "mnemonic unchanged")
  • is A == B (then this will result in 0x00 bytes: "low entropy mnemonic")
  • Is xor(A, 0xff bytes) == B (then this will result in all 0xff bytes: "low entropy mnemonic")
  • is either of the mnenonics all 0xff bytes (then inverted entropy is result: "mnemonic inverted").

I play with the following for testing:
seedxor-test-vectors
MnemonicXOR-gotchas

@qlrd
Copy link
Copy Markdown
Member Author

qlrd commented Oct 1, 2025

I'm not sure I understand why MaixPy has changes. Please verify that this is intended, I suspect that this branch is pointing to an older MaixPy.

I think was a mistake when pushing. Maybe forgot to make submodules stuff. I will check that

@qlrd
Copy link
Copy Markdown
Member Author

qlrd commented Oct 2, 2025

  • Is either of the mnemonic entropies all 0x00 bytes (then this will result: "mnemonic unchanged")
  • is A == B (then this will result in 0x00 bytes: "low entropy mnemonic")
  • Is xor(A, 0xff bytes) == B (then this will result in all 0xff bytes: "low entropy mnemonic")
  • is either of the mnenonics all 0xff bytes (then inverted entropy is result: "mnemonic inverted").

Amazing observations. I will try that and update the PR.

@qlrd qlrd marked this pull request as draft October 2, 2025 13:50
qlrd added 2 commits October 9, 2025 14:52
This commit add test cases to be compliant with coldcard specification,
mostly with 12w/24w with "parts" each (the result mnemonic was derived
from another 3 mnemonics that are XORed).
This commit applies i18n for `Mnemonic XOR` menu entries as well
messages and prompts.
@qlrd qlrd force-pushed the feat/seedxor branch 2 times, most recently from c231ab8 to 2a18e31 Compare October 9, 2025 18:41
@qlrd qlrd requested a review from odudex October 9, 2025 18:42
Comment thread docs/getting-started/features/mnemonic-xor/index.en.md Outdated
Comment thread docs/getting-started/features/mnemonic-xor/three-shares.en.md Outdated
qlrd added 2 commits October 10, 2025 12:23
This commit add a entry to docs in
`docs/getting-started/features/mnemonic-xor.en.md` explaining why we
choose `Mnemonic XOR` term instead `SeedXOR`, the overall algorithm's
perspective and the usage on krux firmware.
@qlrd qlrd requested a review from odudex October 10, 2025 15:25
@qlrd
Copy link
Copy Markdown
Member Author

qlrd commented Oct 10, 2025

I play with the following for testing:

should we add on docs the test vectors showed by @jdlcdl (from CC docs)?

@odudex
Copy link
Copy Markdown
Member

odudex commented Oct 10, 2025

should we add on docs the test vectors showed by @jdlcdl (from CC docs)?

Test vectors that ensure compatibility are good to be added on tests. But I wouldn't use the same example on docs.

@qlrd
Copy link
Copy Markdown
Member Author

qlrd commented Oct 10, 2025

should we add on docs the test vectors showed by @jdlcdl (from CC docs)?

Test vectors that ensure compatibility are good to be added on tests. But I wouldn't use the same example on docs.

All tests are made with CC mnemonic vectors (12 and 24 words)

@qlrd
Copy link
Copy Markdown
Member Author

qlrd commented Oct 10, 2025

Tested live on device with test vectors

@jdlcdl
Copy link
Copy Markdown
Collaborator

jdlcdl commented Oct 11, 2025

Tested live on device with test vectors

I've tried as well this morning, on Amigo.

Comment thread CHANGELOG.md
@@ -0,0 +1,82 @@
# What's the Mnemonic XOR?

It's a implementation of [XOR logical gate](https://www.geeksforgeeks.org/digital-logic/xor-gate/) to operate, indefinitely, an [exclusive OR](https://en.wikipedia.org/wiki/Exclusive_or) upon a loaded mnemonic, based on [Coinkite's SeedXOR](https://github.com/Coldcard/firmware/blob/master/docs/seed-xor.md).
Copy link
Copy Markdown
Collaborator

@jdlcdl jdlcdl Oct 11, 2025

Choose a reason for hiding this comment

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

suggest:

Compatible with Coinkite's SeedXOR, this feature applies the binary-XOR operator between entropy bytes of your loaded mnemonic and another, resulting in -- and optionally loading, a new mnemonic which acquires an interesting lossless hiding attribute from the XOR operation.


# How it works

To derive a new mnemonic (and thus, a new seed) from other mnemonics, an operation occurs with the **mnemonic's entropy bytes**.
Copy link
Copy Markdown
Collaborator

@jdlcdl jdlcdl Oct 11, 2025

Choose a reason for hiding this comment

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

suggest:

XOR is an operation that takes two independent 0 or 1 inputs, and results 1 if both are the same 0 if they are different.

Mnemonic XOR effectively masks your loaded mnemonic with another, resulting in a new mnemonic that can restore either of its input mnemonics by providing the other input mnemonic.

Using BIP39 entropy-bytes between two mnemonics, XOR is applied to each bit so that the result is the set of both entropy-bytes. This can be repeated with more unique mnemonics, indefinitely, and each of its input mnemonics can be restored from the result by providing all of the other input mnemonics. We can XOR mnemonic A with B, creating C, and then we can XOR mnemonic C with A to restore B -- or with B to restore A.


<img src="../../../img/mnemonic_xor.png" align="center">

- We get two different mnemonics (A and B), extract their *entropy bytes*;
Copy link
Copy Markdown
Collaborator

@jdlcdl jdlcdl Oct 11, 2025

Choose a reason for hiding this comment

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

  • Using entropy bytes of two independent mnemonics A and B,

<img src="../../../img/mnemonic_xor.png" align="center">

- We get two different mnemonics (A and B), extract their *entropy bytes*;
- validate the input entropies to avoid useless or dangerous operations:
Copy link
Copy Markdown
Collaborator

@jdlcdl jdlcdl Oct 11, 2025

Choose a reason for hiding this comment

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

  • validation of each input mnemonic's entropy-bytes,

- validate the input entropies to avoid useless or dangerous operations:
- `A XOR B = A`: it will not change the XORed mnemonic;
- `A XOR B = A'` where `A'` is a "inverse" version of `A`;
- once the inputs are checked, krux will apply a XOR between the *entropy bytes*;
Copy link
Copy Markdown
Collaborator

@jdlcdl jdlcdl Oct 11, 2025

Choose a reason for hiding this comment

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

  • apply binary XOR between the input mnemonics' entropy-bytes,

- `A XOR B = A`: it will not change the XORed mnemonic;
- `A XOR B = A'` where `A'` is a "inverse" version of `A`;
- once the inputs are checked, krux will apply a XOR between the *entropy bytes*;
- validate the output entropy (same as above);
Copy link
Copy Markdown
Collaborator

@jdlcdl jdlcdl Oct 11, 2025

Choose a reason for hiding this comment

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

  • validate resulting output entropy bytes,

If `A XOR B = C`, then `B XOR C = A`.

- **A**: Your original mnemonic (the secret to protect);
- **B**: A newly generated, random mnemonic (Share 1);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • B: Another backed-up, random mnemonic;


- **A**: Your original mnemonic (the secret to protect);
- **B**: A newly generated, random mnemonic (Share 1);
- **C**: The resulting mnemonic from the XOR operation (Share 2).
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • C: The resulting mnemonic from the XOR operation

@odudex odudex merged commit cc32f6d into selfcustody:develop Oct 11, 2025
7 checks passed
@odudex
Copy link
Copy Markdown
Member

odudex commented Oct 11, 2025

Great work @qlrd ! Thank you!

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.

4 participants