Розробка системи управління документообігом на блокчейні

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка системи управління документообігом на блокчейні
Складний
від 1 тижня до 3 місяців
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1284
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1196
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    901
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1119
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    586
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    853

Розробка системи управління документооборотом на блокчейні

Класична задача: нотаріально завірена контракт, акт приймання або фінансовий документ повинні бути верифіковані будь-якою стороною без звертання до центральної організації. Існуючі рішення — централізовані реєстри або PKI-інфраструктура з CA — працюють, поки організації довіряють один одному та центральному регулятору. У cross-border сценаріях або при спорах ця умова перестає виконуватися.

Блокчейн тут не замінює систему зберігання документів — він замінює нотаріуса. Документ живе в захищеному сховищі (IPFS, S3), а факт його існування у конкретний момент часу, неїзмінність та список авторизованих підписантів фіксуються on-chain. Цей принциповий розподіл потрібно розуміти з самого початку.

Криптографічні основи документообороту

Document Commitment

Будь-яка система proof-of-existence побудована на одному принципі: hash(document) публікується on-chain. Але реалізація важлива в деталях.

Простий хеш (anti-pattern):

mapping(bytes32 => uint256) public timestamps;

function notarize(bytes32 docHash) external {
    timestamps[docHash] = block.timestamp;
}

Проблема: якщо два документи відрізняються тільки timestamp'ом — вони дадуть різні хеші, обидва будуть «нотаріально завірені». Проблема глибша: якщо документ відозваний — немає механізму це відразити. Немає зв'язку між хешем та підписантом — хто завгодно може нотаріально завірити чужий документ.

Правильна структура:

struct DocumentRecord {
    bytes32 contentHash;      // keccak256 від вмісту
    bytes32 metadataHash;     // хеш від метаданих (без вмісту)
    address registrant;       // хто зареєстрував
    uint256 registeredAt;
    DocumentStatus status;
    bytes32[] signatories;    // DID хеші підписантів
    uint256 expiresAt;        // 0 = бессрочно
}

enum DocumentStatus { PENDING, ACTIVE, REVOKED, EXPIRED }

mapping(bytes32 => DocumentRecord) public documents;
// docId = keccak256(contentHash + registrant + registeredAt)

event DocumentRegistered(
    bytes32 indexed docId,
    bytes32 indexed contentHash,
    address indexed registrant
);

event DocumentSigned(
    bytes32 indexed docId,
    bytes32 indexed signatoryDid,
    bytes signature
);

event DocumentRevoked(
    bytes32 indexed docId,
    address revokedBy,
    string reason
);

Криптографічна часова мітка (RFC 3161 on-chain)

Для юридично значимих документів важливий не просто факт публікації хеша, а доказ що документ існував у момент T. Проблема: block.timestamp в EVM можна маніпулювати майнером/валідатором в межах ~15 секунд. Для більшості use cases це несущественно, але для юридичних документів краще використовувати:

  • Block hash commitment: публікуємо keccak256(docHash || blockHash(N-1)) — привʼязка до конкретного блоку, не просто timestamp
  • Chainlink або інший VRF oracle для додаткового джерела ентропії
  • Anchor to Bitcoin через OP_RETURN — Bitcoin timestamping через сервіси типу OpenTimestamps
function registerWithBlockCommitment(
    bytes32 contentHash,
    bytes32 metadataHash
) external returns (bytes32 docId) {
    // Привʼязуємо до хеша попереднього блоку
    bytes32 blockCommitment = keccak256(abi.encodePacked(
        contentHash,
        blockhash(block.number - 1),
        block.timestamp,
        msg.sender
    ));
    
    docId = keccak256(abi.encodePacked(contentHash, msg.sender, block.timestamp));
    
    documents[docId] = DocumentRecord({
        contentHash: contentHash,
        metadataHash: metadataHash,
        registrant: msg.sender,
        registeredAt: block.timestamp,
        status: DocumentStatus.ACTIVE,
        signatories: new bytes32[](0),
        expiresAt: 0
    });
    
    emit DocumentRegistered(docId, contentHash, msg.sender);
    return docId;
}

Електронний підпис та multi-party signing workflow

EIP-712 структуровані підписи

Для підписування документів замість простого ecrecover використовуємо EIP-712 — типізовані структуровані дані. Це дає читаємі prompt'и в MetaMask та захист від cross-chain replay:

