Introduction
When people talk about Artificial Intelligence today, the conversation usually revolves around chatbots, image generators, coding assistants, or office productivity.
But a recent news shared by Yahoo!Tech caught my attention for a very different reason.
According to the report, an X user known as cprkrn claimed that after being locked out of his Bitcoin wallet for 11 years, he finally recovered access with the help of Claude AI.
The wallet reportedly contained 5 BTC. Based on the approximate market price mentioned in the report, that was worth more than HKD 3 million.
The interesting part was not that AI "hacked Bitcoin".
It did not.
Instead, the user reportedly had:
- An old computer
- Old wallet files
- Some forgotten notes
- A seed phrase
- A long-forgotten password pattern
- Many years of messy digital history
AI helped connect the dots.
This is not a story about breaking cryptography.
It is a story about AI-assisted memory reconstruction.
And that opens up a powerful idea:
AI may not break encryption, but it can help us rediscover the human context around things we forgot.
This article explores:
- How AI can reduce password search space
- Why this is different from brute force cracking
- How old notes can become useful signals
- How to build a safe educational C# demo
- How to use Ollama and Kimi as the LLM engine
- Why privacy and ethics matter
Important:
This project is for education and simulation only.
It does not recover real Bitcoin wallets.
It does not parse real wallet.dat files.
It does not crack encryption.
It must not be used against anyone else’s accounts, wallets, devices, or data.
What Actually Happened Conceptually
The reported Bitcoin recovery story can be understood like this:
Old computer files
↓
Old wallet file discovered
↓
Old notes and memory fragments analyzed
↓
Possible password patterns reconstructed
↓
Seed phrase / wallet artifacts matched
↓
Wallet access recovered
The important distinction is this:
AI did not defeat Bitcoin cryptography.
AI helped organize messy human memory.
Bitcoin itself relies on strong cryptographic systems.
But humans often create passwords based on:
- Emotions
- Old events
- Favorite words
- Personal habits
- Dates
- Naming formats
- Repeated patterns
That is where AI becomes useful.
Brute Force vs AI-Assisted Reasoning
A brute force approach looks like this:
Try every possible combination.
Try millions, billions, or trillions of passwords.
Hope one works.
This is expensive, slow, and usually unrealistic.
AI-assisted reasoning works differently.
Instead of attacking the encryption directly, it attacks the uncertainty around the human-created password.
It looks for clues such as:
- Old notes
- Diary fragments
- File names
- Repeated words
- Emotional context
- Important years
- Formatting habits
- Common personal patterns
So the workflow becomes:
Messy notes
↓
LLM analysis
↓
Ranked password candidates
↓
Local verification
That last step is important.
The LLM should generate candidates.
The actual verification should happen locally.
The Sample Exercise
For this article, we will create a safe simulation.
We will pretend that we found two old text files from an old computer:
old_notes.txt
diary_fragment.txt
These files contain clues.
The hidden mock password will be:
memory_police_2014
But in a real recovery situation, we would not know that.
The goal of the demo is to see whether an LLM can infer likely candidates from the notes.
Sample File 1: old_notes.txt
2014 - bought BTC when it was around 230 USD
Used police story as password theme before
Memory + river + sunset were words I liked back then
Laptop password had underscore format
Sometimes I append the year to important accounts
Sample File 2: diary_fragment.txt
Can't believe I finally bought Bitcoin.
Police documentary inspired me that week.
Maybe I should combine memory and police somehow.
Don't forget 2014 was important.
These notes are simple, but they contain strong human signals:
| Clue | Possible Meaning |
|---|---|
memory |
likely password word |
police |
likely password word |
2014 |
likely suffix |
| underscore format | likely separator |
| "combine memory and police" | likely word order |
| BTC price 230 | possible alternative suffix |
A human might miss the pattern.
An LLM can rank it immediately.
The Prompt We Give to the LLM
Here is the kind of prompt we will send to the model:
You are assisting in a password reconstruction exercise for a simulated wallet recovery.
This is an educational exercise using fake data.
The wallet is a local mock wallet owned by the user.
Your task:
1. Read the recovered historical notes.
2. Identify recurring words, themes, years, and formatting habits.
3. Generate 15 structured password candidates ranked by likelihood.
4. Prefer underscore separators if the notes suggest that style.
5. Consider important years or numbers as suffixes.
6. Do not brute force.
7. Do not generate random unrelated strings.
8. Return JSON only.
Recovered notes:
[old_notes.txt and diary_fragment.txt content here]
The key idea is:
Do not ask the model to crack anything.
Ask the model to reason from context.
Example LLM Result
In my test using Kimi through Ollama, the model generated a ranked candidate list similar to this:
| Rank | Password Candidate | Rationale |
|---|---|---|
| 1 | memory_police_2014 |
Direct use of "combine memory and police" + underscore + year |
| 2 | police_memory_2014 |
Reverse order variation |
| 3 | memory_river_sunset |
Uses three liked words |
| 4 | police_story_2014 |
Police story theme + year |
| 5 | river_sunset_memory |
Alternative ordering of liked words |
| 6 | memory_documentary_2014 |
Memory + documentary clue |
| 7 | memory_river_2014 |
Liked word + year |
| 8 | sunset_memory_2014 |
Liked word + year |
| 9 | police_river_2014 |
Cross-combination |
| 10 | river_memory_2014 |
Alternative pair |
| 11 | police_sunset_2014 |
Cross-combination |
| 12 | memory_2014 |
Minimal pattern |
| 13 | police_memory_230 |
Uses BTC price clue |
| 14 | memory_police_230 |
Price-based variation |
| 15 | sunset_river_2014 |
Nature-themed pair + year |
The important thing is not the exact output.
The important thing is that the model did not generate random strings like:
xJ9#qPz882!
Instead, it generated human-plausible candidates from the notes.
That is the value of AI here.
Demo Architecture
Our safe simulation looks like this:
old_notes.txt
diary_fragment.txt
↓
Ollama + Kimi model
↓
JSON password candidates
↓
Local mock wallet verifier
↓
Success / failure result
The LLM only sees the notes.
It does not receive:
- A real wallet file
- A private key
- A seed phrase
- A real password hash
- A real Bitcoin address
The verification happens locally inside our C# program.
This is safer and cleaner
Technical Setup
This demo uses OllamaApiClient from OllamaSharp. OllamaSharp documents GenerateAsync for single-turn completions and the Chat class for conversational workflows; Ollama’s own API documentation also describes /api/generate and /api/chat streaming endpoints. (awaescher.github.io)
Create a new console app:
dotnet new console -n AiWalletRecoveryDemo
cd AiWalletRecoveryDemo
dotnet add package OllamaSharp
Make sure your Ollama endpoint is running at:
http://localhost:11434
The sample code defaults to:
string modelName = "kimi-k2.6:cloud";
If you used another model, such as kimi-k2.5, pass it as an argument:
dotnet run -- kimi-k2.5
Project Structure
AiWalletRecoveryDemo/
│
├── AiWalletRecoveryDemo.csproj
├── Program.cs
│
└── sample-data/
├── old_notes.txt
├── diary_fragment.txt
└── mock_wallet.json
The program will create the sample files automatically if they do not exist.
Full Educational C# Example
Program.cs
using OllamaSharp;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
internal static class Program
{
// Educational only:
// In a real recovery situation, you would NOT know this value.
// We use it only to create a local mock wallet verifier.
private const string DemoPassword = "memory_police_2014";
public static async Task Main(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
string modelName = args.Length > 0 ? args[0] : "kimi-k2.6:cloud";
string ollamaApiUrl = args.Length > 1 ? args[1] : "http://localhost:11434";
string dataDirectory = Path.Combine(Directory.GetCurrentDirectory(), "sample-data");
string mockWalletPath = Path.Combine(dataDirectory, "mock_wallet.json");
Console.WriteLine("AI-Assisted Wallet Recovery Simulation");
Console.WriteLine("---------------------------------------");
Console.WriteLine($"Ollama URL : {ollamaApiUrl}");
Console.WriteLine($"Model : {modelName}");
Console.WriteLine();
DemoData.EnsureCreated(dataDirectory, DemoPassword);
string recoveredNotes = DemoData.ReadAllNotes(dataDirectory);
var mockWallet = MockWalletVault.Load(mockWalletPath);
var generator = new OllamaCandidateGenerator(
ollamaApiUrl: ollamaApiUrl,
modelName: modelName,
wikiRootDirectory: dataDirectory);
IReadOnlyList<PasswordCandidate> candidates;
try
{
candidates = await generator.GenerateCandidatesAsync(recoveredNotes);
}
catch (Exception ex)
{
Console.WriteLine("LLM generation failed.");
Console.WriteLine($"Reason: {ex.Message}");
Console.WriteLine();
Console.WriteLine("Using deterministic fallback candidates for demo continuity.");
Console.WriteLine();
candidates = FallbackCandidateGenerator.Generate();
}
var uniqueCandidates = candidates
.Where(c => !string.IsNullOrWhiteSpace(c.Password))
.Select(c => c with { Password = c.Password.Trim() })
.GroupBy(c => c.Password, StringComparer.Ordinal)
.Select((g, index) => g.First() with { Rank = index + 1 })
.Take(25)
.ToList();
Console.WriteLine("Ranked Password Candidates");
Console.WriteLine("--------------------------");
foreach (var candidate in uniqueCandidates)
{
Console.WriteLine($"#{candidate.Rank}: {candidate.Password}");
Console.WriteLine($" Reason: {candidate.Rationale}");
}
Console.WriteLine();
Console.WriteLine("Testing candidates against local mock wallet...");
Console.WriteLine();
PasswordCandidate? successfulCandidate = null;
foreach (var candidate in uniqueCandidates)
{
bool isMatch = mockWallet.Verify(candidate.Password);
if (isMatch)
{
successfulCandidate = candidate;
break;
}
}
if (successfulCandidate is not null)
{
Console.WriteLine($"✅ Mock wallet unlocked with candidate #{successfulCandidate.Rank}:");
Console.WriteLine(successfulCandidate.Password);
}
else
{
Console.WriteLine("❌ No candidate unlocked the mock wallet.");
Console.WriteLine("Try adding more historical notes or stronger clues.");
}
Console.WriteLine();
Console.WriteLine("Simulation complete.");
}
}
public sealed class OllamaCandidateGenerator
{
private readonly OllamaApiClient _ollamaClient;
private readonly string _wikiRootDirectory;
public OllamaCandidateGenerator(
string ollamaApiUrl = "http://localhost:11434",
string modelName = "kimi-k2.6:cloud",
string? wikiRootDirectory = null)
{
_ollamaClient = new OllamaApiClient(new Uri(ollamaApiUrl))
{
SelectedModel = modelName
};
_wikiRootDirectory = wikiRootDirectory ?? Directory.GetCurrentDirectory();
}
public async Task<IReadOnlyList<PasswordCandidate>> GenerateCandidatesAsync(
string recoveredNotes,
CancellationToken cancellationToken = default)
{
string prompt = BuildPrompt(recoveredNotes);
var responseBuilder = new StringBuilder();
await foreach (var stream in _ollamaClient.GenerateAsync(
prompt,
context: null,
cancellationToken: cancellationToken))
{
if (!string.IsNullOrEmpty(stream?.Response))
{
responseBuilder.Append(stream.Response);
}
}
string rawResponse = responseBuilder.ToString();
var candidates = ParseCandidates(rawResponse);
if (candidates.Count == 0)
{
throw new InvalidOperationException(
"The model did not return valid password candidates.");
}
return candidates;
}
private string BuildPrompt(string recoveredNotes)
{
return $$"""
You are assisting in a password reconstruction exercise for a simulated wallet recovery.
This is an educational exercise using fake data.
The mock wallet belongs to the user.
Do not provide hacking instructions.
Do not suggest brute force tools.
Do not suggest unauthorized access.
Do not generate random unrelated strings.
Your task:
1. Read the recovered historical notes.
2. Identify recurring words, themes, dates, numbers, and formatting habits.
3. Generate exactly 15 structured password candidates ranked by likelihood.
4. Prefer underscore separators if the notes suggest that style.
5. Consider important years or numbers as suffixes.
6. Prefer candidates that follow explicit diary wording.
7. Return valid JSON only.
8. Do not wrap the JSON in Markdown.
Return this JSON shape:
{
"candidates": [
{
"rank": 1,
"password": "example_candidate",
"rationale": "Short explanation"
}
]
}
Recovered notes:
{{recoveredNotes}}
""";
}
private static IReadOnlyList<PasswordCandidate> ParseCandidates(string rawResponse)
{
string json = ExtractJsonObject(rawResponse);
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
var parsed = JsonSerializer.Deserialize<CandidateResponse>(json, options);
return parsed?.Candidates?
.OrderBy(c => c.Rank)
.ToList()
?? new List<PasswordCandidate>();
}
private static string ExtractJsonObject(string text)
{
string cleaned = text.Trim();
cleaned = Regex.Replace(
cleaned,
"^```
{% endraw %}
(?:json)?\\s*",
"",
RegexOptions.IgnoreCase);
cleaned = Regex.Replace(
cleaned,
"\\s*
{% raw %}
```$",
"",
RegexOptions.IgnoreCase);
int start = cleaned.IndexOf('{');
int end = cleaned.LastIndexOf('}');
if (start < 0 || end <= start)
{
throw new FormatException("No JSON object found in model response.");
}
return cleaned.Substring(start, end - start + 1);
}
}
public sealed record PasswordCandidate
{
[JsonPropertyName("rank")]
public int Rank { get; init; }
[JsonPropertyName("password")]
public string Password { get; init; } = "";
[JsonPropertyName("rationale")]
public string Rationale { get; init; } = "";
}
public sealed class CandidateResponse
{
[JsonPropertyName("candidates")]
public List<PasswordCandidate> Candidates { get; set; } = new();
}
public sealed class MockWalletVault
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true,
PropertyNameCaseInsensitive = true
};
[JsonPropertyName("description")]
public string Description { get; set; } = "Educational mock wallet. This is not a real Bitcoin wallet.";
[JsonPropertyName("saltBase64")]
public string SaltBase64 { get; set; } = "";
[JsonPropertyName("passwordHashBase64")]
public string PasswordHashBase64 { get; set; } = "";
public static MockWalletVault CreateFromPassword(string password)
{
byte[] salt = RandomNumberGenerator.GetBytes(16);
byte[] hash = HashPassword(password, salt);
return new MockWalletVault
{
SaltBase64 = Convert.ToBase64String(salt),
PasswordHashBase64 = Convert.ToBase64String(hash)
};
}
public static MockWalletVault Load(string path)
{
string json = File.ReadAllText(path);
return JsonSerializer.Deserialize<MockWalletVault>(json, JsonOptions)
?? throw new InvalidOperationException("Unable to load mock wallet.");
}
public void Save(string path)
{
string json = JsonSerializer.Serialize(this, JsonOptions);
File.WriteAllText(path, json);
}
public bool Verify(string candidatePassword)
{
byte[] salt = Convert.FromBase64String(SaltBase64);
byte[] expectedHash = Convert.FromBase64String(PasswordHashBase64);
byte[] actualHash = HashPassword(candidatePassword, salt);
return CryptographicOperations.FixedTimeEquals(actualHash, expectedHash);
}
private static byte[] HashPassword(string password, byte[] salt)
{
// Educational local verifier.
// This is not Bitcoin Core wallet encryption.
using var pbkdf2 = new Rfc2898DeriveBytes(
password,
salt,
iterations: 100_000,
hashAlgorithm: HashAlgorithmName.SHA256);
return pbkdf2.GetBytes(32);
}
}
public static class DemoData
{
private const string OldNotesFileName = "old_notes.txt";
private const string DiaryFileName = "diary_fragment.txt";
private const string MockWalletFileName = "mock_wallet.json";
private const string OldNotesText = """
2014 - bought BTC when it was around 230 USD
Used police story as password theme before
Memory + river + sunset were words I liked back then
Laptop password had underscore format
Sometimes I append the year to important accounts
""";
private const string DiaryFragmentText = """
Can't believe I finally bought Bitcoin.
Police documentary inspired me that week.
Maybe I should combine memory and police somehow.
Don't forget 2014 was important.
""";
public static void EnsureCreated(string dataDirectory, string demoPassword)
{
Directory.CreateDirectory(dataDirectory);
string oldNotesPath = Path.Combine(dataDirectory, OldNotesFileName);
string diaryPath = Path.Combine(dataDirectory, DiaryFileName);
string mockWalletPath = Path.Combine(dataDirectory, MockWalletFileName);
if (!File.Exists(oldNotesPath))
{
File.WriteAllText(oldNotesPath, OldNotesText);
}
if (!File.Exists(diaryPath))
{
File.WriteAllText(diaryPath, DiaryFragmentText);
}
if (!File.Exists(mockWalletPath))
{
var wallet = MockWalletVault.CreateFromPassword(demoPassword);
wallet.Save(mockWalletPath);
}
}
public static string ReadAllNotes(string dataDirectory)
{
var builder = new StringBuilder();
foreach (string file in Directory
.EnumerateFiles(dataDirectory, "*.txt", SearchOption.AllDirectories)
.OrderBy(f => f))
{
builder.AppendLine($"--- {Path.GetFileName(file)} ---");
builder.AppendLine(File.ReadAllText(file));
builder.AppendLine();
}
return builder.ToString();
}
}
public static class FallbackCandidateGenerator
{
public static IReadOnlyList<PasswordCandidate> Generate()
{
return new List<PasswordCandidate>
{
new()
{
Rank = 1,
Password = "memory_police_2014",
Rationale = "Direct implementation of diary note: combine memory and police, underscore format, year suffix."
},
new()
{
Rank = 2,
Password = "police_memory_2014",
Rationale = "Reverse order variation using the same strong clues."
},
new()
{
Rank = 3,
Password = "memory_river_sunset",
Rationale = "Uses three liked words with underscore formatting."
},
new()
{
Rank = 4,
Password = "police_story_2014",
Rationale = "References previous police story theme and important year."
},
new()
{
Rank = 5,
Password = "river_sunset_memory",
Rationale = "Alternative ordering of liked words."
},
new()
{
Rank = 6,
Password = "memory_documentary_2014",
Rationale = "Combines memory with police documentary clue and year."
},
new()
{
Rank = 7,
Password = "memory_river_2014",
Rationale = "Liked word pair plus year suffix."
},
new()
{
Rank = 8,
Password = "sunset_memory_2014",
Rationale = "Liked word combination plus year."
},
new()
{
Rank = 9,
Password = "police_river_2014",
Rationale = "Cross-combination of police theme, liked word, and year."
},
new()
{
Rank = 10,
Password = "river_memory_2014",
Rationale = "Alternative liked word pair plus year."
},
new()
{
Rank = 11,
Password = "police_sunset_2014",
Rationale = "Cross-combination of police theme, liked word, and year."
},
new()
{
Rank = 12,
Password = "memory_2014",
Rationale = "Minimal pattern using important word and year."
},
new()
{
Rank = 13,
Password = "police_memory_230",
Rationale = "Uses BTC price clue instead of year."
},
new()
{
Rank = 14,
Password = "memory_police_230",
Rationale = "Price-based variation of the strongest phrase."
},
new()
{
Rank = 15,
Password = "sunset_river_2014",
Rationale = "Nature-themed pair plus year suffix."
}
};
}
}
Example Output
Your result may vary depending on the model, but a successful run may look like this:
This is exactly the concept we want to demonstrate.
The model does not break encryption.
It simply turns messy personal history into a ranked list of likely hypotheses.
What AI Actually Contributes
The difference between brute force and AI reasoning is not magic.
It is context.
Brute force says:
Try everything.
AI-assisted reasoning says:
Try what the human was most likely to create.
In this exercise, the LLM extracted:
| Signal | Extracted Insight |
|---|---|
memory |
important word |
police |
important theme |
combine memory and police |
likely word pairing |
| underscore format | separator style |
2014 |
likely suffix |
BTC price 230
|
alternative numeric suffix |
| diary wording | word order clue |
This changes the search problem from:
Trillions of random guesses
to:
15 structured candidates
That is the power.
AI reduces chaos.
Why This Is Not "AI Cracking Bitcoin"
This point is very important.
Bitcoin was not broken.
Cryptography was not defeated.
The AI did not discover a private key from the blockchain.
The AI did not reverse SHA-256.
The AI did not bypass wallet encryption.
Instead, the AI helped with:
- Pattern recognition
- File interpretation
- Note summarization
- Candidate ranking
- Human memory reconstruction
That is a completely different category.
A good analogy is this:
Encryption is the locked safe.
AI helps you search your old notebooks for the combination.
The safe is still strong.
The weakness is human memory.
Privacy Warning
This is where we need to be careful.
If you use a cloud model, do not upload:
- Real seed phrases
- Private keys
- Real wallet files
- Password databases
- Full disk images
- Sensitive personal documents
Even if your intention is legitimate, sending secrets to a cloud model can create serious privacy risk.
A safer workflow is:
Sensitive file stays local
↓
You extract non-secret clues
↓
LLM generates candidate ideas
↓
Verification happens locally
For example, instead of sending this:
seed phrase: apple apple apple ...
wallet.dat file attached
Send only sanitized hints like:
I often used words related to memory, police, river, and 2014.
I used underscores in passwords at that time.
The LLM should help with reasoning.
It should not become a place where secrets are stored.
How This Pattern Applies Beyond Bitcoin
This same AI-assisted recovery pattern can apply to many areas:
- Old encrypted ZIP archives
- Forgotten developer keystores
- Legacy configuration files
- Personal knowledge archives
- Digital estate planning
- Old project recovery
- Family photo backup restoration
- Enterprise documentation reconstruction
The common pattern is:
Messy historical data
↓
AI-assisted organization
↓
Ranked hypotheses
↓
Local verification
This is why I find the story so interesting.
It shows AI being used not as a chatbot, but as a digital archaeologist.
Security Lessons
There is also a defensive lesson here.
If AI can infer human password patterns from old notes, then we should avoid creating passwords from personal context.
Better practices:
- Use a password manager
- Use long random passwords
- Avoid emotional or memorable phrases
- Avoid years, birthdays, and repeated themes
- Do not store seed phrases digitally
- Use hardware wallets for serious crypto storage
- Keep offline backups in physically secure locations
- Separate password hints from actual recovery material
Human-style passwords are easy to remember.
That is exactly why they can be guessed.
Conclusion
The reported Bitcoin recovery story is not about AI defeating cryptography.
It is about AI helping a person reconstruct forgotten context.
That may become one of AI's most underrated uses.
Not replacing humans.
Not magically solving everything.
But helping us search, organize, and reason through the complexity we created over years of digital life.
In this demo, we used:
- Old notes
- A diary fragment
- Ollama
- Kimi
- C#
- A local mock wallet verifier
And we simulated the same core idea safely:
Forgotten memory → AI reasoning → candidate ranking → local verification
The future of AI may not only be automation.
It may also be recovery.
Recovering old knowledge.
Recovering lost context.
Recovering forgotten work.
Recovering what we once knew but could no longer find.
AI did not break the lock.
It helped us remember where we left the key.
References
- OllamaSharp documentation for
GenerateAsync,Chat, andOllamaApiClient. (awaescher.github.io) - Ollama API documentation for
/api/generateand/api/chat. (github.com)
Love C# & AI!


Top comments (0)