mrcv

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 1, 2026 License: MIT Imports: 13 Imported by: 0

README

MRCV — MineRouter Crypto Vault (Go)

Device-Bound Cryptocontainer

Go-реализация MRCV — побайтово совместима с @minerouter/mrcv (Node/Electron).

XChaCha20-Poly1305 + Argon2id encrypted KV storage
Cryptographically bound to a device

Файл .mrcv, созданный в Electron, открывается в Go, и наоборот — формат, криптография и device-binding полностью идентичны.

Почему MRCV

Традиционные зашифрованные файлы можно скопировать и открыть где угодно, если есть ключ. MRCV решает это через device binding: хранилище криптографически привязано к устройству, для которого создано, и не открывается на другой машине.

┌──────────────────────────────────────────────┐
│  .mrcv file                                  │
│                                              │
│  Header (84 bytes, AEAD-protected)           │
│  ├─ BindingId (SHA-256 of device binding)    │
│  ├─ Salt + Nonce                             │
│  └─ Flags (mode, format)                     │
├──────────────────────────────────────────────┤
│  Payload (XChaCha20-Poly1305 encrypted JSON) │
└──────────────────────────────────────────────┘

Ключевые свойства:

  • Device-bound — открывается только на устройстве с совпадающим BindingId
  • Два режимаbound (при несовпадении файл сохраняется) или strict (самоуничтожение при несовпадении)
  • Без паролей — ключ выводится из device binding (Argon2id)
  • Один файл — всё в одном .mrcv
  • AEAD — заголовок привязан к шифротексту через Poly1305

Совместимость с JS-версией

Параметр JS (libsodium) Go
KDF Argon2id, iter=3, mem=256MB argon2.IDKey (те же)
Шифр XChaCha20-Poly1305, nonce 24 chacha20poly1305.NewX
AAD заголовок 84 байта тот же
Binding SHA-256(mobo UUID | disk serial | [MAC]) тот же
Формат файла MRCV + version + flags + salt + nonce + bindingId + ct + tag побайтово тот же

Проверено кросс-тестами в обе стороны:

  • compatibility_test.go — открывает реальный файл, созданный @minerouter/mrcv (v2.0.2, фикстура в testdata/js-vault.mrcv) и сверяет binding-вектор байт-в-байт. Запускается обычным go test ./....
  • scripts/cross-node.sh — обратное направление: Go создаёт vault, Node.js читает его. Требует npm i @minerouter/mrcv локально.
Совместимость и версионирование

Формат .mrcv — стабильный контракт: magic, смещения полей, параметры Argon2id, шифр и AAD остаются неизменными между реализациями.

  • Изменения, ломающие формат или криптографию, допускаются только в новой мажорной версии (semver) — обе реализации (JS и Go) обновляются вместе.
  • Изменения API-обёртки (методы, конфигурация, ошибки) мажорную версию не трогают — они не влияют на байты файла на диске.

Старые файлы, созданные любой реализацией MRCV, открываются обеими.

Установка

go get github.com/WooonderkinG33/MineRouter-MRCV-Go

Зависимость: только golang.org/x/crypto (официальная, Argon2id + XChaCha20).

Использование

import "github.com/WooonderkinG33/MineRouter-MRCV-Go"

// Открыть хранилище (создаст, если нет), привязанное к устройству.
v, err := mrcv.New(mrcv.Config{Path: "storage.mrcv"})
if err != nil { /* ... */ }

res, err := v.Open()
if err != nil { /* ... */ }
switch res.State {
case "unlocked":
    // хранилище привязано к ЭТОМУ устройству
case "mismatch":
    // файл создан на другом устройстве — не открываем
}

v.Set("client_key", "hex...")
v.Set("nested", map[string]interface{}{"a": 1})
if err := v.Save(); err != nil { /* ... */ }
Конфигурация
Поле Дефолт Зачем
Path ~/.config/@minerouter/mrcv/storage.mrcv где хранить
Mode bound bound (сохранить) / strict (уничтожить)
BindingSources mobo UUID + disk serial (+ MAC на VM) свой отпечаток устройства
Memory 256 MiB Argon2id память
Iterations 3 Argon2id итерации
API
Open()      (OpenResult, error) // привязка + открыть/создать
Unlock()    error               // расшифровать payload
Lock()                          // очистить ключи из памяти
Save()      error               // записать на диск
Get(k)      interface{}
Set(k, v)   // не сохраняет до Save()
Delete(k)
Has(k)      bool
Keys()      []string
IsOpen() / IsUnlocked() bool

Структура

binding.go   device fingerprint (mobo UUID, disk serial, MAC на VM) -> SHA-256
crypto.go    Argon2id + XChaCha20-Poly1305 (AEAD, AAD = header)
storage.go   формат .mrcv побайтово (84-байт header)
vault.go     публичный API (Open/Unlock/Get/Set/Save, bound/strict)
types.go     Config, Mode, BindingSource, ошибки
vault_test.go тесты: roundtrip, формат, strict/bound, подделка файла

Тесты

go test ./...
  • roundtrip create → save → open → read
  • header layout (магия, смещения полей, reserved-байты)
  • strict уничтожает файл при несовпадении binding
  • bound сохраняет файл при несовпадении
  • подделанный шифротекст не расшифровывается

Argon2id с 256 MiB делает каждый Open/Unlock ~1-3 сек — это ожидаемо (та же стоимость, что в Electron).

Лицензия

