npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tnzx/sdk

v1.0.0

Published

Build apps on the TNZX protocol — censorship-resistant messaging over mining channels

Readme

@tnzx/sdk

Developer SDK for the TNZX protocol — censorship-resistant messaging over cryptocurrency mining channels.

Zero external dependencies. Node.js >= 18.

Quick Start

const { VS3Client } = require('@tnzx/sdk');

const client = new VS3Client({ pool: 'host:3333', wallet: '4...' });

client.on('ready', () => console.log('Connected'));
client.on('peer', ({ wallet }) => client.send(wallet, 'Hello TNZX'));
client.on('message', ({ text }) => console.log(text));
client.connect();

Limits

| Constraint | Value | Why | |-----------|-------|-----| | Max plaintext per message | 127 bytes | Single VS3 frame (247B) minus 120B encryption overhead | | Encryption overhead | 120 bytes | replayId(16) + ephPub(32) + salt(32) + nonce(24) + tag(16) | | Ghost shares per message | ~28 for 127B | Frame chunked at 5 bytes/share (Monero V3) | | Send pacing | 150ms default | Configurable via ghostIntervalMs | | Key exchange | Unauthenticated (TOFU) | A malicious pool can MITM the key exchange. See Threat Model below. |

Messages longer than 127 bytes will be rejected with an error. Multi-frame fragmentation is planned for a future release.

API Reference

VS3Client (high-level)

const { VS3Client } = require('@tnzx/sdk');

Constructor

new VS3Client({
  pool: 'host:port',           // required — Stratum pool or VS3 proxy
  wallet: '4...',              // required — 95-char Monero wallet
  privateKey: Buffer,          // optional — 32-byte X25519 (auto-generated if omitted)
  publicKey: Buffer,           // optional — must match privateKey if provided
  ghostIntervalMs: 150,        // optional — delay between ghost share submissions
})

Methods

| Method | Description | |--------|-------------| | connect() | Open TCP connection, send Stratum login | | disconnect() | Close connection, clear timers | | send(wallet, text) | Encrypt and send a text message (queued if key exchange pending) | | sendRaw(wallet, payload) | Encrypt and send a raw Buffer |

Events

| Event | Payload | When | |-------|---------|------| | ready | void | Connected and logged in | | peer | { wallet, publicKey } | Key exchange completed with a peer | | message | { from, text, raw } | Decrypted message received. Note: from is currently null — sender identification requires authenticated key exchange (planned). | | error | Error | Network or protocol error | | close | void | Connection closed |

Key Exchange

Key exchange is automatic. When you call send() for a wallet with no known key, VS3Client:

  1. Queues the message
  2. Sends a KEY_EXCHANGE frame (type 0x04) with your public key
  3. Waits for the peer's KEY_EXCHANGE frame
  4. Encrypts and sends all queued messages

Pending messages are capped at 100 per peer.

StratumClient (low-level)

const { StratumClient } = require('@tnzx/sdk');

For developers who need fine-grained control over frame types, manual encryption, or custom protocols on top of VS3.

Constructor

new StratumClient({
  host: '127.0.0.1',
  port: 3333,
  wallet: '4...',
  pass: 'x',                   // optional
  agent: 'tnzx-sdk/1.0',       // optional
  ghostIntervalMs: 150,         // optional
})

Methods

| Method | Description | |--------|-------------| | connect() | Open TCP connection, send Stratum login | | disconnect() | Close connection | | sendFrame(frameBytes, recipientWallet) | Send a VS3 frame as paced ghost shares. Returns Promise<void>. |

Events

| Event | Payload | When | |-------|---------|------| | ready | { minerId, jobId } | Login accepted, first job received | | job | { job_id, blob, target, ... } | New mining job from pool | | frame | { type, payload, raw } | VS3 frame received in job notification | | error | Error | Network or protocol error | | close | void | Connection closed |

HMAC Sentinel

