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);
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; }