Wallet Provider
NOSTR Event Anatomy

NOSTR Event Anatomy

The expected form of all NOSTR events transferred from, to, and within the wallet provider is as follows (re. NIP-01: Basic Protocol Flow Description (opens in a new tab)):

{
  "id": "{32-bytes lowercase hex-encoded event ID}",
  "pubkey": "{32-bytes lowercase hex-encoded public key of the event's SIGNER}",
  "created_at": {UNIX timestamp in seconds},
  "kind": {1112|21111|31111},
  "tags": [
    [
      "k",
      "{event sub-kind}"
    ],
    [
      "p",
      "{32-bytes lowercase hex-encoded public key of the event's TARGET}"
    ],
    [
      "alt",
      "{human-readable description of the event}"
    ],
    [
      "delegation",
      "{32-bytes lowercase hex-encoded public key of the event's AUTHOR}",
      "{conditions query string: 'kind={.kind}&created_at>{UNIX timestamp lower limit in seconds}&created_at<{UNIX timestamp upper limit in seconds}'}",
      "{delegation token: 64-byte lowercase hex-encoded Schnorr signature of the SHA256 hash of 'nostr:delegation:{value of the .pubkey field}:{conditions query string}' using the AUTHOR's secret key}"
    ],
    [
      "d",
      "{event sub-kind}:{identifier}"
    ],
    {additional tags may follow}
  ],
  "content": "{stringified JSON content}",
  "sig": "{64-byte lowercase hex-encoded Schnorr signature of the event's .id field using the SIGNER's secret key}"
}

The .created_at timestamp MUST be within 10 seconds of the Local NOSTR Relay's current timestamp (cf. NIP-22: Event created_at Limits (opens in a new tab)).

The "k" tag marks what specific event sub-kind this event refers to. Note how a single .kind is reserved, multiplexing sub-kinds based off of the value of the "k" tag. The actual value of the "k" tag need not be an "integer string" and may instead be an arbitrary string (hopefully bearing some mnemonic meaning itself). This tag is REQUIRED and MUST NOT be repeated.

At least one "p" tag MUST be provided, and these tags SHOULD NOT be repeated.

The "alt" tag is prescribed in accordance to NIP-31: Dealing with Unknown Event Kinds (opens in a new tab). This tag is OPTIONAL and MUST NOT be repeated.

The "delegation" tag follows the guidelines exposed in NIP-26: Delegated Event Signing (opens in a new tab). This tag is OPTIONAL and MUST NOT be repeated.

The "d" tag MUST ONLY be present when the event's .kind is 31111. If present, it must consist of the event sub-kind (ie. the value associated to the "k" tag), followed by a colon (ie. :), followed by whatever the actual identifier is to be (this is so that we can keep using a single kind to refer to all parameterized replaceable events).

The "d" and "delegation" tags are mutually exclusive. This directly implies that no event with a .kind of 31111 may be delegated.

The .content field MUST be a (properly escaped) stringified JSON object itself.

Kinds and Sub-Kinds

Notice how we reserve just 3 event kinds for usage:

  • 1112: this is a regular event, expected ot be stored by relays.
  • 21111: this is an ephemeral event, not expected to be actually stored by relays.
  • 31111: this is a parameterized replaceable event, expected to replace any other event with the same kind (ie. 31111), .pubkey, and "d" tag value (notice how, because our "d" tag values are prescribed to contain the {event sub-kind}: prefix, event sub-kinds are also taken into consideration).

Event's AUTHOR, SIGNER, and TARGETs

A NOSTR event is expected to define three related entities:

  • The event's SIGNER: is the entity actually signing the NOSTR event proper, filling in the .pubkey and .signature fields.
  • The event's AUTHOR: is the entity on behalf of whom the NOSTR event is being created, found on index 1 of the "delegation" tag's content. If there's no "delegation" tag, then the event's AUTHOR is the event's SIGNER.
  • The event's TARGETs: are the entities to which the NOSTR event should be routed, these are found on index 1of each "p" tag.

In what follows we'll simply refer to an event's AUTHOR, SIGNER, and TARGETs as defined above, and refrain from referring to specific event fields or tag values or indices.

Replaceable Event Treatment

Since NOSTR replaceable events may "collide" if both of them happen to happen on the exact same second, internal modules are prepared to re-compute and re-publish replaceable events with a one-second delay. This ensures that replaceable events are eventually consistent with the platform's internal state.

Multi-NIP-04

We extend the NIP-04 (opens in a new tab) specification so as to be able to have multi-recipients. To that end, the event's .content MUST be the JSON stringification of the following object:

{
  "mac": BASE64_ENCODE(SHA256("{MESSAGE}")),
  "enc": NIP04LIKE_ENCRYPT("{MESSAGE}", "{RANDOM_MESSAGE_KEY}"),
  "key": {
    HEX_ENCODE("{RECEIVER_1_PUBLIC_KEY}"): NIP04_ENCRYPT("{SENDER_PRIVATE_KEY}", "{RECEIVER_1_PUBLIC_KEY}", HEX_ENCODE("{RANDOM_MESSAGE_KEY}")),
    HEX_ENCODE("{RECEIVER_2_PUBLIC_KEY}"): NIP04_ENCRYPT("{SENDER_PRIVATE_KEY}", "{RECEIVER_2_PUBLIC_KEY}", HEX_ENCODE("{RANDOM_MESSAGE_KEY}")),
    ...
    HEX_ENCODE("{RECEIVER_N_PUBLIC_KEY}"): NIP04_ENCRYPT("{SENDER_PRIVATE_KEY}", "{RECEIVER_N_PUBLIC_KEY}", HEX_ENCODE("{RANDOM_MESSAGE_KEY}"))
  },
  "alg": "sha256:nip-04:nip-04"
}

Where:

  • HEX_ENCODE(): is a function performing the byte-by-byte hex encoding of its given binary argument
  • BASE64_ENCODE(): is a function performing the base64 encoding of its given binary argument
  • SHA256(): is a function calculating the SHA-256 hash of its given binary argument
  • NIP04_ENCRYPT(): is a function applying the standard NIP-04 encryption
  • NIP04LIKE_ENCRYPT(): is a function generating the same output as NIP04_ENCRYPT, but using the given symmetric key instead of deriving a shared secret from the sender's private key and the recipient's public key

Note that a fixed-length (ie. 16 bytes) random message key is used to encrypt the message itself (RANDOM_MESSAGE_KEY in the explanation above), and said key is then itself encrypted under each receiver's public key in turn. Additionally, the function of the .mac field is to ensure that all recipients may check each received the same message. Finally, the .alg field is provided for future extension.

Federation Public Keys

The lawallet.ar federation exposes the following modules and their corresponding public keys:

  • Ledger: bd9b0b60d5cd2a9df282fc504e88334995e6fac8b148fa89e0f8c09e2a570a84
  • URLx: e17feb5f2cf83546bcf7fd9c8237b05275be958bd521543c2285ffc6c2d654b3
  • Card: 18f6a706091b421bd9db1ec964b4f934007fb6997c60e3c500fdaebe5f9f7b18