Skip to content

Commit 73e8596

Browse files
Add a new special case for PrivateKeys missing 3 characters with unknown positions
1 parent 842875e commit 73e8596

File tree

3 files changed

+312
-4
lines changed

3 files changed

+312
-4
lines changed

Src/FinderOuter/Services/Base58Sevice.cs

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Diagnostics;
1313
using System.Linq;
1414
using System.Numerics;
15+
using System.Threading;
1516
using System.Threading.Tasks;
1617

1718
namespace FinderOuter.Services
@@ -334,6 +335,258 @@ private unsafe bool LoopUncomp()
334335
return success;
335336
}
336337

338+
private unsafe bool SpecialLoopComp(string key)
339+
{
340+
byte[] padded;
341+
int uLen;
342+
343+
// Maximum result (58^52) is 39 bytes = 39/4 = 10 uint
344+
uLen = 10;
345+
uint[] powers58 = new uint[Constants.CompPrivKeyLen * uLen];
346+
padded = new byte[4 * uLen];
347+
348+
for (int i = 0, j = 0; i < Constants.CompPrivKeyLen; i++)
349+
{
350+
BigInteger val = BigInteger.Pow(58, i);
351+
byte[] temp = val.ToByteArray(true, false);
352+
353+
Array.Clear(padded, 0, padded.Length);
354+
Buffer.BlockCopy(temp, 0, padded, 0, temp.Length);
355+
356+
for (int k = 0; k < padded.Length; j++, k += 4)
357+
{
358+
powers58[j] = (uint)(padded[k] << 0 | padded[k + 1] << 8 | padded[k + 2] << 16 | padded[k + 3] << 24);
359+
}
360+
}
361+
362+
int[] values = new int[key.Length];
363+
for (int i = 0; i < values.Length; i++)
364+
{
365+
values[i] = Constants.Base58Chars.IndexOf(key[i]);
366+
}
367+
368+
uint[] precomputed = new uint[uLen];
369+
370+
// i starts from 1 becaue it is compressed (K or L)
371+
for (int i = 1; i < Constants.CompPrivKeyLen - 2; i++)
372+
{
373+
for (int j = i + 1; j < Constants.CompPrivKeyLen - 1; j++)
374+
{
375+
for (int k = j + 1; k < Constants.CompPrivKeyLen; k++)
376+
{
377+
Array.Clear(precomputed, 0, precomputed.Length);
378+
379+
for (int index = 0; index < i; index++)
380+
{
381+
ulong carry = 0;
382+
ulong val = (ulong)values[index];
383+
int powIndex = (Constants.CompPrivKeyLen - 1 - index) * uLen;
384+
for (int m = uLen - 1; m >= 0; m--, powIndex++)
385+
{
386+
ulong result = checked((powers58[powIndex] * val) + precomputed[m] + carry);
387+
precomputed[m] = (uint)result;
388+
carry = (uint)(result >> 32);
389+
}
390+
}
391+
392+
for (int index = i + 1; index < j; index++)
393+
{
394+
ulong carry = 0;
395+
ulong val = (ulong)values[index - 1];
396+
int powIndex = (Constants.CompPrivKeyLen - 1 - index) * uLen;
397+
for (int m = uLen - 1; m >= 0; m--, powIndex++)
398+
{
399+
ulong result = checked((powers58[powIndex] * val) + precomputed[m] + carry);
400+
precomputed[m] = (uint)result;
401+
carry = (uint)(result >> 32);
402+
}
403+
}
404+
405+
for (int index = j + 1; index < k; index++)
406+
{
407+
ulong carry = 0;
408+
ulong val = (ulong)values[index - 2];
409+
int powIndex = (Constants.CompPrivKeyLen - 1 - index) * uLen;
410+
for (int m = uLen - 1; m >= 0; m--, powIndex++)
411+
{
412+
ulong result = checked((powers58[powIndex] * val) + precomputed[m] + carry);
413+
precomputed[m] = (uint)result;
414+
carry = (uint)(result >> 32);
415+
}
416+
}
417+
418+
for (int index = k + 1; index < Constants.CompPrivKeyLen; index++)
419+
{
420+
ulong carry = 0;
421+
ulong val = (ulong)values[index - 3];
422+
int powIndex = (Constants.CompPrivKeyLen - 1 - index) * uLen;
423+
for (int m = uLen - 1; m >= 0; m--, powIndex++)
424+
{
425+
ulong result = checked((powers58[powIndex] * val) + precomputed[m] + carry);
426+
precomputed[m] = (uint)result;
427+
carry = (uint)(result >> 32);
428+
}
429+
}
430+
431+
var cancelToken = new CancellationTokenSource();
432+
var options = new ParallelOptions
433+
{
434+
CancellationToken = cancelToken.Token,
435+
MaxDegreeOfParallelism = 4
436+
};
437+
438+
try
439+
{
440+
Parallel.For(0, 58, options, (c1, loopState) =>
441+
{
442+
for (int c2 = 0; c2 < 58; c2++)
443+
{
444+
for (int c3 = 0; c3 < 58; c3++)
445+
{
446+
options.CancellationToken.ThrowIfCancellationRequested();
447+
448+
uint[] temp = new uint[precomputed.Length];
449+
Array.Copy(precomputed, temp, precomputed.Length);
450+
451+
ulong carry = 0;
452+
ulong val = (ulong)c1;
453+
int powIndex = (Constants.CompPrivKeyLen - 1 - i) * uLen;
454+
for (int m = uLen - 1; m >= 0; m--, powIndex++)
455+
{
456+
ulong result = checked((powers58[powIndex] * val) + temp[m] + carry);
457+
temp[m] = (uint)result;
458+
carry = (uint)(result >> 32);
459+
}
460+
461+
carry = 0;
462+
val = (ulong)c2;
463+
powIndex = (Constants.CompPrivKeyLen - 1 - j) * uLen;
464+
for (int m = uLen - 1; m >= 0; m--, powIndex++)
465+
{
466+
ulong result = checked((powers58[powIndex] * val) + temp[m] + carry);
467+
temp[m] = (uint)result;
468+
carry = (uint)(result >> 32);
469+
}
470+
471+
carry = 0;
472+
val = (ulong)c3;
473+
powIndex = (Constants.CompPrivKeyLen - 1 - k) * uLen;
474+
for (int m = uLen - 1; m >= 0; m--, powIndex++)
475+
{
476+
ulong result = checked((powers58[powIndex] * val) + temp[m] + carry);
477+
temp[m] = (uint)result;
478+
carry = (uint)(result >> 32);
479+
}
480+
481+
if (Compute(temp))
482+
{
483+
string foundRes = key.Insert(i, $"{Constants.Base58Chars[c1]}")
484+
.Insert(j, $"{Constants.Base58Chars[c2]}")
485+
.Insert(k, $"{Constants.Base58Chars[c3]}");
486+
AddQueue($"Found a key: {foundRes}");
487+
Task.Run(() => cancelToken.Cancel());
488+
}
489+
}
490+
}
491+
});
492+
}
493+
catch (Exception)
494+
{
495+
return true;
496+
}
497+
}
498+
}
499+
}
500+
501+
return false;
502+
}
503+
504+
505+
private unsafe bool Compute(uint[] keyValueInts)
506+
{
507+
// Sha must be defined here for this method to be thread safe
508+
Sha256 sha = new Sha256();
509+
510+
fixed (uint* hPt = &sha.hashState[0], wPt = &sha.w[0])
511+
fixed (uint* keyPt = &keyValueInts[0])
512+
{
513+
wPt[0] = (keyPt[0] << 16) | (keyPt[1] >> 16);
514+
wPt[1] = (keyPt[1] << 16) | (keyPt[2] >> 16);
515+
wPt[2] = (keyPt[2] << 16) | (keyPt[3] >> 16);
516+
wPt[3] = (keyPt[3] << 16) | (keyPt[4] >> 16);
517+
wPt[4] = (keyPt[4] << 16) | (keyPt[5] >> 16);
518+
wPt[5] = (keyPt[5] << 16) | (keyPt[6] >> 16);
519+
wPt[6] = (keyPt[6] << 16) | (keyPt[7] >> 16);
520+
wPt[7] = (keyPt[7] << 16) | (keyPt[8] >> 16);
521+
wPt[8] = (keyPt[8] << 16) | 0b00000000_00000000_10000000_00000000U;
522+
// from 9 to 14 =0
523+
wPt[15] = 272; // 34 *8 = 272
524+
525+
//for (int i = 16; i < w.Length; i++)
526+
//{
527+
// wPt[i] = SSIG1(wPt[i - 2]) + wPt[i - 7] + SSIG0(wPt[i - 15]) + wPt[i - 16];
528+
//}
529+
530+
wPt[16] = sha.SSIG0(wPt[1]) + wPt[0];
531+
wPt[17] = 11141120 + sha.SSIG0(wPt[2]) + wPt[1];
532+
wPt[18] = sha.SSIG1(wPt[16]) + sha.SSIG0(wPt[3]) + wPt[2];
533+
wPt[19] = sha.SSIG1(wPt[17]) + sha.SSIG0(wPt[4]) + wPt[3];
534+
wPt[20] = sha.SSIG1(wPt[18]) + sha.SSIG0(wPt[5]) + wPt[4];
535+
wPt[21] = sha.SSIG1(wPt[19]) + sha.SSIG0(wPt[6]) + wPt[5];
536+
wPt[22] = sha.SSIG1(wPt[20]) + 272 + sha.SSIG0(wPt[7]) + wPt[6];
537+
wPt[23] = sha.SSIG1(wPt[21]) + wPt[16] + sha.SSIG0(wPt[8]) + wPt[7];
538+
wPt[24] = sha.SSIG1(wPt[22]) + wPt[17] + wPt[8];
539+
wPt[25] = sha.SSIG1(wPt[23]) + wPt[18];
540+
wPt[26] = sha.SSIG1(wPt[24]) + wPt[19];
541+
wPt[27] = sha.SSIG1(wPt[25]) + wPt[20];
542+
wPt[28] = sha.SSIG1(wPt[26]) + wPt[21];
543+
wPt[29] = sha.SSIG1(wPt[27]) + wPt[22];
544+
wPt[30] = sha.SSIG1(wPt[28]) + wPt[23] + 541327392;
545+
wPt[31] = sha.SSIG1(wPt[29]) + wPt[24] + sha.SSIG0(wPt[16]) + 272;
546+
wPt[32] = sha.SSIG1(wPt[30]) + wPt[25] + sha.SSIG0(wPt[17]) + wPt[16];
547+
wPt[33] = sha.SSIG1(wPt[31]) + wPt[26] + sha.SSIG0(wPt[18]) + wPt[17];
548+
wPt[34] = sha.SSIG1(wPt[32]) + wPt[27] + sha.SSIG0(wPt[19]) + wPt[18];
549+
wPt[35] = sha.SSIG1(wPt[33]) + wPt[28] + sha.SSIG0(wPt[20]) + wPt[19];
550+
wPt[36] = sha.SSIG1(wPt[34]) + wPt[29] + sha.SSIG0(wPt[21]) + wPt[20];
551+
wPt[37] = sha.SSIG1(wPt[35]) + wPt[30] + sha.SSIG0(wPt[22]) + wPt[21];
552+
wPt[38] = sha.SSIG1(wPt[36]) + wPt[31] + sha.SSIG0(wPt[23]) + wPt[22];
553+
wPt[39] = sha.SSIG1(wPt[37]) + wPt[32] + sha.SSIG0(wPt[24]) + wPt[23];
554+
wPt[40] = sha.SSIG1(wPt[38]) + wPt[33] + sha.SSIG0(wPt[25]) + wPt[24];
555+
wPt[41] = sha.SSIG1(wPt[39]) + wPt[34] + sha.SSIG0(wPt[26]) + wPt[25];
556+
wPt[42] = sha.SSIG1(wPt[40]) + wPt[35] + sha.SSIG0(wPt[27]) + wPt[26];
557+
wPt[43] = sha.SSIG1(wPt[41]) + wPt[36] + sha.SSIG0(wPt[28]) + wPt[27];
558+
wPt[44] = sha.SSIG1(wPt[42]) + wPt[37] + sha.SSIG0(wPt[29]) + wPt[28];
559+
wPt[45] = sha.SSIG1(wPt[43]) + wPt[38] + sha.SSIG0(wPt[30]) + wPt[29];
560+
wPt[46] = sha.SSIG1(wPt[44]) + wPt[39] + sha.SSIG0(wPt[31]) + wPt[30];
561+
wPt[47] = sha.SSIG1(wPt[45]) + wPt[40] + sha.SSIG0(wPt[32]) + wPt[31];
562+
wPt[48] = sha.SSIG1(wPt[46]) + wPt[41] + sha.SSIG0(wPt[33]) + wPt[32];
563+
wPt[49] = sha.SSIG1(wPt[47]) + wPt[42] + sha.SSIG0(wPt[34]) + wPt[33];
564+
wPt[50] = sha.SSIG1(wPt[48]) + wPt[43] + sha.SSIG0(wPt[35]) + wPt[34];
565+
wPt[51] = sha.SSIG1(wPt[49]) + wPt[44] + sha.SSIG0(wPt[36]) + wPt[35];
566+
wPt[52] = sha.SSIG1(wPt[50]) + wPt[45] + sha.SSIG0(wPt[37]) + wPt[36];
567+
wPt[53] = sha.SSIG1(wPt[51]) + wPt[46] + sha.SSIG0(wPt[38]) + wPt[37];
568+
wPt[54] = sha.SSIG1(wPt[52]) + wPt[47] + sha.SSIG0(wPt[39]) + wPt[38];
569+
wPt[55] = sha.SSIG1(wPt[53]) + wPt[48] + sha.SSIG0(wPt[40]) + wPt[39];
570+
wPt[56] = sha.SSIG1(wPt[54]) + wPt[49] + sha.SSIG0(wPt[41]) + wPt[40];
571+
wPt[57] = sha.SSIG1(wPt[55]) + wPt[50] + sha.SSIG0(wPt[42]) + wPt[41];
572+
wPt[58] = sha.SSIG1(wPt[56]) + wPt[51] + sha.SSIG0(wPt[43]) + wPt[42];
573+
wPt[59] = sha.SSIG1(wPt[57]) + wPt[52] + sha.SSIG0(wPt[44]) + wPt[43];
574+
wPt[60] = sha.SSIG1(wPt[58]) + wPt[53] + sha.SSIG0(wPt[45]) + wPt[44];
575+
wPt[61] = sha.SSIG1(wPt[59]) + wPt[54] + sha.SSIG0(wPt[46]) + wPt[45];
576+
wPt[62] = sha.SSIG1(wPt[60]) + wPt[55] + sha.SSIG0(wPt[47]) + wPt[46];
577+
wPt[63] = sha.SSIG1(wPt[61]) + wPt[56] + sha.SSIG0(wPt[48]) + wPt[47];
578+
579+
sha.Init(hPt);
580+
sha.CompressBlockWithWSet(hPt, wPt);
581+
582+
// Perform second hash
583+
sha.DoSecondHash(hPt, wPt);
584+
585+
return hPt[0] == keyPt[9];
586+
}
587+
}
588+
589+
337590

