paragonie / chronicle Goto Github PK
View Code? Open in Web Editor NEWPublic append-only ledger microservice built with Slim Framework
Public append-only ledger microservice built with Slim Framework
We need to support the following basic operations:
Additionally, we may want to add two additional capabilities for privileged users:
When running:
php bin/scheduled-tasks.php
Sometimes this is generated:
PHP Fatal error: Uncaught ParagonIE\EasyDB\Exception\InvalidIdentifier: Invalid identifier: Invalid characters supplied. in /var/www/vhosts/domain.com/vendor/paragonie/easydb/src/EasyDB.php:295
Stack trace:
#0 /var/www/vhosts/domain.com/vendor/paragonie/easydb/src/EasyDB.php(745): ParagonIE\EasyDB\EasyDB->escapeIdentifier('"chronicle_chai...')
#1 /var/www/vhosts/domain.com/vendor/paragonie/easydb/src/EasyDB.php(512): ParagonIE\EasyDB\EasyDB->buildInsertQueryBoolSafe('"chronicle_chai...', Array)
#2 /var/www/vhosts/domain.com/src/Chronicle/Chronicle.php(153): ParagonIE\EasyDB\EasyDB->insert('"chronicle_chai...', Array)
#3 /var/www/vhosts/domain.com/src/Chronicle/Process/Attest.php(153): ParagonIE\Chronicle\Chronicle::extendBlakechain('{\n "version"...', 'IryGC1_n2byTv9W...', Object(ParagonIE\Sapient\CryptographyKeys\SigningPublicKey))
#4 /var/www/vhosts/domain.com/src/Chronicle in /var/www/vhosts/domain.com/vendor/paragonie/easydb/src/EasyDB.php on line 295
I have no clue why... I'm running an empty Chronicle with the default sqlite
db and a single replica (https://php-chronicle.pie-hosted.com/chronicle
).
The replica seems to be running fine but this error puzzles me and makes me think something is not going ok... any clue?
It should be possible to automatically cross-sign new hashes and summary hashes to other Chronicle instances. This will require the following information:
SigningPublicKey
of each peer.This implies that, server-side, we have a way to rate-limit the number of hashes per client.
Hi,
I added a new encrypted entry to the chain (data encrypted with Sapient\SealingPublicKey). When I try to retrieve the entry for that hash using the lookup api route or export the whole chain, I receive the following error response:
{ "version": "1.0.x", "datetime": "2018-10-19T15:36:07+02:00", "status": "ERROR", "message": "Cannot JSON-encode this message." }
I checked the data stored in the chronicle_chain table for that hash and I found out that it is stored in binary form:
I think the data should either be stored in an encoded form or appropriate encoding should applied to ensure retrieval via REST API is possible.
Best regards,
Ben
Hi, there is an issue when building database for PostgreSQL v9.6.7.
Here is the output:
$ php bin/make-tables.php
/var/www/public/workspace/chronicle/sql/pgsql/00-local.sql
/var/www/public/workspace/chronicle/sql/pgsql/01-remote.sql
PHP Fatal error: Uncaught PDOException: SQLSTATE[42830]: Invalid foreign key: 7 ERROR: there is no unique constraint matching given keys for referenced table "chronicle_replication_chain" in /var/www/public/workspace/chronicle/vendor/paragonie/easydb/src/EasyDB.php:1329
Stack trace:
#0 /var/www/public/workspace/chronicle/vendor/paragonie/easydb/src/EasyDB.php(1329): PDO->query('CREATE TABLE ch...')
#1 /var/www/public/workspace/chronicle/bin/make-tables.php(125): ParagonIE\EasyDB\EasyDB->query('CREATE TABLE ch...')
#2 {main}
thrown in /var/www/public/workspace/chronicle/vendor/paragonie/easydb/src/EasyDB.php on line 1329
Thanks
Is there a PHP client library yet, like something that lets you interact with a chronicle server by calling functions instead of creating POST requests yourself?
Hi,
I noticed that the Chronicle is tested well on SQLite and maybe some on MySQL. But the test not exists to cover all these databases at once. I'm thinking to extend Travis to cover all the others because there are many many features will be added and need to be tested. So, we can push development further with confidence. The background is ready after merge this #49.
Thanks
Hi, while I'm reading CrossSign docs I think if we can retrieve public key when adding Chronicle URL, then after that the script should print the public key and ask the user to confirm before save it to the database, just like SSH when doing the first contact.
Thanks
Hi,
I setup MySQL database for Chronicle and I have an issue with this line:
CREATE INDEX chronicle_clients_clientid_idx ON chronicle_clients(publicid);
The error:
#1170 - BLOB/TEXT column 'publicid' used in key specification without a key length
Server: Localhost via UNIX socket
Server type: MySQL
Server version: 5.6.41-cll-lve - MySQL Community Server (GPL)
Protocol version: 10
User: cpses_xxxxxxxxxxxx@localhost
Server charset: UTF-8 Unicode (utf8)
Thanks
This commit introduced FK constraints:
However, when the first record is inserted the value of prevhash
is NULL
whereas currhash
is an actual Blakechain hash. Given the introduced constraints the insertion of the first record will fail (tested on Postgres) thus preventing chain initialisation entirely.
Hi,
I noticed that /chronicle/export
API endpoint does not provide any sort of pagination, I feel this is an attack surface to D.O.S.
Pagination is recommended for protection & performance. ^_^
Thanks
Hi,
I did setup a two Chronicle instances which are Chronicle-A
and Chronicle-B
. I generated keypairs with php bin/keygin.php
at Chronicle-A
, then I took public key and create a new client with php bin/create-client.php
at Chronicle-B
which gave me a success result.
➜ php bin/create-client.php --publickey="N1DIRAN2XpXaIJpFx2IpDQpqF4j0bkGDhkmlNBI_NHU=" --comment="point-BB"
Client (VeVGBO9gThq_eTzCbAR3vgrhEHrUnRqL) created successfully!
I try to test publish some data with the following code:
<?php
$root = \dirname(__DIR__);
/** @psalm-suppress UnresolvableInclude */
require_once $root . '/cli-autoload.php';
use GuzzleHttp\Client;
use ParagonIE\Sapient\Sapient;
use ParagonIE\ConstantTime\Base64UrlSafe;
use ParagonIE\Sapient\Adapter\Guzzle as GuzzleAdapter;
use ParagonIE\Sapient\CryptographyKeys\SigningPublicKey;
use ParagonIE\Sapient\CryptographyKeys\SigningSecretKey;
use ParagonIE\Sapient\Exception\InvalidMessageException;
$http = new Client([
'base_uri' => 'http://localhost:8080'
]);
$sapient = new Sapient(new GuzzleAdapter($http));
// Keys
$clientSigningKey = new SigningSecretKey(
Base64UrlSafe::decode(
'JblJ6oEbqA8pBQKbwX_gSb7wCP85--C9ANyRU-eYGUM3UMhEA3ZeldogmkXHYikNCmoXiPRuQYOGSaU0Ej80dQ=='
)
);
$serverPublicKey = new SigningPublicKey(
Base64UrlSafe::decode(
'N1DIRAN2XpXaIJpFx2IpDQpqF4j0bkGDhkmlNBI_NHU='
)
);
// We use an array to define our message
$myMessage = [
'date' => (new DateTime)->format(DateTime::ATOM),
'body' => [
'test' => 'hello world!'
]
];
// Create the signed request:
$request = $sapient->createSignedJsonRequest(
'POST',
'/chronicle/publish',
$myMessage,
$clientSigningKey
);
$response = $http->send($request);
try {
/** @var array $verifiedResponse */
$verifiedResponse = $sapient->decodeSignedJsonResponse(
$response,
$serverPublicKey
);
} catch (InvalidMessageException $ex) {
echo $ex->getMessage();
// \http_response_code(500);
exit;
}
Then, I received this error:
Fatal error: Uncaught GuzzleHttp\Exception\ClientException: Client error: `POST http://localhost:8080/chronicle/publish` resulted in a `403 Forbidden` response:
{
"version": "1.0.x",
"datetime": "2018-07-06T19:57:36+00:00",
"status": "ERROR",
"message": "No client (truncated...)
in /Users/vzool/Workspace/chronicleA/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php:113
Stack trace:
#0 /Users/vzool/Workspace/chronicleA/vendor/guzzlehttp/guzzle/src/Middleware.php(66): GuzzleHttp\Exception\RequestException::create(Object(GuzzleHttp\Psr7\Request), Object(GuzzleHttp\Psr7\Response))
#1 /Users/vzool/Workspace/chronicleA/vendor/guzzlehttp/promises/src/Promise.php(203): GuzzleHttp\Middleware::GuzzleHttp\{closure}(Object(GuzzleHttp\Psr7\Response))
#2 /Users/vzool/Workspace/chronicleA/vendor/guzzlehttp/promises/src/Promise.php(156): GuzzleHttp\Promise\Promise::callHandler(1, Object(GuzzleHttp\Psr7\Response), Array)
#3 /Users/vzool/Workspace/chronicleA/vendor/guzzlehttp/promises/src/TaskQueue.php(47): Guzzl in /Users/vzool/Workspace/chronicleA/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php on line 113
After I dig more into Publish Handler
I found the truncated message, which is here:
No client header provided
What I did miss here?
Thanks
Placeholder issue for the moment.
We should have a detailed tutorial for how to deploy your own Chronicle, etc.
I'm sure this is on your radar already....
Package tflori/getopt-php is abandoned, you should avoid using it. Use ulrichsg/getopt-php instead.
This project seems interesting and potentially something I'd like to use for a project, however not entirely sure how the design works on the verification side.
I installed the Chronicle and a replica. How can the clients be sure that the contents returned from a Chronicle are intact? Do you have an pseudo code example of that?
My understanding is that you'd query multiple replicas and compare the contents response? But I feel like I'm missing something.
For example, is it required to compare the contents field against the currhash; if so how may the client compute that hash? Or is this only done as part of the Chronicle replication process?
This was reported last year in #40 but that was related to scheduled tasks. Today I did a fresh install on Ubuntu 18.04 with PHP 7.3 and MariaDB 10.4.7 and am getting it with client creation.
When running:
php bin/create-client.php -p "XXXXXX" -c "Test"
The below is thrown:
PHP Fatal error: Uncaught ParagonIE\EasyDB\Exception\InvalidIdentifier: Invalid identifier: Invalid characters supplied. in /home/chrishaas/chronicle/vendor/paragonie/easydb/src/EasyDB.php:297
Stack trace:
#0 /home/chrishaas/chronicle/vendor/paragonie/easydb/src/EasyDB.php(870): ParagonIE\EasyDB\EasyDB->escapeIdentifier('`chronicle_clie...')
#1 /home/chrishaas/chronicle/vendor/paragonie/easydb/src/EasyDB.php(519): ParagonIE\EasyDB\EasyDB->buildInsertQueryBoolSafe('`chronicle_clie...', Array)
#2 /home/chrishaas/chronicle/bin/create-client.php(126): ParagonIE\EasyDB\EasyDB->insert('`chronicle_clie...', Array)
#3 {main}
thrown in /home/chrishaas/chronicle/vendor/paragonie/easydb/src/EasyDB.php on line 297
MariaDB version:
mysql Ver 15.1 Distrib 10.4.7-MariaDB, for debian-linux-gnu (x86_64) using readline 5.2
PHP Version:
PHP 7.3.8-1+ubuntu18.04.1+deb.sury.org+1 (cli) (built: Aug 7 2019 09:52:12) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.8, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.3.8-1+ubuntu18.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies```
https://chronicle-public-test.paragonie.com/
Post your public keys here, and I'll give you write access.
I started testing out now after fixing database creation for MySQL. So, error show up when publish data:
Type: PDOException
Code: 22007
Message: SQLSTATE[22007]: Invalid datetime format: 1292 Incorrect datetime value: '2018-10-19T21:18:39+00:00' for column 'created' at row 1
File: /home/vagrant/code/my-chronicle/vendor/paragonie/easydb/src/EasyDB.php
Line: 846
Trace: #0 /home/vagrant/code/my-chronicle/vendor/paragonie/easydb/src/EasyDB.php(846): PDOStatement->execute(Array)
#1 /home/vagrant/code/my-chronicle/vendor/paragonie/easydb/src/EasyDB.php(521): ParagonIE\EasyDB\EasyDB->safeQuery('INSERT INTO `ch...', Array, 4, true)
#2 /home/vagrant/code/my-chronicle/src/Chronicle/Chronicle.php(114): ParagonIE\EasyDB\EasyDB->insert('chronicle_chain', Array)
#3 /home/vagrant/code/my-chronicle/src/Chronicle/Handlers/Publish.php(92): ParagonIE\Chronicle\Chronicle::extendBlakechain('{\n "date": "...', 'w8J2tTFL5THh4P_...', Object(ParagonIE\Sapient\CryptographyKeys\SigningPublicKey))
#4 [internal function]: ParagonIE\Chronicle\Handlers\Publish->__invoke(Object(Slim\Http\Request), Ob" while reading response header from upstream, client: 192.168.10.1, server: chronicle.local, request: "POST /chronicle/publish HTTP/1.1", upstream: "fastcgi://unix:/var/run/php/php7.2-fpm.sock:", host: "chronicle.local"
More investigation will going on tomorrow morning, if God's will. ^_^
Hi, this /chronicle/lasthash
endpoint usually is used in one of the two steps to get the last hash with data.
So, I think if we just return all the record data for this API then we'll decrease the number of steps by 50%.
But, if there is a specific use case that I'm not aware of about the current design, then we can create a new API /chronicle/last
.
Thanks
Hi,
After I got some time testing out #24, I noticed that there is no any Genesis Record in chain!
Why it doesn't has one?
Thanks
Hi,
I have an idea to use chronicle in which is any company has many agreements with their customers, agreements like (TOS, privacy & many more).
The company is a very transparent company with their customers and it does want every customer to agree on every change for their business model.
After some time design some considerations I come up with this structure:
$block = [
[
'type' => 'tos',
'hash' => 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
'terms' => ' ... ',
'effective_at' => '2019-06-09 00:00:00',
'functions' => [
'deprecated' => [
// ..
],
'current' => [
// ..
],
'new' => [
// ..
],
],
],
[
'type' => 'privacy',
'hash' => 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
'terms' => ' ... ',
'effective_at' => '2018-06-09 00:00:00',
'functions' => [
'deprecated' => [
// ..
],
'current' => [
// ..
],
'new' => [
// ..
],
],
],
// ...
];
But, If just I want to update the privacy terms I will need to duplicate TOS and many others with it in order to be keep tracking last version for all agreements at /chronicle/lasthash
, I feel current chronicle
does not provide multiple chains, subjects or networks
So, I think it will be a better if add the support for multiple networks, something like this /chronicle/{network}/lasthash
.
I prefer subject term over the network, because the url will be more fluent:
/chronicle/tos/lasthash
/chronicle/privacy/lasthash
The update to APIs will be:
[
{
uri: "/chronicle/{subject}/lasthash",
description: "Get information about the latest entry in this Chronicle at custom subject"
},
{
uri: "/chronicle/lasthash",
description: "Get information about the latest entry in this Chronicle at main subject"
},
{
uri: "/chronicle/lookup/{subject}/{hash}",
description: "Lookup the information for the given hash at custom subject"
},
{
uri: "/chronicle/lookup/{hash}",
description: "Lookup the information for the given hash at main subject"
},
{
uri: "/chronicle/since/{hash}",
description: "List all new entries since a given hash at custom subject"
},
{
uri: "/chronicle/since/{subject}/{hash}",
description: "List all new entries since a given hash at main subject"
},
{
uri: "/chronicle/{subject}/export",
description: "Export the entire Chronicle at custom subject"
},
{
uri: "/chronicle/export",
description: "Export the entire Chronicle at main subject"
},
{
uri: "/chronicle/replica",
description: "List of Chronicles being replicated onto this one (and other options)"
},
{
uri: "/chronicle",
description: "API method description"
}
]
If chronicle
will offer something like this, then it can be used in many other cases like:
/chronicle/new-york/lasthash
as a news feed./chronicle/changelog/lasthash
.Any suggestions are very welcome. <3
Whether this means selling AMIs on the AWS Marketplace (like we do with CMS Airship), or partnering with another provider (Digital Ocean, perhaps?).
If we don't go the AMI route, this issue is up for grabs.
Hi,
I realise its a possibly little "easier said than done" in terms of complexity. But in terms of enhancing the replication capability of chronicle, it might be nice to see cloud support.
Support for "cheap & simple" variants such as AWS Dynamo or Azure Tables could provide a nice replication option.
Hi, I want to create a new client but I faced this issue.
➜ chronicle git:(master) php bin/create-client.php -p "xml6UZZdha1ypeTbcFWmG-CWbgz7zxjDq6DpPdrsDVI=" --administrator
PHP Fatal error: Uncaught Error: Call to a member function escapeIdentifier() on null in /home/vzool/Workspace/chronicle/src/Chronicle/Chronicle.php:67
Stack trace:
#0 /home/vzool/Workspace/chronicle/bin/create-client.php(115): ParagonIE\Chronicle\Chronicle::getTableName('clients')
#1 {main}
thrown in /home/vzool/Workspace/chronicle/src/Chronicle/Chronicle.php on line 67
PHP version information:
PHP 7.2.5 (cli) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
I used SQLite database.
Thanks
Hi, cool project!
I installed it and seems to work fine for the most part, but I am having issues with the replication.
I follow the steps in the docs: installed the first Chronicle instance, configured it up to MySQL, built tables and such. Then added a client and published some data to it.
Then I created a 2nd one, ran bin/replicate.php with a target of http://127.0.0.1:8080/chronicle and the first Chronicle's pub key. This seems to work well too, and adds an entry to the chronicle_replication_sources
.
However, the secondary Chronicle does not seem to be replicating the source. I modified the "scheduled-attestation"
: to 360 seconds but not seeing any hashes on the secondary instance. I tried to execute the scheduled-tasks.php script and got the following error:
Fatal error: Uncaught ParagonIE\Chronicle\Exception\SecurityViolation: Invalid summary hash. Expected Pxqeejbjl-mAipyhtIDevQNmdpBHMN_-XBCZ6sQ1y7c=, calculated bpqrzUNnTsNg4aSHcBXOnn-qJzyUYB62MJtCSORr6bM=
in src\Chronicle\Process\Replicate.php:203
I also truncated the chains and tried to replicate again, but no luck.
Do you have any idea why this might be?
Hi, I'm looking forward to an API that lists all public instances in the chronicle app, then this will lead us to hide other instances for some use cases. By that in mind I suggest to update local/settings.json
to support this feature like the following:
{
...
"instances": {
"": {
"name": "",
"public": true,
"desc": "Default public instance"
}
},
...
}
I mean by public if set to false
is just to hide the instance in the suggested API result. Thanks
Currently, there are two features that can be used to make attacking the immutability of a Chronicle very difficult:
currhash
and summaryhash
onto a peer's Chronicle.This opens the door to several possibilities:
A scheduled attestation means your Chronicle will occasionally publish the latest currhash
and summaryhash
for a replicated Chronicle onto its own history, which may force a cross-sign.
The purpose of replication is to provide a second opinion in case the replicated Chronicle is compromised but yours is not. If we go with scheduled attestation, that will strengthen the resilience. If we expose replicated data, we can strengthen the availability of a Chronicle's contents to resist targeted DoS attacks against the central server.
Which features should be implemented? Please comment or use Github reactions to vote.
This should definitely not be an \Error
, but an \Exception
.
I'm back :)
The replication is failing with the folowing error:
PHP Notice: Undefined index: summary in /var/www/vhosts/example.com/chronicle.example.com/src/Chronicle/Process/Replicate.php on line 183
PHP Warning: hash_equals(): Expected known_string to be a string, null given in /var/www/vhosts/example.com/chronicle.example.com/src/Chronicle/Process/Replicate.php on line 183
PHP Notice: Undefined index: summary in /var/www/vhosts/example.com/chronicle.example.com/src/Chronicle/Process/Replicate.php on line 186
PHP Fatal error: Uncaught ParagonIE\Chronicle\Exception\SecurityViolation: Invalid summary hash. Expected , calculated g_uhhZBFE4iP-k9rPx_TyzdJz6NYfgZYQetvThhAo8M= in /var/www/vhosts/example.com/chronicle.example.com/src/Chronicle/Process/Replicate.php:185
Stack trace:
#0 /var/www/vhosts/example.com/chronicle.example.com/src/Chronicle/Process/Replicate.php(116): ParagonIE\Chronicle\Process\Replicate->appendToChain(Array)
#1 /var/www/vhosts/example.com/chronicle.example.com/src/Chronicle/Scheduled.php(129): ParagonIE\Chronicle\Process\Replicate->replicate()
#2 /var/www/vhosts/example.com/chronicle.example.com/src/Chronicle/Scheduled.php(86): ParagonIE\Chronicle\Scheduled->doReplication()
#3 /var/www/vhosts/example.com/chronicle.example.com/src/Chronicle/Scheduled.php(60): ParagonIE\Chronicle\Scheduled->runAll()
#4 /var/www/vhosts/example.com/chronicle.example.com/bin/scheduled-tasks.php(36): ParagonIE\Chronicle\Scheduled->run()
#5 {main}
thrown in /var/www/vhosts/example.com/chronicle.example.com/src/Chronicle/Process/Replicate.php on line 185
Possibly because of a missing signature in the second to last entry in the chain?
Replicating: https://php-chronicle.pie-hosted.com/chronicle/export
Sorry, but am I missing something stupid here ?
Fatal error: Uncaught ParagonIE\EasyDB\Exception\ConstructorFailed: Could not create a PDO connection. Please check your username and password. in /blah/chronicle/vendor/paragonie/easydb/src/Factory.php:65
local/settings.json is as per your example:
"database": {
"dsn": "pgsql:host=myhost;port=5432;dbname=mydb",
"username": "myuser",
"password": "mypass"
}
and....
psql -U myuser -d mydb -h myhost -W
works just fine (i.e. pg_hba is correctly setup and reloaded, I can manipulate stuff as the user etc. ), and....
No errors are recorded in the postgres logs.
and .....
I know postgres connections work just fine from this machine because I've got other PHP stuff on there accessing the same postgres server. So there's no firewalls or other stuff going on.
This is postgres 9.6.8 and php 7.2.4.
Hi, I think this spot needs some upgrade to let the script create any type of database configurations just with flipping the options.
Thanks
Do not pass around $app
https://github.com/paragonie/chronicle/blob/master/src/Chronicle/Handlers/Lookup.php#L28
It's also never used in this class
https://github.com/paragonie/chronicle/blob/master/src/Chronicle/Handlers/Lookup.php#L191-L207
Resource based limitation.. I think this is a problem in the EasyDB package. The results of this sql query are buffered into RAM. So if there are 128MB of items, PHP will consume 128MB... There's a chance that the process will run OOM.
Your route defintions are not ideal.
$app->group('/chronicle', function () use ($app) {
$app->post('/publish', new Register($app))
->add(new CheckAdminSignature());
$app->post('/publish', new Revoke($app))
->add(new CheckAdminSignature());
$app->post('/publish', new Publish($app))
->add(new CheckClientSignature());
$app->get('/lasthash', new Lookup($app, 'lasthash'));
$app->get('/lookup/[{hash}]', new Lookup($app, 'hash'));
$app->get('/since/[{hash}]', new Lookup($app, 'since'));
$app->get('/export', new Lookup($app, 'export'));
});
When inside the group the router is bound to the anom function allowing you to use $this
instead of currying $app
you can use an empty route group to apply middleware to multiple things
With your current routes every request you are creating each of those Handlers when you do not need to. You may register handlers via a String in the container and then use the container key in the 2nd argument of the http method.
Furthermore, 3 applies to middleware as well.
//Routes
$app->group('/chronicle', function () {
$this->group('', function () {
$this->post('/publish', Register::class); //This is a bug <---
$this->post('/publish', Revoke::class; //This is a bug <---
$this->post('/publish', Publish::class);
})->add(CheckAdminSignature::class);
$this->get('/lasthash', 'lookup.lasthash');
$this->get('/lookup/[{hash}]', 'lookup.hash');
$this->get('/since/[{hash}]', 'lookup.since');
$this->get('/export', 'lookup.export');
});
//Container Definitions
$container[CheckAdminSignature::class] = function ($c) { ... };
$container[Register::class] = function ($c) { ... };
$container[Revoke::class] = function ($c) { ... };
$container[Publish::class] = function ($c) { ... };
$container['lookup.lasthash'] = function ($c) { ... };
$container['lookup.hash'] = function ($c) { ... };
$container['lookup.since'] = function ($c) { ... };
$container['lookup.export'] = function ($c) { ... };
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.