Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
Ğ
Ğcli-v2s
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
clients
Rust
Ğcli-v2s
Commits
93f7d5d4
Commit
93f7d5d4
authored
1 month ago
by
poka
Browse files
Options
Downloads
Patches
Plain Diff
set g1pubkey
parent
fe154a5a
No related branches found
No related tags found
1 merge request
!45
Nostr
Pipeline
#40613
passed
1 month ago
Stage: tests
Changes
1
Pipelines
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
src/commands/profile.rs
+124
-95
124 additions, 95 deletions
src/commands/profile.rs
with
124 additions
and
95 deletions
src/commands/profile.rs
+
124
−
95
View file @
93f7d5d4
...
@@ -11,6 +11,8 @@ use url::Url;
...
@@ -11,6 +11,8 @@ use url::Url;
use
sha2
::{
Sha256
,
Digest
};
use
sha2
::{
Sha256
,
Digest
};
use
secp256k1
::{
Secp256k1
,
SecretKey
,
PublicKey
};
use
secp256k1
::{
Secp256k1
,
SecretKey
,
PublicKey
};
use
bech32
::{
self
,
ToBase32
,
Variant
};
use
bech32
::{
self
,
ToBase32
,
Variant
};
use
sp_core
::
crypto
::
Ss58Codec
;
use
crate
::
commands
::
cesium
;
/// Derive a Nostr key from a Substrate key
/// Derive a Nostr key from a Substrate key
fn
derive_nostr_keys_from_substrate
(
keypair
:
&
KeyPair
)
->
Result
<
(
SecretKey
,
PublicKey
),
GcliError
>
{
fn
derive_nostr_keys_from_substrate
(
keypair
:
&
KeyPair
)
->
Result
<
(
SecretKey
,
PublicKey
),
GcliError
>
{
...
@@ -159,7 +161,7 @@ impl Default for NostrProfile {
...
@@ -159,7 +161,7 @@ impl Default for NostrProfile {
}
}
/// Nostr event structure
/// Nostr event structure
#[derive(Debug,
Serialize,
Deserialize)]
#[derive(Debug,
Serialize,
Deserialize
,
Clone
)]
pub
struct
NostrEvent
{
pub
struct
NostrEvent
{
pub
id
:
String
,
pub
id
:
String
,
pub
pubkey
:
String
,
pub
pubkey
:
String
,
...
@@ -501,7 +503,8 @@ async fn get_profile(data: Data, relay_url: Option<String>) -> Result<(), GcliEr
...
@@ -501,7 +503,8 @@ async fn get_profile(data: Data, relay_url: Option<String>) -> Result<(), GcliEr
// Get Nostr pubkey in bech32 format (for display)
// Get Nostr pubkey in bech32 format (for display)
let
npub
=
get_nostr_npub
(
&
keypair
)
?
;
let
npub
=
get_nostr_npub
(
&
keypair
)
?
;
println!
(
"Searching for profile with pubkey: {}"
,
pubkey
);
println!
(
"Searching for profile with Nostr pubkey (hex): {}"
,
pubkey
);
println!
(
"Nostr pubkey (bech32): {}"
,
npub
);
// Use default relay if none provided
// Use default relay if none provided
let
relay
=
relay_url
.unwrap_or_else
(||
{
let
relay
=
relay_url
.unwrap_or_else
(||
{
...
@@ -545,8 +548,10 @@ async fn get_profile(data: Data, relay_url: Option<String>) -> Result<(), GcliEr
...
@@ -545,8 +548,10 @@ async fn get_profile(data: Data, relay_url: Option<String>) -> Result<(), GcliEr
// Wait for response with timeout
// Wait for response with timeout
let
mut
profile_found
=
false
;
let
mut
profile_found
=
false
;
let
mut
profile
=
NostrProfile
::
default
();
let
mut
profile_content
=
NostrProfile
::
default
();
// To store parsed content
let
mut
all_messages
:
Vec
<
String
>
=
Vec
::
new
();
// Store all received messages for debugging
let
mut
g1pubv2_from_tags
:
Option
<
String
>
=
None
;
let
mut
g1pub_from_tags
:
Option
<
String
>
=
None
;
let
mut
event_received
:
Option
<
NostrEvent
>
=
None
;
// Store the whole event for JSON output
// Set a timeout for receiving messages
// Set a timeout for receiving messages
let
timeout
=
tokio
::
time
::
Duration
::
from_secs
(
10
);
// Increased timeout
let
timeout
=
tokio
::
time
::
Duration
::
from_secs
(
10
);
// Increased timeout
...
@@ -564,7 +569,7 @@ async fn get_profile(data: Data, relay_url: Option<String>) -> Result<(), GcliEr
...
@@ -564,7 +569,7 @@ async fn get_profile(data: Data, relay_url: Option<String>) -> Result<(), GcliEr
)
.await
{
)
.await
{
Ok
(
Some
(
Ok
(
msg
)))
=>
{
Ok
(
Some
(
Ok
(
msg
)))
=>
{
if
let
Message
::
Text
(
text
)
=
msg
{
if
let
Message
::
Text
(
text
)
=
msg
{
all_messages
.push
(
text
.clone
());
event_received
=
Some
(
serde_json
::
from_str
::
<
NostrEvent
>
(
&
text
)
.unwrap
());
// Parse the message
// Parse the message
if
let
Ok
(
json
)
=
serde_json
::
from_str
::
<
Value
>
(
&
text
)
{
if
let
Ok
(
json
)
=
serde_json
::
from_str
::
<
Value
>
(
&
text
)
{
...
@@ -573,25 +578,34 @@ async fn get_profile(data: Data, relay_url: Option<String>) -> Result<(), GcliEr
...
@@ -573,25 +578,34 @@ async fn get_profile(data: Data, relay_url: Option<String>) -> Result<(), GcliEr
if
let
Some
(
event_type
)
=
json
.get
(
0
)
.and_then
(|
v
|
v
.as_str
())
{
if
let
Some
(
event_type
)
=
json
.get
(
0
)
.and_then
(|
v
|
v
.as_str
())
{
if
event_type
==
"EVENT"
&&
json
.get
(
1
)
.is_some
()
&&
json
.get
(
2
)
.is_some
()
{
if
event_type
==
"EVENT"
&&
json
.get
(
1
)
.is_some
()
&&
json
.get
(
2
)
.is_some
()
{
// Attempt to deserialize the whole event first
if
let
Ok
(
full_event_obj
)
=
serde_json
::
from_value
::
<
NostrEvent
>
(
json
[
2
]
.clone
())
{
event_received
=
Some
(
full_event_obj
.clone
());
// Clone for later JSON output
if
let
Some
(
content
)
=
json
[
2
][
"content"
]
.as_str
()
{
// Then parse the content string into NostrProfile
if
let
Ok
(
parsed_profile_content
)
=
serde_json
::
from_str
::
<
NostrProfile
>
(
&
full_event_obj
.content
)
{
// Try to parse the profile
profile_content
=
parsed_profile_content
;
match
serde_json
::
from_str
::
<
NostrProfile
>
(
content
)
{
Ok
(
parsed_profile
)
=>
{
profile
=
parsed_profile
;
profile_found
=
true
;
profile_found
=
true
;
// Close the subscription
// Extract g1pubv2 and g1pub from tags
for
tag_vec
in
&
full_event_obj
.tags
{
if
tag_vec
.len
()
>=
2
{
match
tag_vec
[
0
]
.as_str
()
{
"g1pubv2"
=>
g1pubv2_from_tags
=
Some
(
tag_vec
[
1
]
.clone
()),
"g1pub"
=>
g1pub_from_tags
=
Some
(
tag_vec
[
1
]
.clone
()),
_
=>
{}
}
}
}
let
close_msg
=
json!
([
"CLOSE"
,
"profile-request"
]);
let
close_msg
=
json!
([
"CLOSE"
,
"profile-request"
]);
ws_stream
.send
(
Message
::
Text
(
close_msg
.to_string
()))
.await
ws_stream
.send
(
Message
::
Text
(
close_msg
.to_string
()))
.await
.map_err
(|
e
|
anyhow!
(
"Failed to close subscription: {}"
,
e
))
?
;
.map_err
(|
e
|
anyhow!
(
"Failed to close subscription: {}"
,
e
))
?
;
break
;
break
;
},
}
else
{
Err
(
_
)
=>
{
log
::
warn!
(
"Failed to parse NostrProfile content from event content string."
);
}
}
}
}
else
{
log
::
warn!
(
"Failed to parse full NostrEvent object from relay message."
);
}
}
}
else
if
event_type
==
"EOSE"
{
}
else
if
event_type
==
"EOSE"
{
// End of stored events
// End of stored events
...
@@ -630,25 +644,32 @@ async fn get_profile(data: Data, relay_url: Option<String>) -> Result<(), GcliEr
...
@@ -630,25 +644,32 @@ async fn get_profile(data: Data, relay_url: Option<String>) -> Result<(), GcliEr
match
data
.args.output_format
{
match
data
.args.output_format
{
OutputFormat
::
Human
=>
{
OutputFormat
::
Human
=>
{
println!
(
"Profile for npub: {}"
,
npub
);
println!
(
"Profile for npub: {}"
,
npub
);
if
let
Some
(
name
)
=
&
profile
.name
{
if
let
Some
(
name
)
=
&
profile
_content
.name
{
println!
(
"Name: {}"
,
name
);
println!
(
"Name: {}"
,
name
);
}
}
if
let
Some
(
display_name
)
=
&
profile
.display_name
{
if
let
Some
(
display_name
)
=
&
profile
_content
.display_name
{
println!
(
"Display Name: {}"
,
display_name
);
println!
(
"Display Name: {}"
,
display_name
);
}
}
if
let
Some
(
picture
)
=
&
profile
.picture
{
if
let
Some
(
picture
)
=
&
profile
_content
.picture
{
println!
(
"Picture: {}"
,
picture
);
println!
(
"Picture: {}"
,
picture
);
}
}
if
let
Some
(
about
)
=
&
profile
.about
{
if
let
Some
(
about
)
=
&
profile
_content
.about
{
println!
(
"About: {}"
,
about
);
println!
(
"About: {}"
,
about
);
}
}
if
let
Some
(
website
)
=
&
profile
.website
{
if
let
Some
(
website
)
=
&
profile
_content
.website
{
println!
(
"Website: {}"
,
website
);
println!
(
"Website: {}"
,
website
);
}
}
if
let
Some
(
nip05
)
=
&
profile
.nip05
{
if
let
Some
(
nip05
)
=
&
profile
_content
.nip05
{
println!
(
"NIP-05: {}"
,
nip05
);
println!
(
"NIP-05: {}"
,
nip05
);
}
}
for
(
key
,
value
)
in
&
profile
.additional_fields
{
// Display from tags
if
let
Some
(
g1pubv2
)
=
&
g1pubv2_from_tags
{
println!
(
"g1pubv2 (gdev SS58 Tag): {}"
,
g1pubv2
);
}
if
let
Some
(
g1pub
)
=
&
g1pub_from_tags
{
println!
(
"g1pub (Duniter v1 Pubkey Tag): {}"
,
g1pub
);
}
for
(
key
,
value
)
in
&
profile_content
.additional_fields
{
println!
(
"{}: {}"
,
key
,
value
);
println!
(
"{}: {}"
,
key
,
value
);
}
}
}
}
...
@@ -656,7 +677,12 @@ async fn get_profile(data: Data, relay_url: Option<String>) -> Result<(), GcliEr
...
@@ -656,7 +677,12 @@ async fn get_profile(data: Data, relay_url: Option<String>) -> Result<(), GcliEr
let
output
=
json!
({
let
output
=
json!
({
"pubkey_hex"
:
pubkey
,
"pubkey_hex"
:
pubkey
,
"pubkey_bech32"
:
npub
,
"pubkey_bech32"
:
npub
,
"profile"
:
profile
"profile_content"
:
profile_content
,
// The content part
"tags_custom"
:
{
"g1pubv2"
:
g1pubv2_from_tags
,
"g1pub"
:
g1pub_from_tags
},
"full_event"
:
event_received
// Include the full event if available
});
});
println!
(
"{}"
,
serde_json
::
to_string_pretty
(
&
output
)
.unwrap
());
println!
(
"{}"
,
serde_json
::
to_string_pretty
(
&
output
)
.unwrap
());
}
}
...
@@ -670,7 +696,12 @@ async fn get_profile(data: Data, relay_url: Option<String>) -> Result<(), GcliEr
...
@@ -670,7 +696,12 @@ async fn get_profile(data: Data, relay_url: Option<String>) -> Result<(), GcliEr
let
output
=
json!
({
let
output
=
json!
({
"pubkey_hex"
:
pubkey
,
"pubkey_hex"
:
pubkey
,
"pubkey_bech32"
:
npub
,
"pubkey_bech32"
:
npub
,
"profile"
:
NostrProfile
::
default
()
"profile_content"
:
NostrProfile
::
default
(),
// Default empty profile content
"tags_custom"
:
{
"g1pubv2"
:
Option
::
<
String
>
::
None
,
"g1pub"
:
Option
::
<
String
>
::
None
},
"full_event"
:
Option
::
<
NostrEvent
>
::
None
});
});
println!
(
"{}"
,
serde_json
::
to_string_pretty
(
&
output
)
.unwrap
());
println!
(
"{}"
,
serde_json
::
to_string_pretty
(
&
output
)
.unwrap
());
}
}
...
@@ -719,60 +750,57 @@ async fn set_profile(
...
@@ -719,60 +750,57 @@ async fn set_profile(
// Get Nostr private key in bech32 format (for display)
// Get Nostr private key in bech32 format (for display)
let
nsec
=
get_nostr_nsec
(
&
keypair
)
?
;
let
nsec
=
get_nostr_nsec
(
&
keypair
)
?
;
// Create profile data
// ---- START: Calculate g1pubv2 and g1pub for tags ----
let
mut
profile
=
NostrProfile
::
default
();
let
mut
g1pubv2_for_tag
:
Option
<
String
>
=
None
;
let
mut
g1pub_for_tag
:
Option
<
String
>
=
None
;
if
let
Some
(
name
)
=
name
{
let
account_id
:
sp_core
::
crypto
::
AccountId32
=
match
&
keypair
{
profile
.name
=
Some
(
name
);
KeyPair
::
Sr25519
(
pair
)
=>
pair
.public
()
.into
(),
}
KeyPair
::
Ed25519
(
pair
)
=>
pair
.public
()
.into
(),
if
let
Some
(
display_name
)
=
display_name
{
};
profile
.display_name
=
Some
(
display_name
);
let
gdev_ss58_address
=
account_id
.to_ss58check_with_version
(
sp_core
::
crypto
::
Ss58AddressFormat
::
custom
(
42
));
}
g1pubv2_for_tag
=
Some
(
gdev_ss58_address
);
if
let
Some
(
picture
)
=
picture
{
profile
.picture
=
Some
(
picture
);
match
cesium
::
compute_g1v1_public_key
(
&
keypair
)
{
}
Ok
(
pubkey_g1
)
=>
g1pub_for_tag
=
Some
(
pubkey_g1
),
if
let
Some
(
about
)
=
about
{
Err
(
_e
)
=>
{
profile
.about
=
Some
(
about
);
if
!
matches!
(
keypair
,
KeyPair
::
Ed25519
(
_
))
{
log
::
info!
(
"Cannot compute g1pub (Duniter v1 pubkey) for tag as the key is not Ed25519."
);
}
else
{
log
::
warn!
(
"Failed to compute g1pub (Duniter v1 pubkey) for tag."
);
}
}
if
let
Some
(
website
)
=
website
{
profile
.website
=
Some
(
website
);
}
}
if
let
Some
(
nip05
)
=
nip05
{
profile
.nip05
=
Some
(
nip05
);
}
}
// ---- END: Calculate g1pubv2 and g1pub for tags ----
// Serialize profile to JSON
let
mut
profile_content_obj
=
NostrProfile
::
default
();
// This is for the 'content' field
let
profile_json
=
serde_json
::
to_string
(
&
profile
)
if
let
Some
(
val
)
=
name
{
profile_content_obj
.name
=
Some
(
val
);
}
.map_err
(|
e
|
anyhow!
(
"Failed to serialize profile: {}"
,
e
))
?
;
if
let
Some
(
val
)
=
display_name
{
profile_content_obj
.display_name
=
Some
(
val
);
}
if
let
Some
(
val
)
=
picture
{
profile_content_obj
.picture
=
Some
(
val
);
}
if
let
Some
(
val
)
=
about
{
profile_content_obj
.about
=
Some
(
val
);
}
if
let
Some
(
val
)
=
website
{
profile_content_obj
.website
=
Some
(
val
);
}
if
let
Some
(
val
)
=
nip05
{
profile_content_obj
.nip05
=
Some
(
val
);
}
// Create and sign Nostr event
let
profile_content_json
=
serde_json
::
to_string
(
&
profile_content_obj
)
let
mut
event
=
NostrEvent
::
new
(
pubkey
.clone
(),
0
,
profile_json
)
;
.map_err
(|
e
|
anyhow!
(
"Failed to serialize profile content: {}"
,
e
))
?
;
// Make sure tags is initialized as an empty array, not null
let
mut
event
=
NostrEvent
::
new
(
pubkey
.clone
(),
0
,
profile_content_json
);
event
.tags
=
Vec
::
new
();
event
.tags
=
Vec
::
new
();
// Initialize tags
// Add g1pubv2 and g1pub as tags
if
let
Some
(
g1_ss58
)
=
&
g1pubv2_for_tag
{
event
.tags
.push
(
vec!
[
"g1pubv2"
.to_string
(),
g1_ss58
.clone
()]);
}
if
let
Some
(
g1_key
)
=
&
g1pub_for_tag
{
event
.tags
.push
(
vec!
[
"g1pub"
.to_string
(),
g1_key
.clone
()]);
}
log
::
debug!
(
"Created event with pubkey: {}"
,
event
.pubkey
);
log
::
debug!
(
"Created event with pubkey: {}"
,
event
.pubkey
);
log
::
debug!
(
"Event content: {}"
,
event
.content
);
log
::
debug!
(
"Event content: {}"
,
event
.content
);
log
::
debug!
(
"Event tags: {:?}"
,
event
.tags
);
// Log the tags
// Calculate ID and sign
event
.calculate_id
()
?
;
event
.calculate_id
()
?
;
log
::
debug!
(
"Calculated event ID: {}"
,
event
.id
);
event
.sign
(
&
keypair
)
?
;
event
.sign
(
&
keypair
)
?
;
log
::
debug!
(
"Signed event with signature: {}"
,
event
.sig
);
// Log the complete event for debugging
log
::
debug!
(
"Event to publish: {}"
,
serde_json
::
to_string_pretty
(
&
event
)
.unwrap
());
// Verify the event signature
match
verify_nostr_event
(
&
event
)
{
Ok
(
true
)
=>
log
::
debug!
(
"Event signature verified successfully"
),
Ok
(
false
)
=>
{
log
::
error!
(
"Event signature verification failed - relay will likely reject this event"
);
return
Err
(
anyhow!
(
"Event signature verification failed - cannot proceed"
)
.into
());
},
Err
(
e
)
=>
log
::
warn!
(
"Error verifying event signature: {}"
,
e
),
}
// Use default relay if none provided
// Use default relay if none provided
let
relay
=
relay_url
.unwrap_or_else
(||
{
let
relay
=
relay_url
.unwrap_or_else
(||
{
...
@@ -902,25 +930,22 @@ async fn set_profile(
...
@@ -902,25 +930,22 @@ async fn set_profile(
match
data
.args.output_format
{
match
data
.args.output_format
{
OutputFormat
::
Human
=>
{
OutputFormat
::
Human
=>
{
println!
(
"Profile data published to npub: {}"
,
npub
);
println!
(
"Profile data published to npub (bech32): {}"
,
npub
);
println!
(
"Nostr pubkey (hex): {}"
,
pubkey
);
if
let
Some
(
name
)
=
&
profile
.name
{
if
let
Some
(
val
)
=
&
profile_content_obj
.name
{
println!
(
"Name: {}"
,
val
);
}
println!
(
"Name: {}"
,
name
);
if
let
Some
(
val
)
=
&
profile_content_obj
.display_name
{
println!
(
"Display Name: {}"
,
val
);
}
}
if
let
Some
(
val
)
=
&
profile_content_obj
.picture
{
println!
(
"Picture: {}"
,
val
);
}
if
let
Some
(
display_name
)
=
&
profile
.display_name
{
if
let
Some
(
val
)
=
&
profile_content_obj
.about
{
println!
(
"About: {}"
,
val
);
}
println!
(
"Display Name: {}"
,
display_name
);
if
let
Some
(
val
)
=
&
profile_content_obj
.website
{
println!
(
"Website: {}"
,
val
);
}
}
if
let
Some
(
val
)
=
&
profile_content_obj
.nip05
{
println!
(
"NIP-05: {}"
,
val
);
}
if
let
Some
(
picture
)
=
&
profile
.picture
{
println!
(
"Picture: {}"
,
picture
);
// Print calculated tag values for user feedback
}
if
let
Some
(
g1_ss58
)
=
&
g1pubv2_for_tag
{
if
let
Some
(
about
)
=
&
profile
.about
{
println!
(
"g1pubv2 (Tag to be published): {}"
,
g1_ss58
);
println!
(
"About: {}"
,
about
);
}
}
if
let
Some
(
website
)
=
&
profile
.website
{
if
let
Some
(
g1_key
)
=
&
g1pub_for_tag
{
println!
(
"Website: {}"
,
website
);
println!
(
"g1pub (Tag to be published): {}"
,
g1_key
);
}
if
let
Some
(
nip05
)
=
&
profile
.nip05
{
println!
(
"NIP-05: {}"
,
nip05
);
}
}
println!
(
"
\n
Published to relay: {}"
,
relay
);
println!
(
"
\n
Published to relay: {}"
,
relay
);
...
@@ -936,8 +961,12 @@ async fn set_profile(
...
@@ -936,8 +961,12 @@ async fn set_profile(
"pubkey_hex"
:
pubkey
,
"pubkey_hex"
:
pubkey
,
"pubkey_bech32"
:
npub
,
"pubkey_bech32"
:
npub
,
"seckey_bech32"
:
nsec
,
"seckey_bech32"
:
nsec
,
"profile"
:
profile
,
"profile_content_sent"
:
profile_content_obj
,
// what was in content
"event"
:
event
,
"calculated_tags"
:
{
"g1pubv2"
:
g1pubv2_for_tag
,
"g1pub"
:
g1pub_for_tag
},
"event_sent"
:
event
,
// The full event, including new tags
"relay"
:
relay
,
"relay"
:
relay
,
"success"
:
success
,
"success"
:
success
,
"response"
:
response_message
"response"
:
response_message
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment