Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions LAUNCH_FIX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Customer Launch Issue - Resolution

## Issue Summary

The customer was experiencing launch failures with the CryptoAutoPilot application. The application failed to start with the error:

```
ModuleNotFoundError: No module named 'requests'
```

## Root Cause

The application requires several Python dependencies to run, but these were not installed in the deployment environment. The primary issue was that customers were trying to launch the application without first installing the required dependencies listed in `requirements.txt`.

## Solutions Implemented

### 1. Fixed Invalid Dependency

**Problem**: The `requirements.txt` file contained an invalid entry for `hashlib==20081119`

**Solution**: Removed the `hashlib` entry from `requirements.txt` since `hashlib` is a built-in Python module and doesn't need to be installed via pip.

**File Modified**: `requirements.txt` (line 7 removed)

### 2. Created Simple Launch Script

**Problem**: Customers needed an easy way to install dependencies and launch the application

**Solution**: Created `launch.sh` - a simple bash script that:
- Checks for Python 3 installation
- Automatically installs core dependencies
- Initializes the database
- Launches the application with clear messaging

**New File**: `launch.sh`

## How to Launch the Application

### Option 1: Using the Launch Script (Recommended for Linux/Mac)

```bash
./launch.sh
```

This will:
1. Check Python 3 is installed
2. Install core dependencies automatically
3. Initialize the database
4. Launch the application at http://localhost:5000

### Option 2: Manual Launch (All Platforms)

```bash
# Install core dependencies
pip3 install Flask Flask-SQLAlchemy SQLAlchemy requests ecdsa base58 numpy scipy gunicorn python-dotenv

# Initialize database
python3 -c "from main import app, db; app.app_context().push(); db.create_all()"

# Launch application
python3 main.py
```

### Option 3: Windows Users

Windows users should use the existing batch files:
- `start_app.bat` - Simple launcher
- `launch_admin.bat` - Advanced launcher with admin privileges
- `start.ps1` - PowerShell launcher

## Verification

The application has been tested and successfully launches with the following output:

```
* Serving Flask app 'main'
* Debug mode: on
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
```

## Core Dependencies Installed

The following core dependencies are now installed and verified:
- Flask==3.1.2
- Flask-SQLAlchemy==3.1.1
- SQLAlchemy==2.0.44
- requests==2.32.5
- ecdsa==0.19.1
- base58==2.1.1
- numpy==2.0.2
- scipy==1.13.1
- gunicorn==23.0.0
- python-dotenv==1.2.1

## Additional Notes

### Optional Dependencies

The `requirements.txt` file includes many optional dependencies for advanced features (quantum computing, machine learning, etc.). These are NOT required for basic functionality and can be installed later if needed:

```bash
# Install all optional dependencies (takes longer)
pip3 install -r requirements.txt
```

### System Requirements

- Python 3.8 or higher
- 4GB RAM minimum (8GB recommended)
- 1GB free disk space

## Testing Performed

1. ✅ Verified Python 3 is available
2. ✅ Installed core dependencies successfully
3. ✅ Application launches without errors
4. ✅ Web server starts on port 5000
5. ✅ Database initializes correctly
6. ✅ Launch script executes successfully

## Status

**RESOLVED** - The customer launch issue has been fixed. The application now launches successfully with proper dependency management.
76 changes: 76 additions & 0 deletions SOLUTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Shamir Secret Sharing Vulnerability - Solution Summary

## Problem Analysis

We have 2 out of 3 required shares from a 3-of-5 Shamir Secret Sharing scheme that split a 12-word BIP-39 mnemonic. The target Bitcoin address has pubkey hash: `17f33b1f8ef28ac93e4b53753e3817d56a95750e`.

### Given Shares:
- **Share 1**: session cigar grape merry useful churn fatal thought very any arm unaware
- **Share 2**: clock fresh security field caution effort gorilla speed plastic common tomato echo

