Struth!
'An ancient, rare and defunct name for the (ostrich), ..was struthiocamel, from Latin struthiocamelus. The Romans took it wrongly from Greek strouthokamelos, literally “sparrow camel” or, more loosely, “camel-bird” (the scientific name of the ostrich to this day is Struthio camelus).'
https://www.worldwidewords.org/ww-str2.html
(Struth is a good client name actually 🤔💡)
It's called "Strucc" in Hungarian. Now I know where it came from!
nostr:nprofile1qqsvl47lvfue5ghrsjj2khdgcspxep6mzxws737zw94jpndvnnqlrfsppemhxue69uhkummn9ekx7mp0qywhwumn8ghj7mn0wd68ytnzd96xxmmfdejhytnnda3kjctv9uq3camnwvaz7tmwdaehgu3wdp6hx7n0dejkw7fwwahhymry9uzcc33q ajándéka a relay összehangoló.
Köszönjük a munkád gyümölcsét!
Köszi a megosztást. Azért még messze nincs vállalható szinten, épphogy csak működik. Még profilja sincs szegény polipnak.
We already talked about something similar months ago, if I'm not wrong with nostr:npub1jlrs53pkdfjnts29kveljul2sm0actt6n8dxrrzqcersttvcuv3qdjynqn. The idea was to invite people to join Nostr and do the PoW for them using a specific kind for delegated PoW as soon as they create the account, to allow them immediate access to a PoW-restricted relay.
The difficulty adjustment and forcing a redo is an interesting option.
I think all of these ideas should be available, with the ability to combine them all. Delegated, replaceable PoW on pubkeys or event ids.
This unlocks some interesting functionalities, like being able to set a higher bar for entering a community / flagging content, the difficulty being adjusted by moderators based on the amount of resources they have. If the community sees good quality content with low PoW, they can hive-boost the PoW to raise the content to the front of moderators' eyes. Good content could stand out this way.
Higher PoW is also a nice way to kickstart an anonymous profile with no web of trust at all.
I'm annoyed by most #nip07 clients on desktop. When I visit one, it shows me the landing page, then immediately puts a popup over it, asking for my permissio
n for reading my pubkey.
How should I know if I want to grant permission or not, if I haven't even read the page? I picked gitworkshop in the screenshot, but all applications do this
because there's simply no other way to get the public key via the NIP-07 API. We need one.
I'm submitting a simple, backward-compatible change to NIP-07 to address this problem and give a chance to app developers to provide a decent user experience
on desktop w/ extension.
Trying to log in from feedback.getalby.com.
It does 500 error right now, too. (18:17:38 GMT)
To not loose THE game and stay free, we need Bitcoin. But Bitcoin is not enough. We need to turn our focus in one common direction: towards freedom. We must stand still against all the forces that want to divide us. Thus, we need Nostr, too. We unite on Nostr. But that's not enough. We need to grow up to the challenge. We need to develop focus, morals, consciousness, physical and mental energy, empathy, self confidence and faith in every one of us, individually. We need to be disciplined and kind while we are marching towards our common goal.
Infinite scrolling is not our friend on this path. It is there to gain the masses over from the walled gardens. We need tools for collaboration that work for us, helps us to teach each other to the good things, let us live without unneccessary compromises and NOT to distract us!

