1 /// \file KeyRecover.d
2 
3 module tagion.betterC.wallet.KeyRecover;
4 
5 //use net directly
6 import tagion.betterC.utils.Memory;
7 import tagion.betterC.wallet.Net;
8 
9 // // use better C doc, hibon, hibon record
10 import std.string : representation;
11 import tagion.basic.Types : Buffer;
12 import tagion.betterC.hibon.Document : Document;
13 import tagion.betterC.hibon.HiBON : HiBONT;
14 import std.range : iota, indexed, lockstep; // commented stuff produce error no TypeInfo in betterC
15 import std.algorithm.iteration : filter, map;
16 import std.algorithm.mutation : copy;
17 import tagion.betterC.utils.Miscellaneous;
18 import tagion.betterC.wallet.WalletRecords : RecoverGenerator;
19 
20 struct KeyRecover {
21     enum MAX_QUESTION = 10;
22     enum MAX_SEEDS = 64;
23     protected RecoverGenerator generator;
24 
25     this(RecoverGenerator generator) {
26         this.generator = generator;
27     }
28 
29     inout(HiBONT) toHiBON() inout {
30         return generator.toHiBON;
31     }
32 
33     const(Document) toDoc() {
34         return generator.toDoc;
35     }
36 
37     /**
38      * Generates the quiz hash of the from a list of questions and answers
39      */
40     Buffer[] quiz(scope const(string[]) questions, scope const(char[][]) answers) const @trusted
41     in {
42         assert(questions.length is answers.length);
43     }
44     do {
45 
46         // auto results = new Buffer[questions.length];
47         Buffer[] results;
48         results.create(questions.length);
49 
50         foreach (i, ref result; results) {
51             scope answer = answers[i];
52             scope question = questions[i];
53             scope strip_down = cast(ubyte[]) answer.strip_down;
54             scope answer_hash = rawCalcHash(strip_down);
55             scope question_hash = rawCalcHash(question.representation);
56             // scope (exit) {
57             //     strip_down.sceamble;
58             //     answer_hash.scramble;
59             //     question_hash.scramble;
60             // }
61             //            const hash = net.calcHash(answer);
62             answer_hash.write(question_hash.serialize);
63             result = rawCalcHash(answer_hash);
64         }
65         return results;
66     }
67 
68     static uint numberOfSeeds(const uint M, const uint N) pure nothrow
69     in {
70         assert(M >= N);
71         assert(M <= 10);
72     }
73     do {
74         return (M - N) * N + 1;
75     }
76 
77     static unittest {
78         assert(numberOfSeeds(10, 5) is 26);
79     }
80 
81     @trusted
82     Buffer checkHash(scope const(ubyte[]) value, scope const(ubyte[]) salt = null) const {
83         return rawCalcHash(rawCalcHash(value));
84     }
85 
86     static void iterateSeeds(
87             const uint M, const uint N,
88             scope bool delegate(scope const(uint[]) indices) @safe dg) {
89         // scope include = new uint[N];
90         scope uint[] include;
91         include.create(N);
92         iota(N).copy(include);
93         bool end;
94         void local_search(const int index, const int size) @safe {
95             if ((index >= 0) && !end) {
96                 if (dg(include)) {
97                     end = true;
98                 }
99                 else {
100                     if (include[index] < size) {
101                         include[index]++;
102                         local_search(index, size);
103                     }
104                     else if (index > 0) {
105                         include[index - 1]++;
106                         local_search(index - 1, size - 1);
107                     }
108                 }
109             }
110         }
111 
112         local_search(cast(int) include.length - 1, M - 1);
113     }
114 
115     void createKey(scope const(string[]) questions, scope const(char[][]) answers, const uint confidence) {
116         createKey(quiz(questions, answers), confidence);
117     }
118 
119     void createKey(Buffer[] A, const uint confidence) {
120         // scope R = new ubyte[hashSize];
121         scope ubyte[] R;
122         R.create(hashSize);
123         scramble(R);
124         scope (exit) {
125             scramble(R);
126         }
127         quizSeed(R, A, confidence);
128     }
129 
130     /**
131      * Generates the quiz seed values from the privat key R and the quiz list
132      */
133     void quizSeed(scope ref const(ubyte[]) R, Buffer[] A, const uint confidence) {
134 
135         const number_of_questions = cast(uint) A.length;
136         const seeds = numberOfSeeds(number_of_questions, confidence);
137 
138         // generator.Y = new Buffer[seeds];
139         generator.Y.create(seeds);
140         uint count;
141         bool calculate_this_seeds(scope const(uint[]) indices) @safe {
142             scope list_of_selected_answers_and_the_secret = indexed(A, indices);
143             generator.Y[count] = xor(R, xor(list_of_selected_answers_and_the_secret));
144             count++;
145             return false;
146         }
147 
148         iterateSeeds(number_of_questions, confidence, &calculate_this_seeds);
149     }
150 
151     bool findSecret(scope ref ubyte[] R, scope const(string[]) questions, scope const(char[][]) answers) const {
152         return findSecret(R, quiz(questions, answers));
153     }
154 
155     bool findSecret(scope ref ubyte[] R, Buffer[] A) const {
156         const number_of_questions = cast(uint) A.length;
157         const seeds = numberOfSeeds(number_of_questions, generator.confidence);
158 
159         bool result;
160         bool search_for_the_secret(scope const(uint[]) indices) @safe {
161             scope list_of_selected_answers_and_the_secret = indexed(A, indices);
162             const guess = xor(list_of_selected_answers_and_the_secret);
163             foreach (y; generator.Y) {
164                 xor(R, y, guess);
165                 if (generator.S == checkHash(R)) {
166                     result = true;
167                     return true;
168                 }
169             }
170             return false;
171         }
172 
173         iterateSeeds(number_of_questions, generator.confidence, &search_for_the_secret);
174         return result;
175     }
176 }
177 
178 char[] strip_down(const(char[]) text) @safe
179 out (result) {
180     assert(result.length > 0);
181 }
182 do {
183     // import std.ascii : toLower, isAlphaNum;
184 
185     char[] res;
186     res.create(text.length);
187     foreach (i, letter; text) {
188         res[i] = text[i];
189         //     char c = cast(char) toLower(letter);
190         //     if (isAlphaNum(c)) {
191         //         res[i] = c;
192         //     }
193     }
194     // return res;
195     return res;
196 }
197 
198 static immutable(string[]) standard_questions;
199 
200 // shared static this() {
201 //     standard_questions = [
202 //         "What is your favorite book?",
203 //         "What is the name of the road you grew up on?",
204 //         "What is your mother’s maiden name?",
205 //         "What was the name of your first/current/favorite pet?",
206 //         "What was the first company that you worked for?",
207 //         "Where did you meet your spouse?",
208 //         "Where did you go to high school/college?",
209 //         "What is your favorite food?",
210 //         "What city were you born in?",
211 //         "Where is your favorite place to vacation?"
212 //     ];
213 // }
214 
215 unittest {
216     // import tagion.crypto.SecureNet : StdHashNet;
217     // import std.array : join, array;
218 
219     // auto selected_questions = indexed(standard_questions, [0, 2, 3, 7, 8]).array.idup;
220     // //writefln("%s", selected_questions.join("\n"));
221     // string[] answers = [
222     //     "mobidick",
223     //     "Mother Teresa!",
224     //     "Pluto",
225     //     "Pizza",
226     //     "Maputo"
227     // ];
228     // KeyRecover recover;
229     // recover.createKey(selected_questions, answers, 3);
230 
231     // // auto R = new ubyte[net.hashSize];
232     // ubyte[] R;
233     // R.create(hashSize);
234 
235     // { // All the ansers are correct
236     //     const result = recover.findSecret(R, selected_questions, answers);
237     //     //writefln("R=%s", R.toHexString);
238     //     assert(R.length == hashSize);
239     //     assert(result); // Password found
240     // }
241 
242     // { // 3 out of 5 answers are correct. This is a valid answer to generate the secret key
243     //     string[] good_answers = [
244     //         "MobiDick",
245     //         "MOTHER TERESA",
246     //         "Fido",
247     //         "pizza",
248     //         "Maputo"
249     //     ];
250     //     // auto goodR = new ubyte[hashSize];
251     //     ubyte[] goodR;
252     //     goodR.create(hashSize);
253     //     const result = recover.findSecret(goodR, selected_questions, good_answers);
254     //     assert(R.length == hashSize);
255     //     assert(result); // Password found
256     //     assert(R == goodR);
257     // }
258 
259     // { // 2 out of 5 answers are correct. This is NOT a valid answer to generate the secret key
260     //     string[] bad_answers = [
261     //         "mobidick",
262     //         "Monalisa",
263     //         "Fido",
264     //         "Burger",
265     //         "Maputo"
266     //     ];
267     //     // auto badR = new ubyte[net.hashSize];
268     //     ubyte[] badR;
269     //     badR.create(hashSize);
270     //     const result = recover.findSecret(badR, selected_questions, bad_answers);
271     //     assert(!result); // Password not found
272     //     assert(R != badR);
273 
274     // }
275 }