Bitcoin Forum
May 21, 2026, 08:45:20 AM *
News: Latest Bitcoin Core release: 31.0 [Torrent]
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: Compressing BIP-39 Seeds  (Read 17 times)
SirArthur2 (OP)
Newbie
*
Offline

Activity: 1
Merit: 0


View Profile
May 20, 2026, 09:47:05 PM
 #1

I'd an idea to compress BIP-39 seeds, make them to become a tiny string suitable for QR or RFID storage.

The process:

  • We convert the wordlist into line numbers.
  • We base58 encode the corresponding line number to the given word, so that each word will be turn into exactly 2 characters (zero padded)
  • We can add a + sign to the end of the generated output or not, indicating if the seed is expanded with a unknown word or not

Eg:

Let's assume the following seed (freshly generated using BIP-39 tool):

Code:
cannon garment estate enforce remind attract about that east retreat uncle route method practice chunk

As QR this will generate a massive code that low definition cameras struggle to read. Representing a 102 characters long string.

Using the base58 word-to-line-number compressor, we get
Code:
5dEFBhBFS6345XuAdSQZeT2LMQP6d
, a 30 chars long string, rendering a much smaller and easy readable QR code.

Sample Python code for this (requires tkinter and the wordlist, named as english.txt in the same folder as the .py script - the wordlist can be found at BIP-39's Github [ https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt ]):

Code:
#!/usr/bin/python3
import os
import base64
import tkinter as tk
import tkinter.messagebox as messagebox
from typing import Mapping, Union

BITCOIN_ALPHABET = \
    '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
dir_path = os.path.dirname(__file__)
with open(f"{dir_path}/english.txt", "r") as f:
    words = [line.strip() for line in f if line.strip()]

win = [Suspicious link removed]()
win.title("BIP-39 Compressor/Decompressor")
frame1 = tk.Frame(master=win)
frame1.grid(row=0, column=0, padx=5, pady=5)

label1 = tk.Label(master=frame1, text="Decompressed Seed")
label1.grid(row=0, column=0)
label2 = tk.Label(master=frame1, text="Your words BIP-39 seed")
label2.grid(row=0, column=2)
decomp = tk.Text(master=frame1, height=10)
decomp.grid(row=1, column=0, columnspan=3)

btn = tk.Button(master=frame1, text="Compress", command=lambda: compressSeed())
btn.grid(row=2, column=2)

frame2 = tk.Frame(master=win)
frame2.grid(row=1, column=0, padx=5, pady=5)

label3 = tk.Label(master=frame2, text="Compressed Seed")
label3.grid(row=0, column=0)
label4 = tk.Label(master=frame2, text="Your compressed seed")
label4.grid(row=0, column=2)
comp = tk.Text(master=frame2, height=1)
comp.grid(row=1, column=0, columnspan=3)

btn2 = tk.Button(master=frame2, text="Decompress", command=lambda: decompressSeed())
btn2.grid(row=2, column=2)
expanded_var = tk.BooleanVar()
check_btn = tk.Checkbutton(
    master=win,
    text="Mark as Expanded (adds '+' to output)",
    variable=expanded_var,
)
check_btn.grid(row=3, column=0, sticky='w', pady=10)

def b58encode(i: int, default_one: bool = True, alphabet: bytes = BITCOIN_ALPHABET) -> str:
    if not i and default_one:
        return alphabet[0:1]
    string = ""
    base = len(alphabet)
    while i:
        i, idx = divmod(i, base)
        string = alphabet[idx:idx+1] + string
    return string

def b58decode(v: Union[str, bytes], alphabet: bytes = BITCOIN_ALPHABET,
              *, autofix: bool = False) -> int:
    map = _get_base58_decode_map(alphabet, autofix=autofix)
    decimal = 0
    base = len(alphabet)
    for char in v:
        decimal = decimal * base + map[char]
    return decimal

def _get_base58_decode_map(alphabet: bytes,
                           autofix: bool) -> Mapping[int, int]:
    invmap = {char: index for index, char in enumerate(alphabet)}
    groups = [b'0Oo', b'Il1']
    for group in groups:
        pivots = [c for c in group if c in invmap]
        if len(pivots) == 1:
            for alternative in group:
                invmap[alternative] = invmap[pivots[0]]
    return invmap

def compressSeed():
    bip_words = decomp.get("1.0", "end-1c").strip().split()
    out = []
    for word in bip_words:
        if word in words:
            pos = words.index(word)
            out.append(b58encode(pos + 1))
        else:
                    messagebox.showinfo("Seed Error", "The word " + word + " wasn't found in the dictionary!")
    compressed_seed = "".join(out)
   
    # Append '+' if expanded option is selected
    if expanded_var.get():
        compressed_seed += '+'
   
    comp.delete("1.0", "end-1c")
    comp.insert("1.0", compressed_seed)

def decompressSeed():
    compressed_seed = comp.get("1.0", "end-1c").strip()
   
    # Remove trailing '+' if present (expanded seed indicator)
    if compressed_seed.endswith('+'):
        compressed_seed = compressed_seed[:-1]
        messagebox.showinfo("Expanded Seed Alert", "The seed appears to be expanded with an unknown secret extra word!")

    decompressed_words = []
    i = 0
    while i < len(compressed_seed):
        chunk = compressed_seed[i:i+2]
        if not chunk:
            break
       
        pos = b58decode(chunk)
        decompressed_words.append(words[pos - 1])
        i += 2

    decomp.delete("1.0", "end-1c")
    decomp.insert("1.0", " ".join(decompressed_words))

win.mainloop()

Footnote: sorry for posting from a new account, but I don't know where's my TOTP code (and don't quite remember to had create one) for my old account.
odolvlobo
Legendary
*
Online Online

Activity: 5026
Merit: 3777



View Profile
Today at 08:31:38 AM
 #2

I'd an idea to compress BIP-39 seeds, make them to become a tiny string suitable for QR or RFID storage.

A BIP-39 phrase is an encoding of a binary value. If you want to "compress" the phrase, you can use the binary value instead. It can't be compressed any more than that.

If you want text, I think base-64 is a better choice, because it is more compact and more widely used.

Join an anti-signature campaign: Click ignore on the members of signature campaigns.
PGP Fingerprint: 6B6BC26599EC24EF7E29A405EAF050539D0B2925 Signing address: 13GAVJo8YaAuenj6keiEykwxWUZ7jMoSLt
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!