bytes32 private constant DOCUMENT_SIGNING_TYPEHASH = keccak256(
    "DocumentSigning(bytes32 docId,bytes32 contentHash,uint256 signedAt,string signatoryRole)"
);

struct DocumentSigning {
    bytes32 docId;
    bytes32 contentHash;
    uint256 signedAt;
    string signatoryRole;
}

function signDocument(
    bytes32 docId,
    string calldata signatoryRole,
    uint256 signedAt,
    bytes calldata signature
) external {
    DocumentRecord storage doc = documents[docId];
    require(doc.status == DocumentStatus.ACTIVE, "Document not active");
    require(signedAt <= block.timestamp, "Future timestamp");
    require(block.timestamp - signedAt < 3600, "Signature too old");
    
    // Восстанавливаємо підписанта з EIP-712 підпису
    bytes32 structHash = keccak256(abi.encode(
        DOCUMENT_SIGNING_TYPEHASH,
        docId,
        doc.contentHash,
        signedAt,
        keccak256(bytes(signatoryRole))
    ));
    
    bytes32 digest = _hashTypedDataV4(structHash);
    address signer = ECDSA.recover(digest, signature);
    
    require(isAuthorizedSignatory(signer, docId), "Not authorized signatory");
    require(!hasAlreadySigned(signer, docId), "Already signed");
    
    doc.signatories.push(keccak256(abi.encode(signer)));
    
    emit DocumentSigned(docId, keccak256(abi.encode(signer)), signature);
    
    // Перевіряємо завершення
    if (_checkSigningComplete(docId)) {
        doc.status = DocumentStatus.ACTIVE;
        emit DocumentFullySigned(docId);
    }
}

Workflow multi-party approval

Для складних документів (договір купівлі-продажу, акт приймання) потрібен workflow з порядком підписання:

struct SigningWorkflow {
    bytes32 docId;
    SigningStep[] steps;
    uint256 currentStep;
    bool isSequential;  // послідовна або паралельна підпис
}

struct SigningStep {
    address[] requiredSigners;
    uint256 threshold;      // скільки з requiredSigners повинні підписати
    uint256 deadline;
    bool completed;
}

