summaryrefslogblamecommitdiffstats
path: root/prog/ž/index.php
blob: dc2a2f507170eac660741989c2c9bcd802304221 (plain) (tree)












































































































































































































































































































                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
                                                                                                                 



                                                                                                   
                 




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