parent child handshake
Some checks failed
Build and Deploy / build-linux (push) Failing after 1m45s
Build and Deploy / build-windows (CLANG64) (push) Failing after 8m5s
Build and Deploy / package-dist (push) Has been skipped
Build and Deploy / deploy-itch (push) Has been skipped
Build and Deploy / deploy-gitea (push) Has been skipped

This commit is contained in:
2025-03-06 11:31:37 -06:00
parent e71a823848
commit 01f7e715a4
8 changed files with 3476 additions and 13 deletions

View File

@@ -137,7 +137,7 @@ deps += dependency('soloud', static:true)
deps += dependency('libqrencode', static: true)
sources = []
src += ['anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c','render.c','script.c','simplex.c','spline.c', 'timer.c', 'transform.c','prosperon.c', 'wildmatch.c', 'sprite.c', 'rtree.c', 'qjs_dmon.c', 'qjs_nota.c', 'qjs_enet.c', 'qjs_soloud.c', 'qjs_qr.c', 'qjs_wota.c']
src += ['anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c','render.c','script.c','simplex.c','spline.c', 'timer.c', 'transform.c','prosperon.c', 'wildmatch.c', 'sprite.c', 'rtree.c', 'qjs_dmon.c', 'qjs_nota.c', 'qjs_enet.c', 'qjs_soloud.c', 'qjs_qr.c', 'qjs_wota.c', 'monocypher.c']
# quirc src
src += ['thirdparty/quirc/quirc.c', 'thirdparty/quirc/decode.c','thirdparty/quirc/identify.c', 'thirdparty/quirc/version_db.c']

View File