Приклад: акт приймання робіт

  1. Виконавець підписує (крок 1, обов'язково)
  2. Технадзор замовника підписує (крок 2, обов'язково)
  3. Фінансовий директор замовника підписує АБО будь-який з двох авторизованих представників (крок 3, поріг 1 з 2)

Зберігання документів: гібридна архітектура

IPFS + Encryption

Документи зберігаються зашифрованими в IPFS. Ключ шифрування управляється окремо — через on-chain access control:

import { create } from 'ipfs-http-client'
import { encrypt, decrypt } from '@metamask/eth-sig-util'

async function uploadEncryptedDocument(
  file: Buffer,
  docId: string,
  authorizedAddresses: string[]
): Promise<string> {
  // Генеруємо симетричний ключ
  const aesKey = crypto.getRandomValues(new Uint8Array(32))
  
  // Шифруємо документ
  const iv = crypto.getRandomValues(new Uint8Array(12))
  const cipher = await crypto.subtle.importKey('raw', aesKey, 'AES-GCM', false, ['encrypt'])
  const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, cipher, file)
  
  // Шифруємо AES ключ публічними ключами кожної авторизованої адреси
  const encryptedKeys: Record<string, string> = {}
  for (const address of authorizedAddresses) {
    const pubKey = await getPublicEncryptionKey(address)  // з MetaMask
    encryptedKeys[address] = encryptForPublicKey(pubKey, Buffer.from(aesKey))
  }
  
  // Публікуємо в IPFS: зашифрований документ + encrypted keys bundle
  const ipfs = create({ url: process.env.IPFS_API })
  const { cid } = await ipfs.add(JSON.stringify({
    encrypted: Buffer.from(encrypted).toString('base64'),
    iv: Buffer.from(iv).toString('base64'),
    encryptedKeys,
  }))
  
  return cid.toString()
}

Версіонування документів

Документи в business workflow редагуються. Потрібна ланцюг версій з можливістю довести що версія V2 є апдейтом V1:

struct DocumentVersion {
    bytes32 contentHash;
    bytes32 previousVersionId;  // 0x0 для першої версії
    address updatedBy;
    uint256 updatedAt;
    string changeDescription;
}

mapping(bytes32 => bytes32[]) public documentVersions;  // docId → version hashes
mapping(bytes32 => DocumentVersion) public versionDetails;

function publishNewVersion(
    bytes32 docId,
    bytes32 newContentHash,
    string calldata changeDescription
) external onlyDocumentOwner(docId) {
    bytes32[] storage versions = documentVersions[docId];
    bytes32 previousHash = versions.length > 0 ? versions[versions.length - 1] : bytes32(0);
    
    bytes32 versionId = keccak256(abi.encodePacked(
        docId, newContentHash, block.timestamp, versions.length
    ));
    
    versionDetails[versionId] = DocumentVersion({
        contentHash: newContentHash,
        previousVersionId: previousHash,
        updatedBy: msg.sender,
        updatedAt: block.timestamp,
        changeDescription: changeDescription
    });
    
    versions.push(versionId);
    emit DocumentVersionPublished(docId, versionId, versions.length - 1);
}

Access Control та конфіденціальність

Zero-Knowledge disclosure

У деяких випадках потрібно довести факт про документ без розкриття його вмісту. Приклад: довести що сума в контракті > X, не розкриваючи точну суму.

Вирішується через ZK-proof (наприклад, Groth16 або PLONK):

// Circom circuit: доказ що сума контракту > threshold
pragma circom 2.0.0;

template ContractAmountProof() {
    signal input contractAmount;  // приватний інпут
    signal input threshold;       // публічний
    signal input documentHash;    // публічний (верифікатор знає документ)
    
    signal output isAboveThreshold;
    
    // Перевіряємо що сума > порогу
    component gt = GreaterThan(64);
    gt.in[0] <== contractAmount;
    gt.in[1] <== threshold;
    isAboveThreshold <== gt.out;
    
    // Привʼязка до конкретного документу (commitment)
    // Верифікатор упевнений що ми знаємо contractAmount для цього документу
}

Selective disclosure через NFC/QR

Для фізичних документів (паспорт транспортного засобу, сертифікат якості): QR-код містить docId. Сканування QR, верифікатор запитує on-chain запис та отримує тільки публічні метаданні. Для доступу до повного документу потрібен авторизований гаманець.

Інтеграція з legacy системами

Більшість enterprise-клієнтів мають ERP/ECM системи (SAP, 1C, OpenText). Інтеграція через:

Webhook підхід: при створенні документу в ECM — викликається webhook → oracle service → on-chain реєстрація. Прозоро для користувачів ERP.

OpenAPI adapter:

openapi: "3.0.0"
info:
  title: Document Notarization API
paths:
  /documents/register:
    post:
      summary: Register document hash on blockchain
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                documentId: { type: string }
                contentHash: { type: string, pattern: "^0x[0-9a-f]{64}$" }
                metadata: { type: object }
                signatories: 
                  type: array
                  items: { type: string }  # Ethereum addresses

Blockchain-агностичний шар: розробляємо абстракцію поверх конкретного блокчейну. Клієнт отримує documentId — UUID у звичному форматі. Маппинг UUID → on-chain docId зберігається в PostgreSQL. Це дозволяє мігрувати між блокчейнами без змін API контрактів.

Юридичні аспекти

Blockchain-нотаризація має різний правовий статус в різних юрисдикціях:

  • EU (eIDAS 2.0) — визнає електронні підписи, але blockchain timestamp ≠ кваліфікована електронна підпис автоматично
  • США — ESIGN Act визнає електронні підписи, але для specific use cases (іпотека, нотаріат) вимагає більше
  • ОАЕ — Dubai Blockchain Strategy, активне прийняття blockchain-based документів

Для юридично обов'язуючих документів розробляємо гібридний підхід: blockchain timestamp + кваліфікована електронна підпис (КЭП) через сертифікований CA.

Етапи проекту

Фаза Вміст Тривалість
Requirements Аналіз документообороту, типи документів, учасники, юрисдикція 2–3 тижні
Core contracts Document registry, signing workflow, access control 3–4 тижні
Storage layer IPFS integration, encryption, versioning 2–3 тижні
API & Integration REST API, webhooks, ERP-коннектори 3–4 тижні
Frontend Інтерфейс підписання, верифікації, управління 3–4 тижні
Security audit Смарт-контракти + backend 2–3 тижні
Pilot Запуск з реальними документами, зворотний зв'язок 2–3 тижні

Всього: 17–24 тижні. Критичний шлях — інтеграція з ERP-системами та юридична експертиза в конкретній юрисдикції.