MIT

Documentation

Index

Constants

View Source
const (
	DefaultMemory     = 256 * 1024 * 1024 // bytes
	DefaultIterations = 3
)

Defaults matching the JS implementation's deriveKey defaults.

Variables

View Source
var (
	ErrNotOpen          = errors.New("mrcv: vault is not open")
	ErrAlreadyOpen      = errors.New("mrcv: vault is already open")
	ErrBindingMismatch  = errors.New("mrcv: device binding does not match")
	ErrInvalidMode      = errors.New("mrcv: invalid mode (must be 'bound' or 'strict')")
	ErrInvalidConfig    = errors.New("mrcv: invalid config")
	ErrDecryptionFailed = errors.New("mrcv: decryption failed")
)

Errors returned by the vault.

Functions

func ComputeBinding

func ComputeBinding(sources []BindingSource) []byte

ComputeBinding produces the 32-byte binding ID: SHA-256 of the concatenation of every source value separated by '|'. If no source produced data, the platform name is hashed instead (so the vault still binds to "this OS", not to an empty string).

func Decrypt

func Decrypt(key, ciphertext, nonce, tag, aad []byte) ([]byte, error)

Decrypt opens ciphertext with XChaCha20-Poly1305. It must be given the SAME aad used at encryption.

func DefaultVaultPath

func DefaultVaultPath() string

DefaultVaultPath mirrors the JS default: ~/.config/@minerouter/mrcv/storage.mrcv on non-Windows, %APPDATA%/@minerouter/mrcv/storage.mrcv on Windows.

func DeriveKey

func DeriveKey(bindingID, salt []byte, memory, iterations int) ([]byte, error)

DeriveKey runs Argon2id over the binding ID to produce the 32-byte encryption key. Must match libsodium's crypto_pwhash(ARGON2ID13) with the same opslimit/memlimit/parallelism.

func Encrypt

func Encrypt(key, plaintext, aad, nonce []byte) (ciphertext, outNonce, tag []byte, err error)

Encrypt seals plaintext with XChaCha20-Poly1305, returning ciphertext, the 24-byte nonce, and the 16-byte tag. aad (the file header) is bound to the ciphertext. If nonce is nil, a fresh one is generated. The caller MUST build the aad from a header that already contains the nonce (matching the JS implementation, where the nonce is created before the header).

Types

type BindingSource

type BindingSource struct {
	Name   string
	Getter func() (string, error)
}

BindingSource is one device fingerprint that contributes to the binding ID.

func DefaultBindingSources

func DefaultBindingSources() []BindingSource

DefaultBindingSources are the Linux device fingerprints used to compute the binding ID. Order matters — it must match the JS implementation.

type Config

type Config struct {
	Path           string          // .mrcv file location; defaults to ~/.config/@minerouter/mrcv/storage.mrcv
	Mode           Mode            // bound (default) or strict
	BindingSources []BindingSource // optional custom device-binding sources
	Memory         int             // Argon2id memory in bytes, default 256 MiB
	Iterations     int             // Argon2id iterations, default 3
}

Config configures a Vault. Path is required; everything else has defaults.

type Mode

type Mode string

Mode controls what happens when the vault is opened on a machine whose device binding does not match.

const (
	// ModeBound: on mismatch the file is left untouched, vault is not opened.
	ModeBound Mode = "bound"
	// ModeStrict: on mismatch the file is destroyed (self-destruct).
	ModeStrict Mode = "strict"
)

type OpenResult

type OpenResult struct {
	// State: "unlocked" (vault ready, created = first-time) or "mismatch"
	// (device binding does not match).
	State   string
	Created bool
}

OpenResult is the outcome of Open.

type Vault

type Vault struct {
	// contains filtered or unexported fields
}

Vault is a device-bound encrypted key-value store. It mirrors the JS Vault API: Open -> Unlock -> Get/Set/... -> Save, with bound/strict modes.

func New

func New(cfg Config) (*Vault, error)

New creates a Vault with the given config.

func (*Vault) Delete

func (v *Vault) Delete(key string)

Delete removes a key (does not persist until Save).

func (*Vault) Get

func (v *Vault) Get(key string) interface{}

Get returns the value for a key, or nil if absent.

func (*Vault) Has

func (v *Vault) Has(key string) bool

Has reports whether a key exists.

func (*Vault) IsOpen

func (v *Vault) IsOpen() bool

IsOpen reports whether the vault file is bound (opened).

func (*Vault) IsUnlocked

func (v *Vault) IsUnlocked() bool

IsUnlocked reports whether the payload is decrypted.

func (*Vault) Keys

func (v *Vault) Keys() []string

Keys returns all stored keys.

func (*Vault) Lock

func (v *Vault) Lock()

Lock clears the decrypted data (keys are removed from memory).

func (*Vault) Open

func (v *Vault) Open() (OpenResult, error)

Open computes the device binding and either opens an existing vault or creates a new one. On mismatch in strict mode the file is destroyed.

func (*Vault) Path

func (v *Vault) Path() string

Path returns the vault file location.

func (*Vault) Save

func (v *Vault) Save() error

Save writes the current data back to the vault file.

func (*Vault) Set

func (v *Vault) Set(key string, value interface{})

Set stores a value for a key (does not persist until Save).

func (*Vault) Unlock

func (v *Vault) Unlock() error

Unlock decrypts the payload so Get/Set become available.

Directories

Path Synopsis
scripts
gomake command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL