diff --git a/docs/papers/Makefile b/docs/papers/Makefile index b483ce40..a17c2f45 100644 --- a/docs/papers/Makefile +++ b/docs/papers/Makefile @@ -1,4 +1,4 @@ -all: hbs2-git-problem hbs2-git-new-repo +all: hbs2-git-problem hbs2-git-new-repo hbs2-git-doc .PHONY: all clean @@ -10,6 +10,8 @@ hbs2-git-problem: hbs2-git-problem.pdf hbs2-git-new-repo: hbs2-git-new-repo.pdf +hbs2-git-doc: hbs2-git-doc-0.24.1.pdf + clean: rm -f *.aux *.log *.nav *.out *.snm *.vrb *.toc *.pdf diff --git a/docs/papers/hbs2-git-doc-0.24.1.tex b/docs/papers/hbs2-git-doc-0.24.1.tex new file mode 100644 index 00000000..207b6e5a --- /dev/null +++ b/docs/papers/hbs2-git-doc-0.24.1.tex @@ -0,0 +1,1026 @@ +\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,arrows.meta,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 +\usepackage{fancyvrb} +\usepackage[most]{tcolorbox} +\usepackage{authblk} +\usepackage{url} + +\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} + +\renewcommand\Authands{ и } +\renewcommand\Affilfont{\itshape\small} % Мелкий и курсивный шрифт для аффилиаций + +\newtcolorbox{myverbatim}{colback=lightgray, colframe=lightgray, boxrule=0pt, arc=0pt, + top=0pt, bottom=0pt, left=0pt, right=0pt, + boxsep=5pt, leftupper=5pt, rightupper=5pt} + + +\newcommand{\term}[2]{\textit{#2}} +\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{hbs2-git 0.24.1} + +\author{% + email: dzuikov@gmail.com | telegram: @voidlizard +} + +\begin{document} + +\maketitle + +% \noindent % Убирает отступ слева для таблицы, чтобы она начиналась с края страницы +% \begin{tabular}{l l} % Две колонки выравнивания текста по левому краю +% email: & dzuikov@gmail.com \\ +% telegram: & @voidlizard \\ +% \end{tabular} + +\tableofcontents + +\section{Идея} + +hbs2-git это адаптер, позволяющий git работать с HBS2 в качестве бекенда для сохранения и получения +объектов. HBS2 это распределённый P2P CAS, позволяющий обмениваться данными, синхронизировать их, +подписываться на специфические источники при помощи механизма \term{references}{ссылок}. + +Таким образом, hbs2-git позволяет производить децентрализованную синхронизацию репозиториев без +участия какого-то выделенного сервера/сервиса, используя вместо него множество пиров, которые +находят соседей при помощи механизма \term{pex}{PEX} (Peer Exchange) --- то есть при помощи +broadcast сообщений, DNS бутстрапа и списка известных пиров, то есть примерно теми же способами, +что используются в прочих децентрализованных сетях. + +Авторизация и аутентификация осуществляются при помощи криптографических механизмов: криптоподписи +и шифрования. + +Механизмы эти работают на уровне ссылок, блоков, протоколов и можно еще дополнительно шифровать +собственный контент, если будет такое желание и необходимость. + +\begin{description} + \item[Ссылка] --- это некий уникальный идентификатор, указывающий на блок в \textit{хранилище} по + его хэшу; Ссылки бывают локальные и те, которые распространяются по некоему протоколу. Примеры + распространяемых ссылок и их протоколов: RefLog, LWWRef, RefChan. + + \item[Блок] --- это данные в хранилище, имеющие хэш, которым этот блок и адресуется. Блок может + содержать произвольные данные. Это могут быть сериализованные структуры данных, разбираемые + hbs2, но не обязательно. +\end{description} + + +Вместо того, что бы делать $git~push$ в remote на каком-то сервере с доменным именем, доступом и +прочее, $git~push$ происходит в \term{references}{ссылку}, которая поддерживается всеми пирами, +которые согласны поддерживать эту ссылку. + +Узел hbs2-peer может постить \term{transaction}{транзакции} обновления ссылок и получать их, а также +получать журнал транзакций в виде дерева Меркла. + +Как было сказано выше, hbs2-git использует механизм \term{references}{ссылок} для своей работы. + +Ссылка представляет собой некий идентификатор, как правило, ассоциированный с публичным ключом +шифрования. Технически это может быть что угодно, что может быть хэшировано, но это что-то +должно быть идентифицировано с тем, кто эту ссылку обновляет, а так же, по возможности должно быть +уникально в рамках вселенной. + +Одним из способов добиться подобной уникальности --- является использование пары публичный +ключ/приватный ключ и публичного ключа в качестве уникального идентификатора. + +Владелец приватного ключа очень заинтересован в том, что бы его приватный ключ был уникален и +известен только ему и он изо всех сил старается эту уникальность поддерживать, в частности, +использует качественный генератор случайных чисел и никому свой приватный ключ не сообщает. + +Так же мы предполагаем, что подобрать секретный ключ по известному публичному ключу --- очень +сложная задача. + +Ссылки могут быть разных типов. Конкретно, hbs2-git использует ссылки двух видов: + +\begin{description} + \item[LWWRef] В терминах CRDT это Last~Write~Wins~Register, где некое значение + ассоциировано с монотонно возрастающим счётчиком, и все участники (пиры) принимают + то значение, у которого этот счётчик больше. Так же: lwwref, lww-cсылка. + + \item[RefLog] В терминах CRDT это G-Set или же Grow~Only~Set транзакций, так же множество + транзакций представлено деревом Меркла. Транзакции (в рамках RefLog) упорядочены строго + по их хэшам. Содержимое транзакций может быть произвольно и определяется клиентским приложением. + Наличие порядка, таким образом, приводит к тому, что всё множество может быть адресовано + неким хэшем (дерева Меркла) и хэш этот одинаков для одинакового набора транзакций. Так же: + рефлог. +\end{description} + +Поскольку CRDT в общем случае не обладают стойкостью к византийским ошибкам и атакам, мы +используем криптографические подписи и хэши, что бы минимизировать возможности для таких +атак или же ввести некоторые допущения. + +Например, допущениями для рефлогов и lww-ссылок является то, что они кому-то принадлежат, +и только этот кто-то может их изменять. + +Для lww-ссылки только владелец приватного ключа может изменять эту ссылку, а для рефлога -- +добавлять транзакции в рефлог может только владелец приватного ключа, идентифицирующего +рефлог. + +Технически это означает, что для lww-ссылок -- пирами будут приниматься только такие ссылки, +которые содержат подписанный владельцем приватного ключа блок и подпись валидна. + +Для рефлога -- в рефлог будут включаться только такие транзакции, которые подписаны владельцем +приватного ключа рефлога, и подпись верна. + +Механизм мержа рефлога подразумевает объединение всех верных транзакций, таким образом, мы ожидаем, +что рефлог рано или поздно сойдется у всех пиров, участвующих в обмене. + +Механизм обновления lww-ссылки подразумевает, что каждый пир возьмёт то значение, у которого +больше счётчик и подпись для которого валидна. + +Таким образом, если владелец lww-ссылки на разных пирах опубликует значение ссылки с одинаковым +счётчиком, но разным содержимым --- мы получим разные значения на разных пирах. Для того, что бы +значения всё же сходились -- в случае, если счётчик одинаков --- выбирается то значение, хэш +которого больше. Никакого другого смысла, кроме обеспечения лучшей сходимости, это не несёт. + +Предполагается, что поскольку значение ссылки контролируется владельцем, владелец заинтересован +в том, что бы обеспечить однозначность значения этой ссылки. Если он по ошибке (например, с разных +хостов) опубликовал <<одновременно>> транзакции с одинаковым счётчиком, но разными значениями, то: + +\begin{enumerate} + \item В итоге всеми будет принято то значение, у которого хэш лексикографически больше; + \item У владельца есть шанс устранить беспорядок, опубликовав <<правильное>> значение. +\end{enumerate} + +Мы видим, что такая ссылка так себе подходит для обеспечения конкурентных обновлений чего-либо, +поэтому подразумеваем, что она будет использоваться для хранения некоей настроечной информации и +меняться относительно редко и осознанно, непосредственно владельцем. Кроме того, факт конкурентного +публикования ссылки с разных пиров означает, что владелец экспонировал свой приватный ключ много +раз, а не в одном месте в один момент времени. Значит, он небрежен и к нему есть вопросы -- +например, можно не доверять такому владельцу (не принимать ссылок от него). + +\section{Компоненты} + + +\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,minimum height=2cm] (peer) {hbs2-peer}; + + \node[ box + , minimum height=1cm + , text width=6em + , left=5cm of peer.north west + , anchor = north east + ] (git-remote-hbs2) {git-remote-hbs2}; + + \node[ box + , minimum height=1cm + , text width=6em + , below=1cm of git-remote-hbs2.south east, anchor = north east + ] (git-hbs2) {git-hbs2}; + + \node[ rectangle + , draw + , dashed + , above = 2mm of git-remote-hbs2.north west + , xshift = -2mm + , text width=2.8cm + , minimum height=3.6cm + , anchor=north west] (tools) {}; + + \node[box, minimum height=1cm, below=2cm of git-hbs2.south, anchor=north] (git) {git}; + + \node[db,below=1.6cm of git.north,anchor=north] (repo) {git repo}; + + \draw[->] (git.south) -- ($(repo.north) - (0,+2mm)$) ; + + \draw[->] (git.north) -- ($(tools.south west)!(git.north)!(tools.south east)$) + node[midway,right] {CLI/PIPE}; + + \node[ db + , left=1cm of git-remote-hbs2.north west, anchor=north east + , yshift=-1cm + ] (state) {State}; + + \draw[->] (git-remote-hbs2.west) -| ($(state.north) - (0,+2mm)$) ; + \draw[->] (git-hbs2.west) -| (state.south); + + \node[handle,left=1cm of peer.west] (rpc) {}; + + \draw[-] (peer.west) -- (rpc.east) node [midway,above] {RPC}; + + \draw[->] ($(tools.north east)!(rpc)!(tools.south east)$) -- (rpc); + + + \node[ db + , below=1cm of peer.south + ] (store) {Store}; + + \draw[->] (peer.south) -- ($(store.north) - (0,+2mm)$) ; + + \node[ box + , minimum height=1cm + , below=1cm of store.south, anchor=north + ] (hbs2) {hbs2}; + + \draw[->] (hbs2) -- (store); + + \node[ box + , left=1cm of hbs2.west, anchor=east + , text width=2cm + , minimum height=1cm + ] (hbs2-keyman) {hbs2-keyman}; + + + \draw[-,thick] (tools.320) -| (hbs2-keyman.140) node[near end,right] {library}; + + \node[ db + , below=1cm of hbs2-keyman.south + ] (keyman-state) {State}; + + \draw[->] (hbs2-keyman) -- ($(keyman-state.north) - (0,+2mm)$); + +\end{tikzpicture} +\end{figure} + +\subsection*{git-remote-hbs2} + +Исполняемый файл, git-remote-helper, хэлпер git для протокола hbs2:// + +\subsection*{git-hbs2} + +Исполняемый файл, различные функции для работы с hbs2-git, например, +export для первоначальной инициализации \textit{ссылки} и т.п. + +\subsection*{git} + +Процесс git + +\subsection*{hbs2-keyman} + +Индексатор ключей + +\subsection*{hbs2} + +Утилита для управления storage, ключами и прочим. + +\subsection*{hbs2-git-subscribe} + +Подписаться на обновления ссылки lwwref и reflog для репозитория +без импорта самого репозитория. + +Может быть полезно на промежуточных хостах, что бы распространять +данные. но не клонировать сам репозиторий. + +\subsection*{hbs2-peer} + +Процесс hbs2-peer + +\subsection*{Store} + +Хранилище объектов HBS2 (меркл-деревья, блоки, ссылки, ...) + + + +\section{Установка} + +В настоящий момент hbs2-git доступен в виде исходных кодов или кэшированных +артефактов и пакета для пакетного менеджера nix. + +В случае использования nix необходимо включить поддержку nix flakes и nix profile + +\subsection{Установка при помощи nix profile} + +\begin{verbatim} + +nix profile install git+http://git.hbs2.net/BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP \ + --substituters http://nix.hbs2.net:6000 \ + --trusted-public-keys git.hbs2.net-1:HYIYU3xWetj0NasmHrxsWQTVzQUjawOE8ejZAW2xUS4= \ +\end{verbatim} + +\subsection{Сборка при помощи cabal} + +Проект представляет собой типичный проект для языка haskell и собирается системой сборки cabal. + +В случае такой сборки зависимости придется установить самостоятельно, либо же использовать +прилагаемые файлы ( flake.nix ) для установки окружения и установки зависимостей. + +Желательно использовать direnv + +\begin{verbatim} +nix develop # при наличии +cabal build all +make symlinks # при желании, создаёт симлинки в каталоге ./bin +\end{verbatim} + +\subsection{Сборка при помощи nix} + +\begin{verbatim} +nix build +\end{verbatim} + +\section{Использование} + +\begin{enumerate} + \item Настройка hbs2-peer + \item Запуск hbs2-peer + \item Работа с hbs2-git +\end{enumerate} + +\subsection{hbs2-peer} + +hbs2-git является клиентским приложением для hbs2-peer. +Соответственно, для функционирования необходимо запустить hbs2-peer командой + +\begin{verbatim} +hbs2-peer run +\end{verbatim} + +Конфигурационный файл по-умолчанию находится в \$XDGDIR/hbs2-peer/ + +\begin{verbatim} +$(HOME)/.config/hbs2-peer/config +\end{verbatim} + +Конфиг по умолчанию будет создан самим hbs2-peer при первом запуске, +если разрешена запись в упомянутую локацию. + +Минимальный конфиг: + +\begin{verbatim} +$ cat ~/.config/hbs2-peer/config +;; hbs2-peer config file + +;; порт для UDP +listen "0.0.0.0:7351" + +;; порт для TCP +listen-tcp "0.0.0.0:10351" + +; default storage is $HOME/.local/share/hbs2 +; storage "./storage" + +; edit path to a keyring file +; key "./key" + +; это секретный ключ, и путь относительно конфига +key "./default.key" + +; очищать очередь блоков на скачивание при рестарте +; рекомендуется: on +; downloads-del-on-start on + +; принимать анонсы блоков +; +; * -- от всех +; от конкретного пира: +; accept-block-announce "PEER-KEY" +; +; рекомедуется включать для конкретных известных пиров +; accept-block-announce * + +; поллить ссылки +; poll reflog 5 "BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP" +; poll lwwref 5 "BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP" +; poll refchan 10 ... + + +; запретить общаться с указанным пиром +; blacklist "5tZfGUoQ79EzFUvyyY5Wh1LzN2oaqhrn9kPnfk6ByHpf" + +; порт для http +; рекомендовано: включить +http-port 5000 + +; логгирование +; trace off +; trace on + +; trace1 off +; trace1 on + +; debug on +; debug off + +; сокет для RPC +; рекомендуется: не трогать +; rpc unix "/tmp/hbs2-rpc.socket" + +\end{verbatim} + +\subsection{hbs2-git} + +\subsubsection{Введение} + +Репозитории git сделаны в предположении, что у каждого репозитория +есть только один владелец, который может туда писать. + +Таким образом, у каждого участника проекта --- собственный форк +репозитория, куда он пишет. + +Каждый участник уведомляет других участников о наличии форка, +они добавляют (или нет) его репозиторий (ссылку) к себе в проект +в качестве \textit{git remote}. + +\paragraph{PR} + +В git есть собственный механизм pull requests --- \texttt{git-request-pull}. + +Он может быть использован, если remote участника, который его предлагает, +уже добавлен в репозиторий. + +hbs2-git не предлагает никаких собственных механизмов для PR, однако +существует средство fixme + +\texttt{hbs2://Fujv1Uy4W5d9Z7REEArMxbXSJ8nLLn4dYuvaAs8b86hr} + +которое реализует механизм тикетов и PR с хранением информации непосредственно +в коде проекта, вернее, в объектах git. + +Может быть использован и любой другой распределенный трекер. + +\subsubsection{Инициализация репозитория} + +\paragraph{Создание ключа подписи} + +\begin{verbatim} + +# сделать конфиг для hbs2-keyman +$ mkdir -p ~/.config/hbs2-keyman + +# сказать hbs2-keyman, где искать ключи +$ cat > ~/.config/hbs2-keyman/config +key-files "/home/hbs2/*.key" +^D + +# сделать новый ключ +$ hbs2 keyring-new > newrepo111.key + +# обновить индекс +$ hbs2-keyman update + +# проверить, что ключ есть +$ hbs2-keyman list +8vFu9S79ysdWag4wek53YWXbC5nCRLF7arGp6181G4js sign /home/hbs2/newrepo111.key + +\end{verbatim} + +Созданный \texttt{newrepo111.key} является \textbf{секретом}, необходимо обеспечить его надежное +хранение без попадания в третьи руки. + +\paragraph{Инициализация нового репозитория} + +\begin{verbatim} + +mkdir newrepo111 + +cd newrepo111/ + +git init + +echo HI > README + +git add README + +git commit -a m 'init' + +# инициализировать новый репозиторий + +git hbs2 export --new --public 8vFu9S79ysdWag4wek53YWXbC5nCRLF7arGp6181G4js + +# добавить его как git remote +git remote add origin hbs21://8vFu9S79ysdWag4wek53YWXbC5nCRLF7arGp6181G4js + +git fetch origin + +[user@host:~/newrepo111]$ git fetch origin +From hbs21://8vFu9S79ysdWag4wek53YWXbC5nCRLF7arGp6181G4js + * [new branch] master -> origin/master + +# дальше работать с remote, как обычно + +\end{verbatim} + +\subsubsection{Клонировать (чужой) репозиторий} + +\begin{verbatim} +git clone hbs2://8vFu9S79ysdWag4wek53YWXbC5nCRLF7arGp6181G4js +Cloning into '8vFu9S79ysdWag4wek53YWXbC5nCRLF7arGp6181G4js'... + +\end{verbatim} + +Если клонируется зашифрованный репозиторий, то ключ, которым планируется +расшифровка должен быть добавлен в hbs2-keyman ( hbs2-keyman update \&\& hbs2-keyman list ) + +\subsubsection{Подписка на репозиторий} + +Подписаться на репозиторий, но не клонировать его: + +\begin{verbatim} +hbs2-git-subscribe +\end{verbatim} + +например: + +\begin{verbatim} +hbs2-git-subscribe 8vFu9S79ysdWag4wek53YWXbC5nCRLF7arGp6181G4js + +[user@host:~/tmp]$ hbs2-peer poll list | rg 8vFu9S79ysdWag4wek53YWXbC5nCRLF7arGp6181G4js +8vFu9S79ysdWag4wek53YWXbC5nCRLF7arGp6181G4js 17 lwwref +\end{verbatim} + +В подписках должна быть и ссылка lwwref и соответствующий ей рефлог + +\subsubsection{Разное} + +\paragraph{Посмотреть ссылки} + +\begin{verbatim} +[user@host:~/newrepo111]$ git hbs21 tools show-remotes +1 8vFu9S79ysdWag4wek53YWXbC5nCRLF7arGp6181G4js 57HBZKQeFgsecCvFYgoZXTDpUdpxqZnfTaWMAL6k5MuH +\end{verbatim} + +Версия, ссылка (lwwref), рефлог + +\paragraph{Посмотреть ссылку} + +\begin{verbatim} +[user@host:~/newrepo111]$ hbs2-peer lwwref get 8vFu9S79ysdWag4wek53YWXbC5nCRLF7arGp6181G4js +(lwwref + (seq 1) + (value "BqvS2V3AfakKYwAgTG6fMGW53C6L19YhWaXDY2gdqMkK") + ) +\end{verbatim} + +Десериализованный блок значения ссылки: версия, ссылка на блок данных + +Получить значение рефлога +\begin{verbatim} +[user@host:~/newrepo111]$ hbs2-peer reflog get 57HBZKQeFgsecCvFYgoZXTDpUdpxqZnfTaWMAL6k5MuH +5wwctwSJkNN3tY4jjr2G2nzutsfzNpwpbDDgG2EJcHxL +\end{verbatim} + +\paragraph{Посмотреть транзакции} + +На входе -- хэш дерева Меркла (текущее значение рефлога). См. предыдущий шаг + +\begin{verbatim} +[user@host:~/newrepo111]$ hbs2 cat -H 5wwctwSJkNN3tY4jjr2G2nzutsfzNpwpbDDgG2EJcHxL +2vFtNUg2qNuSsaZLybL9Zbp2M79G141zJHSudEeEprfA +\end{verbatim} + +Ни для чего не нужен в контексте hbs2-git, только для общего развития. + +\paragraph{Посмотреть git heads из текущего RepoHead} + +\begin{verbatim} +[user@host:~/newrepo111]$ git hbs21 tools show-ref +bdfb845c343eb1da14fe1969219c303c1397980e HEAD +bdfb845c343eb1da14fe1969219c303c1397980e refs/heads/master +\end{verbatim} + +\section{Структуры данных} + +\subsection{Состояние (State)} + +\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] (lwwref) {lwwref}; + \node[box,right=4cm of lwwref] (reflog) {reflog}; + + \draw[->] (lwwref.east) -- (reflog.west) + node[above,midway] {HKDF(SK(lwwref, seed))} + node [below,midway] {PubKey}; + + \node[ rectangle split + , rectangle split parts=3 + , draw + , font=\scriptsize + , text width=3cm + , below = 0.5cm of lwwref + ] (merkle) at ($(lwwref)!.5!(reflog)-(0,2)$) + { + $T_1 : SequentialRef$ + \nodepart{two} + ... + \nodepart{three} + $T_n : SequentialRef$ + }; + + + \draw[->] (reflog.south) |- (merkle.east) node [near start, right] {merkle~tree}; + + \node[ box + , below=1cm of merkle, xshift=-1cm, anchor = north east + , text width=3cm + , align=left + , rectangle split + , rectangle split parts=2] (rhead) + {\hspace*{.5cm}{RepoHead}\\ + \nodepart{two} + GK0\\ + time\\ + git refs\\ + manifest\\ + }; + + \draw[->] (merkle.south) -- (rhead.north); + + + \node[ box + , below=1cm of merkle, xshift=1cm, anchor = north west + , text width=3cm + , align=left + , rectangle split + , rectangle split parts=2] (log) + {\hspace*{.5cm}{log~tree}\\ + \nodepart{two} + }; + + \draw[->] (merkle.south) -- (log.north); + + + \node[ box + , below=1cm of log + , text width=3cm + , rectangle split + , rectangle split parts=3] (logdata) + { + $GitObjectPack_1$ + \nodepart{two} + ... + \nodepart{three} + $GitObjectPack_n$ + }; + + + \draw[->] (log.south) -- (logdata.north) node [midway,right] {merkle~tree}; + + +\end{tikzpicture} +\end{figure} + +\textit{Стeйт} состоит из \textit{ссылки} $L_1 : lwwref$ от ключа автора $PK_1(SK_1)$. Ссылка ссылается на + +\textit{рефлог} по публичному ключу $PK_2 = PK(SK_2), SK_2 = HKDF(SK_1,seed)$, выводимому из приватного ключа автора + ( $PK_1 = PK(SK_1)$ ). + + $seed$ содержится в блоке значения ссылки $L_1$. + + $PK_2$ также содержится в блоке значения ссылки $L_1$. + + Таким образом, все имеют доступ к публичному ключу рефлога $PK_2$ и могут проверять подпись автора + для даннго рефлога, и только автор имеет доступ к приватному ключу $SK_2$ и имеет возможность + подписывать свои транзакции, публикуемые в \textit{рефлоге} для $PK_2$. + + Ранее использовалась прямая ссылка на рефлог для репозитория, что не давало возможность + поменять структуру данных репозитория, например, с изменением версии hbs2-git без изменения + самой ссылки. + + Введение еще одного уровня косвенности через lww-ref позволяет не менять исходную (публикуемую) + ссылку, при этом версионировать сам рефлог. Может быть полезным, например, что бы начать с чистого + листа, уменьшив количество исторических данных (транзакций, объектов и т.п.). + + Пример: старое значение ссылки на репозиторий hbs2-git выглядело так: + + \begin{verbatim} +BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP + \end{verbatim} + + Это буквально base-58 от бинарного представления некоторого публичного ключа, ассоциированного с + рефлогом, владельцем которого является автор hbs2. + + Новое значение ссылки на репозиторий hbs2-git выглядит так: + + \begin{verbatim} + BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP + \end{verbatim} + + Однако, теперь это не ссылка типа reflog, а ссылка типа lwwref. Это другой тип данных, + и значение типа \texttt{lwwref(BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP} + имеет другой хэш. + + Таким образом, старая версия hbs2-git продолжит работать с рефлогом + + \texttt{BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP}, + + новая же версия использует lwwref. Ассоциированный с данным lwwref можно посмотреть, например, так: + + \begin{verbatim} +$ git hbs2 tools show-remotes +4 BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP BKtvRLispCM9UuQqHaNxu4SEUzpQNQ3PeRNknecKGPZ6 + \end{verbatim} + +Данная команда покажет, какие lwwref используются в качестве git remote в данном репозитории, +и какие у них версии lwwref и какие значения. + +Каждая транзакция $T_n$ содержит полный самодостаточный снимок репозитория, т.е технически +возможно развернуть репозиторий, имея только транзакцию. + +Каждая транзакция $T_n$ имеет ссылку на дерево Меркла \textit{паков} объектов git +$GitObjectPack_n$, полученных при помощи команды \texttt{git pack-objects} и индекс, +показывающий, какие объекты входят в каждый \textit{пак}. \textit{Паки объектов} упорядочены, +каждый следующий \textit{пак} в дереве содержит новые по отношению к предыдущим \textit{пакам} +объекты. + +Такая структура (а не, например, дерево отдельных объектов) выбрана потому, что: + +\begin{enumerate} + \item git порождает огромное количество слабо различающихся маленьких объектов, следовательно, + огромную избыточность данных. Лучше всего свои объекты упаковывает сам git, используя, в + частности, бинарные дельты. + \item каждый отдельный объект (\textit{блок}) подразумевает, в общем случае, несколько + сетевых запросов для получения: запрос размера, запрос чанков, ответ. Таким образом, + повышение количества объектов ведёт к ухудшению скорости синхронизации. + \item Выше скорость импорта объектов в репозиторий, так как он выполняется стандартной + командой \texttt{git unpack-objects} +\end{enumerate} + +\subsubsection*{RepoHead} + +Заголовок транзакции, содержит необходимые метаданные, в частности, ссылки git, соответствующие +объектам, упакованным в паки. + +Заметим, что несмотря на то, что у нас каждой операции \textit{PUSH} соответствует транзакция, +которая содержит снимок всего репозитория --- реально передаваться будут только новые блоки +для всех деревьев Меркла. И выкачиваться пирами будут только отсутствующие у них блоки, таким +образом, количество копируемых данных на каждый \textit{PUSH} невелико и соответствует реально +изменившимся данным. + + +\subsubsection*{Контроль доступа и шифрование} + +\texttt{hbs2-git} поддерживает открытые и закрытые репозитории. Закрытые репозитории +реализованы при помощи зашифрованных групповым ключом $GK_0$ деревьев Меркла. + +Групповой ключ $GK_0$ представляет собой некоторый секрет для симметричного алгоритма шифрования, +зашифрованный публичными ключами участников. + +Групповой ключ можно менять в процессе работы, например, при добавлении/удалении новых участников. + +При изменении группового ключа для всех <<старых>> деревьев будет произведено обновление метаданных +и перешифрование секрета для нового ключа. + +Для <<новых>> деревьев будет сгенерирован новый секрет. + +Нет смысла обновлять <<старые>> секреты, так как уже записанная в HBS2 информация никуда оттуда уже +не денется, будут ли обновлены перегенерированы старые секреты или нет --- данные, зашифрованные +<<старыми>> секретами все равно останутся в системе. + +Заметим, что удаление участника из группового ключа лишит его доступа к последующим изменениям, но +не тем, которые у него уже есть. + +Метаданные в RepoHead не шифруются, таким образом, публично доступны \textit{манифест} проекта, +название бранчей, тегов и хэши объектов. В целом, публичность этих данных не является острой +необходимостью, и их можно в дальнейшем шифровать. Пока выглядит так, что это не та информация, +которую есть большой смысл скрывать. + +Система команд спроектирована таким образом, что бы затруднить, или, по крайней мере, не упрощать +раскрытие данных, если репозиторий зашифрован. Таким образом мы пытаемся защититься от случайных +непреднамеренных утечек. + + +\subsubsection*{Индексация ключей} + +Безопасное хранение ключей в настоящий момент возложено на пользователя. + +Система в настоящий предолагает, что в процессе работы ключи доступы +на файловой системе, что может быть реализовано при помощи различного +рода монтируемых на время сеанса зашифрованных FS. + +hbs2-keyman индексирует ключи в соответствии с настройками, таким образом, +ему известно, в каком файле находится та или иная ключевая пара. + +Данная информация используется при криптографических операциях, т.е +hbs2-keyman сообщает компонентам, где искать необходимые ключи. + +hbs2-keyman не сохраняет ключи, только индексирует. + +hbs2-keyman не отображает секретные ключи, только соответствующие им публичные. + +В настоящий момент не все средства используют hbs2-keyman, но в итоге все +будут переведены на него. + + + +\section{Операции} + +Основными операциями \texttt{hbs2-git} являются Import и Export. + +\subsection{Import} + +Настраивает ссылки и импортирует объекты из ссылок hbs2 + +\begin{figure}[h!] +\centering +\begin{tikzpicture}[ node distance=2cm, auto, thick + , every label/.style={font=\scriptsize} + , every node/.style={font=\scriptsize} + ] + % Define blocks style + \tikzstyle{block} = [rectangle + , draw + , fill=white + , text width=5em + , text centered + , rounded corners + , minimum height=2em + ] + \tikzstyle{decision} = [ diamond + , draw + , fill=white + , text width=5em + , text centered + , node distance=2cm + , inner sep=0pt + ] + + \tikzstyle{line} = [draw, -Latex] + + \node [block,text width=10em] (init) {Start importRepoWait}; + \node [block, below of=init] (subscribe) {Subscribe to LWWRef}; + \node [block, below of=subscribe] (fetch) {Fetch LWWRef}; + \node [decision, below of=fetch, yshift=-1cm] (checklww) {LWW exists?}; + \node [block, left of=checklww, node distance=5cm] (throwerror) {ThrowError}; + \node [block, right of=checklww, node distance=5cm] (readblock) {Read LWW Block}; + \node [block, below of=readblock] (readreflog) {Read RefLog}; + \node [decision, below of=readreflog] (checkreflog) {RefLog OK?}; + \node [block, right of=checkreflog, node distance=5cm] (processreflog) {Process RefLog}; + \node [block, left of=checkreflog, node distance=5cm] (retryreflog) {Retry fetch RefLog}; + \node [block, below of=checkreflog, node distance=4cm] (continuenext) {Continue Next Steps...}; + + \path [line] (init) -- (subscribe); + \path [line] (subscribe) -- (fetch); + \path [line] (fetch) -- (checklww); + \path [line] (checklww) -- node [near start] {no} (throwerror); + \path [line] (checklww) -- node [near start] {yes} (readblock); + \path [line] (readblock) -- (readreflog); + \path [line] (readreflog) -- (checkreflog); + \path [line] (checkreflog) -- node [near start] {yes} (processreflog); + \path [line] (checkreflog) -- node [near start] {no} (retryreflog); + \path [line] (processreflog) |- (continuenext); + \path [line] (retryreflog) |- (continuenext); + \path [line] (throwerror) |- (continuenext); + + +\end{tikzpicture} + + +\caption{Import} +\end{figure} + +\pagebreak + +\subsection{Export} + +Экспортирует данные в хранилище, обновляет ссылки, +рассылает уведомления. + +\begin{figure}[h!] +\centering + +\begin{tikzpicture}[ node distance=1.5cm and 1.5cm, auto, thick + , every node/.append style={font=\scriptsize} + , decision/.style={diamond, draw, fill=white, text width=4em, text badly centered, node distance=2cm, inner sep=0pt} + , block/.style={rectangle, draw, fill=white, text width=5em, text centered, rounded corners, minimum height=4em} + , line/.style={draw, -Latex} + , check/.style={rectangle, draw, fill=white, text width=5em, text centered, minimum height=4em} + ] + + % Place nodes + \node [block] (init) {Start export}; + \node [block, right=of init] (subscribe) {Subscribe to LWWRef}; + \node [decision, right=of subscribe] (newCheck) {New Key?}; + \node [block, right=of newCheck] (waitOrInit) {Wait or Init LWWRef}; + \node [block, below=of waitOrInit] (makeRepoHead) {Make RepoHead}; + \node [block, below=of makeRepoHead] (createTx) {Create Transaction}; + \node [block, left=of createTx] (postTx) {Post Transaction}; + \node [block, left=of postTx] (exit) {Exit}; + + % Draw edges + \path [line] (init) -- (subscribe); + \path [line] (subscribe) -- (newCheck); + \path [line] (newCheck) -- node {no} (waitOrInit); + \path [line] (newCheck) |- node [near start] {yes} (makeRepoHead); + \path [line] (waitOrInit) -- (makeRepoHead); + \path [line] (makeRepoHead) -- (createTx); + \path [line] (createTx) -- (postTx); + \path [line] (postTx) -- (exit); + +\end{tikzpicture} + +\caption{Export} +\end{figure} + +\pagebreak + +\section{Разное} + +В документации или где-то еще могут спорадически появляться префиксы hbs21 (hbs21://). + +Этот префикс был присвоен новому протоколу hbs2, что бы он не интерферировал +со старым и можно было бы одновременно пользоваться двумя версиями hbs2-git. + +После релиза новой версии и прекращении поддержки старой -- данный префикс +не используется, однако должен пониматься hbs2-git (вместо hbs2://) + +\section{Ссылки} + +\begin{description} + \item[Блог:] @voidlizard.online + \item[Канал:] @hbs2dev \url{https://t.me/hbs2dev} + \item[Группа:] @hbs2support \url{https://t.me/hbs2_support} +\end{description} + +\end{document} + + diff --git a/hbs2-peer/lib/HBS2/Peer/Proto/LWWRef/Internal.hs b/hbs2-peer/lib/HBS2/Peer/Proto/LWWRef/Internal.hs index 55e76ea6..7866f0cf 100644 --- a/hbs2-peer/lib/HBS2/Peer/Proto/LWWRef/Internal.hs +++ b/hbs2-peer/lib/HBS2/Peer/Proto/LWWRef/Internal.hs @@ -96,13 +96,18 @@ lwwRefProto adapter pkt@(LWWRefProto1 req) = do Just rv -> do blk' <- getBlock sto rv maybe1 blk' (forcedUpdateLwwRef sto key bs) $ \blk -> do - let seq0 = deserialiseOrFail @(SignedBox (LWWRef e) e) blk + + let lww0 = deserialiseOrFail @(SignedBox (LWWRef e) e) blk & either (const Nothing) Just >>= unboxSignedBox0 <&> snd - <&> lwwSeq - when (Just (lwwSeq lww) > seq0) do + let seq0 = lwwSeq <$> lww0 + let lwwv0 = lwwValue <$> lww0 + + when ( Just (lwwSeq lww) > seq0 + || (Just (lwwSeq lww) == seq0 && Just (lwwValue lww) > lwwv0) + ) do forcedUpdateLwwRef sto key (serialise box) where diff --git a/nix/peer/flake.lock b/nix/peer/flake.lock index 62438eff..1d21fe87 100644 --- a/nix/peer/flake.lock +++ b/nix/peer/flake.lock @@ -295,11 +295,11 @@ "suckless-conf": "suckless-conf_2" }, "locked": { - "lastModified": 1710558394, - "narHash": "sha256-Io0xjrddivrVznym5rpvwzO0PjP2++dnNlpXtGPyfU0=", + "lastModified": 1710568478, + "narHash": "sha256-hzePQ+JY/42lp9qPXU7hEJq6I+TU8nLQmKFvy0wXOpg=", "ref": "lwwrepo", - "rev": "ce7c1f37c046fa5806613a1d3eb67d4d6b00e84c", - "revCount": 989, + "rev": "c6b90878c3485b98220f7d87ad14e047ab7dcf2a", + "revCount": 991, "type": "git", "url": "http://git.hbs2/BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP" },