Skip to content

Bech32 embeds#1078

Open
vitorpamplona wants to merge 4 commits into
nostr-protocol:masterfrom
vitorpamplona:embed-events
Open

Bech32 embeds#1078
vitorpamplona wants to merge 4 commits into
nostr-protocol:masterfrom
vitorpamplona:embed-events

Conversation

@vitorpamplona

@vitorpamplona vitorpamplona commented Feb 23, 2024

Copy link
Copy Markdown
Collaborator

Creates a nembed bech32 encoding to carry JSON-stringified events as Nostr URIs and facilitate the loading of those events by Clients without having to ping a relay.

This is particularly helpful for exporting existing/pre-signed health information payloads from corporate/private relays and send to users who might not have direct access to them. Health providers are used to including these payloads inside private communications with patients and adding information around them for better context.

A use case would be the following encrypted DM:

Hi Vitor, 

These are your new prescription for eyeglasses. This one is for distance. 
It's a small power but it can help when you are driving. 

nostr:nembed1...

The next one is for reading. This was adjusted to the distance of books. 
If you find yourself struggling to use a desktop computer, we can adjust 
those numbers for that need as well. 

nostr:nembed1...

Please double-check the sign of each number when ordering your glasses online. 

Here's your complete medical record with all the raw data from your visit today: 

nostr:nembed1...

Thanks for dropping by. Your business is very much appreciated. 

PS: I am not attached to the embed name. Suggestions are welcome.

@staab

staab commented Feb 23, 2024

Copy link
Copy Markdown
Member

I like this. It could solve the ninvite problem in #1062, and it seems like it could be applied very broadly to limit the spread of events that shouldn't stand on their own or need to be private. The one question I have is how long are these embeds? If it's 50 lines of text, that breaks the human readable (or at least parseable) requirement of kind 1s. Also, encoding json in a tlv is silly, we should at least use the tlv to reduce the payload size.

@vitorpamplona

Copy link
Copy Markdown
Collaborator Author

So an eyeglasses payload from an electronic medical record looks like this:

{
  "resourceType": "VisionPrescription",
  "status": "active",
  "created": "2014-06-15",
  "patient": {
    "reference": "Patient/example"
  },
  "dateWritten": "2014-06-15",
  "prescriber": {
    "reference": "Practitioner/example"
  },
  "lensSpecification": [
    {
      "eye": "right",
      "sphere": -2,
      "prism": [
        {
          "amount": 0.5,
          "base": "down"
        }
      ],
      "add": 2
    },
    {
      "eye": "left",
      "sphere": -1,
      "cylinder": -0.5,
      "axis": 180,
      "prism": [
        {
          "amount": 0.5,
          "base": "up"
        }
      ],
      "add": 2
    }
  ]
}

It is signed as a kind:82 (Medical Data) like this:

{
  "id": "73a5ace1c95de239ea355a745abfafee39d4dc613081a5c3dc25158c9385620d",
  "pubkey": "bcd4715cc34f98dce7b52fddaf1d826e5ce0263479b7e110a5bd3c3789486ca8",
  "created_at": 1708705561,
  "kind": 82,
  "tags": [],
  "content": "{\"resourceType\":\"VisionPrescription\",\"status\":\"active\",\"created\":\"2014-06-15\",\"patient\":{\"reference\":\"Patient/example\"},\"dateWritten\":\"2014-06-15\",\"prescriber\":{\"reference\":\"Practitioner/example\"},\"lensSpecification\":[{\"eye\":\"right\",\"sphere\":-2,\"prism\":[{\"amount\":0.5,\"base\":\"down\"}],\"add\":2},{\"eye\":\"left\",\"sphere\":-1,\"cylinder\":-0.5,\"axis\":180,\"prism\":[{\"amount\":0.5,\"base\":\"up\"}],\"add\":2}]}",
  "sig": "e87f7c360a1b01fc436b081037eb11b5e0c23714907427c5e124611a3a004d3c13841d965842766dd85a0a3eaa6c14e053764f67848e892f3a4776ce5046b63f"
}

which creates the following bech32:

nembed10v3xjepz8g3rwvmpx4skxef3vvun2er9xgenjetpxv6n2cfhxs6kzcnxv9nx2efn89jrgerrx
ccnxvpcx9sn2cenv33nydf3x5uxxwfn8q6nvv3svs3zcgnsw43xketeygazycnrvs6rwvf4vd3nxdrx8
yuxgcm9xa3r2vnxv3jxze33vsurydn9x43k2vpjxcengdeevgmk2vf3xpsn2cnyxd3nxdec8y6rsdnrv
yuzytpzvdex2ct5v4j97ct5ygarzdes8qmnqdf4xccjcgntd9hxgg368qezcgn5v9nhxg36tdwjcgnrd
ah8getwws3r5gnmts38yetnda6hycm923uhqe2uyga9cgjkd9ekjmmw2pex2umrwf5hqarfdah9cg3vt
s38xarpw36hxhpz8fwzyctrw35hve2uygk9cgnrwfjkzar9v3wzywjuygerqvf595crvtf3x4wzytzuy
fcxzarfv4h8ghpz8fa4cgnjv4nx2un9de3k2hpz8fwzy5rpw35k2mn59ajhsctdwpkx2hpz05k9cgnyv
96x24mjd968getwts3r5hpzxgcrzdpdxqmz6vf4ts3zchpzwpex2umrwf5kyetjts3r576uyfex2en9w
fjkucm9ts3r5hpz2pexzcm5d96xjmmwv4ez7etcv9khqmr9ts386tzuyfkx2mnn2dcx2cmfve5kxct5d
9hkuhpz8fdhkhpzv4uk2hpz8fwzyunfva58ghpz93wzyumsdpjhye2uygaz6v3vts38qunfwdk4cg36t
da4cgnpd4hh2mn5ts3r5vpwx5k9cgnzv9ek2hpz8fwzyer0wah9cgnat5k9cgnpv3j9cg36xf7jc76uy
fjhje2uyga9cgnvv4n8ghpz93wzyumsdpjhye2uygaz6vfvts3xx7tvd9hxgetjts3r5tfs9c6jchpzv
9uxju6uygarzwps93wzyurjd9ek6hpz8fdhkhpzv9kk7atww3wzyw3s9c6jchpzvfshxe2uyga9cgn4w
pwzyl2a93wzyctyv3wzyw3j04wh6g3vyfekjeez8g3x2wphvcmkxvekxpsnzc3sx9nxxdpnxe3rqwp3x
qenwetzxyckydt9xp3nyvehxy6rjvphxserwce4v5cnydpkxyckzvmpxqcrgepnvvcnxwp5x9jrjd348
q6rydekxejxgwp4vycxzvm9v9snvce3x3jnqdfnxumrge3kxuurgwr98qunye3nvy6rwdekvdjn2vp5x
e3rvvmxyf7slvlkjd

Also, encoding json in a tlv is silly, we should at least use the tlv to reduce the payload size.

Agree. But I didn't want to make a whole new serialization scheme for events.

@vitorpamplona

vitorpamplona commented Feb 23, 2024

Copy link
Copy Markdown
Collaborator Author

Funny thing... if we start using compression, we could gzip the JSON-stringified event and the final re-encoded bech32 becomes smaller than the original event.

The full JSON of the signed event is 802 chars
The bech32 encoded of the raw string is 1287 chars

Compression of the signed event JSON string is 474 bytes
The bech32 encoded compression yelds 770 chars

nembed1r79ssqqqqqqqqqqq34fvkmkmxqg0c9u7aktvhp6gly4s9frgp7c4ztrh2u8pz0cyf9hxxp877
7h228x6yzr3upq2kwen8wlyhjvu5enrd3tjgvrc9mkgqztvwj0rxzukjjwu0sj2eqs53qvgxefhzyqcn
0zvpjyg62vet6f0u4z6a2j43ppcyj8yg0tkvfjfykaguxfmurkp4zfy6c6naf9kysqmqxqt00ezndepx
7z9ndtgzzwggllzvsmfqmzwxe4w0j4u6jf5ax7aefxts87due560h8ndxffxykdm0tqcv579upundmt9
adm8kv6aa66ee7ns03rf4r76tl66ktde5uwzaqevurdy4rlexxtak7wkze3p44kt9pecjeafe24mdp5h
d6r9jyfz69c0f052400rkqlzjajn864arr4qfcaa4fmjedqytp60zkr0k50hvpjls4hhyxnlrvqh4dwz
ghdkmy7k306ujl82rmul5ajv8l6ezs24mw7c478uva30rl8ew7gnlgjk428qhr6l879hetw80zepmuhu
ph2h0ayplf864yxlww46rl69re3a669zp07hltxy8dl7d977s0kcaehehq93al656vgkmgsyraxstgkn
w5d20qypm0xd48vs3gq3q68cus4xu4pfxqyp7n4vsp5qscvmpa8d4399lzs43c55zqafe9kpkk5vv46y
emjmduq02gyv4wscm38e4pyxg2h87crymcwxvpzqvqqq9cs8mr

