wilsonsilva / nostr

Asynchronous Nostr client in Ruby
https://nostr-ruby.com
MIT License
24 stars 5 forks source link

Serialization Errors / Documentation Bugs? #14

Open tfreedman opened 3 weeks ago

tfreedman commented 3 weeks ago

While working on a different Nostr project, I used this library to generate some test events. I'm not totally sure if there are errors in my code, but I suspect there are bugs in this library that make the events it produces invalid.

If I run:

kind_0_event = user.create_event(
  kind: Nostr::EventKind::SET_METADATA,
  content: {
    name: 'Whatever',
    display_name: 'Whatever',
    about: 'Whatever,
    picture: 'Whatever',
    website: 'Whatever',
    banner: 'Whatever'
  }
)

Then check kind_0_event.serialize.to_s, I get text that contains:

 {:name=>\"Whatever\",

The content hash appears to not be valid JSON, and is instead using the Ruby syntax for a hash with symbols (as opposed to {"name":"Whatever"}. As a result, Digest::SHA256.hexdigest(kind_0_event.serialize.to_json) produces a SHA256 hash that other programs won't be able to match.

I assume the fix is to add an implicit .to_json call on content in create_event, but the documentation doesn't tell you to do this and the library doesn't do it for you.

--

Next, to validate a signature, the documentation says:

other_public_key = Nostr::PublicKey.new('10be96d345ed58d923a734560680f1adfd2b1006c28ac93b8e1b032a9a32c6e9')
event.verify_signature # => false

but other_public_key is never actually called - I assume it should be some form of

crypto = Nostr::Crypto.new
other_public_key = Nostr::PublicKey.new('10be96d345ed58d923a734560680f1adfd2b1006c28ac93b8e1b032a9a32c6e9')
crypto.check_sig!(<hash of event>, other_public_key, <signature of event>)
wilsonsilva commented 3 weeks ago

Thanks for the feedback, @tfreedman.

To serialize the event as JSON, do event.to_h.to_json. I'll add a method to_json directly on the event to make it easier and I'll also document it on the website.

To sign and validate a signature, the documentation says:

To sign an event, use the private key associated with the event's creator. Here's how to sign a message using a predefined keypair:

require 'nostr'

private_key = Nostr::PrivateKey.new('67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'),
public_key = Nostr::PublicKey.new('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'),

event = Nostr::Event.new(
  pubkey: public_key.to_s,
  kind: Nostr::EventKind::TEXT_NOTE,
  content: 'We did it with security, now we’re going to do it with the economy.',
  created_at: Time.now.to_i,
)

# Sign the event with the private key
event.sign(private_key) # <------------

puts "Event ID: #{event.id}"
puts "Event Signature: #{event.sig}"

I can see how that part about other_public_key can be confusing. What I mean is this:

require 'nostr'

private_key = Nostr::PrivateKey.new('67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'),
public_key = Nostr::PublicKey.new('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'),

event = Nostr::Event.new(
  pubkey: other_public_key.to_s,  # <--- this public key is not the key which pairs with the private key below
  kind: Nostr::EventKind::TEXT_NOTE,
  content: 'We did it with security, now we’re going to do it with the economy.',
  created_at: Time.now.to_i,
)

# Sign the event with the private key
event.sign(private_key)

puts "Event ID: #{event.id}"
puts "Event Signature: #{event.sig}"

I'll also update the documentation to make it more transparent. Thanks for the feedback.