Skip to content

Commit 267fe47

Browse files
mls: update NostrGroupDataExtension to fixed length arrays
- This changes fields on the NostrGroupData and ensures that they all use fixed length arrays. - Updates storage adapters (notable the SQLite adapter) to properly serialize and deserialize these fixed length arrays - Changes RawNostrGroupDataExtension to TlsNostrGroupDataExtension to make it more clear what it's actually used for Pull-Request: #1068 Acked-by: Javier G. Montoya S <[email protected]> Acked-by: Yuki Kishimoto <[email protected]>
1 parent 6da0c2c commit 267fe47

File tree

25 files changed

+458
-310
lines changed

25 files changed

+458
-310
lines changed

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.

mls/nostr-mls-memory-storage/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
- Remove group type from groups
3131
- Replaced `save_group_relay` with `replace_group_relays` trait method (https://github.com/rust-nostr/nostr/pull/1056)
32+
- `image_hash` instead of `image_url` (https://github.com/rust-nostr/nostr/pull/1059)
3233

3334
### Changed
3435

mls/nostr-mls-memory-storage/src/lib.rs

Lines changed: 33 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -171,34 +171,23 @@ impl NostrMlsStorageProvider for NostrMlsMemoryStorage {
171171
mod tests {
172172
use std::collections::BTreeSet;
173173

174-
use aes_gcm::aead::OsRng;
175-
use aes_gcm::{Aes128Gcm, KeyInit};
176174
use nostr::{EventId, Kind, PublicKey, RelayUrl, Tags, Timestamp, UnsignedEvent};
177175
use nostr_mls_storage::groups::types::{Group, GroupExporterSecret, GroupState};
178176
use nostr_mls_storage::groups::GroupStorage;
179177
use nostr_mls_storage::messages::types::{Message, MessageState, ProcessedMessageState};
180178
use nostr_mls_storage::messages::MessageStorage;
179+
use nostr_mls_storage::test_utils::crypto_utils::generate_random_bytes;
181180
use nostr_mls_storage::welcomes::types::{ProcessedWelcomeState, Welcome, WelcomeState};
182181
use nostr_mls_storage::welcomes::WelcomeStorage;
183182
use openmls::group::GroupId;
184183
use openmls_memory_storage::MemoryStorage;
185184

186185
use super::*;
187186

188-
pub fn generate_encryption_key() -> Vec<u8> {
189-
Aes128Gcm::generate_key(OsRng).to_vec()
190-
}
191-
192187
fn create_test_group_id() -> GroupId {
193188
GroupId::from_slice(&[1, 2, 3, 4])
194189
}
195190

196-
fn create_test_nostr_group_id() -> [u8; 32] {
197-
let mut id = [0u8; 32];
198-
id[0..4].copy_from_slice(&[1, 2, 3, 4]);
199-
id
200-
}
201-
202191
#[test]
203192
fn test_new_with_storage() {
204193
let storage = MemoryStorage::default();
@@ -247,10 +236,10 @@ mod tests {
247236
let storage = MemoryStorage::default();
248237
let nostr_storage = NostrMlsMemoryStorage::new(storage);
249238
let mls_group_id = create_test_group_id();
250-
let nostr_group_id = create_test_nostr_group_id();
251-
let image_url = Some("http://blossom_server:4531/fake_img.png".to_owned());
252-
let image_key = Some(generate_encryption_key());
253-
let image_nonce = Some(vec![16u8; 12]);
239+
let nostr_group_id = generate_random_bytes(32).try_into().unwrap();
240+
let image_hash = Some(generate_random_bytes(32).try_into().unwrap());
241+
let image_key = Some(generate_random_bytes(32).try_into().unwrap());
242+
let image_nonce = Some(generate_random_bytes(12).try_into().unwrap());
254243
let group = Group {
255244
mls_group_id: mls_group_id.clone(),
256245
nostr_group_id,
@@ -261,7 +250,7 @@ mod tests {
261250
last_message_at: None,
262251
epoch: 0,
263252
state: GroupState::Active,
264-
image_url,
253+
image_hash,
265254
image_key,
266255
image_nonce,
267256
};
@@ -289,10 +278,10 @@ mod tests {
289278
let storage = MemoryStorage::default();
290279
let nostr_storage = NostrMlsMemoryStorage::new(storage);
291280
let mls_group_id = create_test_group_id();
292-
let nostr_group_id = create_test_nostr_group_id();
293-
let image_url = Some("http://blossom_server:4531/fake_img.png".to_owned());
294-
let image_key = Some(generate_encryption_key());
295-
let image_nonce = Some(vec![16u8; 12]);
281+
let nostr_group_id = generate_random_bytes(32).try_into().unwrap();
282+
let image_hash = Some(generate_random_bytes(32).try_into().unwrap());
283+
let image_key = Some(generate_random_bytes(32).try_into().unwrap());
284+
let image_nonce = Some(generate_random_bytes(12).try_into().unwrap());
296285
let group = Group {
297286
mls_group_id: mls_group_id.clone(),
298287
nostr_group_id,
@@ -303,7 +292,7 @@ mod tests {
303292
last_message_at: None,
304293
epoch: 0,
305294
state: GroupState::Active,
306-
image_url,
295+
image_hash,
307296
image_key,
308297
image_nonce,
309298
};
@@ -334,10 +323,10 @@ mod tests {
334323
let storage = MemoryStorage::default();
335324
let nostr_storage = NostrMlsMemoryStorage::new(storage);
336325
let mls_group_id = create_test_group_id();
337-
let nostr_group_id = create_test_nostr_group_id();
338-
let image_url = Some("http://blossom_server:4531/fake_img.png".to_owned());
339-
let image_key = Some(generate_encryption_key());
340-
let image_nonce = Some(vec![16u8; 12]);
326+
let nostr_group_id = generate_random_bytes(32).try_into().unwrap();
327+
let image_hash = Some(generate_random_bytes(32).try_into().unwrap());
328+
let image_key = Some(generate_random_bytes(32).try_into().unwrap());
329+
let image_nonce = Some(generate_random_bytes(12).try_into().unwrap());
341330
let group = Group {
342331
mls_group_id: mls_group_id.clone(),
343332
nostr_group_id,
@@ -348,7 +337,7 @@ mod tests {
348337
last_message_at: None,
349338
epoch: 0,
350339
state: GroupState::Active,
351-
image_url,
340+
image_hash,
352341
image_key,
353342
image_nonce,
354343
};
@@ -409,7 +398,7 @@ mod tests {
409398

410399
// Create a test welcome
411400
let mls_group_id = create_test_group_id();
412-
let nostr_group_id = create_test_nostr_group_id();
401+
let nostr_group_id = generate_random_bytes(32).try_into().unwrap();
413402
let welcome = Welcome {
414403
id: event_id,
415404
event: UnsignedEvent::new(
@@ -424,7 +413,7 @@ mod tests {
424413
group_name: "Test Welcome Group".to_string(),
425414
group_description: "A test welcome group".to_string(),
426415
group_image_key: None,
427-
group_image_url: None,
416+
group_image_hash: None,
428417
group_image_nonce: None,
429418
group_admin_pubkeys: BTreeSet::from([pubkey]),
430419
group_relays: BTreeSet::from([RelayUrl::parse("wss://relay.example.com").unwrap()]),
@@ -483,10 +472,10 @@ mod tests {
483472
let storage = MemoryStorage::default();
484473
let nostr_storage = NostrMlsMemoryStorage::new(storage);
485474
let mls_group_id = create_test_group_id();
486-
let nostr_group_id = create_test_nostr_group_id();
487-
let image_url = Some("http://blossom_server:4531/fake_img.png".to_owned());
488-
let image_key = Some(generate_encryption_key());
489-
let image_nonce = Some(vec![16u8; 12]);
475+
let nostr_group_id = generate_random_bytes(32).try_into().unwrap();
476+
let image_hash = Some(generate_random_bytes(32).try_into().unwrap());
477+
let image_key = Some(generate_random_bytes(32).try_into().unwrap());
478+
let image_nonce = Some(generate_random_bytes(12).try_into().unwrap());
490479
let group = Group {
491480
mls_group_id: mls_group_id.clone(),
492481
nostr_group_id,
@@ -497,7 +486,7 @@ mod tests {
497486
last_message_at: None,
498487
epoch: 0,
499488
state: GroupState::Active,
500-
image_url,
489+
image_hash,
501490
image_key,
502491
image_nonce,
503492
};
@@ -581,10 +570,10 @@ mod tests {
581570

582571
// Create a test group to verify the cache works
583572
let mls_group_id = create_test_group_id();
584-
let nostr_group_id = create_test_nostr_group_id();
585-
let image_url = Some("http://blossom_server:4531/fake_img.png".to_owned());
586-
let image_key = Some(generate_encryption_key());
587-
let image_nonce = Some(vec![16u8; 12]);
573+
let nostr_group_id = generate_random_bytes(32).try_into().unwrap();
574+
let image_hash = Some(generate_random_bytes(32).try_into().unwrap());
575+
let image_key = Some(generate_random_bytes(32).try_into().unwrap());
576+
let image_nonce = Some(generate_random_bytes(12).try_into().unwrap());
588577
let group = Group {
589578
mls_group_id: mls_group_id.clone(),
590579
nostr_group_id,
@@ -595,7 +584,7 @@ mod tests {
595584
last_message_at: None,
596585
epoch: 0,
597586
state: GroupState::Active,
598-
image_url,
587+
image_hash,
599588
image_key,
600589
image_nonce,
601590
};
@@ -616,10 +605,10 @@ mod tests {
616605

617606
// Create a test group to verify the default implementation works
618607
let mls_group_id = create_test_group_id();
619-
let nostr_group_id = create_test_nostr_group_id();
620-
let image_url = Some("http://blossom_server:4531/fake_img.png".to_owned());
621-
let image_key = Some(generate_encryption_key());
622-
let image_nonce = Some(vec![16u8; 12]);
608+
let nostr_group_id = generate_random_bytes(32).try_into().unwrap();
609+
let image_hash = Some(generate_random_bytes(32).try_into().unwrap());
610+
let image_key = Some(generate_random_bytes(32).try_into().unwrap());
611+
let image_nonce = Some(generate_random_bytes(12).try_into().unwrap());
623612

624613
let group = Group {
625614
mls_group_id: mls_group_id.clone(),
@@ -631,7 +620,7 @@ mod tests {
631620
last_message_at: None,
632621
epoch: 0,
633622
state: GroupState::Active,
634-
image_url,
623+
image_hash,
635624
image_key,
636625
image_nonce,
637626
};

mls/nostr-mls-sqlite-storage/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
- Remove group type from groups
3131
- Replaced `save_group_relay` with `replace_group_relays` trait method (https://github.com/rust-nostr/nostr/pull/1056)
32+
- `image_hash` instead of `image_url` (https://github.com/rust-nostr/nostr/pull/1059)
3233

3334
### Changed
3435

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- groups: drop image_url, add image_hash (BLOB)
2+
ALTER TABLE groups DROP COLUMN image_url;
3+
ALTER TABLE groups ADD COLUMN image_hash BLOB;
4+
5+
-- welcomes: drop group_image_url, add group_image_hash (BLOB)
6+
ALTER TABLE welcomes DROP COLUMN group_image_url;
7+
ALTER TABLE welcomes ADD COLUMN group_image_hash BLOB;

mls/nostr-mls-sqlite-storage/src/db.rs

Lines changed: 103 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,93 @@ use nostr_mls_storage::welcomes::types::{
1313
ProcessedWelcome, ProcessedWelcomeState, Welcome, WelcomeState,
1414
};
1515
use openmls::group::GroupId;
16-
use rusqlite::types::Type;
16+
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, Type, ValueRef};
1717
use rusqlite::{Error, Result as SqliteResult, Row};
1818

19+
/// Wrapper for [u8; 32] to implement rusqlite traits
20+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21+
pub struct Hash32([u8; 32]);
22+
23+
impl From<[u8; 32]> for Hash32 {
24+
fn from(arr: [u8; 32]) -> Self {
25+
Hash32(arr)
26+
}
27+
}
28+
29+
impl From<Hash32> for [u8; 32] {
30+
fn from(hash: Hash32) -> Self {
31+
hash.0
32+
}
33+
}
34+
35+
impl ToSql for Hash32 {
36+
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
37+
Ok(ToSqlOutput::from(self.0.as_slice()))
38+
}
39+
}
40+
41+
impl FromSql for Hash32 {
42+
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
43+
match value {
44+
ValueRef::Blob(blob) => {
45+
if blob.len() == 32 {
46+
let mut arr = [0u8; 32];
47+
arr.copy_from_slice(blob);
48+
Ok(Hash32(arr))
49+
} else {
50+
Err(FromSqlError::InvalidBlobSize {
51+
expected_size: 32,
52+
blob_size: blob.len(),
53+
})
54+
}
55+
}
56+
_ => Err(FromSqlError::InvalidType),
57+
}
58+
}
59+
}
60+
61+
/// Wrapper for [u8; 12] to implement rusqlite traits
62+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63+
pub struct Nonce12([u8; 12]);
64+
65+
impl From<[u8; 12]> for Nonce12 {
66+
fn from(arr: [u8; 12]) -> Self {
67+
Nonce12(arr)
68+
}
69+
}
70+
71+
impl From<Nonce12> for [u8; 12] {
72+
fn from(nonce: Nonce12) -> Self {
73+
nonce.0
74+
}
75+
}
76+
77+
impl ToSql for Nonce12 {
78+
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
79+
Ok(ToSqlOutput::from(self.0.as_slice()))
80+
}
81+
}
82+
83+
impl FromSql for Nonce12 {
84+
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
85+
match value {
86+
ValueRef::Blob(blob) => {
87+
if blob.len() == 12 {
88+
let mut arr = [0u8; 12];
89+
arr.copy_from_slice(blob);
90+
Ok(Nonce12(arr))
91+
} else {
92+
Err(FromSqlError::InvalidBlobSize {
93+
expected_size: 12,
94+
blob_size: blob.len(),
95+
})
96+
}
97+
}
98+
_ => Err(FromSqlError::InvalidType),
99+
}
100+
}
101+
}
102+
19103
#[inline]
20104
fn map_to_text_boxed_error<T>(e: T) -> Error
21105
where
@@ -48,9 +132,13 @@ pub fn row_to_group(row: &Row) -> SqliteResult<Group> {
48132
let nostr_group_id: [u8; 32] = row.get("nostr_group_id")?;
49133
let name: String = row.get("name")?;
50134
let description: String = row.get("description")?;
51-
let image_url: Option<String> = row.get("image_url")?;
52-
let image_key: Option<Vec<u8>> = row.get("image_key")?;
53-
let image_nonce: Option<Vec<u8>> = row.get("image_nonce")?;
135+
let image_hash: Option<[u8; 32]> = row
136+
.get::<_, Option<Hash32>>("image_hash")?
137+
.map(|h| h.into());
138+
let image_key: Option<[u8; 32]> = row.get::<_, Option<Hash32>>("image_key")?.map(|h| h.into());
139+
let image_nonce: Option<[u8; 12]> = row
140+
.get::<_, Option<Nonce12>>("image_nonce")?
141+
.map(|n| n.into());
54142

55143
// Parse admin pubkeys from JSON
56144
let admin_pubkeys_json: &str = row.get_ref("admin_pubkeys")?.as_str()?;
@@ -79,7 +167,7 @@ pub fn row_to_group(row: &Row) -> SqliteResult<Group> {
79167
last_message_at,
80168
epoch,
81169
state,
82-
image_url,
170+
image_hash,
83171
image_key,
84172
image_nonce,
85173
})
@@ -203,9 +291,15 @@ pub fn row_to_welcome(row: &Row) -> SqliteResult<Welcome> {
203291
let nostr_group_id: [u8; 32] = row.get("nostr_group_id")?;
204292
let group_name: String = row.get("group_name")?;
205293
let group_description: String = row.get("group_description")?;
206-
let group_image_url: Option<String> = row.get("group_image_url")?;
207-
let group_image_key: Option<Vec<u8>> = row.get("group_image_key")?;
208-
let group_image_nonce: Option<Vec<u8>> = row.get("group_image_nonce")?;
294+
let group_image_hash: Option<[u8; 32]> = row
295+
.get::<_, Option<Hash32>>("group_image_hash")?
296+
.map(|h| h.into());
297+
let group_image_key: Option<[u8; 32]> = row
298+
.get::<_, Option<Hash32>>("group_image_key")?
299+
.map(|h| h.into());
300+
let group_image_nonce: Option<[u8; 12]> = row
301+
.get::<_, Option<Nonce12>>("group_image_nonce")?
302+
.map(|n| n.into());
209303
let group_admin_pubkeys_json: &str = row.get_ref("group_admin_pubkeys")?.as_str()?;
210304
let group_relays_json: &str = row.get_ref("group_relays")?.as_str()?;
211305
let welcomer_blob: &[u8] = row.get_ref("welcomer")?.as_blob()?;
@@ -242,7 +336,7 @@ pub fn row_to_welcome(row: &Row) -> SqliteResult<Welcome> {
242336
nostr_group_id,
243337
group_name,
244338
group_description,
245-
group_image_url,
339+
group_image_hash,
246340
group_image_key,
247341
group_image_nonce,
248342
group_admin_pubkeys,

0 commit comments

Comments
 (0)