Add full NIP-19 compatibility

note, nprofile, nevent, naddr, npub, nsec and nrelay
This commit is contained in:
Wilson Silva 2023-11-20 21:03:24 +07:00
parent bba18d1bc0
commit 61a88981e6
No known key found for this signature in database
GPG Key ID: 65135F94E23F82C8
10 changed files with 561 additions and 8 deletions

View File

@ -9,8 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added
- Added relay message type enums `Nostr::RelayMessageType`
- Initial compliance with [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md) - bech32-formatted private
keys and public keys
- Compliance with [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md) - bech32-formatted strings
- `Nostr::PrivateKey` and `Nostr::PublicKey` to represent private and public keys, respectively
- Added a validation of private and public keys
- Added an ability to convert keys to and from Bech32 format

View File

@ -131,6 +131,7 @@ I made a detailed documentation for this gem and it's usage. The code is also fu
- [x] [NIP-01 - Basic protocol flow description](https://github.com/nostr-protocol/nips/blob/master/01.md)
- [x] [NIP-02 - Contact List and Petnames](https://github.com/nostr-protocol/nips/blob/master/02.md)
- [x] [NIP-04 - Encrypted Direct Message](https://github.com/nostr-protocol/nips/blob/master/04.md)
- [x] [NIP-19 - Bech32-encoded entities](https://github.com/nostr-protocol/nips/blob/master/19.md)
## 🔨 Development

View File

@ -74,6 +74,13 @@ export default defineConfig(withMermaid({
{ text: 'Encrypted Direct Message', link: '/events/encrypted-direct-message' },
]
},
{
text: 'Common use cases',
collapsed: false,
items: [
{ text: 'Bech32 enc/decoding (NIP-19)', link: '/common-use-cases/bech32-encoding-and-decoding-(NIP-19)' },
]
},
{
text: 'Implemented NIPs',
link: '/implemented-nips',

View File

@ -0,0 +1,190 @@
# Encoding/decoding bech-32 strings (NIP-19)
[NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md) standardizes bech32-formatted strings that can be
used to display keys, ids and other information in clients. These formats are not meant to be used anywhere in the core
protocol, they are only meant for displaying to users, copy-pasting, sharing, rendering QR codes and inputting data.
In order to guarantee the deterministic nature of the documentation, the examples below assume that there is a `keypair`
variable with the following values:
```ruby
keypair = Nostr::KeyPair.new(
private_key: Nostr::PrivateKey.new('67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'),
public_key: Nostr::PublicKey.new('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'),
)
keypair.private_key # => '67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'
keypair.public_key # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
```
## Public key (npub)
### Encoding
```ruby
npub = Nostr::Bech32.npub_encode('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e')
npub # => 'npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg'
```
### Decoding
```ruby
type, public_key = Nostr::Bech32.decode('npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg')
type # => 'npub'
public_key # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
```
## Private key (nsec)
### Encoding
```ruby
nsec = Nostr::Bech32.nsec_encode('67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa')
nsec # => 'nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5'
```
### Decoding
```ruby
type, private_key = Nostr::Bech32.decode('nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5')
type # => 'npub'
private_key # => '67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'
```
## Relay (nrelay)
### Encoding
```ruby
nrelay = Nostr::Bech32.nrelay_encode('wss://relay.damus.io')
nrelay # => 'nrelay1qq28wumn8ghj7un9d3shjtnyv9kh2uewd9hsc5zt2x'
```
### Decoding
```ruby
type, data = Nostr::Bech32.decode('nrelay1qq28wumn8ghj7un9d3shjtnyv9kh2uewd9hsc5zt2x')
type # => 'nrelay'
data.entries.first.label # => 'relay'
data.entries.first.value # => 'wss://relay.damus.io'
```
## Event (nevent)
### Encoding
```ruby{8-12}
user = Nostr::User.new(keypair: keypair)
text_note_event = user.create_event(
kind: Nostr::EventKind::TEXT_NOTE,
created_at: 1700467997,
content: 'Your feedback is appreciated, now pay $8'
)
nevent = Nostr::Bech32.nevent_encode(
id: text_note_event.id,
relays: ['wss://relay.damus.io', 'wss://nos.lol'],
kind: Nostr::EventKind::TEXT_NOTE,
)
nevent # => 'nevent1qgsqlkuslr3rf56qpmd0m5ndfyl39m7q6l0zcmuly8ue0praxwkjagcpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dspsgqqqqqqs03k8v3'
```
### Decoding
```ruby
type, event = Nostr::Bech32.decode('nevent1qgsqlkuslr3rf56qpmd0m5ndfyl39m7q6l0zcmuly8ue0praxwkjagcpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dspsgqqqqqqs03k8v3')
type # => 'nevent'
event.entries[0].label # => 'author'
event.entries[0].value # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
event.entries[1].relay # => 'relay'
event.entries[1].value # => 'wss://relay.damus.io'
event.entries[2].label # => 'relay'
event.entries[2].value # => 'wss://nos.lol'
event.entries[3].label # => 'kind'
event.entries[3].value # => 1
```
## Address (naddr)
### Encoding
```ruby
naddr = Nostr::Bech32.naddr_encode(
pubkey: keypair.public_key,
relays: ['wss://relay.damus.io', 'wss://nos.lol'],
kind: Nostr::EventKind::TEXT_NOTE,
identifier: 'damus',
)
naddr # => 'naddr1qgs8ul5ug253hlh3n75jne0a5xmjur4urfxpzst88cnegg6ds6ka7nspz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dspsgqqqqqqsqptyv9kh2uc3qfs2p'
```
### Decoding
```ruby
type, addr = Nostr::Bech32.decode('naddr1qgs8ul5ug253hlh3n75jne0a5xmjur4urfxpzst88cnegg6ds6ka7nspz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dspsgqqqqqqsqptyv9kh2uc3qfs2p')
type # => 'naddr'
addr.entries[0].label # => 'author'
addr.entries[0].value # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
addr.entries[1].label # => 'relay'
addr.entries[1].value # => 'wss://relay.damus.io'
addr.entries[2].label # => 'relay'
addr.entries[2].value # => 'wss://nos.lol'
addr.entries[3].label # => 'kind'
addr.entries[3].value # => 1
addr.entries[4].label # => 'identifier'
addr.entries[4].value # => 'damus'
```
## Profile (nprofile)
### Encoding
```ruby
relay_urls = %w[wss://relay.damus.io wss://nos.lol]
nprofile = Nostr::Bech32.nprofile_encode(pubkey: keypair.public_key, relays: relay_urls)
nprofile # => nprofile1qqs8ul5ug253hlh3n75jne0a5xmjur4urfxpzst88cnegg6ds6ka7nspz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dsxe58m5
```
### Decoding
```ruby
type, profile = Nostr::Bech32.decode('nprofile1qqs8ul5ug253hlh3n75jne0a5xmjur4urfxpzst88cnegg6ds6ka7nspz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dsxe58m5')
type # => 'nprofile'
profile.entries[0].label # => 'pubkey'
profile.entries[0].value # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
profile.entries[1].label # => 'relay'
profile.entries[1].value # => 'wss://relay.damus.io'
profile.entries[2].label # => 'relay'
profile.entries[2].value # => 'wss://nos.lol'
```
## Other simple types (note)
### Encoding
```ruby{8-9}
user = Nostr::User.new(keypair: keypair)
text_note_event = user.create_event(
kind: Nostr::EventKind::TEXT_NOTE,
created_at: 1700467997,
content: 'Your feedback is appreciated, now pay $8'
)
note = Nostr::Bech32.encode(hrp: 'note', data: text_note_event.id)
note # => 'note10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qnx3ujq'
```
### Decoding
```ruby
type, note = Nostr::Bech32.decode('note1pldep78zxnf5qrk6lhfx6jflzthup47793he7g0ej7z86vad963s42v0rr')
type # => 'note'
note # => '0fdb90f8e234d3400edafdd26d493f12efc0d7de2c6f9f21f997847d33ad2ea3'
```

View File

@ -6,3 +6,4 @@ relay and client software.
- [NIP-01: Basic protocol flow description](https://github.com/nostr-protocol/nips/blob/master/01.md)
- [NIP-02: Contact List and Petnames](https://github.com/nostr-protocol/nips/blob/master/02.md)
- [NIP-04: Encrypted Direct Message](https://github.com/nostr-protocol/nips/blob/master/04.md)
- [NIP-19: Bech32-encoded entities](https://github.com/nostr-protocol/nips/blob/master/19.md)

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative 'nostr/errors'
require_relative 'nostr/bech32'
require_relative 'nostr/crypto'
require_relative 'nostr/version'
require_relative 'nostr/keygen'

203
lib/nostr/bech32.rb Normal file
View File

@ -0,0 +1,203 @@
# frozen_string_literal: true
require 'bech32'
require 'bech32/nostr'
require 'bech32/nostr/entity'
module Nostr
# Bech32 encoding and decoding
#
# @api public
#
module Bech32
# Decodes a bech32-encoded string
#
# @api public
#
# @example
# bech32_value = 'npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg'
# Nostr::Bech32.decode(bech32_value) # => ['npub', '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d8...']
#
# @param [String] bech32_value The bech32-encoded string to decode
#
# @return [Array<String, String>] The human readable part and the data
#
def self.decode(bech32_value)
entity = ::Bech32::Nostr::NIP19.decode(bech32_value)
case entity
in ::Bech32::Nostr::BareEntity
[entity.hrp, entity.data]
in ::Bech32::Nostr::TLVEntity
[entity.hrp, entity.entries]
end
end
# Encodes data into a bech32 string
#
# @api public
#
# @example
# Nostr::Bech32.encode(hrp: 'npub', data: '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e')
# # => 'npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg'
#
# @param [String] hrp The human readable part (npub, nsec, nprofile, nrelay, nevent, naddr, etc)
# @param [String] data The data to encode
#
# @return [String] The bech32-encoded string
#
def self.encode(hrp:, data:)
::Bech32::Nostr::BareEntity.new(hrp, data).encode
end
# Encodes a hex-encoded public key into a bech32 string
#
# @api public
#
# @example
# Nostr::Bech32.npub_encode('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e')
# # => 'npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg'
#
# @param [String] npub The public key to encode
#
# @see Nostr::Bech32#encode
# @see Nostr::PublicKey#to_bech32
# @see Nostr::PrivateKey#to_bech32
#
# @return [String] The bech32-encoded string
#
def self.npub_encode(npub)
encode(hrp: 'npub', data: npub)
end
# Encodes a hex-encoded private key into a bech32 string
#
# @api public
#
# @example
# Nostr::Bech32.nsec_encode('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e')
# # => 'nsec10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg'
#
# @param [String] nsec The private key to encode
#
# @see Nostr::Bech32#encode
# @see Nostr::PrivateKey#to_bech32
# @see Nostr::PublicKey#to_bech32
#
# @return [String] The bech32-encoded string
#
def self.nsec_encode(nsec)
encode(hrp: 'nsec', data: nsec)
end
# Encodes an address into a bech32 string
#
# @api public
#
# @example
# naddr = Nostr::Bech32.naddr_encode(
# pubkey: '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e',
# relays: ['wss://relay.damus.io', 'wss://nos.lol'],
# kind: Nostr::EventKind::TEXT_NOTE,
# identifier: 'damus'
# )
# naddr # => 'naddr1qgs8ul5ug253hlh3n75jne0a5xmjur4urfxpzst88cnegg6ds6ka7ns...'
#
# @param [PublicKey] pubkey The public key to encode
# @param [Array<String>] relays The relays to encode
# @param [String] kind The kind of address to encode
# @param [String] identifier The identifier of the address to encode
#
# @return [String] The bech32-encoded string
#
def self.naddr_encode(pubkey:, relays: [], kind: nil, identifier: nil)
entry_relays = relays.map do |relay_url|
::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_RELAY, relay_url)
end
pubkey_entry = ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_AUTHOR, pubkey)
kind_entry = ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_KIND, kind)
identifier_entry = ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_SPECIAL, identifier)
entries = [pubkey_entry, *entry_relays, kind_entry, identifier_entry].compact
entity = ::Bech32::Nostr::TLVEntity.new(::Bech32::Nostr::NIP19::HRP_EVENT_COORDINATE, entries)
entity.encode
end
# Encodes an event into a bech32 string
#
# @api public
#
# @example
# nevent = Nostr::Bech32.nevent_encode(
# id: '0fdb90f8e234d3400edafdd26d493f12efc0d7de2c6f9f21f997847d33ad2ea3',
# relays: ['wss://relay.damus.io', 'wss://nos.lol'],
# kind: Nostr::EventKind::TEXT_NOTE,
# )
# nevent # => 'nevent1qgsqlkuslr3rf56qpmd0m5ndfyl39m7q6l0zcmuly8ue0pra...'
#
# @param [PublicKey] id The id the event to encode
# @param [Array<String>] relays The relays to encode
# @param [String] kind The kind of event to encode
#
# @return [String] The bech32-encoded string
#
def self.nevent_encode(id:, relays: [], kind: nil)
entry_relays = relays.map do |relay_url|
::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_RELAY, relay_url)
end
id_entry = ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_AUTHOR, id)
kind_entry = ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_KIND, kind)
entries = [id_entry, *entry_relays, kind_entry].compact
entity = ::Bech32::Nostr::TLVEntity.new(::Bech32::Nostr::NIP19::HRP_EVENT, entries)
entity.encode
end
# Encodes a profile into a bech32 string
#
# @api public
#
# @example
# nprofile = Nostr::Bech32.nprofile_encode(
# pubkey: '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e',
# relays: ['wss://relay.damus.io', 'wss://nos.lol']
# )
#
# @param [PublicKey] pubkey The public key to encode
# @param [Array<String>] relays The relays to encode
#
# @return [String] The bech32-encoded string
#
def self.nprofile_encode(pubkey:, relays: [])
entry_relays = relays.map do |relay_url|
::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_RELAY, relay_url)
end
pubkey_entry = ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_SPECIAL, pubkey)
entries = [pubkey_entry, *entry_relays].compact
entity = ::Bech32::Nostr::TLVEntity.new(::Bech32::Nostr::NIP19::HRP_PROFILE, entries)
entity.encode
end
# Encodes a relay URL into a bech32 string
#
# @api public
#
# @example
# nrelay = Nostr::Bech32.nrelay_encode('wss://relay.damus.io')
# nrelay # => 'nrelay1qq28wumn8ghj7un9d3shjtnyv9kh2uewd9hsc5zt2x'
#
# @param [String] relay_url The relay url to encode
#
# @return [String] The bech32-encoded string
#
def self.nrelay_encode(relay_url)
relay_entry = ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_SPECIAL, relay_url)
entity = ::Bech32::Nostr::TLVEntity.new(::Bech32::Nostr::NIP19::HRP_RELAY, [relay_entry])
entity.encode
end
end
end