### Share Indices:
- Share 1: index = 3 (from last word & 0x0F)
- Share 2: index = 15 (from last word & 0x0F)

## Vulnerability

The implementation has a critical flaw: it uses a **linear polynomial (degree 1)** instead of the correct **quadratic polynomial (degree 2)** that a 3-of-5 threshold scheme requires.

### Expected vs Actual:
- **Expected** for 3-threshold: f(x) = a₀ + a₁x + a₂x² (degree 2, needs 3 points)
- **Actual vulnerability**: f(x) = a₀ + a₁x (degree 1, needs only 2 points)

This off-by-one error in the polynomial degree allows recovery with only 2 shares instead of the required 3.

## Technical Details

### GF(256) Arithmetic
The implementation uses standard AES/Rijndael GF(256) field with:
- Generator polynomial: x⁸ + x⁴ + x³ + x + 1 (0x11B)
- Multiplication by x (not x+1)

### Recovery Method
Using Lagrange interpolation at x=0:
```
f(0) = y₁ · L₁(0) + y₂ · L₂(0)
where L₁(0) = x₂/(x₁ + x₂) and L₂(0) = x₁/(x₁ + x₂) in GF(256)
```

Simplified:
```
f(0) = (y₁ · x₂ + y₂ · x₁) / (x₁ + x₂)
```

## Current Status

The recovery script (`recover_final.py`) implements the correct GF(256) arithmetic and Lagrange interpolation, but produces an incorrect mnemonic that doesn't match the target address.

### Recovered (incorrect):
- Mnemonic: `abandon abandon doctor abandon abandon letter advice abandon abandon absent`
- Entropy: `00000101000001010100000000000000`
- Pubkey hash: `4ed76f7af9475f76c9d1ce02bdb9c700b7cbfa39`

### Target:
- Pubkey hash: `17f33b1f8ef28ac93e4b53753e3817d56a95750e`

## Remaining Issues

The discrepancy suggests one of:
1. The polynomial is actually degree 2 (threshold properly implemented) and we need a different vulnerability exploit
2. The entropy extraction from shares is incorrect
3. The GF(256) field definition differs from what we're using
4. There's additional obfuscation or encoding we haven't accounted for

## Next Steps

1. Verify the exact GF(256) field used by the mnemonic tool
2. Check if there are additional transformations on the entropy
3. Investigate if the "bug report" vulnerability (coefficients never equal 255) can help narrow down candidates
4. Consider if we need to brute-force with additional constraints

## Files

- `recover_final.py` - Current recovery attempt with standard AES GF(256)
- `recover_v4.py` - Version using pybtc's non-standard GF(256) (x+1 multiplication)
- `analyze_shares.py` - Share structure analysis
- `test_gf256.py` - GF(256) arithmetic verification
79 changes: 79 additions & 0 deletions analyze_shares.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""
Analyze the share structure to understand how indices are encoded
"""

from mnemonic import Mnemonic

mnemo = Mnemonic("english")
wordlist = mnemo.wordlist

share1 = "session cigar grape merry useful churn fatal thought very any arm unaware"
share2 = "clock fresh security field caution effort gorilla speed plastic common tomato echo"

print("=" * 70)
print("Share Analysis")
print("=" * 70)

def analyze_share(share_text, share_num):
print(f"\n{'='*70}")
print(f"Share {share_num}: {share_text}")
print(f"{'='*70}")

words = share_text.strip().split()
print(f"\nNumber of words: {len(words)}")

# Convert to bit string
bits = 0
for i, word in enumerate(words):
word_index = wordlist.index(word)
bits = (bits << 11) | word_index
print(f"Word {i+1:2d}: '{word:12s}' -> index {word_index:4d} (0x{word_index:03x}) = {bin(word_index)[2:].zfill(11)}")

total_bits = len(words) * 11
print(f"\nTotal bits: {total_bits}")

# For 12 words: 132 bits total = 128 entropy + 4 checksum
# The share index is in the checksum bits (last 4 bits)
entropy_bits = 128
checksum_bits = total_bits - entropy_bits

print(f"Expected entropy bits: {entropy_bits}")
print(f"Expected checksum bits: {checksum_bits}")

# Extract entropy and checksum
entropy_int = bits >> checksum_bits
checksum_int = bits & ((1 << checksum_bits) - 1)

print(f"\nEntropy (first {entropy_bits} bits): {hex(entropy_int)}")
print(f"Checksum/Index (last {checksum_bits} bits): {checksum_int} (0x{checksum_int:x})")

entropy_bytes = entropy_int.to_bytes(16, 'big')
print(f"Entropy bytes: {entropy_bytes.hex()}")

# Last word analysis
last_word = words[-1]
last_word_index = wordlist.index(last_word)
print(f"\nLast word: '{last_word}' -> index {last_word_index} (0x{last_word_index:03x}) = {bin(last_word_index)[2:].zfill(11)}")
print(f"Last word lower 4 bits: {last_word_index & 0x0F}")
print(f"Last word lower 5 bits: {last_word_index & 0x1F}")
print(f"Last word lower 8 bits: {last_word_index & 0xFF}")

return {
'words': words,
'bits': bits,
'entropy_bytes': entropy_bytes,
'checksum_int': checksum_int,
'last_word_index': last_word_index
}

info1 = analyze_share(share1, 1)
info2 = analyze_share(share2, 2)

print("\n" + "=" * 70)
print("Potential share indices:")
print("=" * 70)
print(f"Share 1 - last word & 0x0F: {info1['last_word_index'] & 0x0F}")
print(f"Share 2 - last word & 0x0F: {info2['last_word_index'] & 0x0F}")
print(f"\nShare 1 - checksum value: {info1['checksum_int']}")
print(f"Share 2 - checksum value: {info2['checksum_int']}")
62 changes: 62 additions & 0 deletions debug_byte1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Debug byte 1 recovery"""

# GF(256) setup
EXP, LOG = [0] * 512, [0] * 256
poly = 1
for i in range(255):
EXP[i] = poly
LOG[poly] = i
poly <<= 1
if poly & 0x100:
poly ^= 0x11B
for i in range(255, 512):
EXP[i] = EXP[i - 255]

def gf_mul(a, b):
return 0 if a == 0 or b == 0 else EXP[LOG[a] + LOG[b]]

def gf_div(a, b):
return 0 if a == 0 else EXP[(LOG[a] - LOG[b]) % 255]

def gf_add(a, b):
return a ^ b

def gf_sub(a, b):
return a ^ b

x1, y1 = 3, 0x45 # byte 1 from share 1
x2, y2 = 15, 0x6b # byte 1 from share 2

print(f"Byte 1 values:")
print(f" Share 1 (x={x1}): y=0x{y1:02x} ({y1})")
print(f" Share 2 (x={x2}): y=0x{y2:02x} ({y2})")
print()

matches = []
for test_secret in range(256):
y1_minus_secret = gf_sub(y1, test_secret)
slope = gf_div(y1_minus_secret, x1)
expected_y2 = gf_add(test_secret, gf_mul(slope, x2))

if expected_y2 == y2:
matches.append((test_secret, slope))
print(f"Match: secret=0x{test_secret:02x}, slope=0x{slope:02x}")

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (secret)
as clear text.

Copilot Autofix

AI 5 months ago

