نیپ شماره ۱

روشنگری بنیادی روند پروتکل

پیشنویس ‍‍‍‍بایسته(الزامی)

این نیپ پروتکل آغازینی را میشناساند که باید توسط همه پیاده سازی شود. نیپ های تازه تر ممکن است زمینه ها پیام ها و یا قابلیت های دلخواهی به روند شناسانده شده (معرفی شده) در این نیپ بیفزایند.

رویداد ها و امضا ها

هر کاربر یک جفت کلید دارد. امضا ها کلید های عمومی و رمزگذاری ها بر اساس استاندارد Schnorr signatures standard for the curve secp256k1 انجام میشود.

تنها شی (حالت داده) موجود ‍‍رویداد است که فرمت زیر را دارد:

{
  "id": <32-bytes lowercase hex-encoded sha256 of the serialized event data>, // شناسه
  "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>, // کلید عمومی
  "created_at": <unix timestamp in seconds>, // زمان ساخت شدن رویداد
  "kind": <integer between 0 and 65535>, // نوع

  "tags": [ // برچسب ها
    [<arbitrary string>...],
    // ...
  ],
  "content": <arbitrary string>, // محتوا
  "sig": <64-bytes lowercase hex of the signature of the sha256 hash of the serialized event data, which is the same as the "id" field> // امضا
}

برای محاسبه شناسه رویداد هش sha256 حالت سریالایز شده ان را محاسبه میکنیم.

فرایند سریالایز کردن رویداد ها با استفاده از استاندارد UTF-8 و رشته سریالایز شده جیسان با ساختار زیر اتفاق می افتد:

[
  0,
  <pubkey, as a lowercase hex string>, // کلید عمومی
  <created_at, as a number>, // زمان ساخت بصورت عدد
  <kind, as a number>, // گونه به صورت عدد
  <tags, as an array of arrays of non-null strings>, // برچسب ها له صورت ارایه ای از رشته ها ناتهی
  <content, as a string> // محتوا بصورت رشته
]

برای جلوگیری از ساخت شناسه های متفاوت برای یک رویداد در پیاده سازی های متفاوت قوانین زیر باید دنبال شود:

  • برار رمزگذاری بایذ از UTF-8 استفاده شود.

  • فضا های خالی و خطوط جدید و دگیر فرمت های غیر ضرروی نباید در ساختار جیسان خروجی در نظر گرفته شوند.

  • نشان های زیر در فیلد محتوا (content) باید به شکل زیر نادیده گرفته شوند و دیگر نشان ها باید واژه به واژه در محتوا فراگرفته شوند:

    • برای نشان رفتن به خط بعد (0x0A) باید از \n استفاده شود.

    • برای نشان نقل قول (0x22) باید از \" استفاده شود.

    • برای نشان بک اسلش باید (0x5C) باید از \\ استفاده شود.

    • برای نشان بازگشت carriage (0x0D) باید از \r استفاده شود.

    • برای نشان تب (0x09) باید از \t استفاده شود.

    • برای نشان بک اسپیس باید از (0x08) باید از \b استفاده شود.

    • برای نشان حالت فید (feed) (0x0C) باید از \f استفاده شود.

برچسب ها

هر برچسب ارایه ای از رشته ها با مجموعه ای از قرار داد های مربوط به ان می باشد. به نمونه زیر نگاه کنید:

{
  "tags": [
    ["e", "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36", "wss://nostr.example.com"],
    ["p", "f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca"],
    ["a", "30023:f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca:abcd", "wss://nostr.example.com"],
    ["alt", "reply"],
    // ...
  ],
  // ...
}

بخش یکم هر برچسب نام یا کلید برچسب نامیده میشود و بخش دوم مقدار ان. پس ما میتوانیم با خیال راحت بگوییم در نمونه بالا رویداد ما یک برچسب e با مقدار "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36" و یک برچسب alt با مقدار "reply" دارد.

تمامی بخش ها بعد از دومین نام اصولی ندارند.

