rgb-consignment-transport
v0.1.0
Published
P2P consignment transport for RGB protocol over Hyperswarm
Downloads
16
Maintainers
Readme
rgb-consignment-transport
P2P consignment transport for RGB protocol over Hyperswarm. Replaces rgb-proxy-server with direct peer-to-peer, end-to-end encrypted delivery.
No proxy server. No relay. No federation.
npm install rgb-consignment-transportHow it works
Receiver DHT Sender
| | |
| Generate invoice | |
| Derive topic | |
|------announce(topic)---->| |
| |<----lookup(topic)----------|
| | |
|<======= Noise_XX_25519_XChaChaPoly_BLAKE2b =========>|
| | |
|<-- consignment (Hypercore replication) ---------------|
| | |
| Validate (RGB rules) | |
|------- ACK/NACK -------->|----------ACK/NACK--------->|
| | |
| | Broadcast witness tx |- Receiver generates an RGB invoice. A topic is derived from
BLAKE2b(invoice_id || sender_pubkey || nonce). - Both peers join the Hyperswarm topic. DHT handles discovery.
- Hyperswarm establishes a Noise-encrypted connection. Peer authentication via Ed25519 pubkey verification.
- Sender writes consignment to a Hypercore feed, replicated to the receiver over the encrypted connection.
- Receiver validates the consignment (RGB rules, outside this library's scope) and sends ACK or NACK.
- On ACK, sender broadcasts the witness transaction.
Usage
Sender
const { createSession, generateNonce } = require('rgb-consignment-transport')
// nonce and senderPubkey are shared with the receiver out-of-band
// (alongside the RGB invoice, via QR code, text, NFC, etc.)
const session = createSession({
invoice: 'rgb:2hMDMt-qWBMoP3t-.../RGB20/100+utxob:...',
senderPubkey: myKeyPair.publicKey, // 32-byte Ed25519
nonce, // 32-byte random, from receiver
role: 'sender',
storage: './sender-storage',
keyPair: myKeyPair // Hyperswarm keypair
})
await session.open()
const result = await session.sendConsignment(consignmentBytes)
if (result.isAck) {
// Receiver validated the consignment. Broadcast witness tx.
console.log('Transfer accepted')
} else {
// Receiver rejected. Do NOT broadcast.
console.log('Rejected:', result.errorCode, result.payloadString)
}
await session.destroy()Receiver
const { createSession, generateNonce } = require('rgb-consignment-transport')
const nonce = generateNonce() // 32 random bytes
const session = createSession({
invoice: 'rgb:2hMDMt-qWBMoP3t-.../RGB20/100+utxob:...',
senderPubkey: expectedSenderPubkey, // from out-of-band exchange
nonce,
role: 'receiver',
storage: './receiver-storage'
})
await session.open()
const { header, payload } = await session.receiveConsignment()
// Validate the consignment using rgb-lib or your RGB stack.
// This library is opaque to RGB -- it moves bytes, nothing more.
const valid = await validateWithRgbLib(payload)
if (valid) {
await session.sendAck()
} else {
await session.sendNack(0x0010, 'RGB validation failed')
}
await session.destroy()API
createSession(opts) / new Session(opts)
Create a transfer session.
| Option | Type | Required | Description |
|---|---|---|---|
| invoice | string\|Buffer | yes | RGB invoice |
| senderPubkey | Buffer[32] | yes | Sender's Ed25519 public key |
| nonce | Buffer[32] | yes | Per-transfer random |
| role | string | yes | "sender" or "receiver" |
| storage | string | yes | Corestore storage path |
| receiverPubkey | Buffer[32] | no | For mutual authentication |
| timeout | number | no | Connection timeout ms (default: 120000) |
| ackTimeout | number | no | ACK/NACK timeout ms (default: 300000) |
| maxSize | number | no | Max consignment bytes (default: 10 MB) |
| chunkSize | number | no | Chunk size bytes (default: 64 KB) |
| keyPair | object | no | Hyperswarm keypair { publicKey, secretKey } |
| dht | object | no | HyperDHT instance (for testing) |
session.open()
Join the Hyperswarm topic and begin peer discovery. Returns a promise that resolves when the topic is announced.
session.sendConsignment(buffer)
Sender only. Write a consignment to the Hypercore feed. Returns a promise that resolves with the ACK/NACK result:
{
isAck: true, // or false
isNack: false, // or true
errorCode: 0x0000, // 0x0000 for ACK, error code for NACK
payloadString: '' // optional error description
}session.receiveConsignment()
Receiver only. Wait for a consignment from the sender. Returns:
{
header: { version, mode, flags, totalSize, chunkSize, ... },
payload: Buffer // the complete consignment bytes
}The receiver MUST validate the consignment using RGB rules (rgb-lib,
WDK, etc.) and then call sendAck() or sendNack().
session.sendAck()
Receiver only. Signal that the consignment passed validation.
session.sendNack(errorCode, message?)
Receiver only. Signal that the consignment was rejected.
Error codes (from the protocol spec):
| Code | Name |
|---|---|
| 0x0001 | HASH_MISMATCH |
| 0x0002 | SIZE_MISMATCH |
| 0x0003 | INVALID_HEADER |
| 0x0004 | SIZE_EXCEEDED |
| 0x0005 | AUTH_FAILED |
| 0x0010 | RGB_VALIDATION_FAILED |
| 0x0011 | RGB_SCHEMA_INVALID |
| 0x0012 | RGB_SEAL_INVALID |
| 0x0013 | RGB_DAG_INVALID |
| 0x0014 | RGB_ALUVM_FAILED |
| 0x00F0 | TIMEOUT |
| 0x00FF | INTERNAL_ERROR |
| 0xFF00-0xFFFE | Application-defined |
session.destroy()
Clean up all resources: Protomux channels, Hypercore feeds, Corestore, Hyperswarm topic, Noise connection. Always call this when done.
generateNonce()
Generate a 32-byte random nonce using libsodium's CSPRNG.
deriveTopic(invoice, senderPubkey, nonce)
Compute the 32-byte Hyperswarm topic from transfer inputs.
Events
Set callbacks on the session:
session.onconnection = (peerInfo) => {} // peer connected
session.onconsignment = (payload, header) => {} // consignment received
session.onack = (signal) => {} // ACK/NACK received (sender)
session.ontimeout = () => {} // connection timeout
session.onerror = (err) => {} // error
session.oncomplete = (result) => {} // transfer completed
session.onclose = () => {} // session destroyedProperties
- No servers. Uses Hyperswarm DHT for peer discovery. No relay or proxy required.
- E2E encrypted. Every connection uses Noise_XX with XChaCha20-Poly1305. Forward secrecy by default.
- Integrity verified. Hypercore entries are Ed25519 signed and structured in a BLAKE2b Merkle tree. Tampered data is detectable before it reaches RGB validation.
- Interruption tolerant. Hypercore retains data locally. If a connection drops, replication resumes on reconnect.
- Opaque transport. Carries binary payloads. No knowledge of RGB semantics. Works with v0.11, v0.11.1, or v0.12 consignments.
- Wallet agnostic. Works with rgb-lib, WDK, or any RGB stack.
- Runtime agnostic. Runs on Node.js, Bare (Pear Runtime), and Electron.
Protocol Spec
See spec/protocol.md for the full language-agnostic
protocol specification.
Testing
npm test149 tests (140 unit + 9 integration) covering topic derivation, header encoding, chunking, reassembly, ACK/NACK signaling, peer authentication, end-to-end transfers (ACK, NACK, large payloads), connection timeouts, firewall rejection, and resource cleanup.
Integration tests use a local HyperDHT testnet (no external network).
Runtime Compatibility
| Runtime | Status |
|---|---|
| Node.js >= 18 | Tested |
| Bare (Pear Runtime) | Compatible (no node: builtins used) |
| Electron | Compatible |
The package exports map uses the "bare" condition for Pear Runtime
compatibility. No node: prefixed imports anywhere in lib/.
License
MIT OR Apache-2.0