If the proxy provides a session token in the login response (result.extensions.vs3_session), StratumClient automatically derives an HMAC session key and uses HMAC-tagged nonces instead of the fixed 0xAA sentinel. This makes ghost shares statistically indistinguishable from real shares to a DPI observer. No configuration needed — it is opportunistic.

Crypto Functions

const { encryptOneShot, decryptOneShot, generateKeyPair } = require('@tnzx/sdk');

| Function | Description | |----------|-------------| | generateKeyPair() | Returns { publicKey, privateKey } (32-byte X25519 Buffers) | | encryptOneShot(plaintext, recipientPub) | One-shot PFS encryption. Fresh ephemeral key per call. Returns wire-format Buffer. | | decryptOneShot(packet, myPrivateKey, replayCache?) | Decrypt and verify. Optional Set<string> for replay detection. |

Wire format: replayId(16) || ephPub(32) || salt(32) || nonce(24) || ciphertext || tag(16)

Frame Utilities

const { buildVS3Frame, chunkFrame, encodeGhostShare, MSG_TYPE } = require('@tnzx/sdk');

| Function | Description | |----------|-------------| | buildVS3Frame(payload, msgType) | Build a single-fragment VS3 frame. Max payload 247 bytes. | | chunkFrame(frameBytes, bytesPerChunk?) | Split frame into 5-byte chunks for ghost share encoding. | | encodeGhostShare(reqId, minerId, jobId, chunk, vs3To) | Encode a chunk as a Stratum JSON-RPC submit. |

Constants

const { MSG_TYPE, MAGIC, VERSION_V3, ENCRYPT_OVERHEAD } = require('@tnzx/sdk');

| Constant | Value | Description | |----------|-------|-------------| | MSG_TYPE.TEXT | 0x01 | Plaintext message | | MSG_TYPE.KEY_EXCHANGE | 0x04 | Public key exchange | | MSG_TYPE.ENCRYPTED | 0x05 | Encrypted envelope (external type for all encrypted frames) | | ENCRYPT_OVERHEAD | 120 | Bytes added by one-shot encryption |

Full enum: TEXT (0x01), ACK (0x02), PING (0x03), KEY_EXCHANGE (0x04), ENCRYPTED (0x05), HASHCASH (0x06). Types 0x07-0xFF are available for application-layer protocols.

Threat Model

What the SDK protects:

  • Message content (E2E encrypted, pool cannot read)
  • Message type (encrypted envelope, pool sees only 0x05)
  • Ghost share detection (HMAC sentinel, indistinguishable from real shares)

What the SDK does NOT protect:

  • Key exchange is unauthenticated (TOFU). A malicious pool or MITM can inject a fake public key during key exchange. The SDK trusts the pool's routing. Authenticated key exchange (signed keys) is planned for a future release.
  • Sender identification. The message event provides from: null because the protocol cannot currently verify who sent a message. This will be resolved together with authenticated key exchange — once keys are bound to wallet identities, the sender can be identified cryptographically.
  • Timing correlation. An observer who monitors when Alice and Bob are mining can correlate sessions.
  • Message size. Fragment count is visible to the pool (it sees how many ghost shares form a message).

Wire Format Compatibility

The SDK uses the reference-impl wire format for encryption. It is NOT compatible with tnzx-pool-demo/lib/e2e.js, which uses a different AAD string and omits replay protection. When migrating from pool-demo to SDK, all parties must upgrade simultaneously.

| Field | SDK (canonical) | pool-demo (legacy) | |-------|----------------|-------------------| | Wire layout | replayId + ephPub + salt + nonce + ct + tag | ephPub + salt + nonce + ct + tag | | Overhead | 120 bytes | 104 bytes | | AAD | tnzx-oneshot-v3 | tnzx-demo-v1 | | HKDF info | tnzx-e2e-v3 | tnzx-e2e-demo-v1 | | Replay protection | Yes | No |

Tests

node test/run-all.js

40 tests across 6 suites: crypto (11), keys (6), ghost-share (10), HMAC sentinel (8), StratumClient integration (2), VS3Client E2E integration (3).

Examples

License

LGPL-2.1