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 }