TRUSTed Filters
TRUSTed Filters
nip-trusted-filters
draft
extends nip-01
extends nip-11
kind 17570 "Trust Provider Preferences" r p
tag W "input parameter for generated events"
tag w "output data in generated events"
message > TRUST "ping trust services OR request trusted filtering"
message < TRUST "send ping response with service info"
This is an extendable standard for Trust Services allowing users to:
- offload computationally expensive event processing to subscribed cloud services,
- discover, share, and compose their own filtering algorithms for use across services,
- maintain control over algorithm processing, with granular preferences for each service provider.
While existing NIPs provide basic event filtering (NIP-01 filters) and computational job processing (NIP-90 DVMs), and even a fixed set of algorithms for trusted assertions (NIP-85), none of them address the need for subscription-based processing of user-configurable and sharable event processing algorithms. NIP-01 filters cannot query deep event structures or handle event transformations. NIP-90 handles one-off computational "bounties", but not subscription-based services controlled by user preferences. And NIP-85 only specifies a fixed set of algorithms, with no configurability for end users.
This NIP offers a Nostr native specification for clients to send trusted filter requests via TRUST messages to relays operated by Trust Services Providers, and to receive standard EVENT messages (event streams) in response. TRUST messages are designed to ensure transparency and freedom of choice for end users, whenever TRUST SENSITIVE cloud services are required.
TRUST Messages
Similar to standard REQ messages (as defined in NIP-01), TRUST messages accept one or more filter objects. However, this is where the similarity ends.
TRUSTmessages requireAUTH(NIP-42) confirmation of a subscriber's pubkey.TRUSTmessages accept trusted filters which allow for advanced querying of deep event data.TRUSTtrusted filters may contain plugins, which extend the filtering, transformation, and event publishing capacity.TRUSTmessages have fine grained permissions and configurability built into the spec, allowing users to maintain control at all times across different client and relay implementations.
Client to Relay
// ping for general supported services
["TRUST"]
// ping for subscription supported services
["TRUST", "<subscription_pubkey>"]
// request any number of trusted filters (filters are ORed together)
["TRUST", "<subscription_pubkey>", "<session_id>", "<trusted_filter_json>", ...]<subscription_pubkey>is the pubkey for whichTRUSTservices are being requested from the provider.<session_id>is a unique identifier for messages sent and received in relation to this (subscriber's) session.<trusted_filter_json>is a JSON object representing a trusted filter (more than one may be sent in a single request)
Relay to Client
// ping response
["TRUST", "<subscription_pubkey>", "<trust_relay_support_info>"]
// error messages
["CLOSED", "<session_id>", "<trust_relay_error_message>"]<trust_relay_support_info>is a JSON object containing subscriber specific information about the relay's supported services (see below)<trust_relay_error_message>is a string containing a machine-readable prefix and human-readable error message. (see Appendix 5)
Subscriptions & Authentication
The TRUST message specification is designed to support subscription based services. Service providers MAY generate a unique subscription_pubkey for each subscriber, or one for all subscribers, or none at all. Providers SHOULD be able to associate the subscription_pubkey sent in TRUST messages with the pubkey of a subscriber, and SHOULD ALWAYS send an AUTH (NIP-42) request AND receive successful confirmation of the subscriber_pubkey BEFORE processing TRUST messages.
IMPORTANT : TRUST messages MAY trigger TRUST SENSITIVE algorithms (such as reputation scoring, content analysis, and social graph inference) to be executed by service providers. As such, Clients SHOULD ALWAYS act in their user's best interest.
- Clients SHOULD discover the user's published (kind
17570) "Trust Provider Preferences" before sendingTRUSTmessages. - Clients SHOULD ONLY send
TRUSTmessages to the user's appropriate preferred provider, if one is published. - If NO appropriate provider preference is published, clients MAY send
TRUSTfilter requests to their own preferred provider and SHOULD ALWAYS inform the user of this decision.
TRUSTed Filter Format
A trusted filter is a JSON object that extends the standard NIP-01 REQ filter. Out of the box, this pluggable filter format allows for deep event querying and relative time ranges.
[ "TRUST",
{
// Standard NIP-01 fields (unchanged)
"ids": ["<event_id>", ...],
"authors": ["<pubkey>", ...],
"kinds": [<kind_number>, ...],
"#<single_letter>": ["<value>", ...],
// TRUSTed Filter: EventPath
// matching values on ANY event field or #tag or embedded JSON object
// where * matches any value, and ! matches when NO values exist
"<EventPath>": ["<value>", ...] | "*" | "!" ,
// ... (any number of EventPath filters)
// TRUSTed Filter: Relative time
"since": "<timestamp>" | "<RelativeTime>",
"until": "<timestamp>" | "<RelativeTime>",
// Supported plugins MAY be added to the filter request
// Unsupported plugins SHOULD result in the ENTIRE request being rejected
// Plugins SHOULD be applied after all other filters
// and in the order that they are listed
"plugins": [...],
// Limit is always applied last.
"limit": "<number>"
}
]Plugins
Plugins MAY add additional functionality to a trusted filter. Plugins are executed in the order specified, after standard filter criteria are applied. Each plugin operates on the output of the previous operation in the pipeline.
Request Format:
["TRUST", "<subscription_pubkey>", "<session_id>", {
// Plugin functions are executed in the order specified
// A plugin may have multiple instances in the same filter
// each plugin instance may have multiple function calls
// function calls may have multiple arguments, but the first is always required.
// arguments MAY accept any value, including EventPath or IO queries
// as per the plugin's NIP specification.
"plugins" : [
// plugin instance
[ "<plugin_key>",
// plugin function calls
["<input>", "<param?>"...],
["<input>", "<param?>"...],
]
]
}]Plugin Support:
- Trust services SHOULD advertise their supported plugins to clients (via
TRUSTping responses). - Trust services SHOULD execute ALL plugin function calls requested in a filter, in the order they are specified.
- Trust services SHOULD reject any filter requests that contain plugins that are not supported.
Plugin NIPs:
NIP specifications for Plugins MAY be published by anyone to any URL.
- MUST define a
plugin_keyby which to identify the plugin in filter requests - A
plugin_keyMAY NOT contain the following reserved characters::,#,$,/ - MUST define a human-readable name and description based on what the plugin does
- SHOULD accept multiple argument arrays of strings in each function call
- MAY inspect any EventPath to access event data
- MUST accept at least one input string
- MAY specify additional parameters for input arrays
- MUST specify expected behavior for implementors
Plugin Types:
The following are some conventions for plugin types. Such terminology MAY be indicated in a plugin's NIP specification for clarity.
- Filtering plugins operate on candidate events:
- Read candidate events
- Apply additional filter criteria
- Modify the order or grouping of candidate events.
- Transformation plugins extract and process data from filtered events.
- Read from filtered events
- Read and Write
IOdata
- Flow Control plugins modify filter execution behavior:
- Control scheduling and requirements
- Manage iteration and dependencies
- Affect request-level behavior
- Input & Output plugins manage the
IOnamespace: - Read from filtered events
- Write to
IOnamespace - Composable via
IOdata flow
- Publishing plugins create events from extracted data:
- Read from
IOdata - Render tags from
IOdata - Publish events to relays
IO Interface
IO is a shared interface for plugins to exchange data within a single request. IO starts empty for each request and is progressively populated by plugins as they execute.
- Every plugin SHOULD have write access to an
IOnamespace at<plugin_key>. - Plugins SHOULD have read access to ANY other plugin's namespace, at
<other_plugin_key>. - Plugins MAY specify that ANY other plugins ALSO have write access to their namespace.
- If allowed, other plugins SHOULD write to (colon
:separated)<plugin_key>:<other_plugin_key>. - Plugins MAY use simple key/value pairs,
<plugin_key>.<key>, in theirIOnamespace - OR plugins MAY specify more complex data structures for their
IOnamespace.
**Accessing IO Data:**
Plugins can access IO data in two ways:
- Direct access:
Plugins MAY specify specific arguments that accept IO queries.
``jsonc // This plugin processes data from <other_plugin>.<other_key> // and outputs results in its own IO key <io_key> ["<plugin_that_processes_io_data>", ["<other_plugin>.<other_key>", "<io_key>"]] ``
- String interpolation:
String replacement (by interpolation) may be used in ANY argument (in subsequent filters or plugins) that accepts a string.
``jsonc // this filter uses IO data written in a previous filter // from <another_plugin> into <writable_plugin> // to determine which authors to include "authors": ["${<writable_plugin>:<another_plugin>.<key>}"] ``
The W Plugin
W (uppercase) is a Filtering plugin. It defines named keys for user input and optionally for matching (user input) on candidate events. Input values are stored in IO (at W namespace) and are accessible by other plugins. Outputs data as W tags (using publish plugin).
Functions:
{
"plugins": [
["W", ["<key>", "<type>", "<match?>", "<default?>"]]
]
}Arguments:
<key>:string: (required) : Key for theIOnamespace (may NOT contain colon:)<type>:TypeDescriptor: (required) : Type descriptor for the input parameter<match>:EventPath: (optional) : EventPath for matching candidate events on the input value<default>:string | number | boolean: (optional) : Default value if not provided by requestor
IO:
- Writes to:
IOnamespace atW.<key> - Write Access: Values are only writable by the
Wplugin. - Allowed Values: allowed values for each key are specified by
<type>
Behavior:
- Collects input values and stores them in
IO. - If
<match>is specified, candidate events are filtered by the input value. - Renders as
Wtags (usingpublishplugin).
The w Plugin
w (lowercase) is a Input & Output plugin. It extracts data from filtered events and stores it in IO (at w namespace). The w namespace is writable by any plugin. Outputs data as w tags (using publish plugin).
Functions:
{
"plugins": [
["w", ["<key>", "<match?>", "<default?>"]]
]
}Arguments:
<key>:string: (required) : Key for theIOnamespace (may NOT contain colon:)<match>:EventPath: (required) : EventPath for extracting value(s) from filtered events<default>:any: (optional) : Default value if no data is found at<match>
IO:
- Writes to:
IOnamespace atw.<key> - Write Access: Values are writable by any plugin to
w:<plugin>.<key>. - Allowed Values: any
stringornumberorbooleanis allowed.
Behavior:
- Extracts value(s) from filtered events at
<match>EventPath. - Stores extracted data in
IO, atw.<key>. - If key already exists, overwrites the value.
- Allows other plugins to write values to
w:<plugin>.<key>. - Renders as
wtags (usingpublishplugin).
The # Plugin
# (hash) is a Input & Output plugin. It provides a writable namespace (as an array of string arrays) for any plugin to store pre-rendered tag arrays. Outputs data as tags (using publish plugin).
Functions:
{
"plugins": [
["#", ["<tag_key>", "<tag_value>", ... ]]
]
}Arguments:
<tag_key>:string: (required) : Key for the tag<tag_value>:string: (required) : Value for the tag<tag_value>:string: (optional) : additional tag values
IO:
- Writes to:
IOnamespace#, as a new index in the array - Write Access: Any plugin may add new tags to the array.
- Allowed Values: An array of strings.
Behavior:
- Stores and array of pre-rendered tag arrays in
IO, at#. - Allows any plugin to append tag arrays to
#. - Renders as tags (using
publishplugin).
The publish Plugin
publish is a Publishing plugin. It creates and publishes Nostr events by reading data from IO and rendering these as event tags. Event pubkey and created_at fields (and d tag) SHOULD be set by the publisher, if not otherwise specified.
Signature:
{
"plugins": [
["publish",
// Event Publisher function (required)
// This composes an event with tags
// rendered from IO `W`, `w`, and `#` namespaces
["<kind>", "<relay?>", ...],
// Event Composer function (optional)
// Any number of event composer functions may be specified
// To compose event fields from additional IO data
["<fieldname>", "<io_query>"],
// Tag Composer functions (optional)
// Any number of tag composer functions may be specified
// To compose tags from additional IO data
["#<tagname>", "<io_query>", ...]
]
]
}Event Publisher Arguments:
<kind>:number: (required) : Event kind to publish<relay>:string: (optional) : Any number of relay URL(s) for publishing (defaults to author's write relays)
Event Composer Arguments:
<fieldname>:string: (required) : Any event field name<io_query>:IO: (required) : value from IO to render in the field
Tag Composer Arguments:
<tagname>:string: (required) : Tag name (prefixed with#)<io_query>:IO: (required) : value from IO to render in the tag<io_query>:IO: (optional) : additional IO values to render in the tag
IO:
- Writes to: Does NOT write to
IO. - Reads from:
IOnamespacesW,w,#(by default) - Purpose: Render
IOdata as event tags
Tags Published:
- **
IOW** namespace renders as["W", "<key>:<value>"] - **
IOw** namespace renders as["w", "<key>:<value>"]and["w", "<plugin>:<key>:<value>"] - **
IO#** namespace renders as["<key>", "<value>", ...] - Additional tags MAY be rendered by Tag Composer functions.
Behavior:
- The
publishplugin reads fromIOand renders tags.
Relay Support
Trust Service relays SHOULD respond to TRUST pings with a relay_support_response_JSON object indicating their supported plugins. Supporting relays MAY also add this same object (indicating general support) to a TRUST key in the (NIP-11) "Relay Information Document" sent to clients.
TRUST Ping:
// ping from client RE: *all* supported services
["TRUST"]
// ping from client RE: *subscription* supported services
["TRUST", "<subscription_pubkey>"]TRUST Ping Response:
// ping response from relay RE: *all* supported services
[
"TRUST", "<trust_relay_support_info>"
]
// ping response from relay RE: *subscription* supported services
[
"TRUST", "<subscription_pubkey>", "<trust_relay_support_info>"
]TRUST Relay Information Document:
// relay information document RE: *all* supported services
{
"TRUST": "<trust_relay_support_info>"
// additional relay information ...
}**Format for trust_relay_support_info:**
{
// Services SHOULD specify the exact plugins that they support
// A naddr or url to specification doc is REQUIRED
"plugins?": [
"[<plugin_key>, <naddr_or_url_to_specification>]",
...
],
// Services MAY wish to limit their support for processing
// ONLY certain event structures
"only?" : [
["<EventPath>", ["<value>", ...]],
...
],
// OR NONE of certain event structures
"not?" : [
["<EventPath>", ["<value>", ...]],
...
]
}Client Support
BEFORE sending TRUST filter requests, clients SHOULD determine the Trust Service Provider to send to by considering:
- their user's Trust Provider Preferences (published as kind
17570) for the specific plugins being requested. - the service's list of supported plugins (as received from the relay in a
TRUSTping response OR a NIP-11 "Relay Information Document")
If a user HAS published Trust Provider Preferences, Clients should TRY to send TRUST messages to the user's preferred providers and ONLY use their preferred plugins for each.
If a user HAS published Trust Provider Preferences AND a client has a special need for plugins that are NOT supported by the user's preferences, the client MAY ask for permission to use their OWN provider instead.
If a user HAS NOT published Trust Provider Preferences, Clients may send TRUST messages to their OWN preferred provider AND SHOULD inform the user of this decision and MAY provide a list of Trust Service Providers to subscribe to.
Trust Provider Preferences (kind 17570)
Users wishing to make use of TRUST services SHOULD contact a Service Provider and publish a kind 17570 "Trust Provider Preferences" event. HOWEVER, because these events MAY be published from any client, service providers SHOULD treat user published kind 17570 events as the CANONICAL source of truth for the user's publishing preferences.
Event Format:
{
"kind": 17570,
"tags": [
// provider for UNSUBSCRIBED trust filter requests
["r", "<relay_url>", "<provider_preference_options>"],
// provider for SUBSCRIBED trust filter requests
["p", "<subscription_pubkey>", "<relay_url>", "<provider_preference_options>"]
],
// preferences MAY optionally be encrypted in content
"content": nip44Encrypt(JSON.stringify([
["r", "<relay_url>", "<provider_preference_options>"],
["p", "<subscription_pubkey>", "<relay_url>", "<provider_preference_options>"]
]),
}**Format for provider_preference_options:**
{
"plugins?": [
// either ALLOW ANY or BLOCK ALL trust services
// or ALLOW or BLOCK specific plugins
"*" | "!" | "<plugin_key>" | "!<plugin_key>",
// and maybe specify more plugins
"<plugin_key>" | "!<plugin_key>", // ...
],
}Example Event:
{
"kind": 17570,
"tags" : [
// A subscribed provider for ALL TRUST requests
["p", "35450ebb3c5a32e18276...", "wss://subscribed.example.com", "*"],
// An unsubscribed provider for TRUST requests
// using a specific plugin
["r", "wss://specialty.example.com", "{\"plugins\": [\"my_custom_plugin\"]}"],
// A blacklist DO NOT USE provider
["r", "wss://unsubscribed.example.com", "!"],
]
}APPENDIX 1: W and w Tags
**This NIP reserves the W and w tags for use in plugin generated events of ANY kind.**
The (uppercase) W tag key is reserved for describing user inputs for ANY plugin generated events.
{
"kind": <trusted_event_kind>,
"tags": [
// A colon separated `<key>:<value>` string is the standard format
["W", "<key>:<value>"]
// A single `<value>` string MAY be used in certain cases
["W", "<value>"]
]
}The (lowercase) w tag key is reserved for holding output data in ANY plugin generated events.
{
"kind": <trusted_event_kind>,
"tags": [
// A colon separated `<key>:<value>` string is the standard format
["w", "<key>:<value>"]
// A single `<value>` string MAY be used in certain cases
["w", "<value>"]
]
}APPENDIX 2: EventPath Syntax
EventPath strings are used to navigate into event fields and tags for either matching on or extracting specific values.
Matching on Field Names
<field_name>: EventPath MAY start with any event field name (e.g.,pubkey,created_at,content,kind)
Matching on Tag Names
#<key>: EventPath MAY start with a hash prefix to match on a tagkey(index 0) of ANY tag in an event. (e.g.,#p,#e,#title,#description) By default (same as standard filters),#<key>without modifiers is equivalent to#<key>[*][1].
Matching on Tag Data
#<key>[<nth> | *]: Matches on the<nth>matching<key>tag in a tag array and returns the value at index1of that tag. The wildcard*matches on the FIRST matching tag THAT HAS A VALUE at index1, and returns that value.#<key>[<nth> | *][<value>]: Matches on the<nth>matching<key>tag in a tag array, and returns the value at index<value>of that tag. The wildcard*matches on the FIRST matching tag THAT HAS A VALUE at index<value>, and returns that value.
Matching on Relative Tag Values
#[<value>]: Matches on (and returns the value at) the index at<value>of the FIRST matching tag THAT HAS A VALUE at that index. (This is useful for matching on the values of a known subset of tags.)
Extracting Tag Arrays:
#<key>[]: Explicitly extracts an array of tags matching<key>in a tag array.#<key>[][<value>]: Explicitly extracts an array of tag values (at index<value>) from ALL<key>tags in a tag array which have a value at index<value>.
Match or Extract Embedded JSON Data
- Embedded JSON objects and arrays MAY be navigated from event fields OR tags using dot notation (e.g.,
#description.pubkey) or bracket notation (e.g.,content.items[0]) #<key>syntax MAY also be used to navigate into any array of string arrays in embedded JSON (in the same manner as event tags).- Empty brackets
[]within a path (for extraction processes) SHOULD always return an array.
Examples:
#p- returns the index 1 value of the FIRSTptag with a value at index 1#p[*][2]- returns the index 2 value of the FIRSTptag with a value at index 2#p[1][2]- returns the index 2 value of ONLY the 1stptag.#p[]- returns an array of allptags in a tag array.content.items[2]- returns the index 2 value of an embedded JSON arraycontent.items- returns the index 0 value of an embedded JSON arraypubkey- returns the event's pubkey field#description.tags.#amount- returns the index 1 value of ANYamounttag from an embedded zap request#description.tags.#amount- returns the index 1 value of the FIRSTamounttag with a value at index 1
APPENDIX 3: TimeUnit and RelativeTime Syntax
Relative time strings allow filter configs to remain valid over time.
TimeUnit:
<number>s- seconds<number>m- minutes<number>h- hours<number>d- days<number>w- weeks<number>M- months<number>y- years
Format: now[-+]<TimeUnit>
Examples:
"now"- current timestamp"now-7d"- 7 days ago"now-1w"- 1 week ago"now-3M"- 3 months ago
APPENDIX 4: TypeDescriptor Syntax
A TypeDescriptor can be any string that describes the type of an expected value. These MAY be machine readable, but do NOT need to be. As a convention, the following type descriptors are provided:
"tagletter"- indicates any standard tag letter is allowed"tagname"- indicates any standard tag name is allowed"#<tag_letter>"- the type of value expected in an standard indexable tag (e.g.,#p,#e)"<integer>-<integer>"- an unsigned float between<integer>and<integer>"+-<integer>-<integer>"- a signed or unsigned float between<integer>and<integer>"naddr"- an naddr event reference"nprofile"- an nprofile event reference"nevent"- an nevent event reference"npub"- an npub user reference"string"- any string"integer"- an integer"number"- any number"boolean"- a boolean"url"- any URL
APPENDIX 5: Error Codes
Standard machine-readable error prefixes for CLOSED messages:
error:auth-required- AUTH confirmation required before processingerror:unsupported-plugin:<plugin_key>- Requested plugin not supportederror:invalid-filter- Filter syntax errorerror:rate-limit- Request rate limit exceedederror:subscription-required- Feature requires active subscription