Add coracle-wasm bindings
This commit is contained in:
Generated
+96
@@ -45,6 +45,12 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.20.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.59"
|
version = "1.2.59"
|
||||||
@@ -158,6 +164,18 @@ dependencies = [
|
|||||||
"pulldown-cmark",
|
"pulldown-cmark",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coracle-wasm"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"coracle-lib",
|
||||||
|
"hex",
|
||||||
|
"secp256k1",
|
||||||
|
"serde-wasm-bindgen",
|
||||||
|
"serde_json",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -264,6 +282,16 @@ version = "1.0.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.95"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.184"
|
version = "0.2.184"
|
||||||
@@ -276,6 +304,12 @@ version = "2.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -390,6 +424,12 @@ dependencies = [
|
|||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "salsa20"
|
name = "salsa20"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
@@ -441,6 +481,17 @@ dependencies = [
|
|||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde-wasm-bindgen"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_core"
|
name = "serde_core"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
@@ -578,6 +629,51 @@ version = "0.11.1+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.118"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.118"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.118"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.118"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.48"
|
version = "0.8.48"
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ members = [
|
|||||||
"coracle-domain",
|
"coracle-domain",
|
||||||
"coracle-content",
|
"coracle-content",
|
||||||
"coracle-storage",
|
"coracle-storage",
|
||||||
|
"coracle-wasm",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "coracle-wasm"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Web bindings for coracle-lib via wasm-bindgen"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
coracle-lib = { path = "../coracle-lib" }
|
||||||
|
wasm-bindgen = "0.2"
|
||||||
|
serde-wasm-bindgen = "0.6"
|
||||||
|
serde_json = "1"
|
||||||
|
hex = "0.4"
|
||||||
|
secp256k1 = "0.29"
|
||||||
@@ -0,0 +1,265 @@
|
|||||||
|
//! Web bindings for `coracle-lib` via `wasm-bindgen`.
|
||||||
|
//!
|
||||||
|
//! Wraps the core nostr types — keys, NIP-44 encryption, events — in
|
||||||
|
//! `#[wasm_bindgen]` shims so they can be consumed from JavaScript. The
|
||||||
|
//! wrappers hold the underlying `coracle-lib` value by composition and
|
||||||
|
//! translate its errors into `JsError`.
|
||||||
|
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
use coracle_lib::encryption as ce;
|
||||||
|
use coracle_lib::event as cev;
|
||||||
|
use coracle_lib::keys as ck;
|
||||||
|
|
||||||
|
fn js_err<E: std::fmt::Display>(e: E) -> JsError {
|
||||||
|
JsError::new(&e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Keys ----------
|
||||||
|
|
||||||
|
/// A nostr public key: the x-only 32-byte secp256k1 coordinate.
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct PublicKey(ck::PublicKey);
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl PublicKey {
|
||||||
|
#[wasm_bindgen(js_name = fromHex)]
|
||||||
|
pub fn from_hex(s: &str) -> Result<PublicKey, JsError> {
|
||||||
|
ck::PublicKey::from_hex(s).map(PublicKey).map_err(js_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = fromNpub)]
|
||||||
|
pub fn from_npub(s: &str) -> Result<PublicKey, JsError> {
|
||||||
|
ck::PublicKey::from_npub(s).map(PublicKey).map_err(js_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = toHex)]
|
||||||
|
pub fn to_hex(&self) -> String {
|
||||||
|
self.0.to_hex()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = toNpub)]
|
||||||
|
pub fn to_npub(&self) -> String {
|
||||||
|
self.0.to_npub()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = toBytes)]
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
self.0.as_bytes().to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A nostr secret key. Exporting the raw bytes is explicit via `toHex` /
|
||||||
|
/// `toNsec` — there is no implicit string conversion.
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct SecretKey(ck::SecretKey);
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl SecretKey {
|
||||||
|
/// Generate a new secret key from the OS RNG.
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn generate() -> SecretKey {
|
||||||
|
SecretKey(ck::SecretKey::generate())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = fromHex)]
|
||||||
|
pub fn from_hex(s: &str) -> Result<SecretKey, JsError> {
|
||||||
|
ck::SecretKey::from_hex(s).map(SecretKey).map_err(js_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = fromNsec)]
|
||||||
|
pub fn from_nsec(s: &str) -> Result<SecretKey, JsError> {
|
||||||
|
ck::SecretKey::from_nsec(s).map(SecretKey).map_err(js_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt a NIP-49 `ncryptsec1…` string.
|
||||||
|
#[wasm_bindgen(js_name = fromNcryptsec)]
|
||||||
|
pub fn from_ncryptsec(s: &str, password: &str) -> Result<SecretKey, JsError> {
|
||||||
|
ck::SecretKey::from_ncryptsec(s, password)
|
||||||
|
.map(SecretKey)
|
||||||
|
.map_err(js_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = toHex)]
|
||||||
|
pub fn to_hex(&self) -> String {
|
||||||
|
self.0.to_hex()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = toNsec)]
|
||||||
|
pub fn to_nsec(&self) -> String {
|
||||||
|
self.0.to_nsec()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypt this key as a NIP-49 `ncryptsec1…` string.
|
||||||
|
#[wasm_bindgen(js_name = toNcryptsec)]
|
||||||
|
pub fn to_ncryptsec(
|
||||||
|
&self,
|
||||||
|
password: &str,
|
||||||
|
log_n: u8,
|
||||||
|
security_byte: u8,
|
||||||
|
) -> Result<String, JsError> {
|
||||||
|
self.0
|
||||||
|
.to_ncryptsec(password, log_n, security_byte)
|
||||||
|
.map_err(js_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = publicKey)]
|
||||||
|
pub fn public_key(&self) -> PublicKey {
|
||||||
|
PublicKey(self.0.public_key())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = nip44Encrypt)]
|
||||||
|
pub fn nip44_encrypt(&self, pk: &PublicKey, plaintext: &str) -> Result<String, JsError> {
|
||||||
|
self.0.nip44_encrypt(&pk.0, plaintext).map_err(js_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = nip44Decrypt)]
|
||||||
|
pub fn nip44_decrypt(&self, pk: &PublicKey, payload: &str) -> Result<String, JsError> {
|
||||||
|
self.0.nip44_decrypt(&pk.0, payload).map_err(js_err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Encryption ----------
|
||||||
|
|
||||||
|
/// Raw 32-byte ECDH shared secret between a secret and a public key.
|
||||||
|
#[wasm_bindgen(js_name = sharedSecret)]
|
||||||
|
pub fn shared_secret(sk: &SecretKey, pk: &PublicKey) -> Vec<u8> {
|
||||||
|
ce::shared_secret(&sk.0, &pk.0).to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A reusable NIP-44 conversation key. Derive once per `(sk, pk)` pair and
|
||||||
|
/// hold it when sending many messages to the same counterparty.
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct ConversationKey(ce::nip44::ConversationKey);
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl ConversationKey {
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn derive(sk: &SecretKey, pk: &PublicKey) -> ConversationKey {
|
||||||
|
ConversationKey(ce::nip44::ConversationKey::derive(&sk.0, &pk.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = toBytes)]
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
self.0.as_bytes().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn encrypt(&self, plaintext: &str) -> Result<String, JsError> {
|
||||||
|
self.0.encrypt(plaintext).map_err(js_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = encryptWithNonce)]
|
||||||
|
pub fn encrypt_with_nonce(&self, plaintext: &str, nonce: &[u8]) -> Result<String, JsError> {
|
||||||
|
let nonce: &[u8; 32] = nonce
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| JsError::new("nonce must be exactly 32 bytes"))?;
|
||||||
|
self.0.encrypt_with_nonce(plaintext, nonce).map_err(js_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn decrypt(&self, payload: &str) -> Result<String, JsError> {
|
||||||
|
self.0.decrypt(payload).map_err(js_err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Events ----------
|
||||||
|
|
||||||
|
/// A signed nostr event.
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct Event(cev::Event);
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl Event {
|
||||||
|
/// Build and sign a new event under `sk`. `tags` must be a JS array of
|
||||||
|
/// string arrays (`string[][]`).
|
||||||
|
#[wasm_bindgen(js_name = signNew)]
|
||||||
|
pub fn sign_new(
|
||||||
|
kind: u16,
|
||||||
|
content: &str,
|
||||||
|
tags: JsValue,
|
||||||
|
created_at: u64,
|
||||||
|
sk: &SecretKey,
|
||||||
|
) -> Result<Event, JsError> {
|
||||||
|
let tags: Vec<Vec<String>> =
|
||||||
|
serde_wasm_bindgen::from_value(tags).map_err(|e| JsError::new(&e.to_string()))?;
|
||||||
|
// coracle-lib's SecretKey hides the inner secp type behind a
|
||||||
|
// crate-private accessor, so round-trip through hex to hand
|
||||||
|
// Event::new the low-level key it wants.
|
||||||
|
let bytes = hex::decode(sk.0.to_hex()).map_err(js_err)?;
|
||||||
|
let secp_sk = secp256k1::SecretKey::from_slice(&bytes).map_err(js_err)?;
|
||||||
|
Ok(Event(cev::Event::new(
|
||||||
|
kind, content, tags, created_at, &secp_sk,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse an event from its JSON representation.
|
||||||
|
#[wasm_bindgen(js_name = fromJson)]
|
||||||
|
pub fn from_json(s: &str) -> Result<Event, JsError> {
|
||||||
|
serde_json::from_str::<cev::Event>(s)
|
||||||
|
.map(Event)
|
||||||
|
.map_err(js_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize the event to JSON.
|
||||||
|
#[wasm_bindgen(js_name = toJson)]
|
||||||
|
pub fn to_json(&self) -> Result<String, JsError> {
|
||||||
|
serde_json::to_string(&self.0).map_err(js_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn id(&self) -> String {
|
||||||
|
self.0.id.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn pubkey(&self) -> String {
|
||||||
|
self.0.pubkey.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter, js_name = createdAt)]
|
||||||
|
pub fn created_at(&self) -> u64 {
|
||||||
|
self.0.created_at
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn kind(&self) -> u16 {
|
||||||
|
self.0.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn content(&self) -> String {
|
||||||
|
self.0.content.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn sig(&self) -> String {
|
||||||
|
self.0.sig.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn tags(&self) -> Result<JsValue, JsError> {
|
||||||
|
serde_wasm_bindgen::to_value(&self.0.tags).map_err(|e| JsError::new(&e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Canonical `[0, pubkey, created_at, kind, tags, content]` serialization.
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn serialize(&self) -> String {
|
||||||
|
self.0.serialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = computeId)]
|
||||||
|
pub fn compute_id(&self) -> String {
|
||||||
|
self.0.compute_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = idIsValid)]
|
||||||
|
pub fn id_is_valid(&self) -> bool {
|
||||||
|
self.0.id_is_valid()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn verify(&self) -> bool {
|
||||||
|
self.0.verify()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user