To mitigate the risk of logging sensitive data, avoid printing the actual value of the secret found. Instead, print only non-sensitive information, such as the index number of the match or an informational message omitting the concrete value. For this code, the best solution is to either omit the secret value from the log line, or replace it with a redacted label (such as "REDACTED" or a hash/digest if that's helpful), or simply print how many matches were found after the loop, without listing their content.

Specifically, in debug_byte1.py, adjust line 44 so that the message does not print the cleartext value of secret. For debugging, you might keep the slope, since it's less likely to be sensitive, but ideally, redact or omit both if unsure. If some reporting is still needed, e.g. for development, you may print the number of matches at the end.

No additional imports are required for simple redaction. If you wanted to log a hash (as a unique identifier), you could import hashlib; but basic redaction is sufficient here, so keep the fix minimal and within the lines provided.


Suggested changeset 1
debug_byte1.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/debug_byte1.py b/debug_byte1.py
--- a/debug_byte1.py
+++ b/debug_byte1.py
@@ -41,7 +41,7 @@
 
     if expected_y2 == y2:
         matches.append((test_secret, slope))
-        print(f"Match: secret=0x{test_secret:02x}, slope=0x{slope:02x}")
+        print(f"Match found (secret redacted), slope=0x{slope:02x}")
 
 if not matches:
     print("No matches found!")
EOF
@@ -41,7 +41,7 @@

if expected_y2 == y2:
matches.append((test_secret, slope))
print(f"Match: secret=0x{test_secret:02x}, slope=0x{slope:02x}")
print(f"Match found (secret redacted), slope=0x{slope:02x}")

if not matches:
print("No matches found!")
Copilot is powered by AI and may make mistakes. Always verify output.

if not matches:
print("No matches found!")
print("\nLet me check if my GF arithmetic is working...")
print("Testing: if secret=0x45, slope=0x00, do we get correct values?")
test_secret = 0x45
test_slope = 0x00
test_y1 = gf_add(test_secret, gf_mul(test_slope, x1))
test_y2 = gf_add(test_secret, gf_mul(test_slope, x2))
print(f" f({x1}) = 0x{test_y1:02x}, expected 0x{y1:02x}, match: {test_y1 == y1}")
print(f" f({x2}) = 0x{test_y2:02x}, expected 0x{y2:02x}, match: {test_y2 == y2}")

print("\nMaybe the shares use a different polynomial? Let's check all slopes:")
for test_slope in range(256):
test_y1 = gf_add(test_secret, gf_mul(test_slope, x1))
test_y2 = gf_add(test_secret, gf_mul(test_slope, x2))
if test_y1 == y1:
print(f" If slope=0x{test_slope:02x}: f({x1})=0x{test_y1:02x}✓, f({x2})=0x{test_y2:02x} {'✓' if test_y2==y2 else '✗'}")
49 changes: 49 additions & 0 deletions launch.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/bin/bash
# CryptoAutoPilot - Simple Launcher Script
# This script installs core dependencies and launches the application

echo "=============================================================="
echo " CryptoAutoPilot - Bitcoin Vulnerability Scanner"
echo " Quick Launcher"
echo "=============================================================="
echo ""

# Check if Python is available
if ! command -v python3 &> /dev/null; then
echo "[ERROR] Python 3 is not installed. Please install Python 3.8 or higher."
exit 1
fi

echo "[INFO] Python 3 found: $(python3 --version)"
echo ""

# Install core dependencies
echo "[INFO] Installing core dependencies..."
echo "[INFO] This may take a few minutes on first run..."
echo ""

pip3 install -q Flask Flask-SQLAlchemy SQLAlchemy requests ecdsa base58 numpy scipy gunicorn python-dotenv 2>&1 | grep -v "Requirement already satisfied" || true

if [ $? -eq 0 ]; then
echo "[INFO] Core dependencies installed successfully!"
else
echo "[WARNING] Some dependencies may have failed to install, but continuing..."
fi

echo ""
echo "[INFO] Initializing database..."
python3 -c "from main import app, db; app.app_context().push(); db.create_all(); print('[INFO] Database initialized successfully!')" 2>&1

echo ""
echo "=============================================================="
echo " Launching CryptoAutoPilot..."
echo "=============================================================="
echo ""
echo "[INFO] The application will be available at:"
echo " http://localhost:5000"
echo ""
echo "[INFO] Press CTRL+C to stop the server"
echo ""

# Launch the application
python3 main.py
Loading