summaryrefslogtreecommitdiffstats
path: root/prog/ž/index.php
diff options
context:
space:
mode:
authorAnton Luka Šijanec <anton@sijanec.eu>2023-06-20 01:51:29 +0200
committerAnton Luka Šijanec <anton@sijanec.eu>2023-06-20 01:51:29 +0200
commit530b0fe326d8a9623e27e081b51512cdf1d5b5d7 (patch)
treedfe2b901bf10f9acd416c22f61b475b379e52b44 /prog/ž/index.php
parentdownload changes to website update (diff)
downloadr-530b0fe326d8a9623e27e081b51512cdf1d5b5d7.tar
r-530b0fe326d8a9623e27e081b51512cdf1d5b5d7.tar.gz
r-530b0fe326d8a9623e27e081b51512cdf1d5b5d7.tar.bz2
r-530b0fe326d8a9623e27e081b51512cdf1d5b5d7.tar.lz
r-530b0fe326d8a9623e27e081b51512cdf1d5b5d7.tar.xz
r-530b0fe326d8a9623e27e081b51512cdf1d5b5d7.tar.zst
r-530b0fe326d8a9623e27e081b51512cdf1d5b5d7.zip
Diffstat (limited to 'prog/ž/index.php')
-rw-r--r--prog/ž/index.php311
1 files changed, 311 insertions, 0 deletions
diff --git a/prog/ž/index.php b/prog/ž/index.php
new file mode 100644
index 0000000..5b46ce1
--- /dev/null
+++ b/prog/ž/index.php
@@ -0,0 +1,311 @@
+<?php
+require_once "vendor/autoload.php";
+use Mdanter\Ecc\Crypto\Signature\SignHasher;
+use Mdanter\Ecc\Crypto\Key\PublicKey;
+use Mdanter\Ecc\Primitives\Point;
+use Mdanter\Ecc\EccFactory;
+use Mdanter\Ecc\Crypto\Signature\Signer;
+use Mdanter\Ecc\Serializer\PrivateKey\DerPrivateKeySerializer;
+use Mdanter\Ecc\Serializer\Signature\DerSignatureSerializer;
+use Mdanter\Ecc\Math;
+use Mdanter\Ecc\Primitives\CurveFp;
+use Mdanter\Ecc\Crypto\Signature;
+use Mdanter\Ecc\Math\GmpMath;
+$adapter = EccFactory::getAdapter();
+$curve = EccFactory::getNistCurves()->curve384();
+$generator = EccFactory::getNistCurves()->generator384();
+$useDerandomizedSignatures = true;
+$algorithm = 'sha384';
+$math = new GmpMath();
+function sec1parse ($in) {
+ switch ($in[0]) {
+ case "\x02":
+ $isOdd = false;
+ break;
+ case "\x03":
+ $isOdd = true;
+ break;
+ default:
+ return null;
+ }
+ global $math;
+ global $curve;
+ $x = $math->stringToInt(substr($in, 1, 48));
+ $y = $curve->recoverYfromX($isOdd, $x);
+ global $adapter;
+ global $generator;
+ return new PublicKey($adapter, $generator, new Point($adapter, $curve, $x, $y));
+}
+class Transaction {
+ public $sender;
+ public $recipient;
+ public $amount;
+ public $comment;
+ public $nonce;
+ public $r;
+ public $s;
+ public function parse ($in) {
+ $this->sender = substr($in, 0, 49);
+ $this->recipient = substr($in, 49, 49);
+ $amount = substr($in, 49*2, 4);
+ $this->amount = unpack("N", $amount)[1];
+ $this->comment = substr($in, 49*2+4, 256);
+ $this->nonce = substr($in, 49*2+4+256, 32);
+ $this->r = substr($in, 49*2+4+256+32, 48);
+ $this->s = substr($in, 49*2+4+256+32+48, 48);
+ }
+ public function serialize ($without_signature = false) {
+ return str_pad($this->sender, 49, "\0") . str_pad($this->recipient, 49, "\0") . pack("N", $this->amount) . str_pad($this->comment, 256, "\0") . str_pad($this->nonce, 32, "\0") . ($without_signature ? "" : (str_pad($this->r, 48, "\0") . str_pad($this->s, 48, "\0")));
+ }
+ public function verify () {
+ global $adapter;
+ global $generator;
+ global $algorithm;
+ global $math;
+ $signer = new Signer($adapter);
+ $publickey = sec1parse($this->sender);
+ $hasher = new SignHasher($algorithm, $adapter);
+ $hash = $hasher->makeHash($this->serialize(true), $generator);
+ return $signer->verify($publickey, new \Mdanter\Ecc\Crypto\Signature\Signature($math->stringToInt($this->r), $math->stringToInt($this->s)), $hash);
+ }
+ public function hash () {
+ return hash("sha256", $this->serialize(), true);
+ }
+}
+function tx_from_row($row) {
+ $tx = new Transaction();
+ $tx->sender = $row["sender"];
+ $tx->recipient = $row["recipient"];
+ $tx->amount = $row["amount"];
+ $tx->comment = $row["comment"];
+ $tx->nonce = $row["nonce"];
+ $tx->r = $row["r"];
+ $tx->s = $row["s"];
+ return $tx;
+}
+function last_tx ($db) {
+ foreach ($db->query("select * from transactions order by id desc limit 1") as $row);
+ if ($row)
+ return tx_from_row($row);
+ return;
+}
+if (!empty($_REQUEST["src"])) {
+ header("Content-Type: text/plain");
+ die(file_get_contents($_SERVER["SCRIPT_FILENAME"]));
+}
+if ($_SERVER["REQUEST_METHOD"] == "OPTIONS") {
+ http_response_code(204);
+ header("Access-Control-Allow-Origin: *");
+ header("Access-Control-Allow-Methods: *");
+ header("Access-Control-Allow-Headers: *");
+ header("Access-Control-Max-Age: 86400");
+ die();
+}
+define("TEXT", "text/plain");
+function response ($code, $body="", $type="application/octet-stream") {
+ http_response_code($code);
+ header("Content-Type: " . $type);
+ header("Access-Control-Allow-Origin: *");
+ header("Access-Control-Allow-Methods: *");
+ header("Access-Control-Allow-Headers: *");
+ header("Access-Control-Max-Age: 86400");
+ echo $body;
+}
+if (($ret = @file_get_contents("error_status.txt")) !== false) {
+ response(500, $ret, TEXT);
+ die();
+}
+function computers_post_handler ($in, $db, $forcepost=false) {
+ $numcomp = sizeof($db->query("select url from computers"));
+ if (strlen($in) % 256) {
+ return [413, "content length should've been divisible by 256", TEXT];
+ }
+ $in = str_split($in, 256);
+ $stmt = $db->prepare("insert or ignore into computers (url) values (:url)");
+ foreach ($in as $url) {
+ $stmt->bindParam(":url", $url, PDO::PARAM_LOB);
+ $stmt->execute();
+ }
+ $stmt = null;
+ $computers = [];
+ foreach ($db->query("select url from computers") as $url)
+ $computers[] = $url;
+ if ($numcomp != sizeof($computers) || $forcepost) {
+ foreach ($computers as $url) // this would be better with curl parallel/multi
+ file_get_contents(explode("\0", $url)[0] . "computers", false, stream_context_create(["http" => ["method" => "POST", "content" => implode("", $computers), "timeout" => 1]]));
+ return [201];
+ } else {
+ return [202];
+ }
+}
+function transactions_post_handler ($in, $db) {
+ $tx = new Transaction();
+ $txlen = strlen($tx->serialize());
+ if (strlen($in) % $txlen) {
+ return [469, "body length should've been divisible by $txlen", TEXT];
+ }
+ $in = str_split($in, $txlen);
+ foreach ($in as $txstr) {
+ $tx->parse($txstr);
+ if (!$tx->verify())
+ continue;
+ $stmt = $db->prepare("select * from transactions where hash=:hash");
+ $txhash = $tx->hash();
+ $stmt->bindParam(":hash", $txhash, PDO::PARAM_LOB);
+ $stmt->execute();
+ if ($stmt->rowCount())
+ continue;
+ $stmt = null;
+ $stmt = $db->prepare("insert or ignore into transactions (sender, recipient, amount, comment, nonce, r, s, hash) values (:sender, :recipient, :amount, :comment, :nonce, :r, :s, :hash)");
+ $stmt->bindParam(":sender", $tx->sender, PDO::PARAM_LOB);
+ $stmt->bindParam(":recipient", $tx->recipient, PDO::PARAM_LOB);
+ $stmt->bindParam(":amount", $tx->amount, PDO::PARAM_LOB);
+ $stmt->bindParam(":comment", $tx->comment, PDO::PARAM_LOB);
+ $stmt->bindParam(":nonce", $tx->nonce, PDO::PARAM_LOB);
+ $stmt->bindParam(":r", $tx->r, PDO::PARAM_LOB);
+ $stmt->bindParam(":s", $tx->s, PDO::PARAM_LOB);
+ $stmt->bindParam(":hash", $txhash, PDO::PARAM_LOB);
+ $stmt->execute();
+ $stmt = null;
+ $computers = [];
+ foreach ($db->query("select url from computers") as $url)
+ $computers[] = $url;
+ foreach ($computers as $url)
+ file_get_contents(explode("\0", $url)[0] . "transaction", false, stream_context_create(["http" => ["method" => "POST", "content" => $in, "timeout" => 1]]));
+ }
+ return [200];
+}
+function transactions_get_handler ($db, $after) {
+ $response = "";
+ $ret = $db->query("select * from transactions order by id");
+ $hash = null;
+ $stmt = $db->prepare("select * from transactions where hash=:hash");
+ $stmt->bindParam(":hash", $after, PDO::PARAM_LOB);
+ $stmt->execute();
+ if ($stmt->fetch())
+ $hash = $after;
+ $stmt = null;
+ foreach ($ret as $row)
+ if ($hash) {
+ if ($hash == tx_from_row($row)->hash())
+ $hash = null;
+ } else
+ $response .= tx_from_row($row)->serialize();
+ if ($response == "")
+ return [204];
+ return [200, $response];
+}
+function sync_checkpoint_computer ($db, $url) {
+ $stmt = $db->prepare("select last_hash from computers where url=:url");
+ $stmt->bindParam(":url", $url, PDO::PARAM_LOB);
+ $stmt->execute();
+ return $stmt->fetchColumn(0);
+}
+# create table computers (url TEXT NOT NULL UNIQUE CHECK(length(url) == 256), last_hash TEXT NOT NULL UNIQUE CHECK(length(last_hash) == 32), date default CURRENT_TIMESTAMP);
+# create table transactions (id integer primary key autoincrement, sender TEXT NOT NULL CHECK(length(sender) == 49), recipient TEXT NOT NULL CHECK(length(recipient) == 49), amount INTEGER NOT NULL CHECK(amount >= 0), comment TEXT NOT NULL CHECK(length(comment) == 256), nonce TEXT NOT NULL CHECK(length(nonce) == 32), r TEXT NOT NULL CHECK(length(r) == 48), s TEXT NOT NULL CHECK(length(s) == 48), hash TEXT NOT NULL UNIQUE CHECK(length(hash) == 32), date default CURRENT_TIMESTAMP);
+$db = new PDO("sqlite:db", null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
+if (!$db)
+ response(503, "db: " . $e->getMessage(), TEXT);
+switch ($_REQUEST["e"] . "-" . $_SERVER["REQUEST_METHOD"]) {
+ case "sec1decompress-GET":
+ $x = $math->intToString(sec1parse(hex2bin($_REQUEST["s"]))->getPoint()->getX());
+ $y = $math->intToString(sec1parse(hex2bin($_REQUEST["s"]))->getPoint()->getY());
+ response(200, "\x04$x$y");
+ break;
+ case "sec1decompress-POST":
+ $in = file_get_contents("php://input");
+ global $math;
+ $x = $math->intToString(sec1parse($in)->getPoint()->getX());
+ $y = $math->intToString(sec1parse($in)->getPoint()->getY());
+ response(200, "\x04$x$y");
+ break;
+ case "push-POST":
+ $in = file_get_contents("php://input");
+ for ($i = 0; $i < 60; $i++) {
+ $resp = transactions_get_handler($db, $in);
+ if ($resp[0] == 200) {
+ response(...$resp);
+ break;
+ }
+ usleep(250000);
+ }
+ if ($resp[0] != 200)
+ response(204);
+ break;
+ case "jutro-GET":
+ $computers = [];
+ foreach ($db->query("select url from computers") as $url)
+ $computers[] = $url;
+ $send = "";
+ foreach ($computers as $url) {
+ $recvd = file_get_contents(explode("\0", $url)[0] . "computers");
+ if (strlen($recvd) % 256) {
+ error_log("server $url returned non mod256 computers get response length", 3, "log");
+ continue;
+ }
+ $send .= $recvd;
+ }
+ computers_post_handler($send, $db, true);
+ $computers = [];
+ foreach ($db->query("select url from computers") as $url)
+ $computers[] = $url;
+ foreach ($computers as $url) {
+ $transactions = file_get_contents(explode("\0", $url[0])[0] . "transactions", false, stream_context_create(["http" => ["header" => "After: " . bin2hex(sync_checkpoint_computer($db, $url)) . "\r\n", "timeout" => 1]]));
+ $tx = new Transaction();
+ if (strlen($transactions) % strlen($tx->serialize())) {
+ error_log("server $url returned not correct mod for transactions response length", 3, "log");
+ continue;
+ }
+ foreach (str_split($transactions, strlen($tx->serialize())) as $transaction) {
+ $tx->parse($transaction);
+ $txhash = $tx->hash;
+ $stmt = $db->prepare("update computers set last_hash=:last_hash where url=:url");
+ $stmt->bindParam(":last_hash", $txhash, PDO::PARAM_LOB);
+ $stmt->bindParam(":url", $url, PDO::PARAM_LOB);
+ transactions_post_handler($transaction);
+ }
+ }
+ break;
+ case "computers-GET":
+ $ret = $db->query("select url from computers");
+ response(200);
+ foreach ($ret as $row)
+ echo $row[0];
+ break;
+ case "computers-POST":
+ $in = file_get_contents("php://input");
+ response(...computers_post_handler($in, $db));
+ break;
+ case "transactions-POST":
+ $in = file_get_contents("php://input");
+ response(...transactions_post_handler($in, $db));
+ break;
+ case "transactions-GET":
+ response(...transactions_get_handler($db, hex2bin($_SERVER["HTTP_AFTER"])));
+ break;
+ case "state-GET":
+ $ret = $db->query("select * from transactions order by id");
+ $out = "";
+ $balances = [];
+ foreach ($ret as $row) {
+ $tx = tx_from_row($row);
+ if (!$tx->verify()) {
+ $message = "transaction with internal id {$row["id"]} has an invalid signature.";
+ file_put_contents("error_status.txt", $message);
+ response(500, $message);
+ break 2;
+ }
+ @$balances[$tx->sender] -= $tx->amount;
+ @$balances[$tx->recipient] += $tx->amount;
+ }
+ response(200);
+ foreach ($balances as $key => $value) // do not trust balances provided by this API, since they
+ $packed = pack("q", $value); // are cast to machine dependent int by php
+ if (pack("Q", 123) === pack("P", 123)) // machine is little endian
+ $packed = strrev($packed);
+ echo $key . $packed;
+ break;
+ default:
+ response(400, "unknown endpoint or method not allowed", TEXT);
+ break;
+}