checkpoint

This commit is contained in:
Simon Arnell 2025-01-27 19:43:01 +02:00
parent 00184ff68a
commit af465dfcfa
2 changed files with 179 additions and 701 deletions

View file

@ -10,7 +10,7 @@
import Operation from "../Operation.mjs"; import Operation from "../Operation.mjs";
import * as hydro from "libhydrogen/libHydrogen.js"; import * as hydro from "@configuredthings/libhydrogen-wasm/libHydrogen.js";
/** /**
* LibHydrogen Curve25519 Sign operation * LibHydrogen Curve25519 Sign operation
@ -27,31 +27,30 @@ class LibHydrogenCurve25519Signing extends Operation {
this.module = "Crypto"; this.module = "Crypto";
this.description = "Computes a signature for a message using the lightweight LibHydrogen cryptography library"; this.description = "Computes a signature for a message using the lightweight LibHydrogen cryptography library";
this.infoURL = "https://libhydrogen.org/"; this.infoURL = "https://libhydrogen.org/";
this.inputType = "byteArray"; this.inputType = "JSON";
this.outputType = "JSON"; this.outputType = "JSON";
this.args = [ this.args = [
{ {
name: "First arg", name: "Context",
type: "string", type: "string",
value: "Don't Panic" value: ""
}, },
{ {
name: "Second arg", name: "Sender's private key",
type: "number", type: "byteArray",
value: 42 value: ""
} }
]; ];
} }
/** /**
* @param {byteArray} input * @param {JSON} input
* @param {Object[]} args * @param {Object[]} args
* @returns {JSON} * @returns {JSON}
*/ */
run(input, args) { async run(input, args) {
const [firstArg, secondArg] = args; const [context, privateKey] = args;
(async () => { const wasm_src = await fetch(new URL(`${self.docURL}/assets/libhydrogen-wasm/libhydrogen.wasm`));
const wasm_src = await fetch(new URL(`${self.docURL}/assets/libhydrogen/libhydrogen.wasm`));
const wasm = await WebAssembly.compileStreaming(wasm_src); const wasm = await WebAssembly.compileStreaming(wasm_src);
const imports = { const imports = {
"wasi_snapshot_preview1": { "wasi_snapshot_preview1": {
@ -202,15 +201,8 @@ class LibHydrogenCurve25519Signing extends Operation {
// We must call a start method per WASI specification // We must call a start method per WASI specification
// Libhydrogen's main method is one we have patched to initialise it // Libhydrogen's main method is one we have patched to initialise it
instance.exports._start(); instance.exports._start();
// Run the various examples // Generated signature for JSON input
random_uniform(); return await sign(input, context, privateKey);
hash();
keyed_hash();
public_key_signing();
symmetric_encryption();
symmetric_encryption_via_asymmetric_key_exchange();
})();
return {};
} }
} }
@ -219,7 +211,7 @@ let instance, dataview;
// //
// Helper function to reserve space in the buffer used // Helper function to reserve space in the buffer used
// as a stack between Node and the wasm. // as a stack between js and the wasm.
// //
// Offset must be an an object so we can update it's value // Offset must be an an object so we can update it's value
// {value: n} // {value: n}
@ -238,182 +230,10 @@ function reserve(offset, length) {
return a; return a;
} }
//
// Generate a series of random numbers
//
function random_uniform() {
console.log("\n=== random_uniform ===\n");
const { hydro_random_uniform } = instance.exports;
// Testing a simple case of passing integers and fetching integers
console.log(`generated random - ${hydro_random_uniform(20)}`);
console.log(`generated random - ${hydro_random_uniform(20)}`);
console.log(`generated random - ${hydro_random_uniform(20)}`);
console.log(`generated random - ${hydro_random_uniform(20)}`);
console.log(`generated random - ${hydro_random_uniform(20)}`);
}
//
// Hash Generation
//
function hash() {
console.log("\n=== hash ===\n");
const { hydro_hash_hash } = instance.exports;
const textEncoder = new TextEncoder();
// We have to create the stack frame to pass to libHydrogen
// in the dataview Buffer, and then pass in pointers to
// that buffer
const offset = { value: 0 };
const context_str = "Examples";
const context_arr = reserve(offset, hydro.hash_CONTEXTBYTES);
const examples_ab = textEncoder.encode(context_str);
for (let i = 0; i < hydro.hash_CONTEXTBYTES; i++) {
context_arr.set([examples_ab.at(i)], i);
}
// Our message to be hashed
const message_str = "Arbitrary data to hash";
const message_ab = textEncoder.encode(message_str);
const message_arr = reserve(offset, message_ab.length);
for (let i = 0; i < message_ab.length; i++) {
message_arr.set([message_ab.at(i)], i);
}
// Buffer for libHydrogen to write the hash into
const hash = reserve(offset, hydro.hash_BYTES);
// Call the imported function
hydro_hash_hash(
hash.byteOffset,
hash.length,
message_arr.byteOffset,
message_arr.byteLength,
context_arr.byteOffset,
null,
);
console.log(`generated hash - ${Buffer.from(hash).toString("hex")}`);
}
//
// Hash generation with a key
//
function keyed_hash() {
console.log("\n=== keyed_hash ===\n");
// Importing libhydrogen's hashing keygen and hash functions
const { hydro_hash_keygen, hydro_hash_hash } = instance.exports;
const textEncoder = new TextEncoder();
// We have to create the stack frame to pass to libHydrogen
// in the dataview Buffer, and then pass in pointers to
// that buffer
const offset = { value: 0 };
const context_str = "Examples";
const context_arr = reserve(offset, hydro.hash_CONTEXTBYTES);
const examples_ab = textEncoder.encode(context_str);
for (let i = 0; i < hydro.hash_CONTEXTBYTES; i++) {
context_arr.set([examples_ab.at(i)], i);
}
const message_str = "Arbitrary data to hash";
const message_ab = textEncoder.encode(message_str);
const message_arr = reserve(offset, message_ab.length);
for (let i = 0; i < message_ab.length; i++) {
message_arr.set([message_ab.at(i)], i);
}
// Reserve buffer space for the returned key
const key = reserve(offset, hydro.hash_KEYBYTES);
// Reserve space for the hash result
const keyedhash = reserve(offset, hydro.hash_BYTES);
// Generate hashing key
hydro_hash_keygen(key.byteOffset);
console.log(`generated hash key - ${key}`);
// Create a hash with the key
hydro_hash_hash(
keyedhash.byteOffset,
keyedhash.length,
message_arr.byteOffset,
message_arr.byteLength,
context_arr.byteOffset,
key.byteOffset,
);
const khash1 = keyedhash.toString("hex");
console.log(`khash1 - ${khash1}`);
keyedhash.fill(0); // Resetting output buffer (seems to pollute state otherwise)
// Hashing message with key again
hydro_hash_hash(
keyedhash.byteOffset,
keyedhash.length,
message_arr.byteOffset,
message_arr.byteLength,
context_arr.byteOffset,
key.byteOffset,
);
const khash2 = keyedhash.toString("hex");
console.log(`khash2 - ${khash2}`);
keyedhash.fill(0);
console.log(`${keyedhash.toString("hex")}`);
// Check that the same hash is generated
if (khash1 === khash2) console.log("khash1 equals khash2");
// Testing whether we can load a key and generate a matching hash created previously
const presetKey = "f539065185b3ce774b0c748564e804a3717ca7d0c08231076e8b7920814f0bba";
console.log(`presetKey - ${presetKey}`);
const historicHash = "4004516ceff97883804dbdb221baeb7283256e60165d0715d0152e4d6a6cbad4";
console.log(`historicHash - ${historicHash}`);
const loadedKey = new Uint8Array(presetKey.match(/../g).map(h=>parseInt(h, 16)));
for (let i = 0; i < loadedKey.length; i++) {
key.set([loadedKey.at(i)], i);
}
// Hashing message with presetKey
hydro_hash_hash(
keyedhash.byteOffset,
keyedhash.length,
message_arr.byteOffset,
message_arr.byteLength,
context_arr.byteOffset,
key.byteOffset,
);
const khash3 = keyedhash.toString("hex");
console.log(`khash3 - ${khash3}`);
// Testing hash matches historicHash
if (khash3 === historicHash)
console.log("khash3 using old key, matches historicHash");
// ...and doesn't match the hashes with the latest key
if (khash1 !== khash3)
console.log("khash3 does not equal khash1");
// clear the buffer for the next example
context_arr.fill(0);
message_arr.fill(0);
keyedhash.fill(0);
key.fill(0);
}
// //
// Public key signing // Public key signing
// //
function public_key_signing() { async function sign(input, context, privateKey) {
console.log("\n=== public_key_signing ===\n");
// Importing libhydrogen's signing keygen and signing and verification functions // Importing libhydrogen's signing keygen and signing and verification functions
const { hydro_sign_keygen, hydro_sign_create, hydro_sign_verify } = instance.exports; const { hydro_sign_keygen, hydro_sign_create, hydro_sign_verify } = instance.exports;
const textEncoder = new TextEncoder(); const textEncoder = new TextEncoder();
@ -422,16 +242,14 @@ function public_key_signing() {
// in the dataview Buffer, and then pass in pointers to // in the dataview Buffer, and then pass in pointers to
// that buffer // that buffer
const offset = { value: 0 }; const offset = { value: 0 };
const context_str = "Examples";
const context_arr = reserve(offset, hydro.hash_CONTEXTBYTES); const context_arr = reserve(offset, hydro.hash_CONTEXTBYTES);
const examples_ab = textEncoder.encode(context_str); const context_ab = textEncoder.encode(context);
for (let i = 0; i < hydro.hash_CONTEXTBYTES; i++) { for (let i = 0; i < hydro.hash_CONTEXTBYTES; i++) {
context_arr.set([examples_ab.at(i)], i); context_arr.set([context_ab.at(i)], i);
} }
const message_str = "Arbitrary data to hash"; const message_ab = textEncoder.encode(JSON.stringify(input));
const message_ab = textEncoder.encode(message_str);
const message_arr = reserve(offset, message_ab.length); const message_arr = reserve(offset, message_ab.length);
for (let i = 0; i < message_ab.length; i++) { for (let i = 0; i < message_ab.length; i++) {
@ -439,10 +257,10 @@ function public_key_signing() {
} }
// Generate a key pair // Generate a key pair
const keypair = reserve(offset, hydro.sign_PUBLICKEYBYTES + hydro.sign_SECRETKEYBYTES); const privateKey_arr = reserve(offset, hydro.sign_SECRETKEYBYTES);
const publicKeyOffset = keypair.byteOffset; for (let i = 0; i < privateKey.length; i++) {
const privateKeyOffset = keypair.byteOffset + hydro.sign_PUBLICKEYBYTES; privateKey_arr.set([privateKey.at(i)], i);
hydro_sign_keygen(keypair.byteOffset); }
// Reserving memory for the signature // Reserving memory for the signature
const signature = reserve(offset, hydro.sign_BYTES); const signature = reserve(offset, hydro.sign_BYTES);
@ -453,356 +271,16 @@ function public_key_signing() {
message_arr.byteOffset, message_arr.byteOffset,
message_arr.byteLength, message_arr.byteLength,
context_arr.byteOffset, context_arr.byteOffset,
privateKeyOffset, privateKey_arr.byteOffset,
); );
console.log(`generated signature - ${Buffer.from(signature).toString("hex")}`); console.log(`generated signature - ${Buffer.from(signature).toString("hex")}`);
// Verifying signature with public key return {
const res = hydro_sign_verify( context,
signature.byteOffset, input,
message_arr.byteOffset, signature: Buffer.from(signature).toString("hex")
message_arr.byteLength,
context_arr.byteOffset,
publicKeyOffset,
);
if (res === 0) console.log("Signature is correctly valid");
signature.set([0]); // Modifying signature
console.log(`modified signature - ${Buffer.from(signature).toString("hex")}`);
const manipulatedRes = hydro_sign_verify(
signature.byteOffset,
message_arr.byteOffset, // Reverifying signature
message_arr.byteLength,
context_arr.byteOffset,
publicKeyOffset,
);
if (manipulatedRes !== 0) console.log("Signature is correctly invalid");
message_arr.fill(0);
keypair.fill(0);
signature.fill(0);
}
//
// Secret Key Encryption
//
function symmetric_encryption() {
console.log("\n=== symmetric_encryption ===\n");
// Importing libhydrogen's secretbox keygen and encrypt and decrypt functions
const { hydro_secretbox_keygen, hydro_secretbox_encrypt, hydro_secretbox_decrypt } =
instance.exports;
const textEncoder = new TextEncoder();
// We have to create the stack frame to pass to libHydrogen
// in the dataview Buffer, and then pass in pointers to
// that buffer
const offset = { value: 0 };
const context_str = "Examples";
const context_arr = reserve(offset, hydro.hash_CONTEXTBYTES);
const examples_ab = textEncoder.encode(context_str);
for (let i = 0; i < hydro.hash_CONTEXTBYTES; i++) {
context_arr.set([examples_ab.at(i)], i);
}
// Reserving buffer for the message
const message_str = "Arbitrary data to hash";
const message_ab = textEncoder.encode(message_str);
const message_arr = reserve(offset, message_ab.length);
for (let i = 0; i < message_ab.length; i++) {
message_arr.set([message_ab.at(i)], i);
}
console.log(`message - ${message_arr}`);
// Reserving buffer for the key
const key = reserve(offset, hydro.secretbox_KEYBYTES);
hydro_secretbox_keygen(key.byteOffset);
console.log(`generated key - ${key.toString("hex")}`);
// Reserving buffer for the cipher text
const cipherTextLength = hydro.secretbox_HEADERBYTES + message_arr.length;
const ciphertext = reserve(offset, cipherTextLength);
// Enciphering single message (thus use of msg_id 0n -- 'n' as libhydrogen expects i64)
hydro_secretbox_encrypt(
ciphertext.byteOffset,
message_arr.byteOffset,
message_arr.byteLength,
0n,
context_arr.byteOffset,
key.byteOffset,
);
// Reserving buffer for the decrypted plain text
const decryptedPlaintextLength = ciphertext.byteLength - hydro.secretbox_HEADERBYTES;
const decryptedPlaintext = reserve(offset, decryptedPlaintextLength);
// Deciphering single message (thus use of msg_id 0n -- 'n' as libhydrogen expects i64)
const res = hydro_secretbox_decrypt(
decryptedPlaintext.byteOffset,
ciphertext.byteOffset,
ciphertext.byteLength,
0n,
context_arr.byteOffset,
key.byteOffset,
);
if (res === 0) {
// As secretbox is an authenticated encryption (AEAD) algorithm
// we check that the ciphertext was authentic
console.log("cipherText not forged");
// Decoding Uint8 encoded string
const textDecoder = new TextDecoder();
console.log(`decryptedPlaintext - ${textDecoder.decode(decryptedPlaintext)}`);
}
message_arr.fill(0);
key.fill(0);
ciphertext.fill(0);
decryptedPlaintext.fill(0);
}
//
// Symmetric Encryption using a Key Generated
// via Asymmetric key exchange
//
function symmetric_encryption_via_asymmetric_key_exchange() {
console.log("\n=== symmetric_encryption_via_asymmetric_key_exchange ===\n");
const { hydro_kx_keygen, hydro_kx_kk_1, hydro_kx_kk_2, hydro_kx_kk_3 } = instance.exports;
const textEncoder = new TextEncoder();
//
// Reserve space in the buffer for the keypair exchange
//
// Both Alice and Bob need a keypair
//
// Both Alice and bob need a pair of session keys
// tx to encrpyt
// rx to decrypt
//
// Alice initiates the key exchange, so she also needs a kx state
// buffer
//
const offset = { value: 0 };
const alice = {
static: {
pk: reserve(offset, hydro.kx_PUBLICKEYBYTES),
sk: reserve(offset, hydro.kx_SECRETKEYBYTES),
},
session: {
rx: reserve(offset, hydro.kx_SESSIONKEYBYTES),
tx: reserve(offset, hydro.kx_SESSIONKEYBYTES),
},
}; };
const bob = {
static: {
pk: reserve(offset, hydro.kx_PUBLICKEYBYTES),
sk: reserve(offset, hydro.kx_SECRETKEYBYTES),
},
session: {
rx: reserve(offset, hydro.kx_SESSIONKEYBYTES),
tx: reserve(offset, hydro.kx_SESSIONKEYBYTES),
},
};
const keysOffset = offset.value;
alice.state = reserve(offset, hydro.kx_SESSIONKEYBYTES);
// We need two "packets" for the messages exchanged between
// Alice and Bob during the key exchange
const packets = {
1: reserve(offset, hydro.kx_KK_PACKET1BYTES),
2: reserve(offset, hydro.kx_KK_PACKET2BYTES),
};
console.log("\n=== symmetric_encryption_via_asymmetric_key_exchange ===\n");
//
// Generate the static keys
//
console.log("------------ALICEKEYGEN------------");
hydro_kx_keygen(alice.static.pk.byteOffset);
console.log("---------------ALICE---------------");
console.log(alice);
console.log("-------------BOBKEYGEN-------------");
hydro_kx_keygen(bob.static.pk.byteOffset);
console.log("----------------BOB----------------");
console.log(bob);
//
// Key exchange
//
// Performing kk_1 (Generating alice's ephemeral keypair and packet 1)
console.log("-----------hydro_kx_kk_1-----------");
hydro_kx_kk_1(
alice.state.byteOffset,
packets[1].byteOffset,
bob.static.pk.byteOffset,
alice.static.pk.byteOffset,
);
console.log("---------------ALICE---------------");
console.dir(alice, { maxArrayLength: null });
console.log("--------------packets--------------");
console.log(packets);
// Performing kk_2 (Generating bob's response packet 2 and his copy of session keys)
console.log("-----------hydro_kx_kk_2-----------");
hydro_kx_kk_2(
bob.session.rx.byteOffset,
packets[2].byteOffset,
packets[1].byteOffset,
alice.static.pk.byteOffset,
bob.static.pk.byteOffset,
);
console.log("----------------BOB----------------");
console.dir(bob, { maxArrayLength: null });
console.log("--------------packets--------------");
console.log(packets);
// Performing kk_3 (Generating Alice's copy of session keys)
console.log("-----------hydro_kx_kk_3-----------");
hydro_kx_kk_3(
alice.state.byteOffset,
alice.session.rx.byteOffset,
packets[2].byteOffset,
alice.static.pk.byteOffset,
);
console.log("---------------ALICE---------------");
console.dir(alice, { maxArrayLength: null });
console.log("-----------KEYS EXCHANGED----------");
// Tidy up the buffer space used in the key exchange
alice.state.fill(0);
packets[1].fill(0);
packets[2].fill(0);
offset.value = keysOffset;
//
// Use the session keys to exchange messages
//
const { hydro_secretbox_encrypt, hydro_secretbox_decrypt } = instance.exports;
const context_str = "Examples";
const context_arr = reserve(offset, hydro.hash_CONTEXTBYTES);
const examples_ab = textEncoder.encode(context_str);
for (let i = 0; i < hydro.hash_CONTEXTBYTES; i++) {
context_arr.set([examples_ab.at(i)], i);
}
const message1_str = "Hello Bob";
const message1_ab = textEncoder.encode(message1_str);
const message1_arr = reserve(offset, message1_ab.length);
for (let i = 0; i < message1_ab.length; i++) {
message1_arr.set([message1_ab.at(i)], i);
}
console.log(`message1_arr - ${message1_arr}`);
const ciphertext1_length = hydro.secretbox_HEADERBYTES + message1_arr.length;
const ciphertext1_arr = reserve(offset, ciphertext1_length);
//
// Alice Encypts the message with her session tx key
//
// Enciphering single message (thus use of msg_id 0n -- 'n' as libhydrogen expects i64)
//
hydro_secretbox_encrypt(
ciphertext1_arr.byteOffset,
message1_arr.byteOffset,
message1_arr.byteLength,
0n,
context_arr.byteOffset,
alice.session.tx.byteOffset,
);
//
// Bob Decrypts the mesaage with his session rx key
//
// Deciphering single message (thus use of msg_id 0n -- 'n' as libhydrogen expects i64)
//
const decryptedPlaintext1Length = ciphertext1_arr.byteLength - hydro.secretbox_HEADERBYTES;
const decryptedPlaintext1 = reserve(offset, decryptedPlaintext1Length);
const res1 = hydro_secretbox_decrypt(
decryptedPlaintext1.byteOffset,
ciphertext1_arr.byteOffset,
ciphertext1_arr.byteLength,
0n,
context_arr.byteOffset,
bob.session.rx.byteOffset,
);
// As secretbox is an authenticated encryption (AEAD) algorithm
// we check that the ciphertext was authentic
if (res1 === 0) {
console.log("ciphertext1_arr not forged");
// Decoding Uint8 encoded string
const textDecoder = new TextDecoder();
console.log(`decryptedPlaintext1 - ${textDecoder.decode(decryptedPlaintext1)}`);
}
//
// Bob sends a reply to Alice
//
const message2_str = "Hello Alice";
const message2_ab = textEncoder.encode(message2_str);
const message2_arr = reserve(offset, message2_ab.length);
for (let i = 0; i < message2_ab.length; i++) {
message2_arr.set([message2_ab.at(i)], i);
}
console.log(`message2_arr - ${message2_arr}`);
const ciphertext2Length = hydro.secretbox_HEADERBYTES + message2_arr.length;
const ciphertext2_arr = reserve(offset, ciphertext2Length);
//
// Bob encrypts the message with his session tx key
//
hydro_secretbox_encrypt(
ciphertext2_arr.byteOffset,
message2_arr.byteOffset,
message2_arr.byteLength,
0n,
context_arr.byteOffset,
bob.session.tx.byteOffset,
);
//
// Alice decrypts the message with her session rx key
//
const decryptedPlaintext2Length = ciphertext2_arr.byteLength - hydro.secretbox_HEADERBYTES;
const decryptedPlaintext2_arr = reserve(offset, decryptedPlaintext2Length);
const res2 = hydro_secretbox_decrypt(
decryptedPlaintext2_arr.byteOffset,
ciphertext2_arr.byteOffset,
ciphertext2_arr.byteLength,
0n,
context_arr.byteOffset,
alice.session.rx.byteOffset,
);
// As secretbox is an authenticated encryption (AEAD) algorithm
// we check that the ciphertext was authentic
if (res2 === 0) {
console.log("ciphertext2 not forged");
const textDecoder = new TextDecoder();
// Decoding Uint8 encoded string
console.log(`decryptedPlaintext2 - ${textDecoder.decode(decryptedPlaintext2_arr)}`);
}
} }
export default LibHydrogenCurve25519Signing; export default LibHydrogenCurve25519Signing;

View file

@ -83,9 +83,9 @@ module.exports = {
from: "prime.worker.min.js", from: "prime.worker.min.js",
to: "assets/forge/" to: "assets/forge/"
}, { }, {
context: "node_modules/libhydrogen/", context: "node_modules/@configuredthings/libhydrogen-wasm/",
from: "libhydrogen.wasm", from: "libhydrogen.wasm",
to: "assets/libhydrogen" to: "assets/libhydrogen-wasm"
}, },
] ]
}), }),