As a comparison, a regular nevent with relays in it is this big:

nevent1qqst8w0xhjf9ehy98tmgcmk7zaj6p4lxjwjf8z08s9trzmjg4yzdywcpzemhxue69uhhv6t5d
aezumn0wd68yvfwvdhk6qgawaehxw309ahx7um5wgkhqatz9emk2mrvdaexgetj9ehx2aqpz3mhxue69
uhhyetvv9ujumn0wd68ytnzvupzp78lz8r60568sd2a8dx3wnj6gume02gxaf92vx4fk67qv5kpagt65
gxsjx

@AsaiToshiya

Copy link
Copy Markdown
Collaborator

PS: I am not attached to the embed name. Suggestions are welcome.

These are just ideas: nraw, njson, nstuff

@vitorpamplona

Copy link
Copy Markdown
Collaborator Author

I just want to call attention to this embed spec since we are starting to seriously use it to transfer health data inside NIP-17 DMs.

@fiatjaf

fiatjaf commented Sep 24, 2024

Copy link
Copy Markdown
Member

Why not just shove the JSON in instead of encoding it to bech32? We can envelope it in some wrapper to make the purpose clear, like

this is something:

rawevent:{"kind":1283,"id":"f7be8b2f792a1a45a7d009ac45087e41b848aca48f88b9fbcc349d71b9d9ef9d","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1727186773,"tags":[],"content":"blergh","sig":"28155f17e3597c313af9fcae5077bb2544ec8c87ad9aea6c60da2df9363097f5f0d9cece2ad8e5c92077002e9fae422218debfb0a48672d67eada33af5cd6757"}

bye.

@vitorpamplona

vitorpamplona commented Sep 24, 2024

Copy link
Copy Markdown
Collaborator Author

It's just harder to parse given that the json can have multiple encodings, spaces and so on. Parsers would need to find/count the { and } brackets to do a good job.

Plus, the nembed is GZIP compressed, which actually saves A LOT of bytes, given how verbose JSONs are.

@vitorpamplona

vitorpamplona commented Sep 24, 2024

Copy link
Copy Markdown
Collaborator Author

Plus 2: It allows us to build a nice URI for the event, which can be used directly inside QRs/NFC tags for instance.

@fiatjaf

fiatjaf commented Sep 24, 2024

Copy link
Copy Markdown
Member

Plus, the nembed is GZIP compressed

But then we make gzip a hard requirement? I don't like it. Also zstd is better.

It's just harder to parse given that the json can have multiple encodings, spaces and so on.

What if we ensured the full event was all in one line and nothing else?

@vitorpamplona

Copy link
Copy Markdown
Collaborator Author

But then we make gzip a hard requirement?

We can separate to another NIP. nembeds should be optional to the folks that need it. It doesn't need to be mandatory.

zstd is better.

We can use zstd. I just went for GZIP to make sure all languages have easy access to a stable lib. And since most relays do offer gzip compression for communications, it seemed to be a natural fit.

What if we ensured the full event was all in one line and nothing else?

It doesn't make much sense. Our parsers already have to deal with large bech32 uris for ln, cashu, nevents, naddr... This to me would just be another line in that parser.

@fiatjaf

fiatjaf commented Sep 24, 2024

Copy link
Copy Markdown
Member

But then we make gzip a hard requirement?

We can separate to another NIP. nembeds should be optional to the folks that need it. It doesn't need to be mandatory.

I meant a hard requirement for implementing this NIP. It feels useful to have events mentioned in the context of texts, but first encoding them into a binary format, then compressing, then converting the resulting bytes into ascii feels like a very unnecessary sequence of steps.

Our parsers already have to deal with large bech32 uris for ln, cashu, nevents, naddr... This to me would just be another line in that parser.

LN and Cashu are not supported everywhere, and they don't really have to be. If you have an extra feature that turns LN invoices into a clickable widget in your client that is a nice extra, but LN invoices can be understood without that, they can be copy-pasted into an LN wallet and LN users know how to do that. Same for Cashu. The nevents and naddrs are more well-supported, but even when they're not they can still be copy-pasted into any other Nostr client.

@fiatjaf

fiatjaf commented Sep 24, 2024

Copy link
Copy Markdown
Member

Having bech32 entities inside events was an error already, but it was bound to happen as people copy-pasted stuff so it was good that we embraced it.

But this is unnecessary. It's better to follow the standard of kind 6 reposts.

@vitorpamplona

vitorpamplona commented Sep 24, 2024

Copy link
Copy Markdown
Collaborator Author

The nevents and naddrs are more well-supported, but even when they're not they can still be copy-pasted into any other Nostr client.

That's the goal here too. If clients don't support embedding, people should be able to copy and paste the URI into the client that does support it. Copy pasting a json will be a mess (because of the spaces and so on...)

Having bech32 entities inside events was an error already

Why? I actually think we got it right with bech32 URIs. The URI format is very helpful for both parsers and people.

The only problem is that it should have been base64 instead. :)

