Skip to content

[Feature] Add SLIP-39 Shamir's secret sharing import support for SeedSigner #636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 36 commits into
base: dev
Choose a base branch
from

Conversation

alvroble
Copy link
Contributor

@alvroble alvroble commented Dec 19, 2024

Description

This PR introduces support for importing Shamir Secret Sharing (SSS) shards (SLIP-39) into SeedSigner, focusing on seed recovery for users with existing SSS-based backups. While SeedSigner’s stateless design discourages SSS for routine use, this feature provides flexibility for recovering keys from legacy setups or unique scenarios where SSS is used.

The implementation is strictly limited to key recovery, with no support for creating new SSS setups (this can be discussed along with SeedQR support for SSS). So this feature aligns with SeedSigner’s philosophy of providing versatile recovery options without compromising simplicity.

Relevant issue: #552

The seed recovery flow is designed so that the user first inputs the threshold, then enters the words, and is finally asked for an optional passphrase. This process is similar to the regular seed import flow.

Threshold entry is only numerical: (deleted view)

SeedEntryShamirThresholdView

Then the user is asked about the original seed word length

SeedShamirShareImportSelectWordCount

Then, user enters words using the SLIP-39 wordlist. If the original seed length is 12 words, each Shamir's share will be 20 words long. If the original seed length is 24 words, each Shamir's share will be 33 words long. All shares (up to the threshold number) are required to recover the original secret.

SeedShamirShareMnemonicEntryView

SLIP-39 checksum errors are treated as follows:

SeedShamirShareInvalidView

Then the user will be asked if they want to enter a passphrase (the finalize screen is improvable), after which the device will take them to the SeedFinalizeView view

SeedShamirShareFinalizeView

I'm open to comments around this feature as well as to UX improvements so we can get a final version of the PR

This pull request is categorized as a:

  • New feature
  • Bug fix
  • Code refactor
  • Documentation
  • Other

Checklist

  • I’ve run pytest and made sure all unit tests pass before sumbitting the PR

If you modified or added functionality/workflow, did you add new unit tests?

  • No, I’m a fool
  • Yes
  • N/A

I have tested this PR on the following platforms/os:

@kdmukai
Copy link
Contributor

kdmukai commented Dec 19, 2024

Very cool. I appreciate you honing your approach to fit the preferences we've adopted for the project thus far.

I will be tied up with finish the upcoming multilanguage + Spanish release, but will be looking forward to reviewing this PR in the coming weeks. If we get to 2-3 weeks past the new release and you haven't heard from me, please poke me on telegram and remind me to return here!

@alvroble alvroble marked this pull request as ready for review April 11, 2025 14:43
@alvroble
Copy link
Contributor Author

Hi team, this PR is ready for review. Please take a look when you can. Happy to address any feedback—thanks!

@bitcoinprecept bitcoinprecept moved this from 🆕9.0 In Progress to 9.0 Needs Code Review in @SeedSigner Development Board Apr 26, 2025
@fedebuyito
Copy link
Contributor

fedebuyito commented May 3, 2025

Great work, Álvaro!

I ran the test suite with Python 3.10 and 3.12, and all tests passed:

image
image

Only when I try to go to the SSS recovery option do I encounter this error (video attached for better flow details):
https://github.com/user-attachments/assets/dd50f6eb-d4d7-4777-965b-c35519fafeb3

I could to be creating bad .img for my pi0(?), could be a problem with my raspi device(?):

(build output)

root@d3fbae864001:/opt# time ./build.sh --pi0 --app-repo=https://github.com/alvroble/seedsigner.git --app-branch=slip39_sss_import

Executing post-image script ../pi0/board/post-image-seedsigner.sh
25+0 records in
25+0 records out
26214400 bytes (26 MB, 25 MiB) copied, 0.0324031 s, 809 MB/s
Checking that no-one is using this disk right now ... OK

Disk disk.img: 25 MiB, 26214400 bytes, 51200 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

Script header accepted.
Script header accepted.
Created a new DOS disklabel with disk identifier 0xba5eba11.
disk.img1: Created a new partition 1 of type 'W95 FAT32 (LBA)' and of size 24 MiB.
disk.img2: Done.

New situation:
Disklabel type: dos
Disk identifier: 0xba5eba11

Device Boot Start End Sectors Size Id Type
disk.img1 * 2048 51199 49152 24M c W95 FAT32 (LBA)

The partition table has been altered.
Syncing disks.
mkfs.fat 4.2 (2021-01-31)
/opt/buildroot
a9a65184bed7880e7ac0e12bb6a1de189fa11b7d072739c1d658da301e5dca89 /opt/../images/seedsigner_os.slip39_sss_import.pi0.img

real 67m24.812s
user 32m34.416s
sys 24m59.847s

Maybe you could to build same image for pi0 and I will test it, will be in touch.

Best. -

@alvroble
Copy link
Contributor Author

alvroble commented May 4, 2025