این نیپ سه برچسب استاندارد معرفی میکند که در همه رویداد ها با هر گونه ای با معنی یکی استفاده شود:

  • برچسب e برای واگذاری (ارجاع) به رویدادی استفاده میشود: ‍‍["e", <32-bytes lowercase hex of the id of another event>, <recommended relay URL, optional>]

  • برچسب p برای واگذاری به کاربری استفاده میشود: ["p", <32-bytes lowercase hex of a pubkey>, <recommended relay URL, optional>]

  • برچسب a برای واگذاری به یک رویداد (شاید پارامتر شده) قابل جایگزین استفاده میشود:

    • برای رویداد پارامتر شده قابل جایگزین: ["a", <kind integer>:<32-bytes lowercase hex of a pubkey>:<d tag value>, <recommended relay URL, optional>]

    • برای رویداد پارامتر نشده قابل جایگزین: ‍["a", <kind integer>:<32-bytes lowercase hex of a pubkey>:, <recommended relay URL, optional>]

به عنوان یک اصل تمامی بر چسب های تک حرفی با نام های بندواژه (حروف البفا) انگلیسی کوچک و بزرگ توقع میشود کلید ها یا نام ها توسط رله ها فهرست (index) شوند.

به گونه ای که پرس و جو یا دنبال کردن رویداد هایی که به رویداد با شناسه "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36" واگذاری دارند با استفاده از صافیه (filter) {"#e": ["5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36"]} ممکن باشد.

گونه ها

یک گونه مشخص میکند هر کلاینت چگونه باید معنای هر رویداد و فیلد های ان را تفسیر کند. (برای مثال برچسب در گونه ۱ معنایی داشته باشد و در گونه ۱۰۰۰۲ معنایی کاملا متفاوت داشته باشد."r")

هر نیپ ممکن است معنای مجموعه ای گونه هارا تعریف کند که در جای دیگری تعریف نشده اند. این نیپ گونه های بنیادی ای را تعریف میکند:

  • 0: ‍‍ابرداده(metadata) کاربر: فیلد محتوا با ساختار جیسان رشته شده تنظیم میشود: {name: <username>, about: <string>, picture: <url, string>} کاربری که رویداد را ساخته است را تعریف میکند. فیلد های ابر داده بیشتری میتواند تنظیم شود. رله ها ممکن است رویداد های گونه 0 را حذف کنند اگر رویداد گونه 0 جدیدی برای کلید عمومی یکسانی دریافت کردند.

  • 1: ‍یادداشت متنی: فیلد محتوا با متن خام پر میشود. هر چیزی که کاربر بخواهد بگوید. محتوایی که باید تجزیه شوند (pars) نباید استفاده شود. و کلاینت ها نباید این محتوا را تجزیه کنند.

و همچنین یک اصل برای محدود شماره گونه ها که ازمایش رله هارا اسان تر و پیاده سازی انهارا منعطف تر میکند:

  • یک گونه با شماره ‍n به طوری که: 1000 <= n < 10000 || 4 <= n < 45 || n == 1 || n == 2, رویداد های منظم هستند که انتظار می رود رله همه انهارا نگهداری کند.

  • یک گونه با شماره n به طوری که: 10000 <= n < 20000 || n == 0 || n == 3, رویداد های جایگزین پذیر هستند. یعنی در ازای هر رویداد با این گونه و یک کلید عمومی باید اخرین نسخه ان توسط رله نگهداری شود. نگارش های قدیمی تر ممکن است پاک شوند.

  • یک گونه با شماره n به طوری که 20000 <= n < 30000, رویداد موقت است. یعنی انتظار نمی روند که توسط رله ها ذخیره شوند.

  • یک رویداد با گونه n به طوری که: 30000 <= n < 40000, رویداد جایگزین پذیر پارامتر شده است. یعنی در ازای هر رویداد با این گونه و یک کلید عمومی که یکمین مقدار برچسب d آن هم یکی باشد تنها اخرین رویداد باید توسط رله ذخیره شود. نگارش های قدیمی تر ممکن است پاک شوند.

در مورد گونه جایگزین پذیر دو رویداد با زمان های یکسان رویدادی با با کمترین شناسه (first in lexical order) باید نگهداری شود و دیگری پاک شود.

در زمان پاسخ به پیام REQ برای یک رویداد جایگزین پذیر برای نمونه: ‍{"kinds":[0],"authors":[<hex-key>]} در صورتی که رله چند نسخه از رویداد را داشته باشد باید نسخه اخر را بازگرداند.

اینها تنها اصول هستند. پیاده سازی رله ها میتواند تفاوت داشته باشد.

ارتباط بین رله ها و کلاینت ها