@fiatjaf

fiatjaf commented Sep 24, 2024

Copy link
Copy Markdown
Member

How about we extend nevent with one field for timestamp, one for content, one for signature and any number of tag fields then?

@vitorpamplona

Copy link
Copy Markdown
Collaborator Author

How about we extend nevent with one field for timestamp, one for content, one for signature and any number of tag fields then?

Without compression, it would be 2-3 times bigger. Otherwise, it works as well. Doing Array<Array<String>> inside the TLV could be tricky, though.

I am not sure what reusing the nevent is adding. I think people prefer to not overload things.

@fiatjaf

fiatjaf commented Sep 24, 2024

Copy link
Copy Markdown
Member

Why 3 times bigger? It would be smaller than the event JSON.

I am not sure what reusing the nevent is adding.

It provides an immediate fallback for clients that don't support it, they will try to load the event from a relay.

I think people prefer to not overload things.

We are already overloading bech32 when we could just reuse the JSON format...

Doing Array<Array<String>> inside the TLV could be tricky

Just Array<String>, the external array is handled already by having multiple tag TLV entries. Would be simple to define a binary format, like <number-of-items><length-of-first-item><first-item><length-of-second-item><second-item>.

@vitorpamplona

Copy link
Copy Markdown
Collaborator Author

Why 3 times bigger? It would be smaller than the event JSON.

Tags and content don't change. You are still encoding strings in the TLV. And the strings are the biggest component of the event (especially for health payloads)

It provides an immediate fallback for clients that don't support it, they will try to load the event from a relay.

There won't be a relay to load it from. The event is embedded inside a GiftWrap, it's not available on a relay. The goal is exactly to avoid pinging a relay.

Would be simple to define a binary format, like .

Yep, but why doing all this work if the JSON already does it for you?

@fiatjaf

fiatjaf commented Sep 24, 2024

Copy link
Copy Markdown
Member

Tags and content don't change. You are still encoding strings in the TLV. And the strings are the biggest component of the event

You're set on the need for compressing the event. I don't think that should be a requirement. Yes, compressing reduces the size of the event, doesn't mean we should do it all the time.

It makes no sense to compress just these embedded events and just this time, sounds very ad-hoc.

Also events are already compressed on the WebSocket layer if that is supported.

There won't be a relay to load it from.

In the specific use case you have in mind. But should we standardize this feature just for that?

The event is embedded inside a GiftWrap

Unrelated question, but why not put it in a relay?

Yep, but why doing all this work if the JSON already does it for you?

Exactly, let's just embed the JSON. I'm glad you finally agree.

@mikedilger

Copy link
Copy Markdown
Contributor

It seems to me that what Vitor is defining does not require standardization in a NIP. Because they are his health records inside of GiftWraps never on a relay. And there is no nostr client that cares about these prescriptions except for his.

@vitorpamplona

vitorpamplona commented Sep 24, 2024

Copy link
Copy Markdown
Collaborator Author

You're set on the need for compressing the event.

I started this NIP without compressions. I was convinced by people in this thread to reduce the size of the URI to facilitate view and the copy/paste of the event.

Since I lived through both cases, I now do think there are significant savings in compressing the data before creating a URI.

It is just better. We might not want to do it, but it is undeniably better IMO.

