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 TARGET
s
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 index1
of the"delegation"
tag's content. If there's no"delegation"
tag, then the event'sAUTHOR
is the event'sSIGNER
. - The event's
TARGET
s: are the entities to which the NOSTR event should be routed, these are found on index1
of each"p"
tag.
In what follows we'll simply refer to an event's AUTHOR
, SIGNER
, and TARGET
s 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 argumentBASE64_ENCODE()
: is a function performing the base64 encoding of its given binary argumentSHA256()
: is a function calculating the SHA-256 hash of its given binary argumentNIP04_ENCRYPT()
: is a function applying the standard NIP-04 encryptionNIP04LIKE_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