commit 71edace76a72da7328d9036da41748f92237775a
parent 496407d29ddae7db3a6bcb33e87eb73801d50a7e
Author: William Casarin <jb55@jb55.com>
Date: Mon, 7 Mar 2022 20:14:11 -0800
lnsocket working in the browser!
This adds a wasm build target for lnsocket. See lnsocket_example.js for
a demo
Diffstat:
19 files changed, 670 insertions(+), 98 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -10,3 +10,7 @@
/test
TODO.bak
*.a
+*.wasm
+/node_modules
+lnsocket_module.js
+lnsocket.js
diff --git a/Makefile b/Makefile
@@ -1,5 +1,5 @@
-CFLAGS=-Wall -g -Og -Ideps/secp256k1/include -Ideps/libsodium/src/libsodium/include -Ideps
+CFLAGS=-Wall -g -O2 -Ideps/secp256k1/include -Ideps/libsodium/src/libsodium/include -Ideps
LDFLAGS=
SUBMODULES=deps/libsodium deps/secp256k1
@@ -10,9 +10,11 @@ SIM_SDK=$(XCODEDIR)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimu
IOS_SDK=$(XCODEDIR)/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk
ARS=libsecp256k1.a libsodium.a
+WASM_ARS=target/wasm/libsecp256k1.a target/wasm/libsodium.a target/wasm/lnsocket.a
OBJS=sha256.o hkdf.o hmac.o sha512.o lnsocket.o error.o handshake.o crypto.o bigsize.o commando.o
ARM64_OBJS=$(OBJS:.o=-arm64.o)
X86_64_OBJS=$(OBJS:.o=-x86_64.o)
+WASM_OBJS=$(OBJS:.o=-wasm.o) lnsocket_wasm-wasm.o
BINS=test lnrpc
DEPS=$(OBJS) $(ARS) config.h
@@ -21,6 +23,11 @@ all: $(BINS) lnsocket.a
ios: target/ios/lnsocket.a target/ios/libsodium.a target/ios/libsecp256k1.a
+wasm: lnsocket.js
+
+lnsocket.js: lnsocket_module.js lnsocket_lib.js
+ cat lnsocket_module.js lnsocket_lib.js > $@
+
deps/libsodium/.git:
@tools/refresh-submodules.sh $(SUBMODULES)
@@ -38,6 +45,10 @@ target/x86_64/lnsocket.a: $(X86_64_OBJS)
@mkdir -p target/x86_64
ar rcs $@ $^
+target/wasm/lnsocket.a: $(WASM_OBJS)
+ @mkdir -p target/wasm
+ emar rcs $@ $^
+
target/ios/lnsocket.a: target/x86_64/lnsocket.a target/arm64/lnsocket.a
@mkdir -p target/ios
lipo -create $^ -output $@
@@ -46,6 +57,10 @@ target/ios/lnsocket.a: target/x86_64/lnsocket.a target/arm64/lnsocket.a
@echo "cc $@"
@$(CC) $(CFLAGS) -c $< -o $@ -arch arm64 -isysroot $(IOS_SDK) -target arm64-apple-ios -fembed-bitcode
+%-wasm.o: %.c config.h
+ @echo "emcc $@"
+ @emcc $(CFLAGS) -c $< -o $@
+
%-x86_64.o: %.c config.h
@echo "cc $@"
@$(CC) $(CFLAGS) -c $< -o $@ -arch x86_64 -isysroot $(SIM_SDK) -mios-simulator-version-min=6.0.0 -target x86_64-apple-ios-simulator
@@ -88,9 +103,19 @@ libsodium.a: deps/libsodium/src/libsodium/.libs/libsodium.a
cp $< $@
target/ios/libsodium.a: deps/libsodium/libsodium-ios/lib/libsodium.a
+ mkdir -p target/ios
cp $< $@
target/ios/libsecp256k1.a: deps/secp256k1/libsecp256k1-ios/lib/libsecp256k1.a
+ mkdir -p target/ios
+ cp $< $@
+
+target/wasm/libsecp256k1.a: deps/secp256k1/libsecp256k1-wasm/lib/libsecp256k1.a
+ mkdir -p target/wasm
+ cp $< $@
+
+target/wasm/libsodium.a: deps/libsodium/libsodium-wasm/lib/libsodium.a
+ mkdir -p target/wasm
cp $< $@
deps/libsodium/libsodium-ios/lib/libsodium.a:
@@ -100,10 +125,19 @@ deps/libsodium/libsodium-ios/lib/libsodium.a:
deps/secp256k1/libsecp256k1-ios/lib/libsecp256k1.a:
./tools/secp-ios.sh
+deps/secp256k1/libsecp256k1-wasm/lib/libsecp256k1.a:
+ ./tools/secp-wasm.sh
+
+deps/libsodium/libsodium-wasm/lib/libsodium.a:
+ ./tools/sodium-wasm.sh
+
deps/libsodium/src/libsodium/.libs/libsodium.a: deps/libsodium/config.status
cd deps/libsodium/src/libsodium; \
make -j2 libsodium.la
+check: test
+ @./test
+
test: test.o $(DEPS) $(ARS)
@echo "ld test"
@$(CC) $(CFLAGS) test.o $(OBJS) $(ARS) $(LDFLAGS) -o $@
@@ -112,14 +146,17 @@ lnrpc: rpc.o $(DEPS) $(ARS)
@echo "ld lnrpc"
@$(CC) $(CFLAGS) rpc.o $(OBJS) $(ARS) $(LDFLAGS) -o $@
+lnsocket_module.js: $(WASM_ARS) lnsocket_pre.js
+ emcc --pre-js lnsocket_pre.js -s ENVIRONMENT=web -s MODULARIZE -s EXPORTED_RUNTIME_METHODS=ccall,cwrap $(CFLAGS) -Wl,-whole-archive $(WASM_ARS) -Wl,-no-whole-archive -o $@
+
tags: fake
find . -name '*.c' -or -name '*.h' | xargs ctags
clean: fake
- rm -rf $(BINS) config.h $(OBJS) $(ARM64_OBJS) $(X86_64_OBJS) target
+ rm -rf $(BINS) config.h $(OBJS) $(ARM64_OBJS) $(X86_64_OBJS) $(WASM_OBJS) target
distclean: clean
- rm -rf $(ARS) deps/secp256k1/src/libsecp256k1-config.h deps/libsodium/libsodium-ios deps/secp256k1/libsecp256k1-ios
+ rm -rf $(ARS) deps/secp256k1/src/libsecp256k1-config.h deps/libsodium/libsodium-ios deps/secp256k1/libsecp256k1-ios deps/libsodium/libsodium-wasm deps/secp256k1/libsecp256k1-wasm lnsocket.wasm
cd deps/secp256k1; \
make distclean
cd deps/libsodium; \
diff --git a/commando.c b/commando.c
@@ -3,10 +3,10 @@
#include "cursor.h"
#include "endian.h"
#include "commando.h"
+#include "export.h"
-int commando_make_rpc_msg(const char *method, const char *params,
- const char *rune, uint64_t req_id, unsigned char *buf,
- int buflen, unsigned short *outlen)
+int EXPORT commando_make_rpc_msg(const char *method, const char *params,
+ const char *rune, unsigned int req_id, unsigned char *buf, int buflen)
{
struct cursor msgbuf;
int ok;
@@ -18,7 +18,7 @@ int commando_make_rpc_msg(const char *method, const char *params,
if (!cursor_push_u16(&msgbuf, COMMANDO_CMD))
return 0;
- if (!cursor_push_u64(&msgbuf, req_id))
+ if (!cursor_push_u64(&msgbuf, (u64)req_id))
return 0;
ok = cursor_push_str(&msgbuf, "{\"method\":\"") &&
@@ -32,7 +32,5 @@ int commando_make_rpc_msg(const char *method, const char *params,
if (!ok)
return 0;
- *outlen = msgbuf.p - msgbuf.start;
-
- return 1;
+ return msgbuf.p - msgbuf.start;
}
diff --git a/commando.h b/commando.h
@@ -3,11 +3,12 @@
#define LNSOCKET_COMMANDO
#include <inttypes.h>
+#include "export.h"
#define COMMANDO_CMD 0x4c4f
#define COMMANDO_REPLY_CONTINUES 0x594b
#define COMMANDO_REPLY_TERM 0x594d
-int commando_make_rpc_msg(const char *method, const char *params, const char *rune, uint64_t req_id, unsigned char *buf, int buflen, unsigned short *outlen);
+int EXPORT commando_make_rpc_msg(const char *method, const char *params, const char *rune, unsigned int req_id, unsigned char *buf, int buflen);
#endif /* LNSOCKET_COMMANDO */
diff --git a/export.h b/export.h
@@ -0,0 +1,12 @@
+
+#ifndef LNSOCKET_COMMON_H
+#define LNSOCKET_COMMON_H
+
+#ifdef __EMSCRIPTEN__
+ #include <emscripten.h>
+ #define EXPORT EMSCRIPTEN_KEEPALIVE
+#else
+ #define EXPORT
+#endif
+
+#endif
diff --git a/handshake.c b/handshake.c
@@ -31,6 +31,7 @@ THE SOFTWARE.
#include <secp256k1_ecdh.h>
#include <unistd.h>
#include <sodium/randombytes.h>
+#include "export.h"
struct keypair generate_key(secp256k1_context *ctx)
{
@@ -232,10 +233,11 @@ static int handshake_success(struct lnsocket *ln, struct handshake *h)
return 1;
}
-static int act_three_initiator(struct lnsocket *ln, struct handshake *h)
+static struct act_three *build_act_three(struct lnsocket *ln)
{
u8 spub[PUBKEY_CMPR_LEN];
size_t len = sizeof(spub);
+ struct handshake *h = &ln->handshake;
/* BOLT #8:
* 1. `c = encryptWithAD(temp_k2, 1, h, s.pub.serializeCompressed())`
@@ -258,8 +260,10 @@ static int act_three_initiator(struct lnsocket *ln, struct handshake *h)
* * where `re` is the ephemeral public key of the responder
*/
if (!secp256k1_ecdh(ln->secp, h->ss.data, &h->re.pubkey,
- ln->key.priv.secret.data, NULL, NULL))
- return note_error(&ln->errs, "act3 ecdh handshake failed");
+ ln->key.priv.secret.data, NULL, NULL)) {
+ note_error(&ln->errs, "act3 ecdh handshake failed");
+ return NULL;
+ }
/* BOLT #8:
*
@@ -284,40 +288,30 @@ static int act_three_initiator(struct lnsocket *ln, struct handshake *h)
*/
h->act3.v = 0;
- if (write(ln->socket, &h->act3, ACT_THREE_SIZE) != ACT_THREE_SIZE) {
- return note_error(&ln->errs, "handshake failed on initial send");
- }
+ handshake_success(ln, &ln->handshake);
- return handshake_success(ln, h);
+ return &h->act3;
}
-// act2: read the response to the message sent in act1
-static int act_two_initiator(struct lnsocket *ln, struct handshake *h)
-{
- /* BOLT #8:
- *
- * 1. Read _exactly_ 50 bytes from the network buffer.
- *
- * 2. Parse the read message (`m`) into `v`, `re`, and `c`:
- * * where `v` is the _first_ byte of `m`, `re` is the next 33
- * bytes of `m`, and `c` is the last 16 bytes of `m`.
- */
- ssize_t size;
- if ((size = read(ln->socket, &h->act2, ACT_TWO_SIZE)) != ACT_TWO_SIZE) {
- printf("read %ld bytes, expected %d\n", size, ACT_TWO_SIZE);
- return note_error(&ln->errs, "%s", strerror(errno));
- }
+// act2: handle the response to the message sent in act1
+struct act_three* EXPORT lnsocket_act_two(struct lnsocket *ln, struct act_two *act2)
+{
+ struct handshake *h = &ln->handshake;
- //print_act_two(&h->act2);
+ //print_act_two(act2);
/* BOLT #8:
*
* 3. If `v` is an unrecognized handshake version, then the responder
* MUST abort the connection attempt.
*/
- if (h->act2.v != 0)
- return note_error(&ln->errs, "unrecognized handshake version");
+ if (act2->v != 0) {
+ note_error(&ln->errs, "unrecognized handshake version");
+ return NULL;
+ }
+
+ //print_hex()
/* BOLT #8:
*
@@ -326,9 +320,10 @@ static int act_two_initiator(struct lnsocket *ln, struct handshake *h)
* affine coordinates as encoded by the key's serialized
* composed format.
*/
- if (secp256k1_ec_pubkey_parse(ln->secp, &h->re.pubkey, h->act2.pubkey,
- sizeof(h->act2.pubkey)) != 1) {
- return note_error(&ln->errs, "failed to parse remote pubkey");
+ if (secp256k1_ec_pubkey_parse(ln->secp, &h->re.pubkey, act2->pubkey,
+ sizeof(act2->pubkey)) != 1) {
+ note_error(&ln->errs, "failed to parse remote pubkey");
+ return NULL;
}
/* BOLT #8:
@@ -343,7 +338,8 @@ static int act_two_initiator(struct lnsocket *ln, struct handshake *h)
*/
if (!secp256k1_ecdh(ln->secp, h->ss.data, &h->re.pubkey,
h->e.priv.secret.data, NULL, NULL)) {
- return note_error(&ln->errs, "act2 ecdh failed");
+ note_error(&ln->errs, "act2 ecdh failed");
+ return NULL;
}
/* BOLT #8:
@@ -361,8 +357,9 @@ static int act_two_initiator(struct lnsocket *ln, struct handshake *h)
* MUST terminate the connection without any further messages.
*/
if (!decrypt(&h->temp_k, 0, &h->h, sizeof(h->h),
- h->act2.tag, sizeof(h->act2.tag), NULL, 0)) {
- return note_error(&ln->errs, "handshake decrypt failed");
+ act2->tag, sizeof(act2->tag), NULL, 0)) {
+ note_error(&ln->errs, "handshake decrypt failed");
+ return NULL;
}
/* BOLT #8:
@@ -372,15 +369,17 @@ static int act_two_initiator(struct lnsocket *ln, struct handshake *h)
* This step serves to ensure the payload wasn't modified by a
* MITM.
*/
- sha_mix_in(&h->h, h->act2.tag, sizeof(h->act2.tag));
+ sha_mix_in(&h->h, act2->tag, sizeof(act2->tag));
- return act_three_initiator(ln, h);
+ return build_act_three(ln);
}
// Prepare the very first message and send it the connected node
// Wait for a response in act2
-int act_one_initiator(struct lnsocket *ln, struct handshake *h)
+int act_one_initiator_prep(struct lnsocket *ln)
{
+ struct handshake *h = &ln->handshake;
+
h->e = generate_key(ln->secp);
/* BOLT #8:
@@ -437,9 +436,46 @@ int act_one_initiator(struct lnsocket *ln, struct handshake *h)
check_act_one(&h->act1);
- if (write(ln->socket, &h->act1, ACT_ONE_SIZE) != ACT_ONE_SIZE) {
+ return 1;
+}
+
+// act2: read the response to the message sent in act1
+static int act_two_initiator(struct lnsocket *ln, struct handshake *h)
+{
+ /* BOLT #8:
+ *
+ * 1. Read _exactly_ 50 bytes from the network buffer.
+ *
+ * 2. Parse the read message (`m`) into `v`, `re`, and `c`:
+ * * where `v` is the _first_ byte of `m`, `re` is the next 33
+ * bytes of `m`, and `c` is the last 16 bytes of `m`.
+ */
+ ssize_t size;
+
+ if ((size = read(ln->socket, &h->act2, ACT_TWO_SIZE)) != ACT_TWO_SIZE) {
+ printf("read %ld bytes, expected %d\n", size, ACT_TWO_SIZE);
+ return note_error(&ln->errs, "%s", strerror(errno));
+ }
+
+ struct act_three *act3 = lnsocket_act_two(ln, &h->act2);
+ if (act3 == NULL)
+ return 0;
+
+ if (write(ln->socket, act3, ACT_THREE_SIZE) != ACT_THREE_SIZE) {
+ return note_error(&ln->errs, "handshake failed on initial send");
+ }
+
+ return 1;
+}
+
+int act_one_initiator(struct lnsocket *ln)
+{
+ if (!act_one_initiator_prep(ln))
+ return 0;
+
+ if (write(ln->socket, &ln->handshake.act1, ACT_ONE_SIZE) != ACT_ONE_SIZE) {
return note_error(&ln->errs, "handshake failed on initial send");
}
- return act_two_initiator(ln, h);
+ return act_two_initiator(ln, &ln->handshake);
}
diff --git a/handshake.h b/handshake.h
@@ -140,7 +140,8 @@ void new_handshake(secp256k1_context *secp, struct handshake *handshake,
struct lnsocket;
-int act_one_initiator(struct lnsocket *ln, struct handshake *h);
+int act_one_initiator_prep(struct lnsocket *ln);
+int act_one_initiator(struct lnsocket *ln);
struct keypair generate_key(secp256k1_context *ctx);
#endif /* LNLINK_HANDSHAKE_H */
diff --git a/index.htm b/index.htm
@@ -0,0 +1,18 @@
+
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+
+ <title>Hello World</title>
+ </head>
+ <body>
+ <h1>Hello World</h1>
+ <script src="lnsocket.js"></script>
+ <script src="lnsocket_example.js"></script>
+ <script>
+ </script>
+ </body>
+</html>
+
diff --git a/lnsocket.c b/lnsocket.c
@@ -71,12 +71,12 @@ static int hex_decode(const char *str, size_t slen, void *buf, size_t bufsize)
}
-static int parse_node_id(const char *str, struct node_id *dest)
+int parse_node_id(const char *str, struct node_id *dest)
{
return hex_decode(str, strlen(str), dest->k, sizeof(*dest));
}
-static int pubkey_from_node_id(secp256k1_context *secp, struct pubkey *key,
+int pubkey_from_node_id(secp256k1_context *secp, struct pubkey *key,
const struct node_id *id)
{
return secp256k1_ec_pubkey_parse(secp, &key->pubkey,
@@ -102,18 +102,13 @@ static int read_all(int fd, void *data, size_t size)
return 1;
}
-int lnsocket_perform_init(struct lnsocket *ln)
+int EXPORT lnsocket_make_default_initmsg(unsigned char *msgbuf, int buflen)
{
u8 tlvbuf[1024];
- u8 msgbuf[1024];
+ struct tlv network_tlv;
u8 global_features[2] = {0};
u8 features[5] = {0};
- struct tlv network_tlv;
u16 len;
- u8 *buf;
-
- if (!lnsocket_read(ln, &buf, &len))
- return 0;
const u8 genesis_block[] = {
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72, 0xc1, 0xa6, 0xa2, 0x46,
@@ -129,13 +124,36 @@ int lnsocket_perform_init(struct lnsocket *ln)
const struct tlv *init_tlvs[] = { &network_tlv } ;
- if (!lnsocket_make_init_msg(msgbuf, sizeof(msgbuf),
+ if (!lnsocket_make_init_msg(msgbuf, buflen,
global_features, sizeof(global_features),
features, sizeof(features),
init_tlvs, 1,
&len))
return 0;
+ return (int)len;
+}
+
+static void print_hex(u8 *bytes, int len) {
+ int i;
+ for (i = 0; i < len; ++i) {
+ printf("%02x", bytes[i]);
+ }
+}
+
+int lnsocket_perform_init(struct lnsocket *ln)
+{
+ u8 msgbuf[1024];
+ u16 len;
+ u8 *buf;
+
+ // read the init message from the other side and ignore it
+ if (!lnsocket_read(ln, &buf, &len))
+ return 0;
+
+ if (!(len = lnsocket_make_default_initmsg(msgbuf, sizeof(msgbuf))))
+ return 0;
+
if (!lnsocket_write(ln, msgbuf, len))
return 0;
@@ -180,27 +198,24 @@ int lnsocket_recv(struct lnsocket *ln, u16 *msg_type, unsigned char **payload, u
return 1;
}
-int lnsocket_read(struct lnsocket *ln, unsigned char **buf, unsigned short *len)
+int EXPORT lnsocket_decrypt(struct lnsocket *ln, unsigned char *packet, int len)
{
- struct cursor enc, dec;
+ struct cursor read, enc, dec;
u8 hdr[18];
u16 size;
- reset_cursor(&ln->errs.cur);
- reset_cursor(&ln->msgbuf);
-
- if (!read_all(ln->socket, hdr, sizeof(hdr)))
- return note_error(&ln->errs,"Failed reading header: %s",
- strerror(errno));
+ make_cursor(packet, packet + len, &read);
+ if (!cursor_pull(&read, hdr, 18)) {
+ return note_error(&ln->errs, "not enough bytes in header, have %d, need 18", len);
+ }
- if (!cryptomsg_decrypt_header(&ln->crypto_state, hdr, &size))
+ if (!cryptomsg_decrypt_header(&ln->crypto_state, hdr, &size)) {
return note_error(&ln->errs,
"Failed hdr decrypt with rn=%"PRIu64,
ln->crypto_state.rn-1);
+ }
- if (!cursor_slice(&ln->msgbuf, &enc, size + 16))
- return note_error(&ln->errs, "out of memory");
-
+ reset_cursor(&ln->msgbuf);
if (!cursor_slice(&ln->msgbuf, &dec, size))
return note_error(&ln->errs, "out of memory: %d + %d = %d > %d",
ln->msgbuf.end - ln->msgbuf.p, size,
@@ -208,19 +223,61 @@ int lnsocket_read(struct lnsocket *ln, unsigned char **buf, unsigned short *len)
MSGBUF_MEM
);
- if (!read_all(ln->socket, enc.p, enc.end - enc.start))
- return note_error(&ln->errs, "Failed reading body: %s",
- strerror(errno));
+ if (size + 16 != read.end - read.p)
+ return note_error(&ln->errs, "expected enc body size of %d, got %d",
+ size + 16, read.end - read.p);
+
+ make_cursor(read.p, read.end, &enc);
if (!cryptomsg_decrypt_body(&ln->crypto_state,
enc.start, enc.end - enc.start,
dec.start, dec.end - dec.start))
return note_error(&ln->errs, "error decrypting body");
- *buf = dec.start;
- *len = dec.end - dec.start;
+ return dec.end - dec.start;
+}
- return 1;
+int lnsocket_read(struct lnsocket *ln, unsigned char **buf, unsigned short *len)
+{
+ struct cursor enc, dec;
+ u8 hdr[18];
+ u16 size;
+
+ reset_cursor(&ln->errs.cur);
+ reset_cursor(&ln->msgbuf);
+
+ if (!read_all(ln->socket, hdr, sizeof(hdr)))
+ return note_error(&ln->errs,"Failed reading header: %s",
+ strerror(errno));
+
+ if (!cryptomsg_decrypt_header(&ln->crypto_state, hdr, &size))
+ return note_error(&ln->errs,
+ "Failed hdr decrypt with rn=%"PRIu64,
+ ln->crypto_state.rn-1);
+
+ if (!cursor_slice(&ln->msgbuf, &enc, size + 16))
+ return note_error(&ln->errs, "out of memory");
+
+ if (!cursor_slice(&ln->msgbuf, &dec, size))
+ return note_error(&ln->errs, "out of memory: %d + %d = %d > %d",
+ ln->msgbuf.end - ln->msgbuf.p, size,
+ ln->msgbuf.end - ln->msgbuf.p + size,
+ MSGBUF_MEM
+ );
+
+ if (!read_all(ln->socket, enc.p, enc.end - enc.start))
+ return note_error(&ln->errs, "Failed reading body: %s",
+ strerror(errno));
+
+ if (!cryptomsg_decrypt_body(&ln->crypto_state,
+ enc.start, enc.end - enc.start,
+ dec.start, dec.end - dec.start))
+ return note_error(&ln->errs, "error decrypting body");
+
+ *buf = dec.start;
+ *len = dec.end - dec.start;
+
+ return 1;
}
static int highest_byte(unsigned char *buf, int buflen)
@@ -296,7 +353,7 @@ int lnsocket_make_network_tlv(unsigned char *buf, int buflen,
return 1;
}
-int lnsocket_make_ping_msg(unsigned char *buf, int buflen, u16 num_pong_bytes, u16 ignored_bytes, u16 *outlen)
+int lnsocket_make_ping_msg(unsigned char *buf, int buflen, u16 num_pong_bytes, u16 ignored_bytes)
{
struct cursor msg;
int i;
@@ -314,9 +371,7 @@ int lnsocket_make_ping_msg(unsigned char *buf, int buflen, u16 num_pong_bytes, u
return 0;
}
- *outlen = msg.p - msg.start;
-
- return 1;
+ return msg.p - msg.start;
}
int lnsocket_make_init_msg(unsigned char *buf, int buflen,
@@ -353,20 +408,26 @@ int lnsocket_make_init_msg(unsigned char *buf, int buflen,
return 1;
}
-int lnsocket_write(struct lnsocket *ln, const u8 *msg, unsigned short msglen)
+unsigned char* EXPORT lnsocket_msgbuf(struct lnsocket *ln)
+{
+ return ln->msgbuf.start;
+}
+
+int EXPORT lnsocket_encrypt(struct lnsocket *ln, const u8 *msg, unsigned short msglen)
{
- ssize_t writelen, outcap;
+ ssize_t outcap;
size_t outlen;
- u8 *out;
// this is just temporary so we don't need to move the memory cursor
reset_cursor(&ln->msgbuf);
- out = ln->msgbuf.start;
+ u8 *out = ln->msgbuf.start;
outcap = ln->msgbuf.end - ln->msgbuf.start;
+#ifndef __EMSCRIPTEN__
if (!ln->socket)
return note_error(&ln->errs, "not connected");
+#endif
if (outcap <= 0)
return note_error(&ln->errs, "out of memory");
@@ -374,6 +435,17 @@ int lnsocket_write(struct lnsocket *ln, const u8 *msg, unsigned short msglen)
if (!cryptomsg_encrypt_msg(&ln->crypto_state, msg, msglen, out, &outlen, (size_t)outcap))
return note_error(&ln->errs, "encrypt message failed, out of memory");
+ return outlen;
+}
+
+int lnsocket_write(struct lnsocket *ln, const u8 *msg, unsigned short msglen)
+{
+ ssize_t writelen, outlen;
+ u8 *out = ln->msgbuf.start;
+
+ if (!(outlen = lnsocket_encrypt(ln, msg, msglen)))
+ return 0;
+
if ((writelen = write(ln->socket, out, outlen)) != outlen)
return note_error(&ln->errs,
"write failed. wrote %ld bytes, expected %ld %s",
@@ -423,7 +495,7 @@ void lnsocket_destroy(struct lnsocket *lnsocket)
free(lnsocket->mem.start);
}
-static int is_zero(void *vp, int size)
+int is_zero(void *vp, int size)
{
u8 *p = (u8*)vp;
const u8 *start = p;
@@ -471,7 +543,6 @@ int lnsocket_connect_with(struct lnsocket *ln, const char *node_id, const char *
{
int ret;
struct addrinfo *addrs = NULL;
- struct handshake h;
struct pubkey their_id;
struct node_id their_node_id;
struct timeval timeout = {0};
@@ -523,13 +594,13 @@ int lnsocket_connect_with(struct lnsocket *ln, const char *node_id, const char *
}
// prepare some data for ACT1
- new_handshake(ln->secp, &h, &their_id);
+ new_handshake(ln->secp, &ln->handshake, &their_id);
- h.side = INITIATOR;
- h.their_id = their_id;
+ ln->handshake.side = INITIATOR;
+ ln->handshake.their_id = their_id;
// let's do this!
- return act_one_initiator(ln, &h);
+ return act_one_initiator(ln);
}
int lnsocket_connect(struct lnsocket *ln, const char *node_id, const char *host)
@@ -543,6 +614,11 @@ int lnsocket_fd(struct lnsocket *ln, int *fd)
return 1;
}
+void * lnsocket_secp(struct lnsocket *ln)
+{
+ return ln->secp;
+}
+
void lnsocket_genkey(struct lnsocket *ln)
{
ln->key = generate_key(ln->secp);
diff --git a/lnsocket.h b/lnsocket.h
@@ -6,6 +6,13 @@
struct lnsocket;
+#ifdef __EMSCRIPTEN__
+ #include <emscripten.h>
+ #define EXPORT EMSCRIPTEN_KEEPALIVE
+#else
+ #define EXPORT
+#endif
+
enum peer_wire {
WIRE_INIT = 16,
WIRE_ERROR = 17,
@@ -58,12 +65,12 @@ struct tlv {
unsigned char *value;
};
-struct lnsocket *lnsocket_create();
+struct lnsocket EXPORT *lnsocket_create();
/* messages */
int lnsocket_make_network_tlv(unsigned char *buf, int buflen, const unsigned char **blockids, int num_blockids, struct tlv *tlv_out);
-int lnsocket_make_ping_msg(unsigned char *buf, int buflen, unsigned short num_pong_bytes, unsigned short ignored_bytes, unsigned short *outlen);
+int EXPORT lnsocket_make_ping_msg(unsigned char *buf, int buflen, unsigned short num_pong_bytes, unsigned short ignored_bytes);
int lnsocket_make_init_msg(unsigned char *buf, int buflen, const unsigned char *globalfeatures, unsigned short gflen, const unsigned char *features, unsigned short flen, const struct tlv **tlvs, unsigned short num_tlvs, unsigned short *outlen);
int lnsocket_perform_init(struct lnsocket *ln);
@@ -77,8 +84,11 @@ int lnsocket_read(struct lnsocket *, unsigned char **buf, unsigned short *len);
int lnsocket_send(struct lnsocket *, unsigned short msg_type, const unsigned char *payload, unsigned short payload_len);
int lnsocket_recv(struct lnsocket *, unsigned short *msg_type, unsigned char **payload, unsigned short *payload_len);
-void lnsocket_genkey(struct lnsocket *);
-void lnsocket_destroy(struct lnsocket *);
-void lnsocket_print_errors(struct lnsocket *);
+void* EXPORT lnsocket_secp(struct lnsocket *);
+void EXPORT lnsocket_genkey(struct lnsocket *);
+void EXPORT lnsocket_destroy(struct lnsocket *);
+void EXPORT lnsocket_print_errors(struct lnsocket *);
+int EXPORT lnsocket_make_default_initmsg(unsigned char *msgbuf, int buflen);
+int EXPORT lnsocket_encrypt(struct lnsocket *ln, const unsigned char *msg, unsigned short msglen);
#endif /* LNSOCKET_H */
diff --git a/lnsocket_example.js b/lnsocket_example.js
@@ -0,0 +1,18 @@
+
+async function go() {
+ const LNSocket = await lnsocket_init()
+ const ln = LNSocket()
+
+ ln.genkey()
+ const their_init = await ln.connect_and_init("03f3c108ccd536b8526841f0a5c58212bb9e6584a1eb493080e7c1cc34f82dad71", "cln.jb55.com:443")
+
+ const pong = await ln.ping_pong()
+ console.log("pong", pong)
+
+ const rune = "CVFjq11ZtQ-V14SOeQyV4V2AicYZnHfbBNY8lqYvf8c9NjcmbWV0aG9kPWdldGluZm8mdGltZTwxNjQ2ODc2MTU2"
+ const res = await ln.rpc({ method: "getinfo", rune })
+
+ document.body.innerHTML = `<pre>${JSON.stringify(res.result, undefined, 2)}</pre>`
+}
+
+go()
diff --git a/lnsocket_internal.h b/lnsocket_internal.h
@@ -2,6 +2,8 @@
#define LNSOCKET_INTERNAL_H
#include "crypto.h"
+#include "error.h"
+#include "handshake.h"
struct lnsocket {
const char *errors[8];
@@ -11,8 +13,14 @@ struct lnsocket {
int num_errors;
int socket;
struct keypair key;
+ struct pubkey responder_id;
+ struct handshake handshake;
struct crypto_state crypto_state;
secp256k1_context *secp;
};
+int parse_node_id(const char *str, struct node_id *dest);
+int pubkey_from_node_id(secp256k1_context *secp, struct pubkey *key, const struct node_id *id);
+int is_zero(void *vp, int size);
+
#endif /* LNSOCKET_INTERNAL_H */
diff --git a/lnsocket_lib.js b/lnsocket_lib.js
@@ -0,0 +1,261 @@
+
+async function lnsocket_init() {
+ const module = await Module()
+
+ const ACT_ONE_SIZE = 50
+ const ACT_TWO_SIZE = 50
+ const ACT_THREE_SIZE = 66
+ const DEFAULT_TIMEOUT = 3000
+
+ const COMMANDO_REPLY_CONTINUES = 0x594b
+ const COMMANDO_REPLY_TERM = 0x594d
+
+ const lnsocket_create = module.cwrap("lnsocket_create", "number")
+ const lnsocket_encrypt = module.cwrap("lnsocket_encrypt", "number", ["int", "array", "int", "int"])
+ const lnsocket_decrypt = module.cwrap("lnsocket_decrypt", "number", ["int", "array", "int"])
+ const lnsocket_msgbuf = module.cwrap("lnsocket_msgbuf", "number", ["int"])
+ const lnsocket_act_one = module.cwrap("lnsocket_act_one", "number", ["number", "string"])
+ const lnsocket_act_two = module.cwrap("lnsocket_act_two", "number", ["number", "array"])
+ const lnsocket_print_errors = module.cwrap("lnsocket_print_errors", "int")
+ const lnsocket_genkey = module.cwrap("lnsocket_genkey", "int")
+ const lnsocket_make_default_initmsg = module.cwrap("lnsocket_make_default_initmsg", "int", ["int", "int"])
+ const lnsocket_make_ping_msg = module.cwrap("lnsocket_make_ping_msg", "int", ["int", "int", "int", "int"])
+ const commando_make_rpc_msg = module.cwrap("commando_make_rpc_msg", "int", ["string", "string", "string", "number", "int", "int"])
+
+ function concat_u8_arrays(arrays) {
+ // sum of individual array lengths
+ let totalLength = arrays.reduce((acc, value) => acc + value.length, 0);
+
+ if (!arrays.length) return null;
+
+ let result = new Uint8Array(totalLength);
+
+ let length = 0;
+ for (let array of arrays) {
+ result.set(array, length);
+ length += array.length;
+ }
+
+ return result;
+ }
+
+ function queue_recv(queue) {
+ return new Promise((resolve, reject) => {
+ const checker = setInterval(() => {
+ const val = queue.shift()
+ if (val) {
+ clearInterval(checker)
+ resolve(val)
+ }
+ }, 5);
+ })
+ }
+
+ function parse_msgtype(buf) {
+ return buf[0] << 8 | buf[1]
+ }
+
+ function wasm_mem(ptr, size) {
+ return new Uint8Array(module.HEAPU8.buffer, ptr, size);
+ }
+
+ function LNSocket(opts) {
+ if (!(this instanceof LNSocket))
+ return new LNSocket(opts)
+
+ this.opts = opts || {
+ timeout: DEFAULT_TIMEOUT
+ }
+ this.queue = []
+ this.ln = lnsocket_create()
+ }
+
+ LNSocket.prototype.print_errors = function _lnsocket_print_errors() {
+ lnsocket_print_errors(this.ln)
+ }
+
+ LNSocket.prototype.genkey = function _lnsocket_genkey() {
+ lnsocket_genkey(this.ln)
+ }
+
+ LNSocket.prototype.act_one_data = function _lnsocket_act_one(node_id) {
+ const act_one_ptr = lnsocket_act_one(this.ln, node_id)
+ if (act_one_ptr === 0)
+ return null
+ return wasm_mem(act_one_ptr, ACT_ONE_SIZE)
+ }
+
+ LNSocket.prototype.act_two = function _lnsocket_act_two(act2) {
+ const act_three_ptr = lnsocket_act_two(this.ln, new Uint8Array(act2))
+ if (act_three_ptr === 0) {
+ this.print_errors()
+ return null
+ }
+ return wasm_mem(act_three_ptr, ACT_THREE_SIZE)
+ }
+
+ LNSocket.prototype.connect = async function lnsocket_connect(node_id, host) {
+ await handle_connect(this, node_id, host)
+
+ const act1 = this.act_one_data(node_id)
+ this.ws.send(act1)
+ const act2 = await this.read_clear()
+ if (act2.byteLength != ACT_TWO_SIZE) {
+ throw new Error(`expected act2 to be ${ACT_TWO_SIZE} long, got ${act2.length}`)
+ }
+ const act3 = this.act_two(act2)
+ this.ws.send(act3)
+ }
+
+ LNSocket.prototype.connect_and_init = async function _connect_and_init(node_id, host) {
+ await this.connect(node_id, host)
+ await this.perform_init()
+ }
+
+ LNSocket.prototype.rpc = async function lnsocket_rpc(opts) {
+ const msg = this.make_commando_msg(opts)
+ this.write(msg)
+ return JSON.parse(await this.read_all_rpc())
+ }
+
+ LNSocket.prototype.recv = async function lnsocket_recv() {
+ const msg = await this.read()
+ console.log("recv", msg)
+ const msgtype = parse_msgtype(msg.slice(0,2))
+ return [msgtype, msg.slice(2)]
+ }
+
+ LNSocket.prototype.read_all_rpc = async function read_all_rpc() {
+ let chunks = []
+ while (true) {
+ const [typ, msg] = await this.recv()
+ switch (typ) {
+ case COMMANDO_REPLY_TERM:
+ chunks.push(msg.slice(8))
+ return new TextDecoder().decode(concat_u8_arrays(chunks));
+ case COMMANDO_REPLY_CONTINUES:
+ chunks.push(msg.slice(8))
+ break
+ default:
+ console.log("got unknown type", typ)
+ continue
+ }
+ }
+ }
+
+ LNSocket.prototype.make_commando_msg = function _lnsocket_make_commando_msg(opts) {
+ const buflen = 4096
+ let len = 0;
+ const buf = module._malloc(buflen);
+ module.HEAPU8.set(Uint8Array, buf);
+
+ const params = JSON.stringify(opts.params||{})
+ if (!(len = commando_make_rpc_msg(opts.method, params, opts.rune,
+ 0, buf, buflen))) {
+ throw new Error("couldn't make commando msg");
+ }
+
+ const dat = wasm_mem(buf, len)
+ module._free(buf);
+ return dat
+ }
+
+ LNSocket.prototype.make_ping_msg = function _lnsocket_make_ping_msg(num_pong_bytes=1, ignored_bytes=1) {
+ const buflen = 32
+ let len = 0;
+ const buf = module._malloc(buflen);
+ module.HEAPU8.set(Uint8Array, buf);
+
+ if (!(len = lnsocket_make_ping_msg(buf, buflen, num_pong_bytes, ignored_bytes)))
+ throw new Error("couldn't make ping msg");
+
+ const dat = wasm_mem(buf, len)
+ module._free(buf);
+ return dat
+ }
+
+ LNSocket.prototype.encrypt = function _lnsocket_encrypt(dat) {
+ const len = lnsocket_encrypt(this.ln, dat, dat.length)
+ if (len === 0) {
+ this.print_errors()
+ throw new Error("encrypt error")
+ }
+ const enc = wasm_mem(lnsocket_msgbuf(this.ln), len)
+ return enc
+ }
+
+ LNSocket.prototype.decrypt = function _lnsocket_decrypt(dat) {
+ const len = lnsocket_decrypt(this.ln, dat, dat.length)
+ if (len === 0) {
+ this.print_errors()
+ throw new Error("decrypt error")
+ }
+ return wasm_mem(lnsocket_msgbuf(this.ln), len)
+ }
+
+ LNSocket.prototype.write = function _lnsocket_write(dat) {
+ this.ws.send(this.encrypt(dat))
+ }
+
+ LNSocket.prototype.read_clear = async function _lnsocket_read() {
+ return (await queue_recv(this.queue))
+ }
+
+ LNSocket.prototype.read = async function _lnsocket_read() {
+ const enc = await this.read_clear()
+ return this.decrypt(new Uint8Array(enc))
+ }
+
+ LNSocket.prototype.make_default_initmsg = function _lnsocket_make_default_initmsg() {
+ const buflen = 1024
+ let len = 0;
+ const buf = module._malloc(buflen);
+ module.HEAPU8.set(Uint8Array, buf);
+
+ if (!(len = lnsocket_make_default_initmsg(buf, buflen)))
+ throw new Error("couldn't make initmsg");
+
+ const dat = wasm_mem(buf, len)
+ module._free(buf);
+ return dat
+ }
+
+ LNSocket.prototype.perform_init = async function lnsocket_connect() {
+ await this.read()
+ const our_init = this.make_default_initmsg()
+ console.log("our_init", our_init)
+ this.write(our_init)
+ }
+
+ LNSocket.prototype.ping_pong = async function lnsocket_ping_pong() {
+ const pingmsg = this.make_ping_msg()
+ console.log("ping", pingmsg)
+ this.write(pingmsg)
+ return await this.read()
+ }
+
+ function handle_connect(ln, node_id, host) {
+ const ws = new WebSocket(`wss://${host}`)
+ return new Promise((resolve, reject) => {
+ ws.onmessage = (v) => {
+ ln.queue.push(v.data.arrayBuffer())
+ }
+
+ ws.addEventListener('open', function(ev) {
+ ln.ws = ws
+ resolve(ws)
+ });
+
+ ws.addEventListener('close', function(ev) {
+ reject()
+ });
+
+ const timeout = ln.opts.timeout || DEFAULT_TIMEOUT
+ setTimeout(reject, timeout);
+ })
+ }
+
+ return LNSocket
+}
+
+Module.init = lnsocket_init
diff --git a/lnsocket_pre.js b/lnsocket_pre.js
@@ -0,0 +1,12 @@
+Module.getRandomValue = (function() {
+ const window_ = "object" === typeof window ? window : self
+ const crypto_ = typeof window_.crypto !== "undefined" ? window_.crypto : window_.msCrypto;
+
+ function randomValuesStandard() {
+ var buf = new Uint32Array(1);
+ crypto_.getRandomValues(buf);
+ return buf[0] >>> 0;
+ };
+
+ return randomValuesStandard
+})()
diff --git a/lnsocket_wasm.c b/lnsocket_wasm.c
@@ -0,0 +1,41 @@
+
+#include "handshake.h"
+#include "lnsocket_internal.h"
+#include <emscripten.h>
+
+struct act_three* lnsocket_act_two(struct lnsocket *ln, struct act_two *act2);
+
+void* EMSCRIPTEN_KEEPALIVE lnsocket_act_one(struct lnsocket *ln, const char *node_id)
+{
+ struct pubkey their_id;
+ struct node_id their_node_id;
+
+ if (is_zero(&ln->key, sizeof(ln->key))) {
+ note_error(&ln->errs, "key not initialized, use lnsocket_set_key() or lnsocket_genkey()");
+ return NULL;
+ }
+
+ if (!parse_node_id(node_id, &their_node_id)) {
+ note_error(&ln->errs, "failed to parse node id");
+ return NULL;
+ }
+
+ if (!pubkey_from_node_id(ln->secp, &their_id, &their_node_id)) {
+ note_error(&ln->errs, "failed to convert node_id to pubkey");
+ return NULL;
+ }
+
+ new_handshake(ln->secp, &ln->handshake, &their_id);
+
+ printf("their_id node_id %s\n", node_id);
+
+ ln->handshake.side = INITIATOR;
+ ln->handshake.their_id = their_id;
+
+ if (!act_one_initiator_prep(ln)) {
+ note_error(&ln->errs, "failed to build initial handshake data (act1)");
+ return NULL;
+ }
+
+ return &ln->handshake.act1;
+}
diff --git a/rpc.c b/rpc.c
@@ -69,7 +69,7 @@ int main(int argc, const char *argv[])
if (verbose)
fprintf(stderr, "init success\n");
- if (!(ok = commando_make_rpc_msg(method, params, rune, 1, msgbuf, sizeof(msgbuf), &len)))
+ if (!(ok = len = commando_make_rpc_msg(method, params, rune, 1, msgbuf, sizeof(msgbuf))))
goto done;
if (!(ok = lnsocket_write(ln, msgbuf, len)))
diff --git a/test.c b/test.c
@@ -38,7 +38,7 @@ int main(int argc, const char *argv[])
printf("init ok!\n");
- if (!(ok = lnsocket_make_ping_msg(msgbuf, sizeof(msgbuf), 1, 1, &len)))
+ if (!(ok = len = lnsocket_make_ping_msg(msgbuf, sizeof(msgbuf), 1, 1)))
goto done;
if (!(ok = lnsocket_write(ln, msgbuf, len)))
diff --git a/tools/secp-wasm.sh b/tools/secp-wasm.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+cd deps/secp256k1
+
+export CC=emcc
+export AR=emar
+
+export PREFIX="$(pwd)/libsecp256k1-wasm"
+
+mkdir -p $PREFIX || exit 1
+
+make distclean > /dev/null
+
+./configure --disable-shared \
+ --enable-module-ecdh \
+ --prefix="$PREFIX"
+
+make -j3 install || exit 1
+
+rm -rf -- "$PREFIX/tmp"
+make distclean > /dev/null
diff --git a/tools/sodium-wasm.sh b/tools/sodium-wasm.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+cd deps/libsodium
+
+export CC=emcc
+export AR=emar
+
+export PREFIX="$(pwd)/libsodium-wasm"
+
+mkdir -p $PREFIX || exit 1
+
+make distclean > /dev/null
+
+./configure --disable-shared --enable-minimal --disable-ssp --prefix="$PREFIX"
+
+make -j3 install || exit 1
+
+make distclean > /dev/null