NIP-01은 Nostr의 나머지 부분이 기반으로 하는 기본 이벤트 모델과 릴레이 프로토콜을 정의한다. 클라이언트, 릴레이, 라이브러리가 Nostr를 사용한다면 여기서 시작한다.

작동 방식

이벤트는 Nostr의 유일한 객체 유형이다. 프로필, 노트, 리액션, 릴레이 목록, 다양한 애플리케이션별 페이로드가 모두 동일한 7개 필드 구조를 사용한다:

  • id: 직렬화된 이벤트의 SHA256 해시 (고유 식별자)
  • pubkey: 생성자의 공개키 (32바이트 hex, secp256k1)
  • created_at: Unix 타임스탬프
  • kind: 이벤트 유형을 분류하는 정수
  • tags: 메타데이터를 위한 배열의 배열
  • content: 페이로드 (해석은 kind에 따라 다름)
  • sig: 진위를 증명하는 Schnorr 서명

이벤트 id는 직렬화된 이벤트 데이터의 SHA256 해시이며, 임의의 식별자가 아니다. 이것은 실무에서 중요하다: 태그 순서나 타임스탬프를 포함한 어떤 필드라도 변경하면 다른 이벤트가 생성되고 새 서명이 필요하다.

Kinds

Kind는 릴레이가 이벤트를 저장하고 처리하는 방식을 결정한다:

  • 일반 이벤트 (1, 2, 4-44, 1000-9999): 정상적으로 저장되며, 모든 버전이 보존됨
  • 대체 가능 이벤트 (0, 3, 10000-19999): pubkey당 최신 버전만 보존됨
  • 일회성 이벤트 (20000-29999): 저장되지 않고 구독자에게만 전달됨
  • 주소 지정 가능 이벤트 (30000-39999): pubkey + kind + d 태그 조합당 최신 버전만 보존됨

핵심 kind에는 0 (사용자 메타데이터), 1 (텍스트 노트), 3 (팔로우 목록)이 있다.

클라이언트-릴레이 통신

클라이언트는 WebSocket 연결을 통해 JSON 배열을 사용하여 릴레이와 통신한다:

클라이언트에서 릴레이로:

  • ["EVENT", <event>] - 이벤트 발행
  • ["REQ", <sub-id>, <filter>, ...] - 이벤트 구독
  • ["CLOSE", <sub-id>] - 구독 종료

릴레이에서 클라이언트로:

  • ["EVENT", <sub-id>, <event>] - 일치하는 이벤트 전달
  • ["EOSE", <sub-id>] - 저장된 이벤트의 끝 (이후 실시간 스트리밍)
  • ["OK", <event-id>, <true|false>, <message>] - 수락/거부 확인
  • ["NOTICE", <message>] - 사람이 읽을 수 있는 메시지

실제로 대부분의 상위 NIP은 전송 계층을 변경하지 않는다. NIP-01의 동일한 EVENT, REQ, CLOSE 메시지를 사용하면서 새로운 이벤트 kind, 태그 또는 해석 규칙을 정의한다.

필터

필터는 검색할 이벤트를 지정하며, ids, authors, kinds, #e/#p/#t, since, until, limit 등의 필드를 포함한다. 하나의 필터 내 조건은 AND 논리를 사용한다. 하나의 REQ 내 여러 필터는 OR 논리를 사용한다.

상호운용성 참고사항

구현 버그를 자주 유발하는 두 가지 세부사항이 있다. 첫째, 클라이언트는 릴레이 응답을 전역 순서가 아닌 최종적 일관성으로 처리해야 한다. 서로 다른 릴레이가 히스토리의 서로 다른 부분집합을 반환할 수 있기 때문이다. 둘째, 대체 가능 이벤트와 주소 지정 가능 이벤트에서 “최신"이 프로토콜 모델의 일부이므로, 여러 릴레이가 서로 다른 결과를 반환할 때 클라이언트에 최신 유효 이벤트를 선택하기 위한 결정론적 규칙이 필요하다.


주요 출처:

언급된 뉴스레터:

같이 보기: