1 module tagion.mobile.WalletWrapperSdk; 2 3 import tagion.mobile.DocumentWrapperApi; 4 5 // import tagion.mobile.WalletStorage; 6 import core.runtime : rt_init, rt_term; 7 import core.stdc.stdlib; 8 import std.array; 9 import std.random; 10 import std.stdint; 11 import std.string : fromStringz, toStringz; 12 import tagion.hibon.Document; 13 14 //import std.stdio; 15 import core.stdc.string; 16 import std.algorithm; 17 import std.file : exists, remove; 18 import std.path; 19 import std.range; 20 import std.string : splitLines; 21 import tagion.basic.Types : Buffer, FileExtension; 22 import tagion.communication.HiRPC; 23 import tagion.crypto.Cipher; 24 import tagion.crypto.SecureNet; 25 import tagion.crypto.Types : Pubkey; 26 import tagion.crypto.aes.AESCrypto; 27 import tagion.hibon.HiBON; 28 import tagion.hibon.HiBONFile : fread, fwrite; 29 import tagion.hibon.HiBONJSON; 30 import tagion.hibon.HiBONRecord : HiBONRecord; 31 import tagion.script.TagionCurrency; 32 import tagion.script.common; 33 import tagion.utils.StdTime; 34 import tagion.wallet.AccountDetails; 35 import tagion.wallet.KeyRecover; 36 import Wallet = tagion.wallet.SecureWallet; 37 import tagion.wallet.WalletException; 38 import tagion.wallet.WalletRecords : DevicePIN, RecoverGenerator; 39 40 enum TAGION_HASH = import("revision.mixin").splitLines[2]; 41 42 /// Used for describing the d-runtime status 43 enum DrtStatus { 44 DEFAULT_STS, 45 STARTED, 46 TERMINATED 47 } 48 /// Variable, which repsresents the d-runtime status 49 __gshared DrtStatus __runtimeStatus = DrtStatus.DEFAULT_STS; 50 // Wallet global variable. 51 alias StdSecureWallet = Wallet.SecureWallet!(StdSecureNet); 52 53 //static __wallet_storage.wallet = StdSecureWallet(DevicePIN.init); 54 55 // Storage global variable. 56 static WalletStorage* __wallet_storage; 57 58 static StdSecureWallet[string] wallets; 59 static Exception last_error; 60 /// Functions called from d-lang through dart:ffi 61 extern (C) { 62 enum : uint { 63 NOT_LOGGED_IN = 9, 64 PAYMENT_ERROR = 2, 65 } 66 67 // Staritng d-runtime 68 export static int64_t start_rt() { 69 if (__runtimeStatus is DrtStatus.DEFAULT_STS) { 70 __runtimeStatus = DrtStatus.STARTED; 71 return rt_init; 72 } 73 return -1; 74 } 75 76 // Terminating d-runtime 77 export static int64_t stop_rt() { 78 if (__runtimeStatus is DrtStatus.STARTED) { 79 __runtimeStatus = DrtStatus.TERMINATED; 80 return rt_term; 81 } 82 return -1; 83 } 84 85 // Storage should be initialised once with correct file path 86 // before using other wallet's functionality. 87 export uint wallet_storage_init(const char* pathPtr, uint32_t pathLen) { 88 const directoryPath = cast(char[])(pathPtr[0 .. pathLen]); 89 if (directoryPath.length > 0) { 90 // Full path to stored wallet data. 91 const walletDataPath = directoryPath; 92 __wallet_storage = new WalletStorage(walletDataPath); 93 return 1; 94 } 95 96 return 0; 97 } 98 99 // Check if wallet was already created. 100 export uint wallet_check_exist() { 101 return __wallet_storage.isWalletExist(); 102 } 103 104 version (none) export uint wallet_create( 105 const uint8_t* pincodePtr, 106 const uint32_t pincodeLen, 107 const uint16_t* mnemonicPtr, 108 const uint32_t mnemonicLen) { 109 // Restore data from pointers. 110 auto pincode = cast(char[])(pincodePtr[0 .. pincodeLen]); 111 auto mnemonic = mnemonicPtr[0 .. mnemonicLen]; 112 scope (exit) { 113 scramble(pincode); 114 } 115 // Create a wallet from inputs. 116 __wallet_storage.wallet = StdSecureWallet( 117 mnemonic, 118 pincode 119 ); 120 121 return __wallet_storage.write; 122 123 } 124 125 export uint wallet_create( 126 const uint8_t* pincodePtr, 127 const uint32_t pincodeLen, 128 const uint8_t* mnemonicPtr, 129 const uint32_t mnemonicLen, 130 const uint8_t* saltPtr, 131 const uint32_t saltLen) nothrow { 132 try { 133 auto pincode = cast(char[])(pincodePtr[0 .. pincodeLen]); 134 auto mnemonic = cast(char[]) mnemonicPtr[0 .. mnemonicLen]; 135 136 check(saltPtr is null && saltLen is 0 || saltPtr !is null, "Casting went wrong"); 137 138 auto salt = cast(char[]) saltPtr[0 .. saltLen]; 139 scope (exit) { 140 pincode[]=0; 141 mnemonic[]=0; 142 salt[]=0; 143 } 144 // Create a wallet from inputs. 145 __wallet_storage.wallet = StdSecureWallet( 146 mnemonic, 147 pincode, 148 salt 149 ); 150 __wallet_storage.write; 151 } 152 catch (Exception e) { 153 last_error = e; 154 return 0; 155 } 156 return 1; 157 } 158 159 export uint wallet_login(const uint8_t* pincodePtr, const uint32_t pincodeLen) nothrow { 160 161 // Restore data from ponters. 162 try { 163 auto pincode = cast(char[])(pincodePtr[0 .. pincodeLen]); 164 scope (exit) { 165 pincode[]=0; 166 } 167 168 __wallet_storage.read; 169 return __wallet_storage.wallet.login(pincode); 170 } 171 catch (Exception e) { 172 last_error = e; 173 } 174 return 0; 175 } 176 177 export uint wallet_logout() nothrow { 178 if (__wallet_storage.wallet.isLoggedin()) { 179 __wallet_storage.wallet.logout(); 180 // Set wallet to default. 181 __wallet_storage.wallet = __wallet_storage.wallet.init; 182 return 1; 183 } 184 return 0; 185 } 186 187 export uint wallet_check_login() { 188 return __wallet_storage.wallet.isLoggedin(); 189 } 190 191 export uint wallet_delete() { 192 // Try to remove wallet file. 193 if (__wallet_storage !is null && __wallet_storage.remove()) { 194 __wallet_storage.wallet.logout(); 195 // Set wallet to default. 196 //defaultWallet(); 197 __wallet_storage.wallet = __wallet_storage.wallet.init; 198 return 1; 199 } 200 return 0; 201 } 202 203 export uint validate_pin(const uint8_t* pincodePtr, const uint32_t pincodeLen) { 204 // Restore data from ponters. 205 const pincode = cast(char[])(pincodePtr[0 .. pincodeLen]); 206 207 if (__wallet_storage.wallet.isLoggedin()) { 208 return __wallet_storage.wallet.checkPincode(pincode); 209 } 210 return 0; 211 } 212 213 // TODO: Get info if it's possible to change a pincode without providing a current one. 214 export uint change_pin( 215 const uint8_t* pincodePtr, 216 const uint32_t pincodeLen, 217 const uint8_t* newPincodePtr, 218 const uint32_t newPincodeLen) { 219 const pincode = cast(char[])(pincodePtr[0 .. pincodeLen]); 220 const newPincode = cast(char[])(newPincodePtr[0 .. newPincodeLen]); 221 222 if (__wallet_storage.wallet.isLoggedin()) { 223 if (__wallet_storage.wallet.changePincode(pincode, newPincode)) { 224 __wallet_storage.write; 225 version(NET_HACK) { 226 __wallet_storage.read; 227 } 228 // Since secure_wallet do logout after pincode change 229 // we need to perform a login manualy. 230 __wallet_storage.wallet.login(newPincode); 231 return 1; 232 } 233 } 234 return 0; 235 } 236 237 export uint get_fee(const double amount, double* fees) { 238 TagionCurrency tgn_fees; 239 scope (exit) { 240 *fees = tgn_fees.value; 241 } 242 243 const can_pay = __wallet_storage.wallet.getFee(TagionCurrency(amount), tgn_fees); 244 return can_pay.value ? 1 : 0; 245 } 246 247 export uint create_nft_contract( 248 uint32_t* signedContractPtr, 249 uint8_t* nftPtr, 250 const uint32_t nftLen) { 251 252 immutable nftBuff = cast(immutable)(nftPtr[0 .. nftLen]); 253 254 if (__wallet_storage.wallet.isLoggedin()) { 255 auto nft = Document(nftBuff); 256 257 SignedContract signed_contract; 258 259 const is_created = __wallet_storage.wallet.createNFT(nft, signed_contract); 260 if (is_created) { 261 const nftDocId = recyclerDoc.create(signed_contract.toDoc); 262 // Save wallet state to file. 263 __wallet_storage.write; 264 265 *signedContractPtr = nftDocId; 266 return 1; 267 } 268 } 269 return 0; 270 } 271 272 export uint create_contract( 273 uint32_t* contractPtr, 274 const uint8_t* invoicePtr, 275 const uint32_t invoiceLen, 276 const double amount, 277 double* fees, 278 uint32_t errorLen, 279 uint8_t* errorPtr, 280 ) { 281 282 immutable invoiceBuff = cast(immutable)(invoicePtr[0 .. invoiceLen]); 283 TagionCurrency tgn_fees; 284 scope (exit) { 285 *fees = tgn_fees.value; 286 287 } 288 289 if (!__wallet_storage.wallet.isLoggedin()) { 290 return NOT_LOGGED_IN; 291 } 292 293 auto invoice = Invoice(Document(invoiceBuff)); 294 invoice.amount = TagionCurrency(amount); 295 296 SignedContract signed_contract; 297 const can_pay = 298 __wallet_storage.wallet.payment([invoice], signed_contract, tgn_fees); 299 if (can_pay) { 300 const contract_net = __wallet_storage.wallet.net; 301 const hirpc = HiRPC(contract_net); 302 const contract = hirpc.submit(signed_contract); 303 const contractDocId = recyclerDoc.create(contract.toDoc); 304 // Save wallet state to file. 305 __wallet_storage.write; 306 version(NET_HACK) { 307 __wallet_storage.read; 308 } 309 310 *contractPtr = contractDocId; 311 return 1; 312 } 313 auto error_result = new HiBON(); 314 if (can_pay.msg is null) { 315 error_result["error"] = "error is null?"; 316 } else { 317 error_result["error"] = can_pay.msg; 318 } 319 const errorDocId = recyclerDoc.create(Document(error_result)); 320 *errorPtr = cast(uint8_t) errorDocId; 321 322 return PAYMENT_ERROR; 323 } 324 325 export uint create_invoice( 326 uint8_t* invoicePtr, 327 const double amount, 328 const char* labelPtr, 329 const uint32_t labelLen) { 330 331 immutable label = cast(immutable)(labelPtr[0 .. labelLen]); 332 333 if (__wallet_storage.wallet.isLoggedin()) { 334 auto invoice = StdSecureWallet.createInvoice( 335 label, amount.TGN); 336 __wallet_storage.wallet.registerInvoice(invoice); 337 338 const invoiceDocId = recyclerDoc.create(invoice.toDoc); 339 // Save wallet state to file. 340 __wallet_storage.write; 341 version(NET_HACK) { 342 __wallet_storage.read; 343 } 344 345 *invoicePtr = cast(uint8_t) invoiceDocId; 346 return 1; 347 } 348 return 0; 349 } 350 351 export uint request_update(uint8_t* requestPtr) { 352 353 if (!__wallet_storage.wallet.isLoggedin()) { 354 return NOT_LOGGED_IN; 355 356 } 357 const request = __wallet_storage.wallet.getRequestUpdateWallet(); 358 const requestDocId = recyclerDoc.create(request.toDoc); 359 *requestPtr = cast(uint8_t) requestDocId; 360 return 1; 361 } 362 363 export uint update_response(uint8_t* responsePtr, uint32_t responseLen) { 364 import tagion.hibon.HiBONException; 365 366 immutable response = cast(immutable)(responsePtr[0 .. responseLen]); 367 368 if (!__wallet_storage.wallet.isLoggedin()) { 369 return NOT_LOGGED_IN; 370 } 371 372 HiRPC hirpc = HiRPC(__wallet_storage.wallet.net); 373 try { 374 auto receiver = hirpc.receive(Document(response)); 375 const result = __wallet_storage.wallet.setResponseUpdateWallet(receiver); 376 377 if (result) { 378 // Save wallet state to file. 379 __wallet_storage.write; 380 version(NET_HACK) { 381 __wallet_storage.read; 382 } 383 return 1; 384 } 385 } 386 catch (HiBONException e) { 387 return 0; 388 } 389 return 0; 390 } 391 392 // export void toPretty(uint8_t* docPtr, uint32_t responseLen, char* resultPtr, uint32_t* resultLen) { 393 export void toPretty(uint8_t* docPtr, uint32_t responseLen, uint8_t* resultPtr) { 394 immutable res = cast(immutable)(docPtr[0 .. responseLen]); 395 Document doc = Document(res); 396 397 import tagion.hibon.HiBONJSON : toPretty; 398 399 string docToPretty = doc.toPretty; 400 401 auto result = new HiBON(); 402 result["pretty"] = docToPretty; 403 404 const resultDocId = recyclerDoc.create(Document(result)); 405 *resultPtr = cast(uint8_t) resultDocId; 406 // resultPtr = cast(char*) &result[0]; 407 // *resultLen = cast(uint32_t) result.length; 408 } 409 410 @safe 411 export double get_locked_balance() { 412 const balance = __wallet_storage.wallet.locked_balance(); 413 return balance.value; 414 } 415 416 @safe 417 export double get_balance() { 418 const balance = __wallet_storage.wallet.available_balance(); 419 return balance.value; 420 } 421 422 @safe 423 export double get_total_balance() { 424 const balance = __wallet_storage.wallet.total_balance(); 425 return balance.value; 426 } 427 428 export uint get_public_key(uint8_t* pubkeyPtr) { 429 if (__wallet_storage.wallet.isLoggedin()) { 430 const pubkey = __wallet_storage.wallet.getPublicKey(); 431 432 auto result = new HiBON(); 433 result["pubkey"] = pubkey; 434 435 const pubkeyDocId = recyclerDoc.create(Document(result)); 436 437 *pubkeyPtr = cast(uint8_t) pubkeyDocId; 438 439 return 1; 440 } 441 return 0; 442 } 443 444 export uint get_derivers_state(uint8_t* deriversStatePtr) { 445 if (__wallet_storage.wallet.isLoggedin()) { 446 const deriversState = __wallet_storage.wallet.getDeriversState(); 447 448 auto result = new HiBON(); 449 result["derivers_state"] = deriversState; 450 451 const deviversStateDocId = recyclerDoc.create(Document(result)); 452 453 *deriversStatePtr = cast(uint8_t) deviversStateDocId; 454 455 return 1; 456 } 457 return 0; 458 } 459 460 export uint get_account(uint8_t* accountPtr) { 461 if (__wallet_storage.wallet.isLoggedin()) { 462 463 const accountDocId = recyclerDoc.create(__wallet_storage.wallet.account.toDoc); 464 465 *accountPtr = cast(uint8_t) accountDocId; 466 467 return 1; 468 } 469 return 0; 470 } 471 472 version (none) export uint set_derivers(const uint8_t* deriversPtr, const uint32_t deriversLen) { 473 474 immutable encDerivers = cast(immutable)(deriversPtr[0 .. deriversLen]); 475 476 if (__wallet_storage.wallet.isLoggedin()) { 477 __wallet_storage.wallet.setEncrDerivers(Cipher.CipherDocument(Document(encDerivers))); 478 return 1; 479 } 480 return 0; 481 } 482 483 export uint get_backup(uint8_t* backupPtr) { 484 if (__wallet_storage.wallet.isLoggedin()) { 485 const encrAccount = __wallet_storage.wallet.getEncrAccount(); 486 const backupDocId = recyclerDoc.create(encrAccount.toDoc); 487 488 *backupPtr = cast(uint8_t) backupDocId; 489 490 return 1; 491 } 492 return 0; 493 } 494 495 export uint set_backup(const uint8_t* backupPtr, const uint32_t backupLen) { 496 497 immutable account = cast(immutable)(backupPtr[0 .. backupLen]); 498 499 if (__wallet_storage.wallet.isLoggedin()) { 500 __wallet_storage.wallet.setEncrAccount(Cipher.CipherDocument(Document(account))); 501 __wallet_storage.write; 502 version(NET_HACK) { 503 __wallet_storage.read; 504 } 505 return 1; 506 } 507 return 0; 508 } 509 510 export uint add_bill(const uint8_t* billPtr, const uint32_t billLen) { 511 512 immutable billBuffer = cast(immutable)(billPtr[0 .. billLen]); 513 514 if (__wallet_storage.wallet.isLoggedin()) { 515 auto bill = TagionBill(Document(billBuffer)); 516 __wallet_storage.wallet.account.add_bill(bill); 517 return 1; 518 } 519 return 0; 520 } 521 522 export uint remove_bill(uint8_t* pubKeyPtr, uint32_t pubKeyLen) { 523 immutable(ubyte)[] pubKey = cast(immutable(ubyte)[])(pubKeyPtr[0 .. pubKeyLen]); 524 525 if (__wallet_storage.wallet.isLoggedin()) { 526 const result = __wallet_storage.wallet.account.remove_bill(Pubkey(pubKey)); 527 return result; 528 } 529 return 0; 530 } 531 532 export uint remove_bills_by_contract(const uint8_t* contractPtr, const uint32_t contractLen) { 533 // Collect input and output keys from the contract. 534 // Iterate them and call remove on each. 535 536 immutable contractBuffer = cast(immutable)(contractPtr[0 .. contractLen]); 537 538 if (__wallet_storage.wallet.isLoggedin()) { 539 540 auto contractDoc = Document(contractBuffer); 541 542 // Contract inputs. 543 const messageTag = "$msg"; 544 const paramsTag = "params"; 545 546 auto messageDoc = contractDoc[messageTag].get!Document; 547 auto paramsDoc = messageDoc[paramsTag].get!Document; 548 auto sContract = SignedContract(paramsDoc); 549 550 import std.algorithm; 551 552 sContract.contract.inputs.each!(hash => __wallet_storage.wallet.account.remove_bill_by_hash(hash)); 553 554 return 1; 555 } 556 return 0; 557 } 558 559 export uint ulock_bills_by_contract(const uint8_t* contractPtr, const uint32_t contractLen) { 560 561 immutable contractBuffer = cast(immutable)(contractPtr[0 .. contractLen]); 562 563 if (__wallet_storage.wallet.isLoggedin()) { 564 565 auto contractDoc = Document(contractBuffer); 566 567 // Contract inputs. 568 const messageTag = "$msg"; 569 const paramsTag = "params"; 570 571 auto messageDoc = contractDoc[messageTag].get!Document; 572 auto paramsDoc = messageDoc[paramsTag].get!Document; 573 auto sContract = SignedContract(paramsDoc); 574 575 import std.algorithm; 576 577 sContract.contract.inputs.each!(hash => __wallet_storage.wallet.account.unlock_bill_by_hash(hash)); 578 579 return 1; 580 } 581 return 0; 582 } 583 584 export uint check_contract_payment(const uint8_t* contractPtr, const uint32_t contractLen, uint8_t* statusPtr) { 585 immutable contractBuffer = cast(immutable)(contractPtr[0 .. contractLen]); 586 587 if (__wallet_storage.wallet.isLoggedin()) { 588 589 auto contractDoc = Document(contractBuffer); 590 591 // Contract inputs. 592 const messageTag = "$msg"; 593 const paramsTag = "params"; 594 595 auto messageDoc = contractDoc[messageTag].get!Document; 596 auto paramsDoc = messageDoc[paramsTag].get!Document; 597 auto sContract = SignedContract(paramsDoc); 598 const outputs = PayScript(sContract.contract.script).outputs.map!(output => output.toDoc).array; 599 600 int status = __wallet_storage.wallet.account.check_contract_payment( 601 sContract.contract.inputs, outputs); 602 603 *statusPtr = cast(uint8_t) status; 604 return 1; 605 } 606 return 0; 607 } 608 609 export uint check_invoice_payment(const uint8_t* invoicePtr, const uint32_t invoiceLen, double* amountPtr) { 610 immutable invoiceBuffer = cast(immutable)(invoicePtr[0 .. invoiceLen]); 611 612 if (__wallet_storage.wallet.isLoggedin()) { 613 614 auto amount = TagionCurrency(0); 615 auto invoice = Invoice(Document(invoiceBuffer)); 616 auto isExist = __wallet_storage.wallet.account.check_invoice_payment(invoice.pkey, amount); 617 618 if (isExist) { 619 *amountPtr = amount.value; 620 return 1; 621 } 622 } 623 return 0; 624 } 625 } 626 627 unittest { 628 const work_path = new_test_path; 629 scope (success) { 630 work_path.rmdirRecurse; 631 } 632 { // Init storage should fail with empty path. 633 __wallet_storage = null; 634 const char[] path; 635 const pathLen = cast(uint32_t) path.length; 636 const result = wallet_storage_init(path.ptr, pathLen); 637 // Check the result 638 assert(result == 0); 639 assert(__wallet_storage is null); 640 } 641 642 { // Init storage with correct path. 643 const path = work_path; 644 const uint32_t pathLen = cast(uint32_t) path.length; 645 const uint result = wallet_storage_init(path.ptr, pathLen); 646 // Check the result 647 assert(result != 0, "Expected non-zero result"); 648 assert(__wallet_storage !is null); 649 } 650 651 { // Wallet create. 652 // Create input data 653 const uint8_t[] pincode = cast(uint8_t[]) "1234".dup; 654 const uint32_t pincodeLen = cast(uint32_t) pincode.length; 655 const uint8_t[] mnemonic = cast(uint8_t[]) "some words".dup; 656 const uint32_t mnemonicLen = cast(uint32_t) mnemonic.length; 657 const uint8_t[] salt = cast(uint8_t[]) "salt".dup; 658 const uint32_t saltLen = cast(uint32_t) salt.length; 659 660 // Call the wallet_create function 661 const uint result = wallet_create(pincode.ptr, pincodeLen, mnemonic.ptr, mnemonicLen, salt.ptr, saltLen); 662 663 // Check the result 664 assert(result != 0, "Expected non-zero result"); 665 } 666 667 { // Check if wallet was created. 668 const uint result = wallet_check_exist(); 669 // Check the result 670 assert(result != 0, "Expected non-zero result"); 671 } 672 673 { // Fail to login to a wallet with an incorrect pincode. 674 const uint8_t[] pincode = cast(uint8_t[]) "5555".dup; 675 const uint32_t pincodeLen = cast(uint32_t) pincode.length; 676 const uint result = wallet_login(pincode.ptr, pincodeLen); 677 // Check the result 678 assert(result == 0); 679 } 680 681 { // Login to wallet with a correct pincode. 682 const uint8_t[] pincode = cast(uint8_t[]) "1234".dup; 683 const uint32_t pincodeLen = cast(uint32_t) pincode.length; 684 const uint result = wallet_login(pincode.ptr, pincodeLen); 685 // Check the result 686 assert(result != 0, "Expected non-zero result"); 687 } 688 689 { // Check login 690 const uint result = wallet_check_login(); 691 // Check the result 692 assert(result != 0, "Expected non-zero result"); 693 } 694 695 { // Logout wallet. 696 const result = wallet_logout(); 697 assert(result != 0, "Expected non-zero result"); 698 assert(!__wallet_storage.wallet.isLoggedin); 699 } 700 701 { // Delete wallet. 702 703 const result = wallet_delete(); 704 assert(result != 0, "Expected non-zero result"); 705 assert(!__wallet_storage.wallet.isLoggedin); 706 assert(!__wallet_storage.isWalletExist); 707 } 708 709 { // Validate pin. 710 711 const uint8_t[] pincode = cast(uint8_t[]) "1234".dup; 712 const uint32_t pincodeLen = cast(uint32_t) pincode.length; 713 const uint8_t[] mnemonic = cast(uint8_t[]) "some words".dup; 714 const uint32_t mnemonicLen = cast(uint32_t) mnemonic.length; 715 uint8_t[] pin_copy; 716 // Call the wallet_create function 717 pin_copy = pincode.dup; 718 wallet_create(pin_copy.ptr, pincodeLen, mnemonic.ptr, mnemonicLen, const(uint8_t*).init, uint32_t.init); 719 pin_copy = pincode.dup; 720 wallet_login(pin_copy.ptr, pincodeLen); 721 722 const uint result = validate_pin(pincode.ptr, pincodeLen); 723 assert(result != 0, "Expected non-zero result"); 724 } 725 726 { // Validate pin should fail on incorrect pincode. 727 const uint8_t[] pincode = cast(uint8_t[]) "5555".dup; 728 const uint32_t pincodeLen = cast(uint32_t) pincode.length; 729 const uint result = validate_pin(pincode.ptr, pincodeLen); 730 assert(result == 0); 731 } 732 733 { // Change pincode. 734 const uint8_t[] pincode = cast(uint8_t[]) "1234".dup; 735 const uint32_t pincodeLen = cast(uint32_t) pincode.length; 736 const uint8_t[] newPincode = cast(uint8_t[]) "5555".dup; 737 const uint32_t newPincodeLen = cast(uint32_t) newPincode.length; 738 const uint result = change_pin(pincode.ptr, pincodeLen, newPincode.ptr, newPincodeLen); 739 assert(result != 0, "Expected non-zero result"); 740 assert(__wallet_storage.wallet.isLoggedin, "Expected wallet stays logged in"); 741 } 742 743 // Create input data 744 const uint64_t invAmount = 100; 745 const char[] label = "Test Invoice"; 746 const uint32_t labelLen = cast(uint32_t) label.length; 747 uint8_t invoiceDocId; 748 749 { // Create invoice. 750 751 // Call the create_invoice function 752 const uint result = create_invoice(&invoiceDocId, invAmount, label.ptr, labelLen); 753 754 // Check the result 755 assert(result == 1, "Expected result to be 1"); 756 757 // Verify that invoiceDocId is non-zero 758 assert(invoiceDocId != 0, "Expected non-zero invoiceDocId"); 759 } 760 761 import std.algorithm : map; 762 import std.range : zip; 763 import std.string : representation; 764 765 auto bill_amounts = [200, 500, 100].map!(a => a.TGN); 766 [200, 500, 100] 767 .map!(value => value.TGN) 768 .map!(value => __wallet_storage.wallet.requestBill(value)) 769 .each!(bill => __wallet_storage.wallet.addBill(bill)); 770 771 const net = new StdHashNet; 772 // Add the bills to the account with the derive keys 773 with (__wallet_storage.wallet.account) { 774 775 bills = zip(bill_amounts, derivers.byKey) 776 .map!(bill_derive => TagionBill( 777 bill_derive[0], 778 currentTime, 779 bill_derive[1], 780 Buffer.init)).array; 781 } 782 783 auto invoiceDoc = recyclerDoc(invoiceDocId); 784 785 // Contract input data. 786 const uint8_t[] invoice = cast(uint8_t[])(invoiceDoc.serialize); 787 const uint32_t invoiceLen = cast(uint32_t) invoice.length; 788 const uint64_t contAmount = 100; 789 const uint32_t errorLen = 0; 790 uint8_t errorPtr; 791 792 uint32_t contractDocId; 793 794 { // Create a contract. 795 double fees; 796 const uint result = create_contract(&contractDocId, invoice.ptr, invoiceLen, contAmount, &fees, errorLen, &errorPtr); 797 // Check the result 798 assert(result == 1, "Expected result to be 1"); 799 800 // Verify that invoiceDocId is non-zero 801 assert(contractDocId != 0, "Expected non-zero contractDocId"); 802 } 803 804 auto contractDoc = recyclerDoc(contractDocId); 805 806 const uint8_t[] contract = cast(uint8_t[])(contractDoc.serialize); 807 const uint32_t contractLen = cast(uint32_t) contract.length; 808 809 { // Update request. 810 uint8_t requestDocId; 811 const uint result = request_update(&requestDocId); 812 813 // Check the result 814 assert(result == 1, "Expected result to be 1"); 815 816 // Verify that invoiceDocId is non-zero 817 assert(requestDocId != 0, "Expected non-zero requestDocId"); 818 } 819 { // Get public key. 820 uint8_t pubkeyDocId; 821 uint result = get_public_key(&pubkeyDocId); 822 823 // Check the result 824 assert(result == 1, "Expected result to be 1"); 825 826 // Verify that invoiceDocId is non-zero 827 assert(pubkeyDocId != 0, "Expected non-zero pubkeyDocId"); 828 } 829 830 { // Get and set derivers. 831 uint8_t backupDocId; 832 uint getBackupResult = get_backup(&backupDocId); 833 834 // Check the result 835 assert(getBackupResult == 1, "Expected result to be 1"); 836 837 // Verify that invoiceDocId is non-zero 838 assert(backupDocId != 0, "Expected non-zero backupDocId"); 839 840 auto backupDoc = recyclerDoc(backupDocId); 841 842 // Derivers input data. 843 const uint8_t[] backup = cast(uint8_t[])(backupDoc.serialize); 844 const uint32_t backupLen = cast(uint32_t) backup.length; 845 846 uint setBackupResult = set_backup(backup.ptr, backupLen); 847 848 // Check the result 849 assert(setBackupResult == 1, "Expected result to be 1"); 850 } 851 852 { // Get derivers state. 853 uint8_t deriversDocId; 854 uint getDResult = get_derivers_state(&deriversDocId); 855 856 // Check the result 857 assert(getDResult == 1, "Expected result to be 1"); 858 859 // Verify that invoiceDocId is non-zero 860 assert(deriversDocId != 0, "Expected non-zero deriversDocId"); 861 } 862 863 { // Get Account 864 uint8_t accountDocId; 865 uint getAResult = get_account(&accountDocId); 866 867 // Check the result 868 assert(getAResult == 1, "Expected result to be 1"); 869 870 // Verify that invoiceDocId is non-zero 871 assert(accountDocId != 0, "Expected non-zero accountDocId"); 872 } 873 874 { // Ulock bills by contract 875 876 uint result = ulock_bills_by_contract(contract.ptr, contractLen); 877 878 // Check the result 879 assert(result == 1, "Expected result to be 1"); 880 } 881 { // Check invoice payment 882 883 double amount; 884 auto result = check_invoice_payment(invoice.ptr, invoiceLen, &amount); 885 886 // Check the result 887 assert(result == 1, "Expected result to be 1"); 888 assert(amount != 0, "Expected amount not to be 0"); 889 } 890 { // Check contract payment 891 892 uint8_t status; 893 894 uint result = check_contract_payment(contract.ptr, contractLen, &status); 895 896 // Check the result 897 assert(result == 1, "Expected result to be 1"); 898 assert(status == 0, "Expected status to be 0"); 899 } 900 { // Remove bills by contract. 901 902 uint result = remove_bills_by_contract(contract.ptr, contractLen); 903 904 // Check the result 905 assert(result == 1, "Expected result to be 1"); 906 } 907 } 908 909 alias Store = WalletStorage; 910 struct WalletStorage { 911 StdSecureWallet wallet; 912 enum { 913 accountfile = "account".setExtension(FileExtension.hibon), /// account file name 914 walletfile = "wallet".setExtension( 915 FileExtension.hibon), /// wallet file name 916 devicefile = "device".setExtension(FileExtension.hibon), /// device file name 917 } 918 string wallet_data_path; 919 this(const char[] walletDataPath) { 920 import std.stdio; 921 922 wallet_data_path = walletDataPath.idup; 923 writefln("CREATE %s", walletDataPath); 924 import std.file; 925 926 if (!wallet_data_path.exists) { 927 wallet_data_path.mkdirRecurse; 928 } 929 if (isWalletExist) { 930 read; 931 } 932 } 933 934 string path(string filename) const pure { 935 return buildPath(wallet_data_path, filename); 936 } 937 938 bool isWalletExist() const { 939 return only(accountfile, walletfile, devicefile) 940 .map!(file => path(file).exists) 941 .any; 942 } 943 944 void write() const { 945 // Create a hibon for wallet data. 946 947 path(devicefile).fwrite(wallet.pin); 948 path(accountfile).fwrite(wallet.account); 949 path(walletfile).fwrite(wallet.wallet); 950 951 } 952 953 void read() { 954 955 version(NET_HACK) { 956 auto _pin = path(devicefile).fread!DevicePIN; 957 auto _wallet = path(walletfile).fread!RecoverGenerator; 958 auto _account = path(accountfile).fread!AccountDetails; 959 960 if (wallet.net !is null) { 961 auto __net = cast(shared(StdSecureNet)) wallet.net; 962 scope(exit) { 963 __net = null; 964 } 965 auto copied_net = new StdSecureNet(__net); 966 wallet = StdSecureWallet(_pin, _wallet, _account); 967 968 wallet.set_net(copied_net); 969 } 970 else { 971 wallet = StdSecureWallet(_pin, _wallet, _account); 972 } 973 } 974 else { 975 auto _pin = path(devicefile).fread!DevicePIN; 976 auto _wallet = path(walletfile).fread!RecoverGenerator; 977 auto _account = path(accountfile).fread!AccountDetails; 978 wallet = StdSecureWallet(_pin, _wallet, _account); 979 } 980 981 } 982 983 bool remove() const { 984 if (wallet_data_path.exists) { 985 986 try { 987 only(accountfile, walletfile, devicefile) 988 .each!(file => path(file).remove); 989 //wallet_data_path.rmdir; 990 return 1; 991 } 992 catch (Exception e) { 993 last_error = e; 994 return false; 995 } 996 } 997 return false; 998 } 999 } 1000 1001 unittest { 1002 import std.exception; 1003 import std.stdio; 1004 1005 const work_path = new_test_path; 1006 scope (success) { 1007 work_path.rmdirRecurse; 1008 } 1009 scope (failure) { 1010 writefln("failed work_path %s", work_path); 1011 } 1012 { // Write wallet file. 1013 1014 // Path to stored wallet data. 1015 const walletDataPath = work_path; 1016 1017 auto strg = new WalletStorage(walletDataPath); 1018 1019 assertNotThrown(strg.write(), "Expect write result is true"); 1020 } 1021 1022 { // Read wallet file. 1023 1024 // Path to stored wallet data. 1025 const walletDataPath = work_path; 1026 1027 auto strg = new WalletStorage(walletDataPath); 1028 1029 StdSecureWallet secure_wallet; 1030 1031 assertNotThrown(strg.read(), "Expect read result is true"); 1032 } 1033 1034 { // Check if wallet file is exist. 1035 1036 // Path to stored wallet data. 1037 const walletDataPath = work_path; 1038 1039 auto strg = new WalletStorage(walletDataPath); 1040 bool result = strg.isWalletExist(); 1041 assert(result, "Expect read result is true"); 1042 } 1043 1044 { // Delete wallet file. 1045 1046 // Path to stored wallet data. 1047 const walletDataPath = work_path; 1048 1049 auto strg = new WalletStorage(walletDataPath); 1050 bool result = strg.remove(); 1051 assert(result, "Expect read result is true"); 1052 } 1053 1054 } 1055 1056 version (unittest) { 1057 import std.conv : to; 1058 import std.file; 1059 1060 string new_test_path(string func = __FUNCTION__, const size_t line = __LINE__) { 1061 const result_path = [deleteme, func, line.to!string].join("_"); 1062 result_path.mkdirRecurse; 1063 return result_path; 1064 } 1065 }