رله ها یک پورت وب سوکت را برای اتصال کلاینت ها آزاد میگزارند. هر کلاینت باید یک اتصال وب سوکت به هر رله برای تمامی اشتراک هایش ایجاد کند. رله ها ممکن است تعداد اتصال های ممکن را برای IP ادرس یا کلاینت خاصی محدود کنند.

از کلاینت به رله: فرستادن رویداد ها و ایجاد اشتراک (subscriptions)

کلاینت ها میتوانند سه نوع پیام ارسال کنند که به صورت یک ارایه جیسان است. این پیام ها به حالت های زیرند:

  • ‍‍["EVENT", <event JSON as defined above>], برای فرستادن رویداد استفاده میشود.
  • ["REQ", <subscription_id>, <filters1>, <filters2>, ...], برای گرفتن یک رویداد و یا ثبت اشتراک برای دریافت رویداد های جدید و بروزرسانی ها استفاده میشود.
  • ["CLOSE", <subscription_id>], برای لغو اشتراک های پیشین استفاده میشود.

‍‍<subscription_id>, مجموعه ای از کاراکتر های دلخواه و غیر خالی و تا حداکثر طول ۶۴ کاراکتر می باشد. که این نشان دهنده یک اشتراک برای دریافت رویداد های جدید در ازاری هر اتصال می باشد.

رله ها این نشانی هارا باید بصورت مستقل برای هر اتصال وب سوکت مدیریت کنند.

این نشانی ها تظمین نشده اند که بصورت همگانی یکتا باشند.

<filtersX> تایین میکند چه رویداد هایی باید در این اشتراک فرستاده شوند. این فیلد میتواند ویژگی های زیر را داشته باشد:

{
  "ids": <a list of event ids>,
  "authors": <a list of lowercase pubkeys, the pubkey of an event must be one of these>,
  "kinds": <a list of a kind numbers>,
  "#<single-letter (a-zA-Z)>": <a list of tag values, for #e — a list of event ids, for #p — a list of pubkeys, etc.>,
  "since": <an integer unix timestamp in seconds. Events must have a created_at >= to this to pass>,
  "until": <an integer unix timestamp in seconds. Events must have a created_at <= to this to pass>,
  "limit": <maximum number of events relays SHOULD return in the initial query>
}

در زمان دریافت یک پیام REQ رله در پایگاه داده داخلی خود رویداد هایی با این ویژگی هارا پیدا میکند و ارسال میکند. سپس این فیلتر را نگهداری میکند و رویداد های اینده مربوط به این فیلتر را میفرستد تا زمانی که اتصال وب سوکت بسته شود.

وقتی یک پیام ‍CLOSE یا یک پیام REQ یا شناسه ای مانند فیلتر پیشین ارسال میشود رله باید فیلتر ذخیره شده را بروزرسانی و بازنویسی کند.

ویژگی هایی که آرایه هستند (مانند شناسه ها گونه ها و نویسنده ها و برچسب ها مانند #e) ارایه های جیسانی با بیش از یک مقدار هستند. حداقل یکی از این مقدار ها باید با یکی از فیلد های رویداد یکی باشد تا رویداد با فیلتر همخوانی داشته باشد. برای ویژگی های اسکالر مانند نویسنده یا شناسه همان ويژگی رویداد باید در فهرست فیلتر موجود باشد. برای ویژگی هایی مانند برچسب #e که هر رویداد میتواند برایش مقادیر متفاوتی داشته باشد رویداد و فیلتر باید حداقل یک مورد یکسان داشته باشند تا شرط بر قرار باشد.

شناسه‌ها نویسندگان فهرست های فیلتر #e و #p باید دارای مقادیر مبنای ۱۶ ۶۴ کاراکتری باشند.

خواص since و until برای محدوده زمانی رویداد های اشتراک استفاده میشوند. اگر فیلتر شامل مقدار since بود تنها رویداد هایی با مقدار created_at برابر و بیشتر از مقدار آن با فیلتر همخوانی خواهند داشت. برای ویژگی ‍‍until مشابه است اما رویداد هایی که ‍created_at کمتر و برابر آن با فیلتر همخوانی دارند.

تمامی شروط فیلتر باید برای یک رویداد همخوانی داشته باشند تا از فیلتر عبور کند. چند شرط در یک فیتلر به عنوان && به حساب میاید.

یک پیام REQ ممکن است چندین فیلتر داشته باشد. در این حالت رویدادی که با هرکدارم از این فیتلر ها همخوانی داشته باشد توسط رله به کلاینت فرستاده میشود. چند فیلتر در یک پیام به عنوان || تفسیر میشود.

مقدار limit تنها برای زمان فرستادن اولیه پیام معتبر است و بعد از آن باید نادیده گرفته شود. زمانی که در یک فیلتر ‍limit: n است توقع میرود n رویداد اخر بر اساس created_at بازگردادنده شود. رویداد های تازه تر باید جلوتر فرستاده شوند. در صورت برابر بودن زمان ساخت رویدادی با کمترین شناسه زودتر فرستاده میشود (first in lexical order). رویداد ها میتواند کمتر از مقدار n باشد اما نمیتواند زیادی بیشتر از آن باشد که کلاینت با مقدار زیاد اطلاعات غرق شود.

از رله به کلاینت: فرستادن رویداد ها و اطلاعیه ها

رله ها میتوانند ۵ نوع پیام بفرستند. که باید بصورت ارایه جیسان باشند بر اساس حالت زیر باشند:

  • ["EVENT", <subscription_id>, <event JSON as defined above>], برای فرستادن رویداد درخواست شده به کلاینت استفاده میشود.
  • ["OK", <event_id>, <true|false>, <message>], برای تایید یا رد شدن رویداد فرستاده شده استفاده میشود.
  • ["EOSE", <subscription_id>], برای اعلام پایان رویداد های ذخیره شده و شروع فرستادن رویداد های تازه در حالت بی درنگ استفاده میشود.
  • ["CLOSED", <subscription_id>, <message>], برای اعلام پایان یک اشتراک در سمت سرور (رله) استفاده میشود.
  • ["NOTICE", <message>], برای فرستادن متن های خطا قابل خواندن توسط انسان و چیز های دیگر به سمت کلاینت استفاده میشود.

این NIP هیچ قانونی برای نحوه رفتار و فرستادن پیام NOTICE تعریف نمیکند.

  • EVENT فقط زمانی باید فرستاده شود که با شناسه اشتراک که در یک پیام REQ که پیشتر تعریف شده فرستاده شده باشد.

  • OK این پیام باید در پاسخ به پیام EVENT فرستاده شود. مقدار سوم باید وجود داشته باشد که اگر رویداد با موفقیت فرستاده شده بود مقدار باید true و در غیر این صورت باید false باشد. مقدار چهارم میتواند در صورت موفقیت ارسال یک رشته خالی باشد یا یک رشته که با یک کلمه قابل خواندن توسط ماشین و یک : و بعد از آن یک متن قابل خواندن توسط انسان باشد. چند نمونه:

    • ["OK", "b1a649ebe8...", true, ""]
    • ["OK", "b1a649ebe8...", true, "pow: difficulty 25>=24"]
    • ["OK", "b1a649ebe8...", true, "duplicate: already have this event"]
    • ["OK", "b1a649ebe8...", false, "blocked: you are banned from posting here"]
    • ["OK", "b1a649ebe8...", false, "blocked: please register your pubkey at https://my-expensive-relay.example.com"]
    • ["OK", "b1a649ebe8...", false, "rate-limited: slow down there chief"]
    • ["OK", "b1a649ebe8...", false, "invalid: event creation date is too far off from the current time"]
    • ["OK", "b1a649ebe8...", false, "pow: difficulty 26 is less than 30"]
    • ["OK", "b1a649ebe8...", false, "error: could not connect to the database"]
  • CLOSED در پاسخ یک پیام REQ فرستاده میشود زمانی که یک رله به آن پاسخ داده یا ان را رد کرده است. همچنین زمانی که رله تصمیم بر از بین بردن اشتراک قبل از قطع اتصال یا دریافت پیام CLOSE توسط یک کلاینت را دارد استفاده میشود. این پیام از طرحی مشابه با پیام OK استفاده میکند که با یک پیشوند قابل خواندن توسط ماشین و ادامه ان بصورت قابل خواندن برای انسان می باشد. چند نمونه:

    • ["CLOSED", "sub1", "duplicate: sub1 already opened"]
    • ["CLOSED", "sub1", "unsupported: filter contains unknown elements"]
    • ["CLOSED", "sub1", "error: could not connect to the database"]
    • ["CLOSED", "sub1", "error: shutting down idle subscription"]
  • مقادیر استاندارد برای پیام های OK و CLOSE: duplicate, pow, blocked, rate-limited, invalid هستند و error برای زمانی که هیچ یک از مقادیر مناسب نیست استفاده میشود.