Skip to content

Commit 5e73a4d

Browse files
committed
Add Ed25519-to-X25519 WASM functions for session key vault access
Adds two new WASM-exported functions that enable Ed25519 session keys to participate in vault encryption without requiring a wallet signature: - vault_ed25519_seed_to_x25519: Converts Ed25519 seed to X25519 key pair via SHA-512 derivation (standard Ed25519→X25519 conversion) - vault_ed25519_pub_to_x25519: Converts Ed25519 public key to X25519 public key via Edwards→Montgomery birational map This allows share link recipients (who only have a session key) to derive the X25519 keys needed for vault grant decryption, removing the requirement for a wallet signature to access shared encrypted content.
1 parent 5f34910 commit 5e73a4d

3 files changed

Lines changed: 56 additions & 2 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tinycloud-sdk-wasm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,5 @@ aes-gcm = "0.10"
4141
hkdf = "0.12"
4242
sha2 = "0.10"
4343
x25519-dalek = { version = "2.0", features = ["static_secrets"] }
44+
curve25519-dalek = "4"
4445
serde_bytes = "0.11"

tinycloud-sdk-wasm/src/vault.rs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ use aes_gcm::{
22
aead::{Aead, KeyInit},
33
Aes256Gcm, Nonce,
44
};
5+
use curve25519_dalek::edwards::CompressedEdwardsY;
56
use hkdf::Hkdf;
6-
use sha2::Sha256;
7+
use sha2::{Digest, Sha256, Sha512};
78
use wasm_bindgen::prelude::*;
89
use x25519_dalek::{PublicKey, StaticSecret};
910

@@ -134,6 +135,57 @@ pub fn vault_random_bytes(length: usize) -> Result<Vec<u8>, JsValue> {
134135
/// SHA-256 hash of the input data.
135136
#[wasm_bindgen]
136137
pub fn vault_sha256(data: &[u8]) -> Vec<u8> {
137-
use sha2::Digest;
138138
Sha256::new().chain_update(data).finalize().to_vec()
139139
}
140+
141+
/// Convert an Ed25519 seed (32 bytes) to an X25519 key pair.
142+
///
143+
/// Uses the standard Ed25519-to-X25519 conversion:
144+
/// 1. SHA-512(seed) → take first 32 bytes → X25519 private scalar (clamped by StaticSecret)
145+
/// 2. Derive X25519 public key from private scalar
146+
///
147+
/// This allows session keys (Ed25519) to participate in vault encryption
148+
/// without requiring a wallet signature.
149+
#[wasm_bindgen]
150+
pub fn vault_ed25519_seed_to_x25519(ed25519_seed: &[u8]) -> Result<JsValue, JsValue> {
151+
if ed25519_seed.len() != 32 {
152+
return Err(map_vault_err("ed25519_seed must be 32 bytes"));
153+
}
154+
155+
let hash = Sha512::digest(ed25519_seed);
156+
let mut x25519_bytes: [u8; 32] = hash[..32].try_into().unwrap();
157+
158+
// StaticSecret::from applies X25519 clamping internally
159+
let secret = StaticSecret::from(x25519_bytes);
160+
let public = PublicKey::from(&secret);
161+
162+
// Zero the intermediate key material
163+
x25519_bytes.fill(0);
164+
165+
let keypair = X25519KeyPair {
166+
public_key: public.as_bytes().to_vec(),
167+
private_key: secret.to_bytes().to_vec(),
168+
};
169+
170+
serde_wasm_bindgen::to_value(&keypair).map_err(|e| JsValue::from_str(&e.to_string()))
171+
}
172+
173+
/// Convert an Ed25519 public key (32 bytes, compressed Edwards Y) to X25519 public key.
174+
///
175+
/// Uses the birational Edwards-to-Montgomery map: u = (1 + y) / (1 - y)
176+
/// This lets us resolve X25519 public keys from did:key DIDs (which encode Ed25519 public keys).
177+
#[wasm_bindgen]
178+
pub fn vault_ed25519_pub_to_x25519(ed25519_pub: &[u8]) -> Result<Vec<u8>, JsValue> {
179+
if ed25519_pub.len() != 32 {
180+
return Err(map_vault_err("ed25519_pub must be 32 bytes"));
181+
}
182+
183+
let compressed = CompressedEdwardsY::from_slice(ed25519_pub)
184+
.map_err(|e| JsValue::from_str(&format!("invalid Ed25519 public key: {}", e)))?;
185+
let edwards_point = compressed
186+
.decompress()
187+
.ok_or_else(|| map_vault_err("failed to decompress Ed25519 public key"))?;
188+
let montgomery = edwards_point.to_montgomery();
189+
190+
Ok(montgomery.as_bytes().to_vec())
191+
}

0 commit comments

Comments
 (0)