1 /// \file SecureWallet.d
2 
3 module tagion.betterC.wallet.SecureWallet;
4 
5 import core.time : MonoTime;
6 import std.algorithm : cache, each, filter, map, max, min, sum, until;
7 import std.string : representation;
8 import std.traits;
9 import tagion.betterC.funnel.TagionCurrency;
10 import tagion.betterC.hibon.Document : Document;
11 import tagion.betterC.utils.BinBuffer;
12 import tagion.betterC.utils.Memory;
13 import tagion.betterC.utils.Miscellaneous;
14 import tagion.betterC.wallet.KeyRecover;
15 import tagion.betterC.wallet.Net;
16 import tagion.betterC.wallet.WalletRecords : RecoverGenerator, DevicePIN, AccountDetails,
17     Invoice, StandardBill, SignedContract;
18 
19 struct SecureWallet(Net) {
20     static assert(is(Net : SecureNet));
21     protected RecoverGenerator _wallet;
22     protected DevicePIN _pin;
23 
24     AccountDetails account;
25     protected static SecureNet net;
26 
27     this(DevicePIN pin, RecoverGenerator wallet = RecoverGenerator.init, AccountDetails account = AccountDetails
28             .init) {
29         _wallet = wallet;
30         _pin = pin;
31         this.account = account;
32     }
33 
34     this(const Document wallet_doc, const Document pin_doc = Document.init) {
35         auto __wallet = RecoverGenerator(wallet_doc);
36         DevicePIN __pin;
37         if (!pin_doc.empty) {
38             __pin = DevicePIN(pin_doc);
39         }
40         this(__pin, __wallet);
41     }
42 
43     const(RecoverGenerator) wallet() const {
44         return _wallet;
45     }
46 
47     const(DevicePIN) pin() const {
48         return _pin;
49     }
50 
51     uint confidence() const {
52         return _wallet.confidence;
53     }
54 
55     static SecureWallet createWallet(scope const(string[]) questions,
56     scope const(char[][]) answers, uint confidence, const(char[]) pincode)
57     in {
58         assert(questions.length > 3, "Minimal amount of answers is 3");
59         assert(questions.length is answers.length, "Amount of questions should be same as answers");
60     }
61     do {
62         KeyRecover recover;
63 
64         if (confidence == questions.length) {
65             pragma(msg, "fixme(cbr): Due to some bug in KeyRecover");
66             // Due to some bug in KeyRecover
67             confidence--;
68         }
69 
70         recover.createKey(questions, answers, confidence);
71         //        StdSecureNet net;
72         RecoverGenerator wallet;
73         DevicePIN pin;
74         {
75             //         // auto R = new ubyte[net.hashSize];
76             ubyte[] R;
77             R.create(hashSize);
78 
79             recover.findSecret(R, questions, answers);
80             auto pinhash = recover.checkHash(pincode.representation, pin.U);
81             pin.D = xor(R, pinhash);
82             pin.S = recover.checkHash(R);
83             net.createKeyPair(R);
84             wallet = RecoverGenerator(recover.toDoc);
85         }
86         return SecureWallet(pin, wallet);
87     }
88 
89     // void load(AccountDetails account) {
90     //     this.account = account;
91     // }
92 
93     // protected void set_pincode(const KeyRecover recover, scope const(ubyte[]) R,
94     //         const(ubyte[]) pinhash) {
95     //     _pin.Y = xor(R, pinhash);
96     //     _pin.check = recover.checkHash(R);
97     // }
98 
99     // bool correct(const(string[]) questions, const(char[][]) answers)
100     // in {
101     //     assert(questions.length is answers.length, "Amount of questions should be same as answers");
102     // }
103     // do {
104     //     // net = new Net;
105     //     auto recover = KeyRecover(_wallet);
106     //     ubyte[] R;
107     //     R.create(hashSize);
108     //     return recover.findSecret(R, questions, answers);
109     // }
110 
111     // bool recover(const(string[]) questions, const(char[][]) answers, const(char[]) pincode)
112     // in {
113     //     assert(questions.length is answers.length, "Amount of questions should be same as answers");
114     // }
115     // do {
116     //     // net = new Net;
117     //     auto recover = KeyRecover(_wallet);
118     //     // auto R = new ubyte[net.hashSize];
119     //     ubyte[] R;
120     //     R.create(hashSize);
121     //     const result = recover.findSecret(R, questions, answers);
122     //     if (result) {
123     //         auto pinhash = recover.checkHash(pincode.representation);
124     //         set_pincode(recover, R, pinhash);
125     //         net.createKeyPair(R);
126     //         return true;
127     //     }
128     //     // net = null;
129     //     return false;
130     // }
131 
132     // bool isLoggedin() const {
133     //     // return net !is null;
134     //     return true;
135     // }
136 
137     protected void checkLogin() pure const {
138     }
139 
140     bool login(const(char[]) pincode) {
141         // if (_pin.Y) {
142         //     logout;
143         //     // auto hashnet = new Net;
144         //     // auto recover = KeyRecover(hashnet);
145         //     KeyRecover recover;
146         //     auto pinhash = recover.checkHash(pincode.representation);
147         //     // auto R = new ubyte[hashnet.hashSize];
148         //     ubyte[] R;
149         //     R.create(hashSize);
150         //     xor(R, _pin.Y, pinhash);
151         //     if (_pin.check == recover.checkHash(R)) {
152         //         // net = new Net;
153         //         net.createKeyPair(R);
154         //         return true;
155         //     }
156         // }
157         return false;
158     }
159 
160     void logout() pure nothrow {
161         // net = null;
162     }
163 
164     // bool check_pincode(const(char[]) pincode) {
165     //     // const hashnet = new Net;
166     //     // auto recover = KeyRecover(hashnet);
167     //     KeyRecover recover;
168     //     const pinhash = recover.checkHash(pincode.representation);
169     //     // auto R = new ubyte[hashnet.hashSize];
170     //     ubyte[] R;
171     //     R.create(hashSize);
172     //     xor(R, _pin.Y, pinhash);
173     //     return _pin.check == recover.checkHash(R);
174     // }
175 
176     // bool change_pincode(const(char[]) pincode, const(char[]) new_pincode) {
177     //     // const hashnet = new Net;
178     //     // auto recover = KeyRecover(hashnet);
179     //     KeyRecover recover;
180     //     const pinhash = recover.checkHash(pincode.representation);
181     //     // auto R = new ubyte[hashnet.hashSize];
182     //     ubyte[] R;
183     //     R.create(hashSize);
184     //     xor(R, _pin.Y, pinhash);
185     //     if (_pin.check == recover.checkHash(R)) {
186     //         const new_pinhash = recover.checkHash(new_pincode.representation);
187     //         set_pincode(recover, R, new_pinhash);
188     //         logout;
189     //         return true;
190     //     }
191     //     return false;
192     // }
193 
194     void registerInvoice(ref Invoice invoice) {
195         checkLogin;
196         // string current_time = MonoTime.currTime.toString;
197         // scope seed = new ubyte[net.hashSize];
198         scope ubyte[] seed;
199         seed.create(hashSize);
200         scramble(seed);
201         // account.derive_state = rawCalcHash(
202         //         seed ~ account.derive_state ~ current_time.representation);
203         // account.derive_state = rawCalcHash(seed);
204         scramble(seed);
205         // auto pkey = net.derivePubkey(account.derive_state);
206         // invoice.pkey = pkey;
207         // account.derives[pkey] = account.derive_state;
208     }
209 
210     void registerInvoices(ref Invoice[] invoices) {
211         invoices.each!((ref invoice) => registerInvoice(invoice));
212     }
213 
214     static Invoice createInvoice(string label, TagionCurrency amount, Document info = Document.init) {
215         Invoice new_invoice;
216         new_invoice.name = label;
217         new_invoice.amount = amount;
218         new_invoice.info = info;
219         return new_invoice;
220     }
221 
222     bool payment(const(Invoice[]) orders, ref SignedContract result) {
223         // checkLogin;
224         // const topay = orders.map!(b => b.amount).sum;
225 
226         // if (topay > 0) {
227         // const size_in_bytes = 500;
228         // const fees = globals.fees(topay, size_in_bytes);
229         //     const amount = topay + fees;
230         //     StandardBill[] contract_bills;
231         //     const enough = collect_bills(amount, contract_bills);
232         //     if (enough) {
233         //         const total = contract_bills.map!(b => b.value).sum;
234 
235         //         result.contract.input = contract_bills.map!(b => net.hashOf(b.toDoc)).array;
236         //         const rest = total - amount;
237         //         if (rest > 0) {
238         //             Invoice money_back;
239         //             money_back.amount = rest;
240         //             registerInvoice(money_back);
241         //             result.contract.output[money_back.pkey] = rest.toDoc;
242         //         }
243         //         orders.each!((o) { result.contract.output[o.pkey] = o.amount.toDoc; });
244         //         result.contract.script = Script("pay");
245 
246         //         immutable message = net.hashOf(result.contract.toDoc);
247         //         auto shared_net = (() @trusted { return cast(shared) net; })();
248         //         SecureNet bill_net;
249         //         // Sign all inputs
250         //         result.signs = contract_bills.filter!(b => b.owner in account.derives)
251         //             .map!((b) {
252         //                 immutable tweak_code = account.derives[b.owner];
253         //                 bill_net.derive(tweak_code, shared_net);
254         //                 return bill_net.sign(message);
255         //             }())
256         //             .array;
257         //         return true;
258         //     }
259         //     result = result.init;
260         //     return false;
261         // }
262 
263         return false;
264     }
265 
266     TagionCurrency available_balance() const {
267         return account.available;
268     }
269 
270     TagionCurrency active_balance() const {
271         return account.active;
272     }
273 
274     // TagionCurrency total_balance() const pure {
275     //     return account.total;
276     // }
277 
278     // const(HiRPC.Sender) get_request_update_wallet() const {
279     //     HiRPC hirpc;
280     //     // auto h = new HiBON;
281     //     auto h = HiBON();
282     //     auto account_doc = account.derives;
283     //     h = account_doc[0].get!Buffer;
284     //     // h = account.derives.byKey.map!(p => cast(Buffer) p);
285     //     return hirpc.search(h);
286     // }
287 
288     // bool collect_bills(const TagionCurrency amount, out StandardBill[] active_bills) {
289     //     import std.algorithm.sorting : isSorted, sort;
290     //     import std.algorithm.iteration : cumulativeFold;
291     //     import std.range : takeOne, tee;
292 
293     //     if (!account.bills.isSorted!"a.value > b.value") {
294     //         account.bills.sort!"a.value > b.value";
295     //     }
296 
297     //     // Select all bills not in use
298     //     auto none_active = account.bills.filter!(b => !(b.owner in account.activated));
299 
300     //     // Check if we have enough money
301     //     const enough = !none_active.map!(b => b.value)
302     //         .cumulativeFold!((a, b) => a + b)
303     //         .filter!(a => a >= amount)
304     //         .takeOne
305     //         .empty;
306     //     if (enough) {
307     //         TagionCurrency rest = amount;
308     //         active_bills = none_active.filter!(b => b.value <= rest)
309     //             .until!(b => rest <= 0)
310     //             .tee!((b) { rest -= b.value; account.activated[b.owner] = true; })
311     //             .array;
312     //         if (rest > 0) {
313     //             // Take an extra larger bill if not enough
314     //             StandardBill extra_bill;
315     //             none_active.each!(b => extra_bill = b);
316     //             // .retro
317     //             // .takeOne;
318     //             account.activated[extra_bill.owner] = true;
319     //             active_bills ~= extra_bill;
320     //         }
321     //         assert(rest > 0);
322     //         return true;
323     //     }
324     //     return false;
325     // }
326 
327     // bool set_response_update_wallet(const(HiRPC.Receiver) receiver) nothrow {
328     //     if (receiver.isResponse) { // ???
329     //             account.bills = receiver.method.params[].map!(e => StandardBill(e.get!Document))
330     //                 .array;
331     //             return true;
332     //     }
333     //     return false;
334     // }
335 
336     // TagionCurrency get_balance() const pure {
337     //     return calcTotal(account.bills);
338     // }
339 
340     // static TagionCurrency calcTotal(const(StandardBill[]) bills) pure {
341     //     return bills.map!(b => b.value).sum;
342     // }
343 
344     // unittest {
345     //     import std.stdio;
346     //     import std.range : iota;
347     //     import std.format;
348 
349     //     const pin_code = "1234";
350 
351     //     // Create a new Wallet
352     //     enum {
353     //         num_of_questions = 5,
354     //         confidence = 3
355     //     }
356     //     const dummey_questions = num_of_questions.iota.map!(i => format("What %s", i)).array;
357     //     const dummey_amswers = num_of_questions.iota.map!(i => format("A %s", i)).array;
358     //     const wallet_doc = SecureWallet.createWallet(dummey_questions,
359     //             dummey_amswers, confidence, pin_code).wallet.toDoc;
360 
361     //     const pin_doc = SecureWallet.createWallet(dummey_questions,
362     //             dummey_amswers, confidence, pin_code).pin.toDoc;
363 
364     //     auto secure_wallet = SecureWallet(wallet_doc, pin_doc);
365     //     const pin_code_2 = "3434";
366     //     { // Login test
367     //         assert(!secure_wallet.isLoggedin);
368     //         secure_wallet.login(pin_code);
369     //         assert(secure_wallet.check_pincode(pin_code));
370     //         assert(secure_wallet.isLoggedin);
371     //         secure_wallet.logout;
372     //         assert(secure_wallet.check_pincode(pin_code));
373     //         assert(!secure_wallet.isLoggedin);
374     //         secure_wallet.login(pin_code_2);
375     //         assert(secure_wallet.check_pincode(pin_code));
376     //         assert(!secure_wallet.isLoggedin);
377     //     }
378 
379     //     { // Key Recover faild
380     //         auto test_answers = dummey_amswers.dup;
381     //         test_answers[0] = "Bad answer 0";
382     //         test_answers[3] = "Bad answer 1";
383     //         test_answers[4] = "Bad answer 2";
384 
385     //         const result = secure_wallet.recover(dummey_questions, test_answers, pin_code_2);
386     //         assert(!result);
387     //         assert(!secure_wallet.isLoggedin);
388     //     }
389 
390     //     { // Key Recover test
391     //         auto test_answers = dummey_amswers.dup;
392     //         test_answers[2] = "Bad answer 0";
393     //         test_answers[4] = "Bad answer 1";
394 
395     //         const result = secure_wallet.recover(dummey_questions, test_answers, pin_code_2);
396     //         assert(result);
397     //         assert(secure_wallet.isLoggedin);
398     //     }
399 
400     //     { // Re-login
401     //         secure_wallet.logout;
402     //         assert(secure_wallet.check_pincode(pin_code_2));
403     //         assert(!secure_wallet.isLoggedin);
404     //         secure_wallet.login(pin_code_2);
405     //         assert(secure_wallet.isLoggedin);
406     //     }
407 
408     //     const new_pincode = "7851";
409     //     { // Fail to change pin-code
410     //         const result = secure_wallet.change_pincode(new_pincode, pin_code_2);
411     //         assert(!result);
412     //         assert(secure_wallet.isLoggedin);
413     //     }
414 
415     //     { // Change pincode
416     //         const result = secure_wallet.change_pincode(pin_code_2, new_pincode);
417     //         assert(result);
418     //         assert(!secure_wallet.isLoggedin);
419     //         secure_wallet.login(new_pincode);
420     //         assert(secure_wallet.isLoggedin);
421     //     }
422 
423     //     writeln("END unittest");
424     // }
425 
426     // unittest { // Test for account
427     //     import std.stdio;
428     //     import std.range : zip;
429 
430     //     auto sender_wallet = SecureWallet(DevicePIN.init, RecoverGenerator.init);
431     //     auto net = new Net;
432 
433     //     { // Add SecureNet to the wallet
434     //         immutable very_securet = "Very Secret password";
435     //         net.generateKeyPair(very_securet);
436     //         sender_wallet.net = net;
437     //     }
438 
439     //     { // Create a number of bills in the seneder_wallet
440     //         auto bill_amounts = [4, 1, 100, 40, 956, 42, 354, 7, 102355].map!(a => a.TGN);
441     //         auto gene = net.calcHash("gene".representation);
442     //         const uint epoch = 42;
443 
444     //         const label = "some_name";
445     //         auto list_of_invoices = bill_amounts.map!(a => createInvoice(label, a))
446     //             .each!(invoice => sender_wallet.registerInvoice(invoice))();
447 
448     //         import tagion.utils.Miscellaneous : hex;
449 
450     //         // Add the bulls to the account with the derive keys
451     //         with (sender_wallet.account) {
452     //             bills = zip(bill_amounts, derives.byKey).map!(bill_derive => StandardBill(bill_derive[0],
453     //                     epoch, bill_derive[1], gene)).array;
454     //         }
455 
456     //         assert(sender_wallet.available_balance == bill_amounts.sum);
457     //         assert(sender_wallet.total_balance == bill_amounts.sum);
458     //         assert(sender_wallet.active_balance == 0.TGN);
459     //     }
460 
461     //     auto receiver_wallet = SecureWallet(DevicePIN.init, RecoverGenerator.init);
462     //     { // Add securety to the receiver_wallet
463     //         auto receiver_net = new Net;
464     //         immutable very_securet = "Very Secret password for the receriver";
465     //         receiver_net.generateKeyPair(very_securet);
466     //         receiver_wallet.net = receiver_net;
467     //     }
468 
469     //     pragma(msg,
470     //             "fixme(cbr): The following test is not finished, Need to transfer to money to receiver");
471     //     SignedContract contract_1;
472     //     { // The receiver_wallet creates an invoice to the sender_wallet
473     //         auto invoice = SecureWallet.createInvoice("To sender 1", 13.TGN);
474     //         receiver_wallet.registerInvoice(invoice);
475     //         // Give the invoice to the sender_wallet and create payment
476     //         sender_wallet.payment([invoice], contract_1);
477     //     }
478 
479     //     SignedContract contract_2;
480     //     { // The receiver_wallet creates an invoice to the sender_wallet
481     //         auto invoice = SecureWallet.createInvoice("To sender 2", 53.TGN);
482     //         receiver_wallet.registerInvoice(invoice);
483     //         // Give the invoice to the sender_wallet and create payment
484     //         sender_wallet.payment([invoice], contract_2);
485     //     }
486     // }
487 }