Beispiel #1
0
 function do_spend_internal($args, $reqs, $msg, &$ok, &$assetid, &$issuer, &$storagefee, &$digits)
 {
     $t = $this->t;
     $u = $this->u;
     $db = $this->db;
     $bankid = $this->bankid;
     $parser = $this->parser;
     $ok = false;
     // $t->SPEND => array($t->BANKID,$t->TIME,$t->ID,$t->ASSET,$t->AMOUNT,$t->NOTE=>1),
     $id = $args[$t->CUSTOMER];
     $time = $args[$t->TIME];
     $id2 = $args[$t->ID];
     $assetid = $args[$t->ASSET];
     $amount = $args[$t->AMOUNT];
     $note = @$args[$t->NOTE];
     // Burn the transaction, even if balances don't match.
     $err = $this->deq_time($id, $time);
     if ($err) {
         return $this->failmsg($msg, $err);
     }
     if ($id2 == $bankid) {
         return $this->failmsg($msg, "Spends to the bank are not allowed.");
     }
     $asset = $this->lookup_asset($assetid);
     if (!$asset) {
         return $this->failmsg($msg, "Unknown asset id: {$assetid}");
     }
     if (is_string($asset)) {
         return $this->failmsg($msg, "Bad asset: {$asset}");
     }
     if (!is_numeric($amount)) {
         return $this->failmsg($msg, "Not a number: {$amount}");
     }
     // Make sure there are no inbox entries older than the highest
     // timestamp last read from the inbox
     $inbox = $this->scaninbox($id);
     $last = $this->getacctlast($id);
     foreach ($inbox as $inmsg) {
         $inmsg_args = $this->unpack_bankmsg($inmsg);
         if ($last < 0 || bccomp($inmsg_args[$t->TIME], $last) <= 0) {
             return $this->failmsg($msg, "Please process your inbox before doing a spend");
         }
     }
     $tokens = 0;
     $tokenid = $this->tokenid;
     $feemsg = '';
     $storagemsg = '';
     $fracmsg = '';
     if ($id != $id2 && $id != $bankid) {
         // Spends to yourself are free, as are spends from the bank
         $tokens = $this->tranfee;
     }
     $bals = array();
     $bals[$tokenid] = 0;
     if ($id != $id2) {
         // No money changes hands on spends to yourself
         $bals[$assetid] = bcsub(0, $amount);
     }
     $acctbals = array();
     $accts = array();
     $oldneg = array();
     $newneg = array();
     $state = array('acctbals' => $acctbals, 'bals' => $bals, 'tokens' => $tokens, 'accts' => $accts, 'oldneg' => $oldneg, 'newneg' => $newneg, 'time' => $time);
     $outboxhashreq = false;
     $balancehashreq = false;
     for ($i = 1; $i < count($reqs); $i++) {
         $req = $reqs[$i];
         $reqargs = $u->match_pattern($req);
         if (is_string($reqargs)) {
             return $this->failmsg($msg, $reqargs);
         }
         // match error
         $reqid = $reqargs[$t->CUSTOMER];
         $request = $reqargs[$t->REQUEST];
         $reqtime = $reqargs[$t->TIME];
         if ($reqtime != $time) {
             return $this->failmsg($msg, "Timestamp mismatch");
         }
         if ($reqid != $id) {
             return $this->failmsg($msg, "ID mismatch");
         }
         $reqmsg = $parser->get_parsemsg($req);
         if ($request == $t->TRANFEE) {
             if ($feemsg) {
                 return $this->failmsg($msg, $t->TRANFEE . ' appeared multiple times');
             }
             $tranasset = $reqargs[$t->ASSET];
             $tranamt = $reqargs[$t->AMOUNT];
             if ($tranasset != $tokenid || $tranamt != $tokens) {
                 return $this->failmsg($msg, "Mismatched tranfee asset or amount ({$tranasset} <> {$tokenid} || {$tranamt} <> {$tokens})");
             }
             $feemsg = $this->bankmsg($t->ATTRANFEE, $reqmsg);
         } elseif ($request == $t->STORAGEFEE) {
             if ($storagemsg) {
                 return $this->failmsg($msg, $t->STORAGEFEE . ' appeared multiple times');
             }
             $storageasset = $reqargs[$t->ASSET];
             $storageamt = $reqargs[$t->AMOUNT];
             if ($storageasset != $assetid) {
                 return $this->failmsg($msg, "Storage fee asset id doesn't match spend");
             }
             $storagemsg = $this->bankmsg($t->ATSTORAGEFEE, $reqmsg);
         } elseif ($request == $t->FRACTION) {
             if ($fracmsg) {
                 return $this->failmsg($msg, $t->FRACTION . ' appeared multiple times');
             }
             $fracasset = $reqargs[$t->ASSET];
             $fracamt = $reqargs[$t->AMOUNT];
             if ($fracasset != $assetid) {
                 return $this->failmsg($msg, "Fraction asset id doesn't match spend");
             }
             $fracmsg = $this->bankmsg($t->ATFRACTION, $reqmsg);
         } elseif ($request == $t->BALANCE) {
             if ($time != $reqargs[$t->TIME]) {
                 return $this->failmsg($msg, "Time mismatch in balance item");
             }
             $errmsg = $this->handle_balance_msg($id, $reqmsg, $reqargs, $state);
             if ($errmsg) {
                 return $this->failmsg($msg, $errmsg);
             }
             $newbals[] = $reqmsg;
         } elseif ($request == $t->OUTBOXHASH) {
             if ($outboxhashreq) {
                 return $this->failmsg($msg, $t->OUTBOXHASH . " appeared multiple times");
             }
             if ($time != $reqargs[$t->TIME]) {
                 return $this->failmsg($msg, "Time mismatch in outboxhash");
             }
             $outboxhashreq = $req;
             $outboxhashmsg = $reqmsg;
             $outboxhash = $reqargs[$t->HASH];
             $outboxhashcnt = $reqargs[$t->COUNT];
         } elseif ($request == $t->BALANCEHASH) {
             if ($balancehashreq) {
                 return $this->failmsg($msg, $t->BALANCEHASH . " appeared multiple times");
             }
             if ($time != $reqargs[$t->TIME]) {
                 return $this->failmsg($msg, "Time mismatch in balancehash");
             }
             $balancehashreq = $req;
             $balancehash = $reqargs[$t->HASH];
             $balancehashcnt = $reqargs[$t->COUNT];
             $balancehashmsg = $reqmsg;
         } else {
             return $this->failmsg($msg, "{$request} not valid for spend. Only " . $t->TRANFEE . ', ' . $t->BALANCE . ", and " . $t->OUTBOXHASH);
         }
     }
     $acctbals = $state['acctbals'];
     $bals = $state['bals'];
     $tokens = $state['tokens'];
     $accts = $state['accts'];
     $oldneg = $state['oldneg'];
     $newneg = $state['newneg'];
     $charges = $state['charges'];
     // Work the storage fee into the balances
     $storagefee = false;
     if ($charges) {
         $assetinfo = $charges[$assetid];
         if ($assetinfo) {
             $percent = $assetinfo['storagefee'];
             if ($percent) {
                 $issuer = $assetinfo['issuer'];
                 $storagefee = $assetinfo['storagefee'];
                 $fraction = $assetinfo['fraction'];
                 $digits = $assetinfo['digits'];
                 $bal = $bals[$assetid];
                 $bal = bcsub($bal, $storagefee, $digits);
                 $u->normalize_balance($bal, $fraction, $digits);
                 $bals[$assetid] = $bal;
                 if (bccomp($fraction, $fracamt) != 0) {
                     return $this->failmsg($msg, "Fraction amount was: {$fractamt}, sb: {$fraction}");
                 }
                 if (bccomp($storagefee, $storageamt) != 0) {
                     return $this->failmsg($msg, "Storage fee was: {$storageamt}, sb: {$storagefee}");
                 }
             }
         }
     }
     if (!$storagefee && ($storagemsg || $fracmsg)) {
         return $this->failmsg($msg, "Storage or fraction included when no storage fee");
     }
     // tranfee must be included if there's a transaction fee
     if ($tokens != 0 && !$feemsg && $id != $id2) {
         return $this->failmsg($msg, $t->TRANFEE . " missing");
     }
     if (bccomp($amount, 0) < 0) {
         // Negative spend allowed only for switching issuer location
         if (!$oldneg[$assetid]) {
             return $this->failmsg($msg, "Negative spend on asset for which you are not the issuer");
         }
         // Spending out the issuance.
         // Mark the new "acct" for the negative as being the spend itself.
         if (!$newneg[$assetid]) {
             $newneg[$assetid] = $args;
         }
     }
     // Check that we have exactly as many negative balances after the transaction
     // as we had before.
     if (count($oldneg) != count($newneg)) {
         return $this->failmsg($msg, "Negative balance count not conserved");
     }
     foreach ($oldneg as $asset => $acct) {
         if (!$newneg[$asset]) {
             return $this->failmsg($msg, "Negative balance assets not conserved");
         }
     }
     // Charge the transaction and new balance file tokens;
     $bals[$tokenid] = bcsub($bals[$tokenid], $tokens);
     $errmsg = "";
     $first = true;
     // Check that the balances in the spend message, match the current balance,
     // minus amount spent minus fees.
     foreach ($bals as $balasset => $balamount) {
         if (bccomp($balamount, 0) != 0) {
             $name = $this->lookup_asset_name($balasset);
             if (!$first) {
                 $errmsg .= ', ';
             }
             $first = false;
             $errmsg .= "{$name}: {$balamount}";
         }
     }
     if ($errmsg != '') {
         return $this->failmsg($msg, "Balance discrepancies: {$errmsg}");
     }
     // Check outboxhash
     // outboxhash must be included, except on self spends
     $spendmsg = $parser->get_parsemsg($reqs[0]);
     if ($id != $id2 && $id != $bankid) {
         if (!$outboxhashreq) {
             return $this->failmsg($msg, $t->OUTBOXHASH . " missing");
         } else {
             $hasharray = $this->outboxhash($id, $spendmsg);
             $hash = $hasharray[$t->HASH];
             $hashcnt = $hasharray[$t->COUNT];
             if ($outboxhash != $hash || $outboxhashcnt != $hashcnt) {
                 return $this->failmsg($msg, $t->OUTBOXHASH . ' mismatch');
             }
         }
     }
     // balancehash must be included, except on bank spends
     if ($id != $bankid) {
         if (!$balancehashreq) {
             return $this->failmsg($msg, $t->BALANCEHASH . " missing");
         } else {
             $hasharray = $u->balancehash($db, $id, $this, $acctbals);
             $hash = $hasharray[$t->HASH];
             $hashcnt = $hasharray[$t->COUNT];
             if ($balancehash != $hash || $balancehashcnt != $hashcnt) {
                 return $this->failmsg($msg, $t->BALANCEHASH . " mismatch, hash sb: {$hash}, was: {$balancehash}, count sb: {$hashcnt}, was: {$balancehashcnt}");
             }
         }
     }
     // All's well with the world. Commit this puppy.
     // Eventually, the commit will be done as a second phase.
     $outbox_item = $this->bankmsg($t->ATSPEND, $spendmsg);
     if ($feemsg) {
         $outbox_item .= ".{$feemsg}";
     }
     $res = $outbox_item;
     $newtime = false;
     if ($id2 != $t->COUPON) {
         if ($id != $id2) {
             $newtime = $this->gettime();
             $inbox_item = $this->bankmsg($t->INBOX, $newtime, $spendmsg);
             if ($feemsg) {
                 $inbox_item .= ".{$feemsg}";
             }
         }
     } else {
         // If it's a coupon request, generate the coupon
         $ssl = $this->ssl;
         $random = $this->random;
         if (!$random) {
             require_once "LoomRandom.php";
             $random = new LoomRandom();
             $this->random = $random;
         }
         $coupon_number = $random->random_id();
         $bankurl = $this->bankurl;
         if ($note) {
             $coupon = $this->bankmsg($t->COUPON, $bankurl, $coupon_number, $assetid, $amount, $note);
         } else {
             $coupon = $this->bankmsg($t->COUPON, $bankurl, $coupon_number, $assetid, $amount);
         }
         $coupon_number_hash = sha1($coupon_number);
         $db->put($t->COUPON . "/{$coupon_number_hash}", "{$outbox_item}");
         $pubkey = $this->pubkeydb->get($id);
         $coupon = $ssl->pubkey_encrypt($coupon, $pubkey);
         $coupon = $this->bankmsg($t->COUPONENVELOPE, $id, $coupon);
         $res .= ".{$coupon}";
         $outbox_item .= ".{$coupon}";
     }
     // I considered adding the transaction tokens to the bank
     // balances here, but am just leaving them in the outbox,
     // to be credited to this customer, if the spend is accepted,
     // or to the recipient, if he rejects it.
     // This means that auditing has to consider balances, outbox
     // fees, and inbox spend items.
     // Update balances
     $balancekey = $this->balancekey($id);
     foreach ($acctbals as $acct => $balances) {
         $acctdir = "{$balancekey}/{$acct}";
         foreach ($balances as $balasset => $balance) {
             $balance = $this->bankmsg($t->ATBALANCE, $balance);
             $res .= ".{$balance}";
             $db->put("{$acctdir}/{$balasset}", $balance);
         }
     }
     if ($fracmsg) {
         $key = $this->fractionbalancekey($id, $assetid);
         $db->put($key, $fracmsg);
         $res .= ".{$fracmsg}";
     }
     if ($storagemsg) {
         $res .= ".{$storagemsg}";
     }
     if ($id != $id2 && $id != $bankid) {
         // Update outboxhash
         $outboxhash_item = $this->bankmsg($t->ATOUTBOXHASH, $outboxhashmsg);
         $res .= ".{$outboxhash_item}";
         $db->put($this->outboxhashkey($id), $outboxhash_item);
         // Append spend to outbox
         $db->put($this->outboxdir($id) . "/{$time}", $outbox_item);
     }
     if ($id != $bankid) {
         // Update balancehash
         $balancehash_item = $this->bankmsg($t->ATBALANCEHASH, $balancehashmsg);
         $res .= ".{$balancehash_item}";
         $db->put($this->balancehashkey($id), $balancehash_item);
     }
     // Append spend to recipient's inbox
     if ($newtime) {
         $db->put($this->inboxkey($id2) . "/{$newtime}", $inbox_item);
     }
     // Force the user to do another getinbox, if anything appears
     // in his inbox since he last processed it.
     $db->put($this->acctlastkey($id), -1);
     // We're done
     $ok = true;
     return $res;
 }
                 $look_hash = $res['hash'];
             }
         }
     } elseif ($_POST['buy_archive'] != '') {
         $res = $client->buy_archive($touch_loc, $buy_usage, &$url);
     } elseif ($_POST['sell_archive'] != '') {
         $res = $client->sell_archive($touch_loc, $buy_usage, &$url);
     } elseif ($_POST['write_archive'] != '') {
         $res = $client->write_archive($touch_loc, $buy_usage, html_entity_decode($content), &$url);
         if ($res['hash'] != '') {
             $look_hash = $res['hash'];
         }
     }
 } else {
     if ($_POST['random_id'] != '') {
         $random = new LoomRandom();
         $id = $random->random_id();
         $idhash = '';
         $passphrase = '';
     } elseif ($_POST['id_hash'] != '') {
         $idhash = $client->sha256($client->hex2bin($id));
     } elseif ($_POST['random_passphrase'] != '') {
         require_once "Diceware.php";
         if (!isset($diceware)) {
             $diceware = new Diceware();
         }
         $passphrase = $diceware->random_words(5);
         $id = '';
         $idhash = '';
     } elseif ($_POST['hash_passphrase'] != '') {
         $hash = $client->sha256($passphrase);
Beispiel #3
0
 function newsessionid()
 {
     $random = $this->random;
     if (!$random) {
         require_once "LoomRandom.php";
         $random = new LoomRandom();
         $this->random = $random;
     }
     $res = bin2hex($random->urandom_bytes(20));
     if (strlen($res) < 40) {
         $res = str_repeat("0", 40 - strlen($res)) . $res;
     }
     return $res;
 }