All NIP-19 uris should've been compressed, IMO. But it's too late for that.

In the specific use case you have in mind. But should we standardize this feature just for that?

We can do embedding with everything. I don't think this is a niche feature. Quote posts, for instance, could embed the full event instead of just the IDs to make sure the inner events don't disappear when they inevitably get deleted from relays.

Unrelated question, but why not put it in a relay?

Certain events must stay so private relays don't even know about them. That's why we created gift wraps.

@vitorpamplona

Copy link
Copy Markdown
Collaborator Author

And there is no nostr client that cares about these prescriptions except for his.

Agree for now, but I am sure people will start to care when clients can't display DMs with prescriptions in them. So, I am making an effort to discuss this before that happens.

@trbouma

trbouma commented Feb 14, 2025

Copy link
Copy Markdown

For my project, I have implemented a nembed functions for creating and parsing that accept and return any json object. I like this format better than embedded stringified json and plan to use it in my implementation.

Beow, is what is it looks like for an eye prescription, encoded and unencoded below.

I don't really gain any space advantage (it looks bigger than the json). I don't think that really matters when I am putting into the .content field. The big advantage is that it is easily human copy-and-pastable and Qr-code friendly. This example is a bit too long for a QR code, but at least it doesn't choke - it just truncates.

'nembed10v38yetnda6hycm923uhqefz8gszy4nfwd5k7mjswfjhxcmjd9c8g6t0dc3zcgpzd9jzyw3qyfjhjet8d3shxuedwpex2umrwf5hqarfdahz6vpsxy3zcgpzwd6xzar4wv3r5gpzv93hg6tkv53zcgpzvdex2ct5v4jzyw3qygerqv3495crytf3x32rzvp6xqcr5vpstg3zcgpzwpshg6t9de6zyw3q0v38yetxv4ex2mnrv53r5gpz2pshg6t9de6z7vfjxv6r2g3vyq3xg6tnwpkxz7fz8gszyjn0dphzq3r0v5386tpqyfjkucm0w4h8getjygazq7ezwfjkvetjv4hxxefz8gszy3twvdhh2mn5v4ez7d3h8qunqgna9sszyerpw3j4wunfw36x2m3z8gszyv3sxg6j6vpj95cnq4p3x5arqvp6xqc95g3vyq38qun9wd3hy6tzv4ezyw3q0v38yetxv4ex2mnrv53r5gpz2pexzcm5d96xjmmwv4ez7dfkxuurjg3vyq3xg6tnwpkxz7fz8gszy3rj9csy2mtfd3ujq5mdd96xsgna9sszymr9dee4xur9vd5kv6trv96xjmmwygazqkmmyfc8ymmyw43hgg36ypajycm0v35kueez8gs9k7ezwduhxar9d53r5gpzdp68gup69uhhgetjd45kummvdanhjtngdsmjummjvuh5xmmyv4fhjum5v4kj7etc94mxjumfdah8qun9wd3hy6tsw35k7mnswfhkgatrws3zcgpzvdhkgefz8gszymr9deejytpqyfjxjumsd3shjg36yq3y27t9vakxzumnv4ejyl2a05kzqgn909jjyw3qyfexjemgws3zcgpzwdcxsetjv53r5gpdxghr2tpqyf3hjmrfdejx2u3z8gsz6vfwxqkzqgnp0p5hxg36yqcnsvpvyq38qunfwdkjyw3qtdajyctdda6kuapz8gsrqt349sszycnpwdjjyw3qyf6hqgnat5kzqgnpv3jzyw3qxghrqtpqyfj82unpw35k7m3z8gs8kgnkv9k82efz8gsrydpvyq382mnfws3r5gpzd4hkuargwv3zcgpzwduhxar9d53r5gpzdp68gup69uhh2mnfw3ek7endv4shxatjv5hx7un8ygkzqgnrdajx2g36yq3x6mez05kzqgnwda6x2g36ypdhkgn5v4u8gg36yq39y6t8dp6zqetev5s8qun9wd3hy6tsw35k7m3qvehhygrwv4shyttnd9nksar9v3hx2umnypmkjargypshxarfvakkzarfwdkjugnat47jcgrmyfc8ymmyw43hgg36ypajycm0v35kueez8gs9k7ezwduhxar9d53r5gpzdp68gup69uhhgetjd45kummvdanhjtngdsmjummjvuh5xmmyv4fhjum5v4kj7etc94mxjumfdah8qun9wd3hy6tsw35k7mnswfhkgatrws3zcgpzvdhkgefz8gszymr9deejytpqyfjxjumsd3shjg36yq3y27t9vakxzumnv4ejyl2a05kzqgn909jjyw3qyfkx2en5ygkzqgnnwp5x2un9ygazqtfn9cczcgpzvdukc6twv3jhyg36yqknqt3hx5kzqgnp0p5hxg36yqcnvvpvyq38qunfwdkjyw3qtdajyctdda6kuapz8gsrqt349sszycnpwdjjyw3qyfjx7amwyf746tpqyfskgepz8gsryt3s9sszyer4wfshg6t0dc3r5grmyfmxzmr4v53r5gpjxskzqgn4de5hgg36yq3x6mmww358xg3vyq38x7tnw3jk6g36yq3xsar5wqaz7tm4de5hgum0vekk2ctnw4ex2tn0wfnjytpqyf3k7er9ygazqgnddu386tpqyfhx7ar9ygazqkmmyf6x27r5ygazqgjvv4n8ggr909jjqurjv4ekxunfwp6xjmmwypnx7u3qdejkzu3dwd5kw6r5v4jxuetnwvs8w6t5dqsxzum5d9nk6ct5d9ek6t3z04wh6hta5csml3'

{ "resourceType": "VisionPrescription", "id": "eyeglass-prescription-001", "status": "active", "created": "2025-02-14T10:00:00Z", "patient": { "reference": "Patient/12345", "display": "John Doe" }, "encounter": { "reference": "Encounter/67890" }, "dateWritten": "2025-02-10T15:00:00Z", "prescriber": { "reference": "Practitioner/56789", "display": "Dr. Emily Smith" }, "lensSpecification": [ { "product": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/ex-visionprescriptionproduct", "code": "lens", "display": "Eyeglasses" } ] }, "eye": "right", "sphere": -2.50, "cylinder": -1.00, "axis": 180, "prism": [ { "amount": 0.5, "base": "up" } ], "add": 2.0, "duration": { "value": 24, "unit": "months", "system": "http://unitsofmeasure.org", "code": "mo" }, "note": [ { "text": "Right eye prescription for near-sightedness with astigmatism." } ] }, { "product": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/ex-visionprescriptionproduct", "code": "lens", "display": "Eyeglasses" } ] }, "eye": "left", "sphere": -3.00, "cylinder": -0.75, "axis": 160, "prism": [ { "amount": 0.5, "base": "down" } ], "add": 2.0, "duration": { "value": 24, "unit": "months", "system": "http://unitsofmeasure.org", "code": "mo" }, "note": [ { "text": "Left eye prescription for near-sightedness with astigmatism." } ] } ] }

@vitorpamplona

Copy link
Copy Markdown
Collaborator Author

Keep in mind that if the embed doesn't have the doctor signature, when the user copy and pastes the URI, the signature of the doctor is lost. Meaning that if they receive their prescription on a DM, they cannot forward it to an optician.

That's why we putted the FHIR data inside the KIND 82 first, then compressed it with gzip and only then generated the nembed URI.

In that way, if the user wants, they can copy-paste the URI and send it to anyone else with the doctor's signature in it.

@mikedilger

Copy link
Copy Markdown
Contributor

I didn't count, but it looks like that nembed is longer than the data it allegedly compresses.

@trbouma

trbouma commented Feb 14, 2025

Copy link
Copy Markdown

I didn't count, but it looks like that nembed is longer than the data it allegedly compresses.

Yeah, it's about double the size of what it purportedly compresses. So not great at compression...

@trbouma

trbouma commented Feb 14, 2025

Copy link
Copy Markdown

Keep in mind that if the embed doesn't have the doctor signature, when the user copy and pastes the URI, the signature of the doctor is lost. Meaning that if they receive their prescription on a DM, they cannot forward it to an optician.

That's why we putted the FHIR data inside the KIND 82 first, then compressed it with gzip and only then generated the nembed URI.

In that way, if the user wants, they can copy-paste the URI and send it to anyone else with the doctor's signature in it.

I created a kind 82 event that is signed with the eye prescription embedded. Uncompressed it is 3018 characters in length; compress (using gzip) it is 1234 characters in length. Much better!

@trbouma

trbouma commented Feb 14, 2025

Copy link
Copy Markdown

My Python code for doing the compressed nembeds, using gzip. I pass in the signed event as a json dict

def create_nembed_compressed(json_obj):
    buffer = io.BytesIO()
    encoded_data = []
    if type(json_obj) != dict:
        raise ValueError("not a json object")
    json_obj_str = json.dumps(json_obj)

    with gzip.GzipFile(fileobj=buffer, mode="wb") as gz:
        gz.write(json_obj_str.encode())
    
    json_bytes = buffer.getvalue() 
    encoded_data.extend(json_bytes)  # Public key bytes    
    converted_data = convertbits(encoded_data, 8, 5, True)
    
    return bech32_encode("nembed",converted_data )

def parse_nembed_compressed(encoded_string):
    # Decode the Bech32 string
    hrp, data = bech32_decode(encoded_string)
    # print(f"hrp {hrp} data {data}")
    if hrp not in {"nembed"} or data is None:
        raise ValueError("Invalid Bech32 string or unsupported prefix")

    # Convert 5-bit data to 8-bit for processing
    decoded_data = bytes(convertbits(data, 5, 8, False))
    # this is gzipped data

    buffer = io.BytesIO(decoded_data)
    with gzip.GzipFile(fileobj=buffer, mode="rb") as gz:
        decompressed_data = gz.read()
    
    try:
        json_obj = json.loads(decompressed_data.decode())  
    except:
        json_obj = {}

    return json_obj

@vitorpamplona

vitorpamplona commented Feb 14, 2025

Copy link
Copy Markdown
Collaborator Author

I created a kind 82 event that is signed with the eye prescription embedded. Uncompressed it is 3018 characters in length; compress (using gzip) it is 1234 characters in length. Much better!

Yeah, that sounds about right. JSONs are VERY sparse. They compress pretty well.

Give me a nembed with this new method. I can add as a test case on Amethyst. :)

@trbouma

trbouma commented Feb 14, 2025

Copy link
Copy Markdown

I created a kind 82 event that is signed with the eye prescription embedded. Uncompressed it is 3018 characters in length; compress (using gzip) it is 1234 characters in length. Much better!

Yeah, that sounds about right. JSONs are VERY sparse. They compress pretty well.

Give me a nembed with this new method. I can add as a test case on Amethyst. :)

Here we go. You can see my code in a previous comment

nembed1r79ssq9446hkwqhl642ukmku8qg0c92pu7w3j0jyfte8tc7tvg85vmrys8x3sqgle5vjy7jpjswqhphl0kd6yf4sz0n3peyjq5rp3zkat4w6c6j3f7um0724jmfu5456xxgg2yxkn8dp23j64xsn9npcggzafyh2effyntqrqxzja8dp52kpcvc9zqxlj86e8mx05vevzxkeprjkfs4wmppxm3p96vj6yvu2mqgf5l4v99492r2qsggquxuv93uzx244652h2kkj8xseg9xkq0afpygknjtty9j4ju5v0nm9mezux9wyl6s5wr7lzce7cj397mnu0u04ha7aq3w7exelrhe3zs3l3urwa9sp36u80npllrs0hmsxqdn0fsuyav3nv0azjs5suzuurg2uymncjxez8p9xksc2j6gw992enjflgrdd7n5uq2xrpvfrd3rckw624ey0elvm6grr27tyzlf4vaswgm5vc3hdyczsl983g2j8e67r6z5zt30lat84ma4wclkwwxxrcflvdsuwd7346h7zqav4vdwe3gkt9lr87sfk4aqd2aey03tt4eyspldrqcmkx9pqe2pn63rv7grwwalr86akuldnvjm6m87wrw9sdwns8wq0rnsmj57vqwtc3g7hkwum3vl2dda78dwkycgfzw6qna3ufhpatcvq5a4hm4ehl45an8umwt0clf7rn77ctke475qglwu86hhfwhn7dkca4pkfpyc4y75rll6nvr5qc8nlhf8mk22celn5mecvyuzxd830drhdck9tcdpcafymk8wajwu2w8ha8gatggjfvq0a4jlf2sdamzj0ysqks9dk8me3q7a0qpmf6vykurkrcls4pug3u4pn4u26ezx3h8e482n07x2nsmu80dpufxqc0ttcyzhnppguxma4d8aumdawnlsyy7yzcuxl7lw5y9p4nv5h8fn6u8anpm2tsze3p6mgxy9j9uuqfxg2jvlmtjpakna5m4hln0msmw804hnun96h66fh62270yhhljnmmdl7jln07ll5vft7e870hemcld34a09n943ed6629fgtctsftma9q6tf4jfm2p0ukd2j2n2dpz53fqrkk4ctdcy2j5jar095g5jntf6u807ggkzauzt6uqkwk4tg5w7w55kskspc9663zx5dzzzfwpg3q546g2ve4kukr70n0a46eyce2crsqqq247ql5

@vitorpamplona

Copy link
Copy Markdown
Collaborator Author

Working great here! :)

