В TweetNaCl.js используется реализация Ed25519 — схемы
цифровой подписи на основе эллиптических кривых (EdDSA). Базовый набор
операций включает:
Подпись в классической модели представляет собой результат функции:
signature = Sign(privateKey, message)Verify(publicKey, message, signature) → true/falseЭта модель строго одно-подписантная и не предусматривает нативной поддержки мультиподписи. Любые мультиподписные схемы в экосистеме NaCl/TweetNaCl.js строятся поверх базовых примитивов.
Классическая схема цифровой подписи предполагает наличие одного владельца секретного ключа. В мультиподписных сценариях требуется выполнение условия:
Ключевые требования:
В NaCl/TweetNaCl.js отсутствует встроенный механизм мультиподписи, поэтому применяются схемы композиции поверх Ed25519.
Самый простой подход — каждый участник подписывает сообщение отдельно.
Для участников A, B, C:
каждый получает одно и то же сообщение m
каждый вычисляет подпись:
sA = Sign(a_private, m)sB = Sign(b_private, m)sC = Sign(c_private, m)итоговая структура:
S = {sA, sB, sC}Проверяются все подписи независимо:
Следующий шаг — создание общего публичного ключа группы.
Публичные ключи участников комбинируются в один:
P = P1 + P2 + ... + PnВ контексте Ed25519 прямое сложение не всегда безопасно без дополнительных протоколов, но для учебных моделей используется упрощённая схема.
Каждый участник вносит вклад в подпись, но итоговая подпись формируется централизованно:
Ed25519 не является схемой, где безопасна простая линейная агрегация ключей без протоколов защиты от rogue-key атак.
Чтобы избежать атак подмены ключей, вводится обязательная стадия фиксации списка участников.
Все участники согласуют:
P1...PnПример:
group = hash(P1 || P2 || P3)
Каждый участник создаёт частичную подпись:
Si = Sign(private_i, message || group_id)Далее выполняется агрегация:
S = combine(S1, S2, ..., Sn)В NaCl/TweetNaCl.js это реализуется вручную как структура данных:
const multisig = {
message,
groupId,
signatures: [sigA, sigB, sigC],
publicKeys: [pkA, pkB, pkC]
}
Проверка:
Более сложный класс — пороговые схемы, где требуется не вся группа, а только часть.
Любые два участника могут подписать сообщение.
NaCl не поддерживает threshold natively, поэтому используется конструкция:
Каждый участник имеет:
xiКаждый участник создаёт:
si = Sign(xi, message)Сборщик формирует:
const thresholdSig = {
msg,
sigs: [
{ id: 1, sig: sig1 },
{ id: 3, sig: sig3 }
]
}
Проверка проходит по правилам:
MuSig — современная схема мультиподписи для Schnorr-подписей. Ed25519 не является Schnorr напрямую, но можно имитировать поведение.
Ed25519 использует хеширование с включением секретного ключа и сообщения, что делает прямую агрегацию сложной.
import nacl from 'tweetnacl'
import util from 'tweetnacl-util'
const keyA = nacl.sign.keyPair()
const keyB = nacl.sign.keyPair()
const keyC = nacl.sign.keyPair()
const msg = util.decodeUTF8("important message")
const sigA = nacl.sign.detached(msg, keyA.secretKey)
const sigB = nacl.sign.detached(msg, keyB.secretKey)
const sigC = nacl.sign.detached(msg, keyC.secretKey)
const multisignature = {
message: msg,
publicKeys: [
keyA.publicKey,
keyB.publicKey,
keyC.publicKey
],
signatures: [sigA, sigB, sigC]
}
function verifyMulti(msig) {
return msig.signatures.every((sig, i) => {
return nacl.sign.detached.verify(
msig.message,
sig,
msig.publicKeys[i]
)
})
}
Если не фиксировать набор публичных ключей, участник может:
Решение:
Подпись может быть повторно использована.
Решение:
message = msg || timestamp || sessionId
NaCl не даёт компактного представления мультиподписи, что приводит к:
Для практических систем используется структура:
{
version: 1,
algorithm: "ed25519-multisig",
groupHash: "...",
message: "...",
sigs: [...],
bitmap: "101"
}
bitmap позволяет:
При увеличении числа участников возникают проблемы:
Типичные оптимизации:
Сценарии:
Это приводит к тому, что мультиподпись в TweetNaCl.js всегда является протокольной надстройкой, а не встроенной функцией криптографической библиотеки.