% \documentclass[11pt,a4paper]{article} \usepackage{polyglossia} \usepackage{xltxtra} \usepackage[margin=2cm,a4paper]{geometry}% http://ctan.org/pkg/geometry \usepackage{pdfpages} \usepackage{graphicx} \usepackage[ddmmyyyy]{datetime} \usepackage{booktabs} \usepackage{enumitem} \usepackage{amssymb} \usepackage{amsmath} \usepackage{bm} \usepackage[nomessages]{fp} \usepackage{caption} \usepackage{url} \usepackage{indentfirst} \usepackage[parfill]{parskip} \usepackage[ colorlinks=true , linkcolor=black , anchorcolor=black , citecolor=black , filecolor=black , menucolor=black , runcolor=black , urlcolor=blue]{hyperref} \usepackage{tikz} \usetikzlibrary{arrows,snakes,shapes,backgrounds,positioning,calc} \usepackage{marvosym} \usepackage{pifont} \usepackage{fontspec} \usepackage{fontawesome5} \usepackage{listings} \usepackage{verbatim} \usepackage{xcolor} \usepackage{float} % Needed for the floating environment \setmainlanguage{russian} \defaultfontfeatures{Ligatures=TeX,Mapping=tex-text} \setmainfont{Liberation Serif} \newfontfamily\cyrillicfont{Liberation Serif}[Script=Cyrillic] \newfontfamily{\cyrillicfonttt}{Liberation Mono}[Scale=0.8] \setlist{noitemsep} \setlength{\intextsep}{2cm} \newcommand{\term}[2]{\textit{#2}} \newcommand{\Peer}{\term{peer}{пир}} \newcommand{\Relay}{\term{relay}{Relay}} \newcommand{\Acc}{\term{acc}{Accumulator}} \newcommand{\Dude}{\term{dude}{Dude}} \newcommand{\Mailbox}{\term{mailbox}{Mailbox}} \renewcommand{\dateseparator}{.} \renewcommand*\contentsname{Содержание} \lstset{ language=Haskell, basicstyle=\ttfamily\small, keywordstyle=\color{blue}, commentstyle=\color{green}, stringstyle=\color{red}, % numberstyle=\tiny\color{gray}, % numbers=left, % stepnumber=1, showstringspaces=false, breaklines=true, frame=single, } \newfloat{Code}{t}{myc} \graphicspath{ {img/}} \title{Протокол <>} \begin{document} \maketitle \section{О документе} Документ рассматривает протокол доставки данных <> по паттерну $* \rightarrow 1$ <> в P2P окружении, как подпротокола для hbs2-peer. Протокол предполагается к использованию в ситуациях, когда между \term{actor}{акторами} нет общего авторизованного канала связи (в смысле hbs2-peer). Протокол не подразумевает нахождения акторов постоянно онлайн. Протокол не подразумевает использования механизмов вроде DNS, сертификатов PKCS и Authority, или каких-либо (скомпрометированных) централизованных сервисов. Протокол не подразумевает постоянной связности сети. Для адресации используются публичные ключи подписи. Для E2E шифрования используется механизм групповых ключей. Для упаковки и распространения данных используются примитивы hbs2-peer: \term{block}{блоки}, \term{merkle}{(шифрованные) деревья Меркла} с метаданными, и протоколы для работы с ними. Отличие от протоколов IMAP,SMTP,POP3 в том, что это другой протокол для другого окружения и исходящий из других предпосылок. Теоретически, в качестве несложного упражнения, можно поднять сервер IMAP как локальный фронтенд для hbs2-peer и тогда это будет IMAP-via-P2P. \section{Предпосылки} В текущей реализации HBS2 существуют следующие релевантные виды каналов (протоколов,\term{ref}{ссылок}): \paragraph{RefLog:} Обеспечивает коммуникацию по паттерну $1 \rightarrow *$, то есть один -- ко всем, канал распространяет сообщения одного автора. Пруфом записи является подпись \term{ksign}{ключом подписи} автора. \term{peer}{Пиры} должны подписаться на канал для его распространения, распространять канал (ссылку) может любой любой подписанный на него \term{peer}{пир}, так как валидность записей проверяется подписью автора. Канал является \term{GSET}{CRDT G-SET} записей. Метафорой рефлога может являться твиттер-аккаунт либо канал в телеграме, с одним писателем и множеством подписчиков. \paragraph{RefChan:} Обеспечивает коммуникацию по паттерну ${A} \rightarrow {R}$, то есть определяет множество \term{author}{авторов} $A$ и множество \term{reader}{читателей} $R$, и пруфом записи является подпись \term{author}{автора}, а \term{permission}{разрешением} на чтение --- опциональное шифрование сообщения \term{GK0}{групповым ключом}, куда входят читатели $R$, то есть $GK = \{ k_i \}_{i \in R}$, где каждый $k_i$ --- секретный ключ, зашифрованный публичным ключом $r_i$ из множества $R$. Кроме того, \term{refchan}{RefChan} определяет множество пиров ${P}$, которые могут отправлять сообщение в данный \term{refchan}{RefChan} и принимаются только такие сообщения. Данное ограничение необходимо для борьбы с атакой Сивиллы в случае, если \Peer{} игнорирует настройки ${A}$. Кроме того, у \term{refchan}{рефчана} есть владелец, который может менять настройки $A,R$, а блок настроек представляет собой \term{lww}{CRDT LWW регистр} со ссылкой на блок настроек, подписанный ключом владельца. Как видно, распространять сообщения из \term{refchan}{рефчана} могут только пиры $p_i \in P$ То есть, распространять транзакции может кто угодно, т.к каждая транзакция подписана ключом \term{peer}{пира}, но вот при запросе состояния будут учитываться только ответы пиров $p_i \in P$. Метафорой \term{refchan}{рефчана} является модерируемый чат с ограниченным множеством участников и администраторами. Таким образом, при наличии этих протоколов, мы можем \begin{enumerate} \item посылать сообщения от одного автора всему миру, то есть тем пирам, которые слушают (подписаны) на данный рефлог или \item осуществлять коммуникацию между ограниченными множествами пиров и авторов/читателей. \end{enumerate} Общим является то, что бы получать обновления рефлога или рефчана, мы (как пир) должны быть на них \term{subscribed}{подписаны}, т.е мы должны знать, что такие \term{ref}{ссылки} существуют и явно на них подписаться. Возникает вопрос, как можно обеспечить коммуникацию между произвольными \term{actor}{акторами} Алиса и Боб, у которых нет общего канала. Куда писать Алисе, что бы её сообщение достигло Боба? Рефчана, куда бы входили бы и Алиса и Боб в общем случае еще не существует, канал связи отсутствует. Алиса может быть подписана на какую-то ссылку Боба, но Боб не подписан на каналы Алисы. Или наоборот. Предлагается ввести новый протокол, \term{mailbox}{Mailbox}, который будет обеспечивать коммуникацию по паттерну $ * \rightarrow 1 $, то есть кто угодно может отправлять сообщения в почтовый ящик получателя. Получатель проверяет почтовый ящик и забирает оттуда сообщения. При этом обеспечивается отправка и доставка в условиях, когда \term{peer}{пиры} получателя и отправителя не находятся онлайн всё время. Данный протокол может быть полезен при установлении канала связи (например, создании общего рефчана), или просто оффлайн обмене сообщениями в условиях необязательного наличия каналов, например, при рассылке патчей и пулл/мерж реквестов в git или создании тикетов или для отсылки \textit{реакций}, в общем --- в любом случае, когда между акторами нет какого-то прямого канала. Важным является то, что получатель подписан только на свои, известные ему каналы, куда все (при выполнении определённых условий) могут отправлять сообщения. \section{Протокол} Протокол является подпротоколом \textit{hbs2-peer} и в отношении него верно всё, что верно для семейства этих протоколов --- авторизация и аутентификация пиров, черные и белые списки пиров, транспортное шифрование сообщений через ByPass и так далее. Идентификаторами являются публичные ключи подписи и шифрования. Для e2e шифрования используется тот же механизм групповых ключей. Передаваемыми единицами являются либо короткие сообщения \texttt{SmallEncryptedBlock} либо \term{merkle}{деревья Меркла} с шифрованием и метаданными. Протокол использует примитивы \textit{hbs2-core} и \textit{hbs2-peer}, как минимум: \begin{itemize} \item[-] SignedBox \item[-] SmallEncryptedBlock \item[-] MerkleTree \end{itemize} Протокол определяет служебные сообщения, специфичные для него, однако обмен данными идёт через обычные протоколы (GetBlock,GetBlockSize). Короткие сообщения могут доставляться непосредственно через (сигнальные) сообщения протокола. \subsection{Участники} \paragraph{Пир} Узел hbs2, поддерживающий данный протокол \paragraph{Актор} также \term{dude}{Dude}. Отправители и получатели сообщений. Требуется определить, что явлется идентификатором, или идентификаторами \Dude{}. \paragraph{Message} Сообщение. Определяется отправителем, получателем (получателями?), и содержимым. Видится,что сообщения могут быть двух классов: \textit{маленькое}, где всё сообщение вместе со служебной информацией помещается в один пакет и может быть доставлено непосредственно через коммуникационный протокол (GOSSIP), и \textit{большое}, когда \Peer{} поддерживающий данный протокол -- будет выкачивать все ссылки на части сообщения (большой текст, аттачменты и т.п.) \paragraph{Mailbox} Единица хранения и распространения сообщений. Mailbox бывают видов \term{Relay}{Relay} и \term{Accumulator}{Accumulator}. Разница между ними в том, что \Relay{} просто принимает и выкачивает сообщения, пришедшие по протоколу, и не пытается опрашивать соседей и объединять все известные сообщения дла \Dude{} в общее множество. Назначание \Relay{} --- временное хранение сообщений, пока их не заберёт один из \term{acc}{аккумуляторов}. \Mailbox{} \Relay{} занимает фиксированное, заранее определенное место на диске Поскольку мы в общем не знаем, забрали ли сообщение или нет, видится так, что \Relay{} организует ограниченную очередь сообщений, и при исчерпании лимита места, отведённого под почтовый ящик -- просто удаляет наиболее старые сообщения из очереди. Назначание \Acc{} -- хранить все сообщения для своего \Dude{}, т.е это его <<распределённый почтовый аккаунт>>. То есть, \Acc{} образуют \term{GSET}{CRDT G-SET} сообщений, и постепенно сходятся к одному значению (объединению всех сообщений всех \Acc{}). Очевидно, нужно предусмотреть или записи вида \textit{Tomb}, или иной способ удаления сообщений, например, через команду протокола. \Acc{} опрашивает всех соседей, получает ссылки на \term{merkle}{деревья~Меркла} сообщений, выкачивает сообщения и объединяет их в общее множество. \subsection{Примеры} \subsection*{Минимальная конфигурация} Два пира при условии наличия прямой сетевой доступности в обоих направлениях. \begin{figure}[h!] \centering \begin{tikzpicture}[ every label/.style={font=\scriptsize}, every node/.style={font=\scriptsize}, handle/.style={ draw=black , circle , inner sep=2pt }, box/.style={ draw=black , rounded corners, , anchor=base , font=\scriptsize , minimum height=1.5cm , text width=1.5cm , align=center }, ] \node[box,minimum height=2cm,label={below:{hbs2-peer}}] (dudeA) {{\underline{Dude~A}}\\ \Acc{}}; \node[ box , minimum height=2cm , label={below:{hbs2-peer}} , right=2.5cm of dudeA ] (dudeB) {{\underline{Dude~B}}\\ \Acc{}}; \draw[<->] (dudeA) -- (dudeB) node[midway,above] {Mailbox} node[midway,below] {GOSSIP}; \end{tikzpicture} \caption{минимальная конфигурация} \end{figure} \pagebreak \begin{itemize} \item[-] Обмен сообщениями возможен только при одновременном нахождении обоих пиров онлайн и наличия между ними связи \item[-] При потере узла Dude~A или Dude~B теряют все адресованные им сообщения \end{itemize} \subsection*{Примерно оптимальная конфигурация} \begin{figure}[h!] \centering \begin{tikzpicture}[ every label/.style={font=\scriptsize}, every node/.style={font=\scriptsize}, handle/.style={ draw=black , circle , inner sep=2pt }, box/.style={ draw=black , rounded corners, , anchor=base , font=\scriptsize , minimum height=1.5cm , text width=1.5cm , align=center }, ] \node[box,minimum height=2cm,label={below:{hbs2-peer}}] (dudeA) {{\underline{Dude~A}}\\ \Acc{}}; \node[ box , minimum height=2cm , label={below:{hbs2-peer}} , right=1.5cm of dudeA ] (relayA) {{\underline{Relay~1}}\\ \Relay{}}; \node[ box , minimum height=2cm , label={below:{hbs2-peer}} , below=1.5cm of dudeA ] (A1) {{\underline{A1}}\\ \Acc{}}; \node[ box , minimum height=2cm , label={below:{hbs2-peer}} , right=1.5cm of relayA ] (relayB) {{\underline{Relay~2}}\\ \Relay{}}; \node[ box , minimum height=2cm , label={below:{hbs2-peer}} , right=1.5cm of relayB ] (dudeB) {{\underline{Dude~B}}\\ \Acc{}}; \node[ box , minimum height=2cm , label={below:{hbs2-peer}} , below=1.5cm of dudeB ] (B1) {{\underline{B1}}\\ \Acc{}}; \node[ box, circle, draw, dashed , minimum size=2.5cm , minimum height=2.5cm , yshift=-0.5cm , right=2.75cm of A1 , label={below: protocol} ] (gossip) {{\underline{Mailbox}}\\GOSSIP }; \draw[<->,dashed] (dudeA) -- (relayA); \draw[<->,dashed] (dudeB) -- (relayB); \draw[<->,dashed] (dudeA) -- (A1); \draw[<->,dashed] (dudeB) -- (B1); \draw[<->,dashed] (dudeA) -- (gossip); \draw[<->,dashed] (dudeB) -- (gossip); \draw[<->,dashed] (relayA) -- (gossip); \draw[<->,dashed] (relayB) -- (gossip); \draw[<->,dashed] (A1) -- (gossip); \draw[<->,dashed] (B1) -- (gossip); \end{tikzpicture} \caption{Примерно оптимальная конфигурация} \end{figure} \begin{itemize} \item[-] Каждый Dude имеет некоторое количество Mailbox типа \Acc{} и \Relay{}. \item[-] Часть из них находится на пирах, которые большую часть времени доступны. \item[-] Часть доступных пиров имеет между собой прямую связь по GOSSIP. \item[-] Не требуется полная связность сети между Dude~A и Dude~B, достаточно, что бы была цепочка соединений, доступных хотя бы время от времени. \item[-] Сообщения Dude~A и Dude~B реплицированы между узлами типа \Acc{} (для каждого Dude -- свои мейлбоксы, естественно) и сообщения будут утрачены только в случае полной одновременной утраты всех узлов такого типа или если на всех этих узлах будут удалены \term{mailbox}{мейлбоксы} для Dude~A или Dude~B. \end{itemize} \pagebreak \section{Структуры данных} \subsection{Message} \begin{figure}[h!] \centering \begin{tikzpicture}[ every label/.style={font=\scriptsize}, every node/.style={font=\scriptsize}, handle/.style={ draw=black , circle , inner sep=2pt }, box/.style={ draw=black , rounded corners, , anchor=base , font=\scriptsize , minimum height=1.5cm , text width=1.5cm , align=center }, ] \node[ draw , minimum height=2cm , minimum width=12cm % , label={[yshift=5mm]south:SignedBox} ] (msg) {}; \node[draw,below=5mm of msg.north west,anchor=north west,xshift=2mm ] (sender) {$Sender$}; \node[above=1.5cm of sender.north west, anchor = south west, text width=1.8cm] (label1) {Публичный ключ отправителя}; \draw[->] (label1.south) -- ($(sender.north west)!(label1.south)!(sender.north east)$); % \node[draw,below=5mm of msg.north west,anchor=north west,xshift=2mm \node[draw,right=5mm of sender ] (flags) {$\{F\}$}; \node[draw,right=1mm of flags ] (rcpt) {$\{Recipients\}$}; \node[draw,right=1mm of rcpt ] (gk) {$GK^*$}; \node[draw,right=1mm of gk ] (ref) {$\{Ref\}$}; \node[draw,right=1mm of ref,minimum width=4cm ] (payload) {$Payload$}; \node[above=1.5cm of payload.north west, anchor = south west, text width=2cm] (labelP) {SmallEncryptedBlock}; \draw[->] (labelP.south) -- ($(payload.north west)!(labelP.south)!(payload.north east)$); \node[ draw , above=2mm of flags.north west, xshift=-2.5mm , anchor=north west , minimum width = 10cm , minimum height = 1.1cm , label={[yshift=-1mm]south:SignedBox} ] (box) {}; \end{tikzpicture} \caption{Структура сообщения} \end{figure} \paragraph{Sender:} Публичный (адрес) ключ подписи отправителя \paragraph{F:} Флаги (опции) сообщения. Например, TTL. TBD. \paragraph{Recipients:} Публичные ключи подписи (адреса) получателей Так как \term{peer}{пиру} нужно знать, в какой \Mailbox{} положить сообщение \paragraph{GK:} (Опционально) групповой ключ шифрования, которым зашифровано сообщение \paragraph{Refs:} Ссылки на части сообщения, (зашифрованные) \term{merkle}{деревья} с метаданными \paragraph{Payload:} Непосредственное короткое сообщение \section{Сообщения протокола} \subsection{SEND} Пир~A \Dude~A посылает сообщение \Dude~B или списку \Dude{} через Пир~X. Если Пир~X не поддерживает протокол -- то сообщение не обрабатывается. Если Пир~X поддерживает протокол -- то сообщение пересылается соседям Пир~X. Если Пир~X имеет \Mailbox{} для одного из получателей (\Dude{}) --- то сообщение кладётся в \Mailbox{}. Если это \Acc{} -- то просто кладётся. Если задана квота на размер и размер \Mailbox{} превышен (переполнен), то сообщение может игнорироваться. Если это \Relay{} то кладётся, если квота размера не превышена. Если превышена, то удаляются наиболее старые сообщения, пока не освободится достаточно места на диске. Если не удалось, то сообщение удаляется. Если сообщение содержит хэш-ссылки (вложения), то они скачиваются в соответствии с политиками (размеры,etc). Каждая ссылка сообщения проверяется на целостность, скачивание продолжается, пока оно не станет целостным или до тех пор, пока (определяется политикой). Если пир \Dude~A не имеет блоков, на которые ссылается сообщениe --- то мы прекращаем скачивать зависимости. Возможно, такое сообщение стоит дропнуть. Авторизация: сообщение SEND подписано \Dude~A, отправителем сообщения. \subsection{CHECK-MAIL} Получатель (владелец \Mailbox{}) запрашивает хэш \term{merkle}{дерева Меркла} сообщений, содержащихся в \Mailbox{}. Авторизация: сообщение SEND подписано \Dude --- владельцем \Mailbox{}. \subsection{MAIL-STATUS} Ответ на сообщение \texttt{CHECK-MAIL}, содержит хэш ссылку \term{merkle}{дерева Меркла} сообщений, содержащихся в \Mailbox{} или признак отсутствия сообщений. Поведение. Получаем сообщения из дерева, пишем в результат только валидные. Если затесались невалидные -- то это повод для каких-то действий в отношении пира, который обслуживает \Mailbox{}. Авторизация: сообщение подписано \Dude --- владельцем \Mailbox{}. \subsection{DELETE} Удалить сообщение для \Mailbox{}. Содержит признак рассылать по GOSSIP или нет, допустим, оно адресовано только одному конкретному узлу. Содержит предикат, какие сообщения удалять (все, для определенного отправителя, старше, чем X, больше, чем X, и т.п.). TBD. Полезно для освобождения ресурсов и экономии сетевого трафика. Опциональное. Авторизация: сообщение подписано \Dude --- владельцем \Mailbox{}. \subsection{SET-POLICY} Устанавливает политики обработки сообщений и \Mailbox{}. Параметры: GOSSIP (да/нет) Данные: \term{term}{дерево Меркла} текстового файла с инструкциями. Авторизация: сообщение подписано \Dude --- владельцем \Mailbox{}. Инструкции: TBD, расширяемо. Возможный примерный вид: \begin{verbatim} dudes drop * peers drop * dudes accept GmtNGbawvxqykjDaBiT3LsqP7xqDPHVTxqfBqoFQ6Mre dudes accept 3WtddmcE8zzgBAPR7Bu7mKMaVMTN423NNXSPUJp3Hx42 dudes delete G5K9QvFaomXdP4Y9HcYEt3diS2cCWyU8nBd2eTzrcq1j dude set-pow-factor 94wrDGvcnSitP8a6rxLSTPBhXSwdGYrQqkuk2FcuiM3T 10 peer set-pow-factor Gu5FxngYYwpRfCUS9DJBGyH3tvtjXFbcZ7CbxmJPWEGH 10 peers accept yFSaUfb97ZRtQqzHWdERsR7KJvN8qyWX1M8rJcxnsiu peer cooldown * 120 peer cooldown yFSaUfb97ZRtQqzHWdERsR7KJvN8qyWX1M8rJcxnsiu 60 dude cooldown * 120 dude cooldown G5K9QvFaomXdP4Y9HcYEt3diS2cCWyU8nBd2eTzrcq1j 300 \end{verbatim} \section{Возможные атаки и противодействие им} \subsection{Спам} Массовые нежелательные рассылки. \paragraph{Тактика борьбы:} \begin{itemize} \item[-] Отвергать сообщения с множеством реципиентов. \item[-] Вводить cooldown периоды для пиров и \Dude{}. \item[-] Вводить (общие) белые списки и принимать сообщения только от них. \item[-] Сделать ненулевой стоимость попадания в белые списки. \item[-] Ввести иструменты репутации и т.п. \item[-] Ввести регулируемый PoW на сообщения. \end{itemize} \subsubsection{DoS} Атаки на работоспособность пира и сети в целом. \subsubsection{Посылка огромных данных} TBD \subsubsection{Посылка невалидных данных} TBD \subsubsection{Ссылки на отсутствующие данные} TBD \subsubsection{Анализ метаданных, построение графа взаимодействий} Поскольку \texttt{GOSSIP} проходит через пиров и имеет открытые метаданные, можно сохранять граф коммуникаций и запоминать публичные ключи. Что бы этому противодействовать -- можно только взаимодействовать с заведомо надёжными пирами через, возможно, отдельную сеть. К сожалению. Для по-настоящему анонимного и неотслеживаемого общения нужно использовать другие механизмы. \section{Примеры применения} \subsection{Issues/Pull requests} \begin{figure}[h!] \centering \begin{tikzpicture}[ every label/.style={font=\scriptsize}, every node/.style={font=\scriptsize}, handle/.style={ draw=black , circle , inner sep=2pt }, box/.style={ draw=black , rounded corners, , anchor=base , font=\scriptsize , minimum height=1.5cm , text width=1.5cm , align=center }, db/.style={ cylinder , draw , fill=gray!10 , minimum height=1cm , minimum width=1.5cm , shape border rotate=90 , aspect=0.5 } ] \node[box] (hbs2-peer1) {hbs2-peer1}; \node[box,right=3cm of hbs2-peer1] (hbs2-peer2) {\underline{hbs2-peer2}\\Relay}; \node[box,below=3cm of hbs2-peer2] (hbs2-peerN) {\underline{hbs2-peerN}\\\Acc{}}; \draw[->] (hbs2-peer1) -- (hbs2-peer2) node[below,midway] {MAILBOX:SEND} node[above,midway] {PR~Message}; \draw[->] (hbs2-peer2) -- (hbs2-peerN) node[left,midway] {MAILBOX:SEND} node[left,midway,yshift=4mm] {PR~Message}; \node[box,right=2cm of hbs2-peerN] (process) {filter-process}; \node[box,right=2cm of process] (fixme) {fixme}; \node[db,right=1cm of fixme,anchor=west,yshift=-4mm] (db) {fixme-state}; \draw[->] (process.150) -- ($(hbs2-peerN.north east)!(process.150)!(hbs2-peerN.south east)$) node[midway,above] {MAIL-CHECK}; \draw[->] (process.180) -- ($(hbs2-peerN.north east)!(process.180)!(hbs2-peerN.south east)$) node[midway,above] {READ}; \draw[->] (process) -- (fixme) node[above,midway] {import}; \draw[->] (fixme.south) -- ($(fixme.south) - (0,+2cm)$) -| (hbs2-peerN.south) node[below,near start] {refchan:export}; \draw[->] (fixme.east) -- (db.152); \end{tikzpicture} \end{figure} Пользователь формирует сообщение специального вида (plaintext/fixme) которое посылается по протоколу MAILBOX получателю -- владельцу мейлобокса, который указан в manifest проекта, как контакт для посылки подобных сообщений. На некоем хосте существует процесс, который время от времени проверяет \Mailbox{} и при обнаружении новых сообщений экспортирует их в fixme, который, в свою очередь, помещает их в RefChan делая доступными и видимыми для подписчиков этого рефчана. Обновления данного Issue/PR возможны, если в качестве fixme-key выбран некий уникальный идентификатор, который и будет указан в каждом сообщении. \end{document}