From 09950fd5475910a6da0326756876ad7dbbdc6a93 Mon Sep 17 00:00:00 2001 From: ennmichael Date: Mon, 20 Mar 2023 21:23:03 +0100 Subject: [PATCH 1/3] nip-05 --- packages/nostr/README.md | 6 +- packages/nostr/docker-compose.yaml | 7 +- .../nostr/{ => docker}/relay/.dockerignore | 0 packages/nostr/{ => docker}/relay/.gitignore | 0 packages/nostr/{ => docker}/relay/Dockerfile | 2 +- packages/nostr/{ => docker}/relay/config.toml | 0 packages/nostr/{ => docker}/relay/index.ts | 0 .../nostr/{ => docker}/relay/package.json | 0 packages/nostr/docker/well-known/Dockerfile | 4 + packages/nostr/docker/well-known/nostr.json | 10 ++ packages/nostr/src/event/contact-list.ts | 34 ++++--- packages/nostr/src/event/direct-message.ts | 17 +--- packages/nostr/src/event/index.ts | 28 +++--- packages/nostr/src/event/set-metadata.ts | 93 +++++++++++++++++-- packages/nostr/src/event/text.ts | 21 +++-- packages/nostr/test/contact-list.ts | 6 +- packages/nostr/test/dm.ts | 10 +- packages/nostr/test/internet-identifier.ts | 75 +++++++++++++++ packages/nostr/test/set-metadata.ts | 54 +++++++++++ packages/nostr/test/text-note.ts | 39 +++----- 20 files changed, 312 insertions(+), 94 deletions(-) rename packages/nostr/{ => docker}/relay/.dockerignore (100%) rename packages/nostr/{ => docker}/relay/.gitignore (100%) rename packages/nostr/{ => docker}/relay/Dockerfile (50%) rename packages/nostr/{ => docker}/relay/config.toml (100%) rename packages/nostr/{ => docker}/relay/index.ts (100%) rename packages/nostr/{ => docker}/relay/package.json (100%) create mode 100644 packages/nostr/docker/well-known/Dockerfile create mode 100644 packages/nostr/docker/well-known/nostr.json create mode 100644 packages/nostr/test/internet-identifier.ts create mode 100644 packages/nostr/test/set-metadata.ts diff --git a/packages/nostr/README.md b/packages/nostr/README.md index b088f31f..62684ccc 100644 --- a/packages/nostr/README.md +++ b/packages/nostr/README.md @@ -9,16 +9,15 @@ A strongly-typed nostr client for Node and the browser. The goal of the project is to have all of the following implemented and tested against a real-world relay implementation. -_Progress: 7/34 (20%)._ +_Progress: 8/34 (23%)._ - [X] NIP-01: Basic protocol flow description - [X] NIP-02: Contact List and Petnames - [ ] NIP-03: OpenTimestamps Attestations for Events - [X] NIP-04: Encrypted Direct Message -- [ ] NIP-05: Mapping Nostr keys to DNS-based internet identifiers +- [X] NIP-05: Mapping Nostr keys to DNS-based internet identifiers - [ ] NIP-06: Basic key derivation from mnemonic seed phrase - [ ] NIP-07: window.nostr capability for web browsers -- [ ] NIP-08: Handling Mentions - [ ] NIP-09: Event Deletion - [ ] NIP-10: Conventions for clients' use of `e` and `p` tags in text events - TODO Check if this applies @@ -36,6 +35,7 @@ _Progress: 7/34 (20%)._ - [ ] NIP-23: Long-form Content - [ ] NIP-25: Reactions - [ ] NIP-26: Delegated Event Signing +- [ ] NIP-27: Text Note References - [ ] NIP-28: Public Chat - [ ] NIP-36: Sensitive Content - [ ] NIP-39: External Identities in Profiles diff --git a/packages/nostr/docker-compose.yaml b/packages/nostr/docker-compose.yaml index 49470d0f..cc573f8e 100644 --- a/packages/nostr/docker-compose.yaml +++ b/packages/nostr/docker-compose.yaml @@ -1,7 +1,12 @@ version: "3.1" services: + well-known: + build: ./docker/well-known + restart: on-failure + ports: + - 12647:80 relay: - build: ./relay + build: ./docker/relay restart: on-failure ports: - 12648:8080 diff --git a/packages/nostr/relay/.dockerignore b/packages/nostr/docker/relay/.dockerignore similarity index 100% rename from packages/nostr/relay/.dockerignore rename to packages/nostr/docker/relay/.dockerignore diff --git a/packages/nostr/relay/.gitignore b/packages/nostr/docker/relay/.gitignore similarity index 100% rename from packages/nostr/relay/.gitignore rename to packages/nostr/docker/relay/.gitignore diff --git a/packages/nostr/relay/Dockerfile b/packages/nostr/docker/relay/Dockerfile similarity index 50% rename from packages/nostr/relay/Dockerfile rename to packages/nostr/docker/relay/Dockerfile index 5552ffde..9e6bcb3a 100644 --- a/packages/nostr/relay/Dockerfile +++ b/packages/nostr/docker/relay/Dockerfile @@ -9,4 +9,4 @@ EXPOSE 8000 COPY . . USER $APP_USER RUN yarn -CMD yarn app /bin/bash -c "rm -rf /usr/src/app/db/* && ./nostr-rs-relay --db /usr/src/app/db --config ./config.toml" +CMD ["/bin/bash", "-c", "while :; do yarn app /bin/bash -c 'rm -rf /usr/src/app/db/* && ./nostr-rs-relay --db /usr/src/app/db --config ./config.toml'; done;"] diff --git a/packages/nostr/relay/config.toml b/packages/nostr/docker/relay/config.toml similarity index 100% rename from packages/nostr/relay/config.toml rename to packages/nostr/docker/relay/config.toml diff --git a/packages/nostr/relay/index.ts b/packages/nostr/docker/relay/index.ts similarity index 100% rename from packages/nostr/relay/index.ts rename to packages/nostr/docker/relay/index.ts diff --git a/packages/nostr/relay/package.json b/packages/nostr/docker/relay/package.json similarity index 100% rename from packages/nostr/relay/package.json rename to packages/nostr/docker/relay/package.json diff --git a/packages/nostr/docker/well-known/Dockerfile b/packages/nostr/docker/well-known/Dockerfile new file mode 100644 index 00000000..bbbb8afe --- /dev/null +++ b/packages/nostr/docker/well-known/Dockerfile @@ -0,0 +1,4 @@ +# An nginx server to return a .well-known/nostr.json file for testing. +FROM nginx +RUN mkdir /usr/share/nginx/html/.well-known +COPY nostr.json /usr/share/nginx/html/.well-known/nostr.json diff --git a/packages/nostr/docker/well-known/nostr.json b/packages/nostr/docker/well-known/nostr.json new file mode 100644 index 00000000..8d268ecf --- /dev/null +++ b/packages/nostr/docker/well-known/nostr.json @@ -0,0 +1,10 @@ +{ + "names": { + "bob": "be4be3c0c4f2d18f11215087d7d18fad2c9e0269fc50e19d526bdfb11a522a64" + }, + "relays": { + "be4be3c0c4f2d18f11215087d7d18fad2c9e0269fc50e19d526bdfb11a522a64": [ + "ws://example.com" + ] + } +} diff --git a/packages/nostr/src/event/contact-list.ts b/packages/nostr/src/event/contact-list.ts index e3740c74..f3b31998 100644 --- a/packages/nostr/src/event/contact-list.ts +++ b/packages/nostr/src/event/contact-list.ts @@ -1,6 +1,6 @@ -import { EventKind, RawEvent, Unsigned } from "." +import { EventKind, RawEvent, signEvent } from "." import { NostrError } from "../common" -import { PublicKey } from "../crypto" +import { HexOrBechPrivateKey, PublicKey } from "../crypto" /** * Contact list event. @@ -28,18 +28,24 @@ export interface Contact { /** * Create a contact list event. */ -export function createContactList(contacts: Contact[]): Unsigned { - return { - kind: EventKind.ContactList, - tags: contacts.map((contact) => [ - "p", - contact.pubkey, - contact.relay?.toString() ?? "", - contact.petname ?? "", - ]), - content: "", - getContacts, - } +export function createContactList( + contacts: Contact[], + priv?: HexOrBechPrivateKey +): Promise { + return signEvent( + { + kind: EventKind.ContactList, + tags: contacts.map((contact) => [ + "p", + contact.pubkey, + contact.relay?.toString() ?? "", + contact.petname ?? "", + ]), + content: "", + getContacts, + }, + priv + ) } export function getContacts(this: ContactList): Contact[] { diff --git a/packages/nostr/src/event/direct-message.ts b/packages/nostr/src/event/direct-message.ts index ade4b0c8..c90111d5 100644 --- a/packages/nostr/src/event/direct-message.ts +++ b/packages/nostr/src/event/direct-message.ts @@ -1,12 +1,5 @@ -import { - EventId, - EventKind, - RawEvent, - signEvent, - Unsigned, - UnsignedWithPubkey, -} from "." -import { defined, NostrError } from "../common" +import { EventId, EventKind, RawEvent, signEvent } from "." +import { NostrError } from "../common" import { aesDecryptBase64, aesEncryptBase64, @@ -77,7 +70,7 @@ export async function createDirectMessage( } export async function getMessage( - this: UnsignedWithPubkey, + this: DirectMessage, priv?: HexOrBechPrivateKey ): Promise { if (priv !== undefined) { @@ -96,7 +89,7 @@ export async function getMessage( return undefined } -export function getRecipient(this: Unsigned): PublicKey { +export function getRecipient(this: DirectMessage): PublicKey { const recipientTag = this.tags.find((tag) => tag[0] === "p") if (typeof recipientTag?.[1] !== "string") { throw new NostrError( @@ -108,7 +101,7 @@ export function getRecipient(this: Unsigned): PublicKey { return recipientTag[1] } -export function getPrevious(this: Unsigned): EventId | undefined { +export function getPrevious(this: DirectMessage): EventId | undefined { const previousTag = this.tags.find((tag) => tag[0] === "e") if (previousTag === undefined) { return undefined diff --git a/packages/nostr/src/event/index.ts b/packages/nostr/src/event/index.ts index 91bc184f..c5e56395 100644 --- a/packages/nostr/src/event/index.ts +++ b/packages/nostr/src/event/index.ts @@ -9,7 +9,11 @@ import { } from "../crypto" import { Timestamp, unixTimestamp, NostrError } from "../common" import { TextNote } from "./text" -import { getUserMetadata, SetMetadata } from "./set-metadata" +import { + getUserMetadata, + SetMetadata, + verifyInternetIdentifier, +} from "./set-metadata" import { DirectMessage, getMessage, @@ -94,11 +98,10 @@ export type Unsigned = { pubkey?: PublicKey } -// TODO This doesn't need to be exposed by the lib /** * Same as @see {@link Unsigned}, but with the pubkey field. */ -export type UnsignedWithPubkey = { +type UnsignedWithPubkey = { [Property in keyof T as Exclude< Property, "id" | "sig" | "created_at" @@ -113,7 +116,7 @@ export type UnsignedWithPubkey = { * Add the "id," "sig," and "pubkey" fields to the event. Set "created_at" to the current timestamp * if missing. Return the event. */ -export async function signEvent( +export async function signEvent( event: Unsigned, priv?: HexOrBechPrivateKey ): Promise { @@ -164,6 +167,7 @@ export async function parseEvent(event: RawEvent): Promise { ...event, kind: EventKind.SetMetadata, getUserMetadata, + verifyInternetIdentifier, } } @@ -194,14 +198,14 @@ export async function parseEvent(event: RawEvent): Promise { async function serializeEventId( event: UnsignedWithPubkey ): Promise { - // It's not defined whether JSON.stringify produces a string with whitespace stripped. - // Building the JSON string manually as follows ensures that there's no whitespace. - // In hindsight using JSON as a data format for hashing and signing is not the best - // design decision. - const serializedTags = `[${event.tags - .map((tag) => `[${tag.map((v) => `"${v}"`).join(",")}]`) - .join(",")}]` - const serialized = `[0,"${event.pubkey}",${event.created_at},${event.kind},${serializedTags},"${event.content}"]` + const serialized = JSON.stringify([ + 0, + event.pubkey, + event.created_at, + event.kind, + event.tags, + event.content, + ]) return await sha256(Uint8Array.from(charCodes(serialized))) } diff --git a/packages/nostr/src/event/set-metadata.ts b/packages/nostr/src/event/set-metadata.ts index 0c20c05f..25fb257d 100644 --- a/packages/nostr/src/event/set-metadata.ts +++ b/packages/nostr/src/event/set-metadata.ts @@ -1,5 +1,6 @@ -import { EventKind, RawEvent, Unsigned } from "." +import { EventKind, RawEvent, signEvent } from "." import { NostrError, parseJson } from "../common" +import { HexOrBechPrivateKey } from "../crypto" /** * Set metadata event. Used for disseminating use profile information. @@ -13,29 +14,45 @@ export interface SetMetadata extends RawEvent { * Get the user metadata specified in this event. */ getUserMetadata(): UserMetadata + /** + * Verify the NIP-05 DNS-based internet identifier associated with the user metadata. + * Throws if the internet identifier is invalid or fails verification. + * @param pubkey The public key to use if the event does not specify a pubkey. If the event + * does specify a pubkey + * @return The internet identifier. `undefined` if there is no internet identifier. + */ + verifyInternetIdentifier( + opts?: VerificationOptions + ): Promise } export interface UserMetadata { name: string about: string picture: string + nip05?: string } /** * Create a set metadata event. */ export function createSetMetadata( - content: UserMetadata -): Unsigned { - return { - kind: EventKind.SetMetadata, - tags: [], - content: JSON.stringify(content), - getUserMetadata, - } + content: UserMetadata, + priv?: HexOrBechPrivateKey +): Promise { + return signEvent( + { + kind: EventKind.SetMetadata, + tags: [], + content: JSON.stringify(content), + getUserMetadata, + verifyInternetIdentifier, + }, + priv + ) } -export function getUserMetadata(this: Unsigned): UserMetadata { +export function getUserMetadata(this: SetMetadata): UserMetadata { const userMetadata = parseJson(this.content) if ( typeof userMetadata.name !== "string" || @@ -48,3 +65,59 @@ export function getUserMetadata(this: Unsigned): UserMetadata { } return userMetadata } + +export async function verifyInternetIdentifier( + this: SetMetadata, + opts?: VerificationOptions +): Promise { + const metadata = this.getUserMetadata() + if (metadata.nip05 === undefined) { + return undefined + } + const [name, domain] = metadata.nip05.split("@") + if ( + name === undefined || + domain === undefined || + !/^[a-zA-Z0-9-_]+$/.test(name) + ) { + throw new NostrError( + `invalid NIP-05 internet identifier: ${metadata.nip05}` + ) + } + const res = await fetch( + `${ + opts?.https === false ? "http" : "https" + }://${domain}/.well-known/nostr.json?name=${name}`, + { redirect: "error" } + ) + const wellKnown = await res.json() + const pubkey = wellKnown.names?.[name] + if (pubkey !== this.pubkey) { + throw new NostrError( + `invalid NIP-05 internet identifier: ${ + metadata.nip05 + } pubkey does not match, ${JSON.stringify(wellKnown)}` + ) + } + const relays = wellKnown.relays?.[pubkey] + if ( + relays !== undefined && + (!(relays instanceof Array) || + relays.some((relay) => typeof relay !== "string")) + ) { + throw new NostrError(`invalid NIP-05 relays: ${wellKnown}`) + } + return { + name, + relays, + } +} + +export interface InternetIdentifier { + name: string + relays?: string[] +} + +export interface VerificationOptions { + https?: boolean +} diff --git a/packages/nostr/src/event/text.ts b/packages/nostr/src/event/text.ts index 0fb845a2..bbe2175a 100644 --- a/packages/nostr/src/event/text.ts +++ b/packages/nostr/src/event/text.ts @@ -1,4 +1,5 @@ -import { EventKind, RawEvent, Unsigned } from "." +import { EventKind, RawEvent, signEvent } from "." +import { HexOrBechPrivateKey } from "../crypto" /** * A text note event. Used for transmitting user posts. @@ -9,10 +10,16 @@ export interface TextNote extends RawEvent { kind: EventKind.TextNote } -export function createTextNote(content: string): Unsigned { - return { - kind: EventKind.TextNote, - tags: [], - content, - } +export function createTextNote( + content: string, + priv?: HexOrBechPrivateKey +): Promise { + return signEvent( + { + kind: EventKind.TextNote, + tags: [], + content, + }, + priv + ) } diff --git a/packages/nostr/test/contact-list.ts b/packages/nostr/test/contact-list.ts index a6dbedc4..77c5448e 100644 --- a/packages/nostr/test/contact-list.ts +++ b/packages/nostr/test/contact-list.ts @@ -1,5 +1,5 @@ import assert from "assert" -import { EventKind, signEvent } from "../src/event" +import { EventKind } from "../src/event" import { createContactList } from "../src/event/contact-list" import { setup } from "./setup" @@ -47,9 +47,7 @@ describe("contact-list", () => { // After the subscription event sync is done, publish the test event. subscriber.on("eose", async () => { // TODO No signEvent, have a convenient way to do this - publisher.publish( - await signEvent(createContactList(contacts), subscriberSecret) - ) + publisher.publish(await createContactList(contacts, subscriberSecret)) }) }) }) diff --git a/packages/nostr/test/dm.ts b/packages/nostr/test/dm.ts index ec09324d..56170071 100644 --- a/packages/nostr/test/dm.ts +++ b/packages/nostr/test/dm.ts @@ -1,4 +1,4 @@ -import { EventKind, signEvent } from "../src/event" +import { EventKind } from "../src/event" import { parsePublicKey } from "../src/crypto" import assert from "assert" import { setup } from "./setup" @@ -49,13 +49,11 @@ describe("dm", () => { const subscriptionId = subscriber.subscribe([]) subscriber.on("eose", async () => { - // TODO No signEvent, do something more convenient - const event = await signEvent( - await createDirectMessage({ + const event = await createDirectMessage( + { message, recipient: subscriberPubkey, - priv: publisherSecret, - }), + }, publisherSecret ) publisher.publish(event) diff --git a/packages/nostr/test/internet-identifier.ts b/packages/nostr/test/internet-identifier.ts new file mode 100644 index 00000000..03fda8fe --- /dev/null +++ b/packages/nostr/test/internet-identifier.ts @@ -0,0 +1,75 @@ +import assert from "assert" +import { defined } from "../src/common" +import { EventKind } from "../src/event" +import { createSetMetadata } from "../src/event/set-metadata" +import { setup } from "./setup" + +describe("internet-identifier", () => { + it("present", (done) => { + setup(done, ({ publisher, publisherSecret, subscriber, done }) => { + subscriber.on("event", async ({ event }) => { + // Assert that the internet identifier can be verified. + assert.strictEqual(event.kind, EventKind.SetMetadata) + if (event.kind === EventKind.SetMetadata) { + const identifier = await event.verifyInternetIdentifier({ + https: false, + }) + assert.ok(identifier) + const { name, relays } = defined(identifier) + assert.strictEqual(name, "bob") + assert.deepStrictEqual(relays, ["ws://example.com"]) + } + done() + }) + + subscriber.subscribe([]) + + // After the subscription event sync is done, publish the test event. + subscriber.on("eose", async () => { + publisher.publish({ + ...(await createSetMetadata( + { + about: "", + name: "", + picture: "", + nip05: "bob@localhost:12647", + }, + publisherSecret + )), + }) + }) + }) + }) + + it("missing", (done) => { + setup(done, ({ publisher, publisherSecret, subscriber, done }) => { + subscriber.on("event", async ({ event }) => { + // Assert that undefined is returned if the internet identifier is missing. + assert.strictEqual(event.kind, EventKind.SetMetadata) + if (event.kind === EventKind.SetMetadata) { + const identifier = await event.verifyInternetIdentifier({ + https: false, + }) + assert.strictEqual(identifier, undefined) + } + done() + }) + + subscriber.subscribe([]) + + // After the subscription event sync is done, publish the test event. + subscriber.on("eose", async () => { + publisher.publish({ + ...(await createSetMetadata( + { + about: "", + name: "", + picture: "", + }, + publisherSecret + )), + }) + }) + }) + }) +}) diff --git a/packages/nostr/test/set-metadata.ts b/packages/nostr/test/set-metadata.ts new file mode 100644 index 00000000..1fd261ad --- /dev/null +++ b/packages/nostr/test/set-metadata.ts @@ -0,0 +1,54 @@ +import { EventKind } from "../src/event" +import { parsePublicKey } from "../src/crypto" +import assert from "assert" +import { setup } from "./setup" +import { createSetMetadata } from "../src/event/set-metadata" + +describe("set metadata", () => { + const name = "bob" + const about = "this is bob" + const picture = "https://example.com/bob.jpg" + + // Test that a set metadata event can be published by one client and received by the other. + it("publish and receive", (done) => { + setup( + done, + ({ + publisher, + publisherSecret, + publisherPubkey, + subscriber, + timestamp, + done, + }) => { + // Expect the test event. + subscriber.on("event", ({ event }) => { + assert.strictEqual(event.kind, EventKind.SetMetadata) + if (event.kind === EventKind.SetMetadata) { + const user = event.getUserMetadata() + assert.strictEqual(event.pubkey, parsePublicKey(publisherPubkey)) + assert.strictEqual(event.created_at, timestamp) + assert.strictEqual(event.tags.length, 0) + assert.strictEqual(user.name, name) + assert.strictEqual(user.about, about) + assert.strictEqual(user.picture, picture) + } + done() + }) + + subscriber.subscribe([]) + + // After the subscription event sync is done, publish the test event. + subscriber.on("eose", async () => { + publisher.publish({ + ...(await createSetMetadata( + { name, about, picture }, + publisherSecret + )), + created_at: timestamp, + }) + }) + } + ) + }) +}) diff --git a/packages/nostr/test/text-note.ts b/packages/nostr/test/text-note.ts index 217ccfc2..e078960b 100644 --- a/packages/nostr/test/text-note.ts +++ b/packages/nostr/test/text-note.ts @@ -1,8 +1,8 @@ -import { EventKind, signEvent, Unsigned } from "../src/event" +import { EventKind } from "../src/event" import { parsePublicKey } from "../src/crypto" import assert from "assert" import { setup } from "./setup" -import { createTextNote, TextNote } from "../src/event/text" +import { createTextNote } from "../src/event/text" describe("text note", () => { const note = "hello world" @@ -40,16 +40,10 @@ describe("text note", () => { assert.strictEqual(nostr, subscriber) assert.strictEqual(id, subscriptionId) - // TODO No signEvent, have a convenient way to do this - publisher.publish( - await signEvent( - { - ...createTextNote(note), - created_at: timestamp, - } as Unsigned, - publisherSecret - ) - ) + publisher.publish({ + ...(await createTextNote(note, publisherSecret)), + created_at: timestamp, + }) }) } ) @@ -57,19 +51,16 @@ describe("text note", () => { // Test that a client interprets an "OK" message after publishing a text note. it("publish and ok", function (done) { - setup(done, ({ publisher, publisherSecret, url, done }) => { - // TODO No signEvent, have a convenient way to do this - signEvent(createTextNote(note), publisherSecret).then((event) => { - publisher.on("ok", (params, nostr) => { - assert.strictEqual(nostr, publisher) - assert.strictEqual(params.eventId, event.id) - assert.strictEqual(params.relay.toString(), url.toString()) - assert.strictEqual(params.ok, true) - done() - }) - - publisher.publish(event) + setup(done, async ({ publisher, publisherSecret, url, done }) => { + const event = await createTextNote(note, publisherSecret) + publisher.on("ok", (params, nostr) => { + assert.strictEqual(nostr, publisher) + assert.strictEqual(params.eventId, event.id) + assert.strictEqual(params.relay.toString(), url.toString()) + assert.strictEqual(params.ok, true) + done() }) + publisher.publish(event) }) }) }) From 2cb9f5a12b51d3a1f7b2734f1d3ccfb24ada81d9 Mon Sep 17 00:00:00 2001 From: ennmichael Date: Sat, 1 Apr 2023 14:53:03 +0200 Subject: [PATCH 2/3] formatting --- packages/nostr/.prettierignore | 2 ++ packages/nostr/README.md | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 packages/nostr/.prettierignore diff --git a/packages/nostr/.prettierignore b/packages/nostr/.prettierignore new file mode 100644 index 00000000..77731f20 --- /dev/null +++ b/packages/nostr/.prettierignore @@ -0,0 +1,2 @@ +dist/ +src/legacy diff --git a/packages/nostr/README.md b/packages/nostr/README.md index 62684ccc..edfadc2f 100644 --- a/packages/nostr/README.md +++ b/packages/nostr/README.md @@ -11,26 +11,26 @@ and tested against a real-world relay implementation. _Progress: 8/34 (23%)._ -- [X] NIP-01: Basic protocol flow description -- [X] NIP-02: Contact List and Petnames +- [x] NIP-01: Basic protocol flow description +- [x] NIP-02: Contact List and Petnames - [ ] NIP-03: OpenTimestamps Attestations for Events -- [X] NIP-04: Encrypted Direct Message -- [X] NIP-05: Mapping Nostr keys to DNS-based internet identifiers +- [x] NIP-04: Encrypted Direct Message +- [x] NIP-05: Mapping Nostr keys to DNS-based internet identifiers - [ ] NIP-06: Basic key derivation from mnemonic seed phrase - [ ] NIP-07: window.nostr capability for web browsers - [ ] NIP-09: Event Deletion - [ ] NIP-10: Conventions for clients' use of `e` and `p` tags in text events - TODO Check if this applies -- [X] NIP-11: Relay Information Document -- [X] NIP-12: Generic Tag Queries +- [x] NIP-11: Relay Information Document +- [x] NIP-12: Generic Tag Queries - [ ] NIP-13: Proof of Work - [ ] NIP-14: Subject tag in text events -- [X] NIP-15: End of Stored Events Notice +- [x] NIP-15: End of Stored Events Notice - [ ] NIP-19: bech32-encoded entities - - [X] `npub` - - [X] `nsec` + - [x] `npub` + - [x] `nsec` - [ ] `note`, `nprofile`, `nevent`, `nrelay`, `naddr` -- [X] NIP-20: Command Results +- [x] NIP-20: Command Results - [ ] NIP-21: `nostr:` URL scheme - [ ] NIP-23: Long-form Content - [ ] NIP-25: Reactions From 1637dad366be99f4d9fdebb8fa2daada066132a4 Mon Sep 17 00:00:00 2001 From: ennmichael Date: Tue, 4 Apr 2023 22:04:35 +0200 Subject: [PATCH 3/3] formatting @snort/app --- packages/app/src/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/translations/en.json b/packages/app/src/translations/en.json index b9af6751..592d6406 100644 --- a/packages/app/src/translations/en.json +++ b/packages/app/src/translations/en.json @@ -321,4 +321,4 @@ "zjJZBd": "You're ready!", "zonsdq": "Failed to load LNURL service", "zvCDao": "Automatically show latest notes" -} \ No newline at end of file +}