@@ -522,6 +522,7 @@ js.eval(DOCPATH, script)()
var enet = use('enet')
var util = use('util')
var math = use('math')
var crypto = use('crypto')
var $_ = {}
@@ -546,6 +547,9 @@ var portal = undefined
var receive_fn = undefined;
var ephemeralkeys = {}
var knownsecrets = {}
$_.contact = function(callback, record)
{
@@ -590,9 +594,18 @@ var greeters = {}
$_.start = function(cb, prg, arg)
{
var ephemeral = crypto.keypair()
var guid = util.guid()
greeters[guid] = cb
os.createprocess(["./prosperon", "spawn", "--program", prg, "--overling", $_.host.port(), "--guid", guid])
ephemeralkeys[guid] = ephemeral
os.createprocess([
"./prosperon",
"spawn",
"--program",prg,
"--overling", $_.host.port(),
"--guid", guid,
"--parentpub", ephemeral.public
])
guid2actor.set(guid, {peer:undefined, guid:guid})
}
@@ -633,6 +646,8 @@ $_.couple = function(actor)
use('cmd')(prosperon.argv)
var child_ephemeral
if (prosperon.overling) {
$_.host.connect("localhost", prosperon.args.overling)
}
@@ -640,6 +655,11 @@ if (prosperon.overling) {
if (prosperon.program)
actor.spawn(prosperon.program)
if (prosperon.args.parentpub)
child_ephemeral = crypto.keypair()
else if (prosperon.overling)
console.warn("No parentpub provided; secure handshake won't proceed!");
if (!prosperon.guid) prosperon.guid = util.guid()
var ar = 60 // seconds before reclamation
@@ -650,6 +670,39 @@ function handle_receive(e)
{
var data = e.data
switch(data.type) {
case "handshake":
var ep = ephemeralkeys[data.guid]
if (!ep)
throw new Error(`No stored ephemeral keypair found for guid=${data.guid}. Cannot do secure handshake.`)
var parent_private = ep.private
if (!data.child_public)
throw new Error("No child public key found in handshake message.")
var shared = crypto.shared({public:data.child_public, private: ep.private})
knownsecrets[data.guid] = shared
console.log("Sending handshake ok ..")
e.peer.send({
type: "handshake_ok",
guid:data.guid
})
break;
case "handshake_ok":
if (!child_ephemeral)
throw new Error("We didn't generate a child ephemeral key. Something is off. Not deriving a shared secret!")
if (!prosperon.args.parentpub)
throw new Error("No parent's ephemeral public key was provided. Cannot continue with shared secret.")
if (!data.guid)
throw new Error("handshake_ok message missing guid. We won't store the shared secret.")
console.log("got handshake ok")
knownsecrets[data.guid] = crypto.shared({public:prosperon.args.parentpub, private: child_ephemeral.private})
break;
case "greet":
if (greeters[data.guid]) {
var actor = guid2actor.get(data.guid)
@@ -671,7 +724,6 @@ function handle_receive(e)
function handle_actor_disconnect(actor)
{
console.log(`actor ${json.encode(actor)} disconnected`)
guid2actor.delete(actor.guid)
guid2actor.delete(actor.peer)
if (couplings.has(actor)) {
@@ -690,12 +742,22 @@ while (1) {
unneeded_timer()
switch(e.type) {
case "connect":
console.log(`connected. sending greet with guid ${prosperon.guid} to peer ${e.peer}`)
e.peer.send({
type: "greet",
guid: prosperon.guid
});
break;
if (child_ephemeral) {
console.log(`Child connected. Sending handshake ...`)
e.peer.send({
type: "handshake",
guid: prosperon.guid,
child_public: child_ephemeral.public
})
} else {
console.log(`connected. sending greet with guid ${prosperon.guid} to peer ${e.peer}`)
e.peer.send({
type: "greet",
guid: prosperon.guid
});
}
break;
case "receive":
handle_receive(e);

View File

@@ -156,6 +156,7 @@ Cmdline.register_order(
prosperon.guid = prosperon.args.guid
prosperon.overling = prosperon.args.overling
prosperon.program = prosperon.args.program
prosperon.parentpub = prosperon.args.parentpub
},
"Spawn a new prosperon actor.",
"TOPIC"

View File

@@ -7655,6 +7655,129 @@ static void exit_handler()
script_stop();
}
#include "monocypher.h"
static inline void to_hex(const uint8_t *in, size_t in_len, char *out)
{
static const char hexchars[] = "0123456789abcdef";
for (size_t i = 0; i < in_len; i++) {
out[2*i ] = hexchars[(in[i] >> 4) & 0x0F];
out[2*i + 1] = hexchars[ in[i] & 0x0F];
}
out[2 * in_len] = '\0'; // null-terminate
}
#include <ctype.h> // for isxdigit
static inline int nibble_from_char(char c, uint8_t *nibble)
{
if (c >= '0' && c <= '9') { *nibble = (uint8_t)(c - '0'); return 0; }
if (c >= 'a' && c <= 'f') { *nibble = (uint8_t)(c - 'a' + 10); return 0; }
if (c >= 'A' && c <= 'F') { *nibble = (uint8_t)(c - 'A' + 10); return 0; }
return -1; // invalid char
}
static inline int from_hex(const char *hex, uint8_t *out, size_t out_len)
{
for (size_t i = 0; i < out_len; i++) {
uint8_t hi, lo;
if (nibble_from_char(hex[2*i], &hi) < 0) return -1;
if (nibble_from_char(hex[2*i + 1], &lo) < 0) return -1;
out[i] = (uint8_t)((hi << 4) | lo);
}
return 0;
}
#include <string.h> // for size_t, memcpy
#include "quickjs.h"
// Convert a JSValue containing a 64-character hex string into a 32-byte array.
static inline void js2crypto(JSContext *js, JSValue v, uint8_t *crypto)
{
size_t hex_len;
const char *hex_str = JS_ToCStringLen(js, &hex_len, v);
if (!hex_str)
return;
if (hex_len != 64) {
JS_FreeCString(js, hex_str);
JS_ThrowTypeError(js, "js2crypto: expected 64-hex-char string");
return;
}
if (from_hex(hex_str, crypto, 32) < 0) {
JS_FreeCString(js, hex_str);
JS_ThrowTypeError(js, "js2crypto: invalid hex encoding");
return;
}
JS_FreeCString(js, hex_str);
}
static inline JSValue crypto2js(JSContext *js, const uint8_t *crypto)
{
char hex[65]; // 32*2 + 1 for null terminator
to_hex(crypto, 32, hex);
return JS_NewString(js, hex);
}
JSC_CCALL(crypto_keypair,
ret = JS_NewObject(js);
uint8_t public[32];
uint8_t private[32];
for (int i = 0; i < 32; i++)
private[i] = (uint8_t)rand();
private[0] &= 248;
private[31] &= 127;
private[31] |= 64;
crypto_x25519_public_key(public,private);
JS_SetPropertyStr(js, ret, "public", crypto2js(js, public));
JS_SetPropertyStr(js, ret, "private", crypto2js(js,private));
)
JSC_CCALL(crypto_shared,
{
if (argc < 1 || !JS_IsObject(argv[0])) {
return JS_ThrowTypeError(js, "crypto.shared: expected an object argument");
}
JSValue obj = argv[0];
JSValue val_pub = JS_GetPropertyStr(js, obj, "public");
if (JS_IsException(val_pub)) {
JS_FreeValue(js, val_pub);
return JS_EXCEPTION;
}
JSValue val_priv = JS_GetPropertyStr(js, obj, "private");
if (JS_IsException(val_priv)) {
JS_FreeValue(js, val_pub);
JS_FreeValue(js, val_priv);
return JS_EXCEPTION;
}
uint8_t pub[32], priv[32];
js2crypto(js, val_pub, pub);
js2crypto(js, val_priv, priv);
JS_FreeValue(js, val_pub);
JS_FreeValue(js, val_priv);
uint8_t shared[32];
crypto_x25519(shared, priv, pub);
ret = crypto2js(js, shared);
})
static const JSCFunctionListEntry js_crypto_funcs[] = {
MIST_FUNC_DEF(crypto, keypair, 0),
MIST_FUNC_DEF(crypto, shared, 1),
};
MISTUSE(io)
MISTUSE(os)
MISTUSE(input)
@@ -7669,6 +7792,7 @@ MISTUSE(video)
MISTUSE(event)
MISTUSE(camera)
MISTUSE(debug)
MISTUSE(crypto)
JSValue js_imgui_use(JSContext *js);
@@ -7705,6 +7829,7 @@ void ffi_load(JSContext *js, int argc, char **argv) {
arrput(module_registry, MISTLINE(enet));
arrput(module_registry, MISTLINE(qr));
arrput(module_registry, MISTLINE(wota));
arrput(module_registry, MISTLINE(crypto));
#ifdef TRACY_ENABLE
arrput(module_registry, MISTLINE(tracy));

2956
source/monocypher.c Normal file

File diff suppressed because it is too large Load Diff

321
source/monocypher.h Normal file
View File

@@ -0,0 +1,321 @@
// Monocypher version __git__
//
// This file is dual-licensed. Choose whichever licence you want from
// the two licences listed below.
//
// The first licence is a regular 2-clause BSD licence. The second licence
// is the CC-0 from Creative Commons. It is intended to release Monocypher
// to the public domain. The BSD licence serves as a fallback option.
//
// SPDX-License-Identifier: BSD-2-Clause OR CC0-1.0
//
// ------------------------------------------------------------------------
//
// Copyright (c) 2017-2019, Loup Vaillant
// All rights reserved.
//
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the
// distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// ------------------------------------------------------------------------
//
// Written in 2017-2019 by Loup Vaillant
//
// To the extent possible under law, the author(s) have dedicated all copyright
// and related neighboring rights to this software to the public domain
// worldwide. This software is distributed without any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication along
// with this software. If not, see
// <https://creativecommons.org/publicdomain/zero/1.0/>
#ifndef MONOCYPHER_H
#define MONOCYPHER_H
#include <stddef.h>
#include <stdint.h>
#ifdef MONOCYPHER_CPP_NAMESPACE
namespace MONOCYPHER_CPP_NAMESPACE {
#elif defined(__cplusplus)
extern "C" {
#endif
// Constant time comparisons
// -------------------------
// Return 0 if a and b are equal, -1 otherwise
int crypto_verify16(const uint8_t a[16], const uint8_t b[16]);
int crypto_verify32(const uint8_t a[32], const uint8_t b[32]);
int crypto_verify64(const uint8_t a[64], const uint8_t b[64]);
// Erase sensitive data
// --------------------
void crypto_wipe(void *secret, size_t size);
// Authenticated encryption
// ------------------------
void crypto_aead_lock(uint8_t *cipher_text,
uint8_t mac [16],
const uint8_t key [32],
const uint8_t nonce[24],
const uint8_t *ad, size_t ad_size,
const uint8_t *plain_text, size_t text_size);
int crypto_aead_unlock(uint8_t *plain_text,
const uint8_t mac [16],
const uint8_t key [32],
const uint8_t nonce[24],
const uint8_t *ad, size_t ad_size,
const uint8_t *cipher_text, size_t text_size);
// Authenticated stream
// --------------------
typedef struct {
uint64_t counter;
uint8_t key[32];
uint8_t nonce[8];
} crypto_aead_ctx;
void crypto_aead_init_x(crypto_aead_ctx *ctx,
const uint8_t key[32], const uint8_t nonce[24]);
void crypto_aead_init_djb(crypto_aead_ctx *ctx,
const uint8_t key[32], const uint8_t nonce[8]);
void crypto_aead_init_ietf(crypto_aead_ctx *ctx,
const uint8_t key[32], const uint8_t nonce[12]);
void crypto_aead_write(crypto_aead_ctx *ctx,
uint8_t *cipher_text,
uint8_t mac[16],
const uint8_t *ad , size_t ad_size,
const uint8_t *plain_text, size_t text_size);
int crypto_aead_read(crypto_aead_ctx *ctx,
uint8_t *plain_text,
const uint8_t mac[16],
const uint8_t *ad , size_t ad_size,
const uint8_t *cipher_text, size_t text_size);
// General purpose hash (BLAKE2b)
// ------------------------------
// Direct interface
void crypto_blake2b(uint8_t *hash, size_t hash_size,
const uint8_t *message, size_t message_size);
void crypto_blake2b_keyed(uint8_t *hash, size_t hash_size,
const uint8_t *key, size_t key_size,
const uint8_t *message, size_t message_size);
// Incremental interface
typedef struct {
// Do not rely on the size or contents of this type,
// for they may change without notice.
uint64_t hash[8];
uint64_t input_offset[2];
uint64_t input[16];
size_t input_idx;
size_t hash_size;
} crypto_blake2b_ctx;
void crypto_blake2b_init(crypto_blake2b_ctx *ctx, size_t hash_size);
void crypto_blake2b_keyed_init(crypto_blake2b_ctx *ctx, size_t hash_size,
const uint8_t *key, size_t key_size);
void crypto_blake2b_update(crypto_blake2b_ctx *ctx,
const uint8_t *message, size_t message_size);
void crypto_blake2b_final(crypto_blake2b_ctx *ctx, uint8_t *hash);
// Password key derivation (Argon2)
// --------------------------------
#define CRYPTO_ARGON2_D 0
#define CRYPTO_ARGON2_I 1
#define CRYPTO_ARGON2_ID 2
typedef struct {
uint32_t algorithm; // Argon2d, Argon2i, Argon2id
uint32_t nb_blocks; // memory hardness, >= 8 * nb_lanes
uint32_t nb_passes; // CPU hardness, >= 1 (>= 3 recommended for Argon2i)
uint32_t nb_lanes; // parallelism level (single threaded anyway)
} crypto_argon2_config;
typedef struct {
const uint8_t *pass;
const uint8_t *salt;
uint32_t pass_size;
uint32_t salt_size; // 16 bytes recommended
} crypto_argon2_inputs;
typedef struct {
const uint8_t *key; // may be NULL if no key
const uint8_t *ad; // may be NULL if no additional data
uint32_t key_size; // 0 if no key (32 bytes recommended otherwise)
uint32_t ad_size; // 0 if no additional data
} crypto_argon2_extras;
extern const crypto_argon2_extras crypto_argon2_no_extras;
void crypto_argon2(uint8_t *hash, uint32_t hash_size, void *work_area,
crypto_argon2_config config,
crypto_argon2_inputs inputs,
crypto_argon2_extras extras);
// Key exchange (X-25519)
// ----------------------
// Shared secrets are not quite random.
// Hash them to derive an actual shared key.
void crypto_x25519_public_key(uint8_t public_key[32],
const uint8_t secret_key[32]);
void crypto_x25519(uint8_t raw_shared_secret[32],
const uint8_t your_secret_key [32],
const uint8_t their_public_key [32]);
// Conversion to EdDSA
void crypto_x25519_to_eddsa(uint8_t eddsa[32], const uint8_t x25519[32]);
// scalar "division"
// Used for OPRF. Be aware that exponential blinding is less secure
// than Diffie-Hellman key exchange.
void crypto_x25519_inverse(uint8_t blind_salt [32],
const uint8_t private_key[32],
const uint8_t curve_point[32]);
// "Dirty" versions of x25519_public_key().
// Use with crypto_elligator_rev().
// Leaks 3 bits of the private key.
void crypto_x25519_dirty_small(uint8_t pk[32], const uint8_t sk[32]);
void crypto_x25519_dirty_fast (uint8_t pk[32], const uint8_t sk[32]);
// Signatures
// ----------
// EdDSA with curve25519 + BLAKE2b
void crypto_eddsa_key_pair(uint8_t secret_key[64],
uint8_t public_key[32],
uint8_t seed[32]);
void crypto_eddsa_sign(uint8_t signature [64],
const uint8_t secret_key[64],
const uint8_t *message, size_t message_size);
int crypto_eddsa_check(const uint8_t signature [64],
const uint8_t public_key[32],
const uint8_t *message, size_t message_size);
// Conversion to X25519
void crypto_eddsa_to_x25519(uint8_t x25519[32], const uint8_t eddsa[32]);
// EdDSA building blocks
void crypto_eddsa_trim_scalar(uint8_t out[32], const uint8_t in[32]);
void crypto_eddsa_reduce(uint8_t reduced[32], const uint8_t expanded[64]);
void crypto_eddsa_mul_add(uint8_t r[32],
const uint8_t a[32],
const uint8_t b[32],
const uint8_t c[32]);
void crypto_eddsa_scalarbase(uint8_t point[32], const uint8_t scalar[32]);
int crypto_eddsa_check_equation(const uint8_t signature[64],
const uint8_t public_key[32],
const uint8_t h_ram[32]);
// Chacha20
// --------
// Specialised hash.
// Used to hash X25519 shared secrets.
void crypto_chacha20_h(uint8_t out[32],
const uint8_t key[32],
const uint8_t in [16]);
// Unauthenticated stream cipher.
// Don't forget to add authentication.
uint64_t crypto_chacha20_djb(uint8_t *cipher_text,
const uint8_t *plain_text,
size_t text_size,
const uint8_t key[32],
const uint8_t nonce[8],
uint64_t ctr);
uint32_t crypto_chacha20_ietf(uint8_t *cipher_text,
const uint8_t *plain_text,
size_t text_size,
const uint8_t key[32],
const uint8_t nonce[12],
uint32_t ctr);
uint64_t crypto_chacha20_x(uint8_t *cipher_text,
const uint8_t *plain_text,
size_t text_size,
const uint8_t key[32],
const uint8_t nonce[24],
uint64_t ctr);
// Poly 1305
// ---------
// This is a *one time* authenticator.
// Disclosing the mac reveals the key.
// See crypto_lock() on how to use it properly.
// Direct interface
void crypto_poly1305(uint8_t mac[16],
const uint8_t *message, size_t message_size,
const uint8_t key[32]);
// Incremental interface
typedef struct {
// Do not rely on the size or contents of this type,
// for they may change without notice.
uint8_t c[16]; // chunk of the message
size_t c_idx; // How many bytes are there in the chunk.
uint32_t r [4]; // constant multiplier (from the secret key)
uint32_t pad[4]; // random number added at the end (from the secret key)
uint32_t h [5]; // accumulated hash
} crypto_poly1305_ctx;
void crypto_poly1305_init (crypto_poly1305_ctx *ctx, const uint8_t key[32]);
void crypto_poly1305_update(crypto_poly1305_ctx *ctx,
const uint8_t *message, size_t message_size);
void crypto_poly1305_final (crypto_poly1305_ctx *ctx, uint8_t mac[16]);
// Elligator 2
// -----------
// Elligator mappings proper
void crypto_elligator_map(uint8_t curve [32], const uint8_t hidden[32]);
int crypto_elligator_rev(uint8_t hidden[32], const uint8_t curve [32],
uint8_t tweak);
// Easy to use key pair generation
void crypto_elligator_key_pair(uint8_t hidden[32], uint8_t secret_key[32],
uint8_t seed[32]);
#ifdef __cplusplus
}
#endif
#endif // MONOCYPHER_H

View File

@@ -145,7 +145,6 @@ static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val,
ENetEvent event;
while (enet_host_service(host, &event, secs*1000.0f) > 0) {
JSValue event_obj = JS_NewObject(ctx);
printf("hit from peer with js value %p\n", event.peer->data);
JS_SetPropertyStr(ctx, event_obj, "peer", peer_get_value(ctx, event.peer));
switch (event.type) {

View File

@@ -3,17 +3,16 @@ var os = use('os')
$_.start(e => {
switch(e.type) {
case "greet":
console.log(`parent got message from child with greet.`)
$_.connection(e => console.log(json.encode(e)), e.data)
$_.delay(_ => {
console.log(`sending stop message to ${json.encode(e.data)}`)
$_.stop(e.data)
}, 1);
// $_.couple(e.data)
$_.couple(e.data)
}
}, "tests/underling.js");
$_.unneeded(_ => {console.log("I haven't got a message in a while")}, 3)
$_.contact((actor, reason) => {
}, {
address: "localhost",