338591
public async Task<bool> Find(string key, char missingChar)
339592
{
@@ -435,5 +688,37 @@ await Task.Run(() =>
435688
return CopyQueueToMessage(success);
436689
}
437690

691+
692+
public async Task<bool> FindUnknownLocation(string key)
693+
{
694+
InitReport();
695+
696+
if (string.IsNullOrEmpty(key) || key.Any(c => !Constants.Base58Chars.Contains(c)))
697+
return Fail("Input contains invalid base-58 character(s).");
698+
if (key.Length != Constants.CompPrivKeyLen - 3 ||
699+
(!key.StartsWith(Constants.CompPrivKeyChar2) && key.StartsWith(Constants.CompPrivKeyChar2)))
700+
{
701+
// Only this special case is supported for now
702+
return Fail("This option is only defined for compressed private keys that " +
703+
"have 3 missing characters with unknown locations.");
704+
}
705+
706+
// 51! / 3! *((51-3)!)
707+
BigInteger total = ((51 * 50 * 49) / (3 * 2 * 1)) * BigInteger.Pow(58, 3);
708+
AddMessage($"Start searching.{Environment.NewLine}Total number of keys to check: {total:n0}");
709+
710+
Stopwatch watch = Stopwatch.StartNew();
711+
bool success = await Task.Run(() =>
712+
{
713+
return SpecialLoopComp(key);
714+
}
715+
);
716+
717+
watch.Stop();
718+
AddQueue($"Elapsed time: {watch.Elapsed}");
719+
AddQueue(GetKeyPerSec(total, watch.Elapsed.TotalSeconds));
720+
721+
return CopyQueueToMessage(success);
722+
}
438723
}
439724
}