Hi @fedebuyito. Thanks a lot for the review and for the bug catch. The code had not been adapted to a recent refactor in HardwareButtons's wait_for method. I solved it and tested different import flows (with and without passphrase, and it should be working now. I also made some cleanup from previous code iterations.

@fedebuyito
Copy link
Contributor

Hi, @alvroble ! You're welcome, I hope to can help. I could to navigate into sss feature and the bug was not presented anymore. Regarding to UX/UI I saw some bit minor details maybe can be considerered:

  • When the user selects '0' as the threshold, an unhandled error is shown:
    image

  • It might be more intuitive to refer to 'entropy' in this view so that the word count for BIP39 (12/24) doesn't get mixed up with the number of words entered for SLIP39 (20/33):
    image

  • When user step to that view and go back, an system error is showned:
    image

  • Finalizing the SLIP39 for SSS is different from finalizing the original BIP39 seed in its views. Can't they be similar in terms of displaying fingerprint data before completion or during passphrase entry?
    image
    image

Best!

seed_screens.SeedMnemonicEntryScreen,
title=_("Share #{} Word #{}").format(self.cur_set_index+1, self.cur_word_index+1), # Human-readable 1-indexing!
initial_letters=list(self.cur_word) if self.cur_word else ["a"],
wordlist=Seed.get_slip39_wordlist(wordlist_language_code=self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE)),
Copy link
Contributor

@kdmukai kdmukai Jun 15, 2025

Choose a reason for hiding this comment

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

On first load, this took about 1.5-2 seconds to load, making the UX feel sluggish when loading this View.

I would put the LoadingScreenThread in front of this. Should be pretty easy. See PSBTOverviewView (though now that I look at it, it would be cleaner if the thread was stopped in a finally block).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kdmukai I don't see this delay of 1.5-2 seconds. Was it on SeedSignerOS or on Raspberry Pi OS manual build?

@alvroble
Copy link
Contributor Author

With help from @fedebuyito (thank you!!), we identified an inconsistency between this implementation and the behavior of Trezor and the recently added Sparrow Wallet support. After recovery, the fingerprint and derived addresses don’t match as expected. I still need to investigate whether this is an issue with embit (the underlying slip39 module) or if it’s caused by a bug in my implementation.

I’m marking this PR as draft again while I dig into it. Thanks a lot @kdmukai for your review, I’ll definitely incorporate your suggestions regarding the UI and user flows.

@alvroble alvroble marked this pull request as draft June 18, 2025 16:58
@fedebuyito
Copy link
Contributor

Thanks to you, Alvaro, I'm grateful to have been able to help you. I'll wait for your new approach so I can test it.

Great work you’re doing with @kdmukai!

@alvroble
Copy link
Contributor Author

alvroble commented Jul 14, 2025

Hi! I've resolved the issue and aligned our implementation with Sparrow's. The problem we found has been discussed in diybitcoinhardware/embit#90. Luckily, embit allows us to recover the secret from the provided shares and load a BIP-32 HDKey (as per spec), resulting in a wallet identical to Sparrow's when using the same shares and passphrase.

A key difference is that Sparrow recovers the HD Wallet from the provided shares but only supports backing up a single share. For SeedSigner, I propose adopting the same approach, avoiding:

  • Redundant backups of the input shares
  • The creation of a new k-of-n share set, which is not intended to be implemented or would be object to further discussion (see first comment).

To support single-share export like Sparrow, extendable backup flag must be supported by embit implementation. This is addressed in diybitcoinhardware/embit#91. Currently, words backup has been disabled pending this functionality (TODO comments added for reference).

Will focus now on implementing @kdmukai suggestions.

@alvroble alvroble marked this pull request as ready for review July 14, 2025 17:11
alvroble added 3 commits July 14, 2025 19:31
* New SeedShamirShareOptionsView
* Deleted SeedEntryShamirThresholdView
* Refactored the code according to this flow
* Updated test cases and screenshots
@alvroble
Copy link
Contributor Author

New refactor allowing for this flow that @kdmukai suggested:

Actually do we even need to know that beforehand? What if after each share is entered the UX goes to a screen that offers:
* Add another share
* Finalize (or something)

So now the user gets directly to the SeedShamirShareMnemonicEntryView. When the share is entered, the UX asks if they want to add one more or just finalize in SeedShamirShareOptionsView:

SeedShamirShareOptionsView

Feels much smoother this way and aligns with the Seed XOR flows that are being implemented.

The other screens that received changes now looks like this:

SeedShamirShareMnemonicEntryView

↑ Removed spaces in the title "Share#1, Word#1" because with spaces, when reaching 2-digit word index the text starts scrolling and makes the UX very laggish.

SeedShamirShareImportSelectWordCount SeedShamirShareFinalizeView

@alvroble alvroble mentioned this pull request Jul 16, 2025
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 9.0 Needs Code Review
Development

Successfully merging this pull request may close these issues.

4 participants