Перейти к содержанию

Идентификаторы

Любую сущность можно выразить точкой на кривой secp256k1.
Чат, пользователь, группа, конкретное сообщение — всё может стать публичной точкой, если нам это удобно.

В контексте использования точки как идентификатора мы получаем несколько ценных возможностей:

  • Любая группа, канал или пользователь может быть представлен как уникальная точка на кривой, что позволяет нам агрегировать сообщения вокруг сущности, которой они принадлежат.
  • Любой, кто создал сущность, может доказать свое авторство, что на уровне криптографии создает примитивную систему управления. Например, создатель канала может публиковать свои сообщения, добавляя к ним подпись с помощью имеющегося у него скаляра. Тем самым любой участник канала может убедиться, что то что он читает было написано истинным владельцем, а не подставным лицом.

Подпись и проверка

Идентификатор как сущность и права владения

Identifier — абстрактная сущность: канал, группа, тема и т. п.
Владение SecretIdentifier означает владение правами на эту сущность: можно публиковать от её имени и доказывать авторство.


Подпись произвольных данных

Любое сообщение можно подписать SecretIdentifier, а проверять — и по публичному Identifier, и по SecretIdentifier.

1
2
3
4
5
const { SecretIdentifier } = require("kaspeak-sdk");

const sid = SecretIdentifier.random();
const sig = await sid.sign("hello");
const ok1 = await sid.verify(sig, "hello");
1
2
3
4
5
6
const { Identifier, SecretIdentifier } = require("kaspeak-sdk");

const sid = SecretIdentifier.random();
const id = Identifier.fromHex(sid.hex);
const sig = await sid.sign("hello");
const ok2 = await id.verify(sig, "hello");

Типы подписей:

В Kaspeak SDK есть два режима подписи сообщений:

  • single — подпись только приватным ключом отправителя (по умолчанию).
  • multi — агрегированная подпись из секретного ключа отправителя и секретного идентификатора (SecretIdentifier).

Выбор режима задаётся статическим полем класса сообщения.

Выбор режима подписи для сообщения

1
2
3
4
5
6
import { BaseMessage, type SignatureType } from "kaspeak-sdk";

export class ChannelMessage extends BaseMessage {
    static messageType = 3;
    static signatureType: SignatureType = "multi";
}

Отправка сообщения в режиме мультподписи

ОТПРАВКА сообщения с мультподписью возможна только при передаче SecretIdentifier в sdk.createPayload — его секрет участвует в агрегации подписи. Для отправки сообщения в режиме single допустима передача и обычного Identifier, и SecretIdentifier. Не передавайте секрет идентификатора в открытом виде.

const { Kaspeak, SecretIdentifier } = require("kaspeak-sdk");

const sdk = await Kaspeak.create(6, "TEST");
await sdk.connectNode();

const msg = new ChannelMessage();
const body = await sdk.encode(msg);

const channel = SecretIdentifier.random();
const tx = await sdk.createTransaction(body.length);
const outIds = sdk.getOutpointIds(tx);

const plHex = await sdk.createPayload(outIds, ChannelMessage, channel, body);
const txid = await sdk.sendTransaction(tx, plHex);

Получение

SDK сам различает signatureType и проверяет агрегированную подпись. Получателю не нужно знать секрет идентификатора, чтобы ее проверить. SDK так же проверяет потребность в агрегированной подписи у зарегестрированного типа сообщения. Вы не получите сообщение, если его тип подписи не соответствует ожидаемому.

1
2
3
4
sdk.registerMessage(ChannelMessage, async (header, raw) => {
    const decoded = await sdk.decode(header, raw);
    console.log(header.signatureType);
});

Вы можете использовать Identifier и SecretIdentifier как обычный повторяющийся идентификатор, не прибегая к сложной криптографии, описанной далее. Однако, если Вы хотите охватить все возможности Kaspeak SDK, то рекомендую внимательно ознакомиться со следующим разделом, который способен добавить в прозрачный blockdag элемент конфиденциальности.

Суть

Пусть у нас есть произвольная «начальная точка» BasePoint.
Любой следующий объект той же природы — это просто BasePoint · t, где t — известный обоим сторонам скаляр-множитель.

  • Так мы получаем дерево или цепочку сущностей, не храня лишних данных.
  • Внешний наблюдатель видит только случайные точки, не понимая их семантики.

Формула идентификатора

Идентификатор (Identifier) — это 33-байтовая сжатая точка secp256k1, вычисляемая по формуле

ID(i) = PK_A · (chainKey^i mod n), i ≥ 1

  • PK_A — открытый ключ автора (может быть и любой другой фиксированной точкой).
  • chainKey — 32-байтовый скаляр, общий для обоих участников;
  • i — порядковый номер сообщения внутри диалога.
  • n— порядок кривой secp256k1.

Формирование chainKey

const { secret, chainKey } = sdk.deriveConversationKeys(peerPublicKey)

secret — результат ECDH.
chainKey = SHA-256(secret) > bigint ( mod n ).

Вывод идентификатора

Операция Формула
следующий (+1) ID_(i+1) = ID_i · chainKey
предыдущий (-1) ID_(i-1) = ID_i · chainKey⁻¹
произвольный +k ID_(i+k) = ID_i · chainKey^k

Все вычисления — обычная скалярная мультипликация точек.

1
2
3
const id2 = Identifier.fromChainKey(chainKey, 2, sdk.publicKey) // первый ID
const id3 = id1.next(chainKey)  // второй (ID₁·k)
const id1 = id1.prev(chainKey)  // предыдущий (ID₁·k⁻¹)

Внутри next/prev используется быстрое возведение в степень.

Свойства безопасности

  1. Конфиденциальность цепочки. Без chainKey нельзя отличить ID_1 от ID_999; все точки выглядят случайно.
  2. Одна пара - одна точка. chainKey тождественно вычисляется только для пары А и Б. Третья сторона не сможет «склеить» цепочки разных участников.
  3. Неотслеживаемость. Алгоритм «следующий / предыдущий» работает лишь с секретным множителем, следовательно нельзя проследить историю сообщений, даже имея их полный архив.

Благодаря этому нехитрому приему и мощному шифрованию из раздела Шифрование сообщений, мы можем вести по настоящему безопасное и конфиденциальное общение внутри сети Kaspa. Теперь злоумышленник не только не может расшифровать наши сообщения, но даже быть уверенным в том, с кем именно Вы общаетесь.