View File

@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'bech32'
module Nostr
# Abstract class for all keys
#
@ -50,11 +48,11 @@ module Nostr
# @return [Key] the key.
#
def self.from_bech32(bech32_value)
entity = Bech32::Nostr::NIP19.decode(bech32_value)
type, data = Bech32.decode(bech32_value)
raise InvalidHRPError.new(entity.hrp, hrp) unless entity.hrp == hrp
raise InvalidHRPError.new(type, hrp) unless type == hrp
new(entity.data)
new(data)
end
# Abstract method to be implemented by subclasses to provide the HRP (npub, nsec)
@ -81,7 +79,7 @@ module Nostr
#
# @return [String] The bech32 string representation of the key
#
def to_bech32 = Bech32::Nostr::BareEntity.new(self.class.hrp, self).encode
def to_bech32 = Bech32.encode(hrp: self.class.hrp, data: self)
protected

14
sig/nostr/bech32.rbs Normal file
View File

@ -0,0 +1,14 @@
module Nostr
module Bech32
# Perhaps a bug in RBS/Steep. +decode+ and +encode+ are not recognized as public class methods.
def self?.decode: (String data) -> [String, String]
def self?.encode: (hrp: String, data: String) -> String
def naddr_encode: (pubkey: PublicKey, ?relays: Array[String], ?kind: Integer, ?identifier: String) -> String
def nevent_encode: (id: PublicKey, ?relays: Array[String], ?kind: Integer) -> String
def nprofile_encode: (pubkey: PublicKey, ?relays: Array[String]) -> String
def npub_encode: (String npub) -> String
def nrelay_encode: (String nrelay) -> String
def nsec_encode: (String nsec) -> String
end
end

139
spec/nostr/bech32_spec.rb Normal file
View File

@ -0,0 +1,139 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Nostr::Bech32 do
let(:keypair) { Nostr::Keygen.new.generate_key_pair }
let(:private_key) { keypair.private_key }
let(:public_key) { keypair.public_key }
describe '.encode' do
it 'encodes data into the bech32 format' do
npub = described_class.encode(hrp: 'npub', data: public_key)
expect(npub).to match(/npub1\w+/)
end
end
describe '.decode' do
it 'decodes data from the bech32 format' do
npub = described_class.encode(hrp: 'npub', data: public_key)
type, decoded = described_class.decode(npub)
aggregate_failures do
expect(type).to eq('npub')
expect(decoded).to eq(public_key)
end
end
end
describe '.nsec_encode' do
it 'encodes and decodes hexadecimal private keys' do
nsec = described_class.nsec_encode(private_key)
type, data = described_class.decode(nsec)
aggregate_failures do
expect(nsec).to match(/nsec1\w+/)
expect(type).to eq('nsec')
expect(data).to eq(private_key)
end
end
end
describe '.npub_encode' do
it 'encodes and decodes hexadecimal public keys' do
npub = described_class.npub_encode(public_key)
type, data = described_class.decode(npub)
aggregate_failures do
expect(npub).to match(/npub1\w+/)
expect(type).to eq('npub')
expect(data).to eq(public_key)
end
end
end
describe '.nprofile_encode' do
it 'encodes and decodes nprofiles with relays' do
relay_urls = %w[wss://relay.damus.io wss://nos.lol]
nprofile = described_class.nprofile_encode(pubkey: public_key, relays: relay_urls)
type, profile = described_class.decode(nprofile)
aggregate_failures do
expect(nprofile).to match(/nprofile1\w+/)
expect(type).to eq('nprofile')
expect(profile.entries[0].value).to eq(public_key)
expect(profile.entries[1].value).to eq(relay_urls[0])
expect(profile.entries[2].value).to eq(relay_urls[1])
end
end
it 'encodes and decodes nprofiles without relays' do
nprofile = described_class.nprofile_encode(pubkey: public_key)
type, profile = described_class.decode(nprofile)
aggregate_failures do
expect(nprofile).to match(/nprofile1\w+/)
expect(type).to eq('nprofile')
expect(profile.entries[0].value).to eq(public_key)
end
end
end
describe '.naddr_encode' do
it 'encodes and decodes naddr' do
relay_urls = %w[wss://relay.damus.io wss://nos.lol]
naddr = described_class.naddr_encode(
pubkey: public_key,
relays: relay_urls,
kind: 1984,
identifier: 'damus'
)
type, addr = described_class.decode(naddr)
aggregate_failures do
expect(naddr).to match(/naddr1\w+/)
expect(type).to eq('naddr')
expect(addr.entries[0].value).to eq(public_key)
expect(addr.entries[1].value).to eq(relay_urls[0])
expect(addr.entries[2].value).to eq(relay_urls[1])
expect(addr.entries[3].value).to eq(1984)
expect(addr.entries[4].value).to eq('damus')
end
end
end
describe '.nevent_encode' do
it 'encodes and decodes nevent' do
relay_urls = %w[wss://relay.damus.io wss://nos.lol]
nevent = described_class.nevent_encode(
id: '0fdb90f8e234d3400edafdd26d493f12efc0d7de2c6f9f21f997847d33ad2ea3',
relays: relay_urls,
kind: Nostr::EventKind::TEXT_NOTE
)
type, event = described_class.decode(nevent)
aggregate_failures do
expect(nevent).to match(/nevent1\w+/)
expect(type).to eq('nevent')
expect(event.entries[0].value).to eq('0fdb90f8e234d3400edafdd26d493f12efc0d7de2c6f9f21f997847d33ad2ea3')
expect(event.entries[1].value).to eq(relay_urls[0])
expect(event.entries[2].value).to eq(relay_urls[1])
expect(event.entries[3].value).to eq(Nostr::EventKind::TEXT_NOTE)
end
end
end
describe '.nrelay_encode' do
it 'encodes and decodes nrelay' do
relay_url = 'wss://relay.damus.io'
nrelay = described_class.nrelay_encode(relay_url)
type, data = described_class.decode(nrelay)
aggregate_failures do
expect(nrelay).to match(/nrelay1\w+/)
expect(type).to eq('nrelay')
expect(data.entries[0].value).to eq(relay_url)
end
end
end
end