Showing posts with label encode. Show all posts
Showing posts with label encode. Show all posts

Saturday, 20 December 2025

Cryptographic Application of Building Block Numbers

Given that any number up to 243373 can be represented using only the building blocks 1, 2, 4, 11, 25, 64, 171, 569, 3406 and 27697, I thought that capital letters could be used to represent these building block numbers: A for 1, B or 2, C for 4, D for 11, E for 25, F for 64, G for 171, H for 569, I for 3406 and J for 27697. 

With the input of a given natural number, I asked Gemini to output the building block representations using these letters, retaining brackets and any + signs but omitting any multiplication signs (as in algebra). Thus 12 = (1 + 2) * 4 would become (A + B) C. I'm was thinking in terms of cryptography where the number will be disguised using the alphabet cyphers just described and only be dicipherable using the key (A=1, B=2 and C=4 etc.). The program only needs to deal with building blocks up to 27697. 


Infographic generated by Nano Bananas using this blog's content.

Gemini duly came up with the Python program and Figure 1 shows the output:

Figure 1:
see Python code below

The program times out in SageMathCell so I've included the Python code below for reference. If making practical use of this form of encryption, one of these representations would be chosen at random and used to replace the decimal number.

*******************************
PYTHON CODE
(courtesy of Gemini)

import itertools

def solve_oeis_crypto_cipher(target):
    """
    Finds algebraic representations of 'target' using OEIS A086424 terms.
    Outputs the result using Letter Ciphers (A=1, B=2, ...).
    
    Rules:
    - A=1, B=2, C=4, D=11, E=25, F=64, G=171, H=569, I=3406, J=27697
    - Multiplication (*) is implicit (omitted). e.g., A*B -> AB
    - Addition (+) is retained.
    - Brackets included only when necessary.
    """
    
    # 1. Define the Cipher Mapping
    # We only go up to J (27697) as requested
    term_map = {
        1: 'A', 2: 'B', 4: 'C', 11: 'D', 25: 'E', 
        64: 'F', 171: 'G', 569: 'H', 3406: 'I', 27697: 'J'
    }
    
    full_sequence = sorted(term_map.keys())
    
    # Filter terms to only those small enough to be useful
    terms = [x for x in full_sequence if x <= target]
    n = len(terms)
    
    # DP State: dp[mask][value] = set of Canonical Tuples
    # Tuple Structure: ('TYPE', (operands...))
    dp = [{} for _ in range(1 << n)]
    
    # Helper to generate a consistent sort key for uniqueness
    def get_sort_key(item):
        return str(item)

    # Initialize with single terms
    for i in range(n):
        val = terms[i]
        mask = (1 << i)
        dp[mask][val] = { ('NUM', val) }

    # Iterate through mask sizes
    for r in range(2, n + 1):
        for indices in itertools.combinations(range(n), r):
            mask = sum(1 << i for i in indices)
            set_bits = [b for b in range(n) if (mask >> b) & 1]
            
            # Split mask
            for k in range(1, r // 2 + 1):
                for sub_indices in itertools.combinations(set_bits, k):
                    s = sum(1 << i for i in sub_indices)
                    comp = mask - s # SageMath safe subtraction
                    
                    if k * 2 == r and s > comp: continue
                    if not dp[s] or not dp[comp]: continue
                    
                    # Combine results from submasks
                    for v1, exprs1 in dp[s].items():
                        for v2, exprs2 in dp[comp].items():
                            
                            for e1 in exprs1:
                                for e2 in exprs2:
                                    
                                    # --- ADDITION ---
                                    res_sum = v1 + v2
                                    if res_sum <= target:
                                        ops = []
                                        # Flatten left
                                        if e1[0] == 'SUM': ops.extend(e1[1])
                                        else: ops.append(e1)
                                        # Flatten right
                                        if e2[0] == 'SUM': ops.extend(e2[1])
                                        else: ops.append(e2)
                                        
                                        ops.sort(key=get_sort_key)
                                        new_struct = ('SUM', tuple(ops))
                                        
                                        if res_sum not in dp[mask]: dp[mask][res_sum] = set()
                                        dp[mask][res_sum].add(new_struct)
                                    
                                    # --- MULTIPLICATION ---
                                    # Skip *1 (Identity)
                                    if v1 == 1 or v2 == 1: continue
                                    
                                    res_prod = v1 * v2
                                    if res_prod <= target:
                                        ops = []
                                        # Flatten left
                                        if e1[0] == 'PROD': ops.extend(e1[1])
                                        else: ops.append(e1)
                                        # Flatten right
                                        if e2[0] == 'PROD': ops.extend(e2[1])
                                        else: ops.append(e2)
                                        
                                        ops.sort(key=get_sort_key)
                                        new_struct = ('PROD', tuple(ops))
                                        
                                        if res_prod not in dp[mask]: dp[mask][res_prod] = set()
                                        dp[mask][res_prod].add(new_struct)

    # --- Formatting Function for Cipher Output ---
    def format_cipher(expr):
        etype, content = expr
        
        if etype == 'NUM':
            # Return the Cipher Letter
            return term_map[content]
            
        elif etype == 'SUM':
            # Join with "+"
            return "+".join(format_cipher(c) for c in content)
            
        elif etype == 'PROD':
            parts = []
            for c in content:
                s = format_cipher(c)
                # Add brackets ONLY if the child is a SUM
                if c[0] == 'SUM':
                    s = f"({s})"
                parts.append(s)
            # Join with empty string (Implicit multiplication)
            return "".join(parts)

    # --- Collect and Print Solutions ---
    solutions = []
    # Check every mask for the target value
    for m in range(1 << n):
        if target in dp[m]:
            for struct in dp[m][target]:
                solutions.append(format_cipher(struct))
    
    if not solutions:
        return [f"No solution found for {target}."]
    
    # Sort by length for readability
    return sorted(list(set(solutions)), key=len)

# --- User Input Section ---
target_integer = 28020

# Run
results = solve_oeis_crypto_cipher(target_integer)

# Output
print(f"Cipher Results for {target_integer}:")
print(f"Key: A=1, B=2, C=4, D=11, E=25, F=64, G=171, H=569, I=3406, J=27697")
print("-" * 60)
for res in results:
    print(res)

Thursday, 8 May 2025

Encoding Secret Numbers

The previous post about Heinz numbers got me thinking about how secret numbers that we might use as pass numbers for access to various services could be encoded. Let say we have a secret number 659347 that we want to encode using the one to one correspondence between Heinz numbers and integer sequences. Let's treat 659347 as a Heinz number, generate the corresponding integer sequence and then concatenate the numbers in the sequence:$$ \begin{align} \text{Heinz}(659347) &=(134, 19, 6) \\ &\rightarrow 134196 \end{align}$$If we concatenate the numbers in this sequence, there is ambiguity because the sequences (1341, 96) or (13419, 6) give the same result when the members are concatenated. It would thus be necessary to introduce a dot between the sequence members so as to resemble an IP address. An additional dummy 0 could be added so that it does indeed look like an IP address (0 cannot appear in any Heinz integer sequence). Thus we would have (permalink):$$ \begin{align} \text{Heinz}(659347) &= (134, 19, 6) \\ &\rightarrow 134.19.6  \text{ or} \\ &\rightarrow 134.19.6.0 \end{align}$$The point is that 134.19.6 or 134.19.6.0 can be written in plain sight and there's no way to decode the encrypted secret number (unless you know the magic word: Heinz).

An alternative method would be use the Gray Code. I've written about this in a post titled Gray Code in June of 2023. Here the secret number undergoes the following transformation:$$ \begin{align}659347 &= 10100000111110010011_2 \\ &\rightarrow 11110000100001011010_2 \\ &= 985178 \end{align} $$Again this number (985178) can be written in plain sight and there's no way to decode it without the magic phrase: Gray Code. Another encoding mechanism is Binary Complement. See my post Binary Complement from October of 2024. Here the encoding goes as follows:$$ \begin{align} 659347 &= 10100000111110010011_2\\ &\rightarrow 01011111000001101100_2\\ &=389228 \end{align} $$There is a problem once we try to decode from 389228. If we convert this number to binary then we don't have a leading zero anymore, we have 1011111000001101100. We need to add this leading zero because binary complements always have a leading zero. Once we do this, we can flip each 0 to 1 and each 1 to 0 to recover our secret number once we convert from binary. If one wanted to be really devious then two or even three encoding techniques could be used sequentially.