If there is no blossom sha256 for a tag, it cannot be cloned from blossom. I think it is not the expected behaviour from the user. If he makes the move from github to blossom, then removes github.com clone url from the repo announcement even, then he will be disappointed if he cannot clone old versions.
I think blossom keys should be added to each ref upon the first push to blossom to keep the functionality.
Yes, we can hardcode it, but then I guess we don't even need to handle it in ngit init nor add it to the blossom tag. Or I might misunderstand something.
The way it works in the current version of git-remote-blossom is that it saves both the sha1 and the sha256 hash in the state event. So a ref tag looks like this:
`["refs/heads/master", "3437fd950d4ba37ba51e3bc788025698cfb20ca1", "3b58e484825e5b96428ab0b0c19cb760d5ebbd39acb4795446a89c99482e2d62"],`
It needs the sha256 to be able to start the graph traversal at the HEAD. All the rest can be calculated.
This is somewhat diverges from the current NIP-34. It needs to be updated with the sha256 - which is right now in the place of the optional parent commit ids.
Passing commit ID's are fine for pushes. But for fetch, I need the blossom sha256 hashes instead of the commit IDs in order to find the blobs on blossom.
It can just simply be a positional argument.
here you go nostr:npub1elta7cneng3w8p9y4dw633qzdjr4kyvaparuyuttyrx6e8xp7xnq32cume. I didn't build any dummy binaries to test it with, so don't expect it to work yet or anything.
Sure, no expectations here!
As an initial note, I think I'll need the blossom server to be passed down in args to the scripts.
Call your parents. Tell them that you love them. Do it now.
https://dergigi.com/2024/11/15/he-hanged-himself-in-the-morning/
Very sad to read. Wish you force to process this and come out stronger!
#asknostr
Do you prefer "master" branch or "main" branch in git?
(master: 👍 main: 🤙)
Sorry, I googled it myself and I didn't realize it's such difficult to find.
So here it goes: https://handshake.org
The idea originated from Aaron Swartz: http://www.aaronsw.com/weblog/squarezooko
Like NVK?
From 6bcb58925ad5a7ec2421718fb2996add9080f7bc Mon Sep 17 00:00:00 2001
From: DanConwayDev
Date: Fri, 15 Nov 2024 11:57:10 +0000
Subject: [PATCH] feat(blossom): blossom as remote using packs
This is a WIP exploration of the use of blossom as an optional
alternative to using a git server.
The incomplete code focuses on how blossom could fit with nip34
to most efficently replace the git server. It is missing the actual
blossom interaction which would hopefully would be facilited by
a new blossom feature in rust-nostr.
This implementation tries to minimise the number of blobs required
for download by using packs.
If a branch tip is at height 1304 it will split the commits in into
a number of packs. a pack the first 1024 commits, the next 256, the
next 16 and the final 8.
I planned for the identification of blossom servers to mirror the
approach taken for relays:
1. list repository blossom servers in repo announcement event
kind 30617
2. also push to user blossom servers in the standard event for that
This is not implemented, along with the rest of the blossom aspects.
I'm publishing this now as
nostr:npub1elta7cneng3w8p9y4dw633qzdjr4kyvaparuyuttyrx6e8xp7xnq32cume
has recently published a POC of an alternative approach and it makes
sense to this alternative idea.
---
Cargo.lock | 1 +
Cargo.toml | 1 +
src/bin/git_remote_nostr/fetch.rs | 4 ++++
src/bin/git_remote_nostr/list.rs | 23 ++++++++++++++++++++++-
src/bin/git_remote_nostr/push.rs | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
src/lib/repo_state.rs | 17 ++++++++++++++++-
6 files changed, 163 insertions(+), 7 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index b20b60a..72b37a2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1805,6 +1805,7 @@ dependencies = [
"serde_json",
"serde_yaml",
"serial_test",
+ "sha2",
"test_utils",
"tokio",
"urlencoding",
diff --git a/Cargo.toml b/Cargo.toml
index ed99aea..320a9f0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -38,6 +38,7 @@ serde_yaml = "0.9.27"
tokio = "1.33.0"
urlencoding = "2.1.3"
zeroize = "1.6.0"
+sha2 = "0.10.8"
[dev-dependencies]
assert_cmd = "2.0.12"
diff --git a/src/bin/git_remote_nostr/fetch.rs b/src/bin/git_remote_nostr/fetch.rs
index a972a2f..a1116c5 100644
--- a/src/bin/git_remote_nostr/fetch.rs
+++ b/src/bin/git_remote_nostr/fetch.rs
@@ -49,6 +49,10 @@ pub async fn run_fetch(
let term = console::Term::stderr();
for git_server_url in &repo_ref.git_server {
+ if git_server_url.eq("blossom") {
+ // TODO download missing blobs
+ continue;
+ }
let term = console::Term::stderr();
if let Err(error) = fetch_from_git_server(
git_repo,
diff --git a/src/bin/git_remote_nostr/list.rs b/src/bin/git_remote_nostr/list.rs
index 92faa6b..d71c2d1 100644
--- a/src/bin/git_remote_nostr/list.rs
+++ b/src/bin/git_remote_nostr/list.rs
@@ -43,7 +43,28 @@ pub async fn run_list(
let term = console::Term::stderr();
- let remote_states = list_from_remotes(&term, git_repo, &repo_ref.git_server, decoded_nostr_url);
+ let mut remote_states = list_from_remotes(
+ &term,
+ git_repo,
+ &repo_ref
+ .git_server
+ .iter()
+ // blossom will always match nostr state
+ .filter(|s| !s.starts_with("blossom"))
+ .map(std::borrow::ToOwned::to_owned)
+ .collect::
+ decoded_nostr_url,
+ );
+ if repo_ref.git_server.iter().any(|s| s.eq("blossom")) {
+ if let Some(nostr_state) = nostr_state.clone() {
+ remote_states.insert("blossom".to_owned(), nostr_state.state.clone());
+ } else if let Some((_, state)) = remote_states.iter().last() {
+ remote_states.insert("blossom".to_owned(), state.clone());
+ } else {
+ // create blank state if no nostr state exists yet
+ remote_states.insert("blossom".to_owned(), HashMap::new());
+ }
+ }
let mut state = if let Some(nostr_state) = nostr_state {
for (name, value) in &nostr_state.state {
diff --git a/src/bin/git_remote_nostr/push.rs b/src/bin/git_remote_nostr/push.rs
index db86c04..a12e8ba 100644
--- a/src/bin/git_remote_nostr/push.rs
+++ b/src/bin/git_remote_nostr/push.rs
@@ -2,6 +2,7 @@ use core::str;
use std::{
collections::{HashMap, HashSet},
io::Stdin,
+ str::FromStr,
sync::{Arc, Mutex},
time::Instant,
};
@@ -11,7 +12,7 @@ use auth_git2::GitAuthenticator;
use client::{get_events_from_cache, get_state_from_cache, send_events, sign_event, STATE_KIND};
use console::Term;
use git::{sha1_to_oid, RepoActions};
-use git2::{Oid, Repository};
+use git2::{Buf, Commit, Oid, Repository};
use git_events::{
generate_cover_letter_and_patch_events, generate_patch_event, get_commit_id_from_patch,
};
@@ -29,11 +30,17 @@ use ngit::{
};
use nostr::nips::nip10::Marker;
use nostr_sdk::{
- hashes::sha1::Hash as Sha1Hash, Event, EventBuilder, EventId, Kind, PublicKey, Tag,
+ hashes::{
+ hex::DisplayHex,
+ sha1::Hash as Sha1Hash,
+ sha256::{self, Hash as Sha256Hash},
+ },
+ Event, EventBuilder, EventId, Kind, PublicKey, Tag,
};
use nostr_signer::NostrSigner;
use repo_ref::RepoRef;
use repo_state::RepoState;
+use sha2::{Digest, Sha256};
use crate::{
client::Client,
@@ -74,7 +81,17 @@ pub async fn run_push(
let list_outputs = match list_outputs {
Some(outputs) => outputs,
- _ => list_from_remotes(&term, git_repo, &repo_ref.git_server, decoded_nostr_url),
+ _ => list_from_remotes(
+ &term,
+ git_repo,
+ &repo_ref
+ .git_server
+ .iter()
+ .filter(|s| !s.eq(&"blossom"))
+ .map(std::string::ToString::to_string)
+ .collect(),
+ decoded_nostr_url,
+ ),
};
let nostr_state = get_state_from_cache(git_repo.get_path()?, repo_ref).await;
@@ -150,11 +167,24 @@ pub async fn run_push(
}
}
+ let mut blossom_packs: Option
if !git_server_refspecs.is_empty() {
let new_state = generate_updated_state(git_repo, &existing_state, &git_server_refspecs)?;
+ let blossom_hashes = if repo_ref.git_server.contains(&"blossom".to_string()) {
+ let (blossom_hashes, packs) = create_blossom_packs(&new_state, git_repo)?;
+ blossom_packs = Some(packs);
+ blossom_hashes
+ } else {
+ HashSet::new()
+ };
- let new_repo_state =
- RepoState::build(repo_ref.identifier.clone(), new_state, &signer).await?;
+ let new_repo_state = RepoState::build(
+ repo_ref.identifier.clone(),
+ new_state,
+ blossom_hashes,
+ &signer,
+ )
+ .await?;
events.push(new_repo_state.event);
@@ -325,6 +355,13 @@ pub async fn run_push(
// TODO make async - check gitlib2 callbacks work async
+ if let Some(packs) = blossom_packs {
+ // TODO: upload blossom packs
+ for (_hash, _pack) in packs {
+ // blossom::upload(pack)
+ }
+ }
+
for (git_server_url, remote_refspecs) in remote_refspecs {
let remote_refspecs = remote_refspecs
.iter()
@@ -863,6 +900,71 @@ fn generate_updated_state(
Ok(new_state)
}
+fn create_blossom_packs(
+ state: &HashMap
+ git_repo: &Repo,
+) -> Result<(HashSet
+ let mut blossom_hashes = HashSet::new();
+ let mut blossom_packs = HashMap::new();
+ for commit_id in state.values() {
+ if let Ok(oid) = Oid::from_str(commit_id) {
+ if let Ok(commit) = git_repo.git_repo.find_commit(oid) {
+ let height = get_height(&commit, git_repo)?;
+ let mut revwalk = git_repo.git_repo.revwalk()?;
+ revwalk.push(oid)?;
+ let mut counter = 0;
+ for pack_size in split_into_powers_of_2(height) {
+ let mut pack = git_repo.git_repo.packbuilder()?;
+ while counter < pack_size {
+ if let Some(oid) = revwalk.next() {
+ pack.insert_commit(oid?)?;
+ counter += 1;
+ }
+ }
+ let mut buffer = Buf::new();
+ pack.write_buf(&mut buffer)?;
+ let hash = buffer_to_sha256_hash(&buffer);
+ blossom_hashes.insert(hash);
+ blossom_packs.insert(hash, buffer);
+ counter = 0;
+ }
+ }
+ }
+ }
+ Ok((blossom_hashes, blossom_packs))
+}
+
+fn get_height(commit: &Commit, git_repo: &Repo) -> Result
+ let mut revwalk = git_repo.git_repo.revwalk()?;
+ revwalk.push(commit.id())?;
+ Ok(u32::try_from(revwalk.count())?)
+}
+
+fn split_into_powers_of_2(height: u32) -> Vec
+ let mut powers = Vec::new();
+ let mut remaining = height;
+
+ // Decompose the height into powers of 2
+ for i in (0..32).rev() {
+ let power = 1 << i; // Calculate 2^i
+ while remaining >= power {
+ powers.push(power);
+ remaining -= power;
+ }
+ }
+
+ powers
+}
+
+fn buffer_to_sha256_hash(buffer: &Buf) -> sha256::Hash {
+ let mut hasher = Sha256::new();
+ hasher.update(buffer.as_ref());
+ let hash = hasher
+ .finalize()
+ .to_hex_string(nostr_sdk::hashes::hex::Case::Lower);
+ sha256::Hash::from_str(&hash).unwrap()
+}
+
async fn get_merged_status_events(
term: &console::Term,
repo_ref: &RepoRef,
@@ -1186,6 +1288,7 @@ trait BuildRepoState {
async fn build(
identifier: String,
state: HashMap
+ blossom: HashSet
signer: &NostrSigner,
) -> Result
}
@@ -1193,6 +1296,7 @@ impl BuildRepoState for RepoState {
async fn build(
identifier: String,
state: HashMap
+ blossom: HashSet
signer: &NostrSigner,
) -> Result
let mut tags = vec![Tag::identifier(identifier.clone())];
@@ -1202,10 +1306,20 @@ impl BuildRepoState for RepoState {
vec![value.clone()],
));
}
+ if !blossom.is_empty() {
+ tags.push(Tag::custom(
+ nostr_sdk::TagKind::Custom("blossom".into()),
+ blossom
+ .iter()
+ .map(std::string::ToString::to_string)
+ .collect::
+ ));
+ }
let event = sign_event(EventBuilder::new(STATE_KIND, "", tags), signer).await?;
Ok(RepoState {
identifier,
state,
+ blossom,
event,
})
}
diff --git a/src/lib/repo_state.rs b/src/lib/repo_state.rs
index c3a7606..19e78b6 100644
--- a/src/lib/repo_state.rs
+++ b/src/lib/repo_state.rs
@@ -1,11 +1,17 @@
-use std::collections::HashMap;
+use std::{
+ collections::{HashMap, HashSet},
+ str::FromStr,
+};
use anyhow::{Context, Result};
use git2::Oid;
+use nostr_sdk::hashes::sha256::Hash;
+#[derive(Clone)]
pub struct RepoState {
pub identifier: String,
pub state: HashMap
+ pub blossom: HashSet
pub event: nostr::Event,
}
@@ -14,6 +20,7 @@ impl RepoState {
state_events.sort_by_key(|e| e.created_at);
let event = state_events.first().context("no state events")?;
let mut state = HashMap::new();
+ let mut blossom = HashSet::new();
for tag in event.tags.iter() {
if let Some(name) = tag.as_slice().first() {
if ["refs/heads/", "refs/tags", "HEAD"]
@@ -26,6 +33,13 @@ impl RepoState {
}
}
}
+ if name.eq("blossom") {
+ for s in tag.clone().to_vec() {
+ if let Ok(hash) = Hash::from_str(&s) {
+ blossom.insert(hash);
+ }
+ }
+ }
}
}
Ok(RepoState {
@@ -35,6 +49,7 @@ impl RepoState {
.context("existing event must have an identifier")?
.to_string(),
state,
+ blossom,
event: event.clone(),
})
}
--
libgit2 1.8.1
Thanks for the upload!
The Nostr stand at FOSDEM is approved 🎉
I feel honored to present Nostr there 1 & 2 February 2025 with nostr:nprofile1qqsqvcu68pkfcyq5y9mz9n9u7sys33835rpnuglc6mtg7j4lv40c7ugpzemhxue69uhhyetvv9ujuvrcvd5xzapwvdhk6qg5waehxw309aex2mrp0yhxgctdw4eju6t0qyt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueq93artg nostr:nprofile1qqst32wlsgvqsnjfpkygxs4f6jyt0nc0kg935xdevwlv668dd26uh0gpzemhxue69uhkummnw3ezuumvda6xs7fwwa5kuqgkwaehxw309aex2mrp0yhxummnw3ezucnpdejqz8thwden5te0dehhxarj94c82c3wwajkcmr0wfjx2u3wdejhg3qrxjx nostr:nprofile1qqsfmd476kacwlqe920d9qrwazqrxpm3flwy85p2y3sj50qcexcvfmcpzfmhxue69uhhqatjwpkx2urpvuhx2ucpzemhxue69uhhyetvv9ujuurjd9kkzmpwdejhgqgkwaehxw309ahx7um5wghrs6edd3skytnrdaksxfatgv nostr:nprofile1qqspchlne2kdsskqrh9g7duzxxckv963d5s5mf6u0t4th60pal5upaspzamhxue69uhhyetvv9ujumn0wd68ytnzv9hxgtcpremhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet59uq3qamnwvaz7tmwdaehgu3wd4hk6tc5kgfql .
https://fosdem.org/2025/news/2024-11-16-stands-announced/
We hope to see many of you in Brussels next year and especially to meet many open source fans, onboarding them and have Nostr expanded in other FOSS initiatives as well.
Ps. Thanks to nostr:nprofile1qqs09z9zyjnpkump42wugx5s4w529hl523xmp0pcvu5wvw9jrkshjtqpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhk2er9dchxummnw3ezumrpdejqz9rhwden5te0wfjkccte9ejxzmt4wvhxjmcpeddte nostr:nprofile1qqs9afryspzmk8ljyfj4mhfkumwwmhzrtyxzvzgvfp477w80g5x6t0gpz4mhxue69uhhyetvv9ujuerpd46hxtnfduhs8h8rfc nostr:nprofile1qqs8d3c64cayj8canmky0jap0c3fekjpzwsthdhx4cthd4my8c5u47spzfmhxue69uhhqatjwpkx2urpvuhx2ucpz3mhxue69uhhyetvv9ujuerpd46hxtnfduq3vamnwvaz7tmjv4kxz7fwdehhxtnnda3kjctvll3q2p for their support with the submission document.
#FOSDEM #FOSDEM202 #grownostr #foss #opensource


Congrats! Nice job!