Src/FinderOuter/ViewModels/MissingBase58ViewModel.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,27 @@ public char MissingChar
5252
set => this.RaiseAndSetIfChanged(ref _mis, value);
5353
}
5454

55+
private bool _isSpecial;
56+
public bool IsSpecialCase
57+
{
58+
get => _isSpecial;
59+
set => this.RaiseAndSetIfChanged(ref _isSpecial, value);
60+
}
61+
5562
public string MissingToolTip => $"Choose one of these symbols {Constants.Symbols} to use instead of the missing characters";
63+
public string SpecialToolTip => "Select this for a special case where you have a compressed private key that is missing " +
64+
"exactly 3 characters and you don't know their locations.";
5665

5766
public override void Find()
5867
{
59-
_ = b58Service.Find(Input, MissingChar);
68+
if (IsSpecialCase)
69+
{
70+
_ = b58Service.FindUnknownLocation(Input);
71+
}
72+
else
73+
{
74+
_ = b58Service.Find(Input, MissingChar);
75+
}
6076
}
6177
}
6278
}

Src/FinderOuter/Views/MissingBase58View.xaml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,15 @@
1313
<TextBox Text="{Binding Input}" Watermark="Base-58 encoded string" UseFloatingWatermark="True"
1414
TextWrapping="Wrap" AcceptsReturn="True" ScrollViewer.VerticalScrollBarVisibility="Auto"
1515
Margin="3" Grid.Column="0"/>
16-
<TextBox Text="{Binding MissingChar}" TextAlignment="Center" VerticalAlignment="Top"
17-
ToolTip.Tip="{Binding MissingToolTip}"
18-
Height="40" Width="40" Margin="3" Grid.Column="1"/>
16+
17+
<StackPanel Orientation="Vertical" Spacing="5" Margin="3" Grid.Column="1">
18+
<TextBox Text="{Binding MissingChar}" TextAlignment="Center" VerticalAlignment="Top"
19+
ToolTip.Tip="{Binding MissingToolTip}"
20+
Height="40" Width="40" Margin="3"/>
21+
<TextBlock Text="Special case:" Margin="0,15,0,0"/>
22+
<CheckBox IsChecked="{Binding IsSpecialCase}" ToolTip.Tip="{Binding SpecialToolTip}"
23+
HorizontalAlignment="Center"/>
24+
</StackPanel>
25+
1926
</Grid>
2027
</UserControl>

0 commit comments

Comments
 (0)