@trbouma

trbouma commented Feb 15, 2025

Copy link
Copy Markdown

Working great here! :)

Super. I am going to go with the compressed approach for nembeds. We should propose this for the Bech32 encoded NIP?

@trbouma

trbouma commented Feb 15, 2025

Copy link
Copy Markdown

Here is an example of an immunization record in a signed kind 82 event. 1500 characters in length. Very manageable.

nembed1r79ssqz7szcxwqhl44256m7m8qg062uyf6acq0eg3x32fh4uxytwhsjdh36azjcuzs2rnv5ff23yq5twh3qll7cw95jkj5ehtzs8kyk9rxl8jlrurslqgks9dayfqvq9knj2cyyucevuj3yc6fxy39s5yt8g48qkr8xjqt3glmryq6vy5v7ww5fwgny8934nvppg9d308mpung639ej9dfy39j9gsswpzysykw2txtsyefyvxy0vv6ww6vcwgef2zgynrcmfngcg4xy2ulx2xjgtc2ql5fquy2rxnrx49qhvfjr8m4uvukvyvvnlv8vwdahc8430ltjuyamhh66xj8p5heh0pvqt9362ddcj86w94k8pmsh934t4ml58lpxn5mvkrkga3guyfcz763ks25lagdrw0p8thvs5kx5lsjd3ksj8yxjkacx24qelaq602zadn7ult07wkn5asda9s7ns4e7n88cl4f6xdmr7tc0tf8thttx7tut7qzfj8j3w3p5r5npfmk6e2wqdkdtnw86hed5esn6glhl85vfpa04u6apmyxdn7fey5j2dgu3l57ecf4rt5fxgwjzlau7gfqmm7l7h4un3vpxhu7u8z4kjxlk8tj0a4c4x2x78zfckf2cdpjy36dnq3nj8n5h0q7mtpcx4jxsc6xxff0faqvkdzrglwn7mfwvgf45ngr0fsrg45hgt00dfun725uudhqc023rgafms388h4r97gt5y4wcnly95fegwuj4d9rvkyw645dra9xjwktwce05rwa30rewlqj5929q09pekpmjxrp903ja7a6mzcr0q2cg5kdjrrtyxldtdkva2zlj5nyryrfrdxwqulknd6lmkjvp4h67x23n8lx3a8t9mkutmf6w3lclfg2aw6x0s0w9669chk6ecd553skkm6a7w8wc6dlelhx70wuphu4pxr8hwnn77dy3axddmmw7qdn2kqakaw7e2lpra5tnhmlnh9udgwpeakg7g423lq0s4tupf0zt5077hadgau8uelwjva9p43q8zmcvfhujw29c0wuzxc6tv8kw269azasdlnr2t0av626y2h5e8uk0lvee5r9qm30hvx6nyv0x4z2mczcrt6mt7utkgm3dq76xvr0hzajjemydhq2dufdkkecdhh6px35vlecph7lw9n3k7gzw2ns866e8ud3jgheq9tyf03pqp5saskkpdyxp8ggwuzajq7zsn3z7he50dk4tt35eay4ytk6knf2x8ttl2fpemk5chs0l5z9ty8gc2earc5vjqklzfemj98ppyrl8t6t5m6lcnrqyzsw2yg5tzwz5yef9pnuayv3fq4js2ce3u4u3ccsrsk8c4y6fr3zwnxg4v9p8pyagc9cj5c95255skegjfdwf252cvsfvys2nhjgknyn3kz4rxhsky5hs5jrnxn2zycmegsu54fg2swhllqkqnmzw9kcrsqqqs2yral

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants