hyperspace

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Jul 6, 2026 License: MIT Imports: 10 Imported by: 0

README

Hyperspace

Hyperspace is a Go library that provides P2P connectivity using the Holepunch ecosystem's Hyperbeam. It enables two parties with X25519 keypairs to establish direct connections through NAT traversal.

Overview

Hyperspace derives a passcode from X25519 keys and uses it with Hyperbeam for P2P connections. Both parties compute the same passcode from their X25519 shared secret, enabling them to find each other via the DHT and establish an encrypted connection.

Alice                              Bob
  │                                  │
  │ X25519(keyA_secret, keyB_public)│ X25519(keyB_secret, keyA_public)
  │         = shared_secret          │         = shared_secret
  │                                  │
  │ SHA-256(shared_secret ||         │ SHA-256(shared_secret ||
  │          "hyperspace-v1")         │          "hyperspace-v1")
  │         = passcode               │         = passcode
  │                                  │
  │     z-base-32(passcode)          │     z-base-32(passcode)
  │                                  │
  │       hyperbeam <passcode>       │       hyperbeam <passcode>
  │                                  │
  └──────────── DHT ─────────────────┘
                    │
              P2P Connection

Installation

go get codeberg.org/OperatorFoundation/hyperspace

Prerequisites

Hyperbeam must be installed:

npm install -g hyperbeam

Or use the library's installation function:

if err := hyperspace.InstallHyperbeam(); err != nil {
    log.Fatal(err)
}

Quick Start

package main

import (
    "fmt"
    "log"

    "codeberg.org/OperatorFoundation/hyperspace"
)

func main() {
    // Your X25519 private key (32 bytes)
    myPrivateKey := make([]byte, 32)
    // ... load from secure storage ...

    // Peer's X25519 public key (32 bytes)
    theirPublicKey := make([]byte, 32)
    // ... received from peer ...

    // Derive passcode
    passcode, err := hyperspace.DerivePasscodeFromKeys(myPrivateKey, theirPublicKey)
    if err != nil {
        log.Fatal(err)
    }

    // Convert to z-base-32 (format expected by Hyperbeam)
    z32 := hyperspace.PasscodeToZ32(passcode)
    fmt.Printf("Passcode: %s\n", z32)

    // Connect via Hyperbeam
    conn, err := hyperspace.Connect(z32)
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    // Use the connection (implements io.ReadWriteCloser)
    conn.Write([]byte("Hello, peer!"))

    buf := make([]byte, 1024)
    n, err := conn.Read(buf)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Received: %s\n", buf[:n])
}

API Reference

Passcode Derivation
func ComputeSharedSecret(privateKey, peerPublicKey []byte) ([]byte, error)

Performs X25519 key exchange between a private key and a peer's public key.

sharedSecret, err := hyperspace.ComputeSharedSecret(myPrivateKey, theirPublicKey)
  • privateKey: Your X25519 private key (32 bytes)
  • peerPublicKey: Peer's X25519 public key (32 bytes)
  • Returns: The shared secret (32 bytes)
func DerivePasscode(sharedSecret []byte) ([]byte, error)

Derives a 32-byte passcode from an X25519 shared secret using SHA-256.

passcode, err := hyperspace.DerivePasscode(sharedSecret)
  • sharedSecret: The X25519 shared secret (32 bytes)
  • Returns: The derived passcode (32 bytes)

The passcode is computed as: SHA-256(sharedSecret || "hyperspace-v1")

func DerivePasscodeFromKeys(myPrivateKey, theirPublicKey []byte) ([]byte, error)

Convenience function that combines ComputeSharedSecret and DerivePasscode.

passcode, err := hyperspace.DerivePasscodeFromKeys(myPrivateKey, theirPublicKey)
Encoding Functions
func PasscodeToZ32(passcode []byte) string

Encodes a passcode to z-base-32 format. This is the format expected by Hyperbeam.

z32 := hyperspace.PasscodeToZ32(passcode)
// Example: "ybxwmkpqnbfdtpjyquvfrk3q5zgjmqse9r4k6hyno81gdmwz4sco"
func Z32ToPasscode(z32 string) ([]byte, error)

Decodes a z-base-32 string back to a passcode.

passcode, err := hyperspace.Z32ToPasscode(z32)
func PasscodeToHex(passcode []byte) string

Converts a passcode to hexadecimal format (useful for debugging).

hex := hyperspace.PasscodeToHex(passcode)
// Example: "a1b2c3d4..."
Hyperbeam Management
func IsHyperbeamInstalled() bool

Returns true if the hyperbeam executable is available.

if hyperspace.IsHyperbeamInstalled() {
    // hyperbeam is available
}
func InstallHyperbeam() error

Installs hyperbeam globally via npm. Returns an error if npm is not available or installation fails.

if err := hyperspace.InstallHyperbeam(); err != nil {
    log.Fatal(err)
}
func EnsureHyperbeam() error

Ensures hyperbeam is installed, installing if necessary.

if err := hyperspace.EnsureHyperbeam(); err != nil {
    log.Fatal(err)
}
func HyperbeamPath() (string, error)

Returns the path to the hyperbeam executable.

path, err := hyperspace.HyperbeamPath()
Connection
func Connect(passcode string) (*Connection, error)

Establishes a P2P connection via Hyperbeam using the given passcode (z-base-32 format).

conn, err := hyperspace.Connect("ybxwmkpqnbfdtpjyquvfrk3q5zgjmqse9r4k6hyno81gdmwz4sco")
if err != nil {
    log.Fatal(err)
}
defer conn.Close()
func ConnectWithKeys(myPrivateKey, theirPublicKey []byte) (*Connection, error)

Convenience function that derives the passcode from keys and connects.

conn, err := hyperspace.ConnectWithKeys(myPrivateKey, theirPublicKey)
if err != nil {
    log.Fatal(err)
}
defer conn.Close()
type Connection

Represents an active Hyperbeam connection. Implements io.ReadWriteCloser.

type Connection struct {
    // Has unexported fields.
}

func (c *Connection) Read(p []byte) (n int, err error)
func (c *Connection) Write(p []byte) (n int, err error)
func (c *Connection) Close() error
func (c *Connection) Stderr() io.Reader

Integration with Velada

Hyperspace is designed to integrate with Velada, which provides Nahoft-compatible key management via the keychain library.

Basic Integration
import (
    "codeberg.org/OperatorFoundation/hyperspace"
    "codeberg.org/OperatorFoundation/keychain"
)

func ConnectToContact(store *keychain.Store, contactID string) (*hyperspace.Connection, error) {
    // Get the default identity's private key
    identity, err := store.GetDefaultIdentity()
    if err != nil {
        return nil, fmt.Errorf("get identity: %w", err)
    }

    // Load the private key (protected memory)
    privBuf, _, err := store.LoadSecret(identity.X25519KeyID)
    if err != nil {
        return nil, fmt.Errorf("load private key: %w", err)
    }
    defer privBuf.Destroy()

    // Get the contact's public key
    addressBook := keychain.NewAddressBook(store)
    pubKeys, err := addressBook.GetContactPublicKeys(contactID)
    if err != nil {
        return nil, fmt.Errorf("get contact public key: %w", err)
    }

    // Derive passcode
    passcode, err := hyperspace.DerivePasscodeFromKeys(privBuf.Bytes(), pubKeys.X25519PublicKey)
    if err != nil {
        return nil, fmt.Errorf("derive passcode: %w", err)
    }

    // Connect
    return hyperspace.Connect(hyperspace.PasscodeToZ32(passcode))
}
With CryptoManager (Velada's wrapper)
import (
    "codeberg.org/OperatorFoundation/hyperspace"
    "codeberg.org/OperatorFoundation/velada"
)

// Velada's CryptoManager provides higher-level access
func ConnectWithCryptoManager(cm *velada.CryptoManager, contactID string) (*hyperspace.Connection, error) {
    // Get your public key
    myPublicKey, err := cm.GetPublicKey()
    if err != nil {
        return nil, err
    }

    // Get contact's public key
    theirPublicKey, err := cm.GetContactPublicKey(contactID)
    if err != nil {
        return nil, err
    }

    // Note: You'll need access to the private key from the keychain.
    // This may require extending Velada's CryptoManager to expose
    // a method for passcode derivation, or exposing the underlying Store.

    // ... derive passcode and connect ...
}

For Velada, consider adding a helper function:

// In velada/crypto.go (proposed addition)
func (cm *CryptoManager) DeriveHyperspacePasscode(contactID string) ([]byte, error) {
    // Get identity's X25519 key ID
    identity, err := cm.store.GetDefaultIdentity()
    if err != nil {
        return nil, err
    }

    // Load private key
    privBuf, _, err := cm.store.LoadSecret(identity.X25519KeyID)
    if err != nil {
        return nil, err
    }
    defer privBuf.Destroy()

    // Get contact's public key
    pubKeys, err := cm.addressBook.GetContactPublicKeys(contactID)
    if err != nil {
        return nil, err
    }

    // Derive passcode
    return hyperspace.DerivePasscodeFromKeys(privBuf.Bytes(), pubKeys.X25519PublicKey)
}

Security Properties

Property Implementation
Authentication Only parties with the correct private key can derive the shared secret
Deterministic Same keypair produces the same passcode every time
Domain separation Uses "hyperspace-v1" context to prevent cross-protocol attacks
Forward secrecy Not provided by this layer - use ephemeral keys if needed
Defense in depth Application should layer additional encryption (e.g., NaCl box)

How It Works

  1. X25519 Key Exchange: Both parties have X25519 keypairs. Each computes the shared secret using their private key and the peer's public key.

  2. Passcode Derivation: The shared secret is hashed with a context string:

    passcode = SHA-256(sharedSecret || "hyperspace-v1")
    
  3. z-base-32 Encoding: The passcode is encoded in z-base-32, a human-friendly encoding used by the Holepunch ecosystem.

  4. Hyperbeam Connection: The passcode is passed to hyperbeam, which uses it to derive a keypair for DHT announcement and encrypted connection establishment.

  5. NAT Traversal: Hyperbeam uses the DHT for peer discovery and UDP hole-punching for NAT traversal.

Constants

const (
    // PasscodeContext is the domain separation string
    PasscodeContext = "hyperspace-v1"

    // PasscodeSize is the size of the derived passcode in bytes
    PasscodeSize = 32
)

Errors

var (
    // ErrInvalidKeySize is returned when a key is not 32 bytes
    ErrInvalidKeySize = fmt.Errorf("key must be %d bytes", 32)

    // ErrHyperbeamNotFound is returned when hyperbeam is not installed
    ErrHyperbeamNotFound = fmt.Errorf("hyperbeam not found - install with: npm install -g hyperbeam")
)

License

MIT

Documentation

Overview

Package hyperspace provides a Go interface to Hyperbeam for P2P connections using X25519-derived passcodes.

Hyperspace enables two parties with X25519 keypairs to establish a direct P2P connection through NAT traversal. The passcode is derived from the X25519 shared secret, allowing both parties to compute the same passcode without exchanging any additional information.

Usage:

// Derive passcode from X25519 keys
passcode, err := hyperspace.DerivePasscode(mySecretKey, theirPublicKey)
if err != nil {
    log.Fatal(err)
}

// Connect via Hyperbeam
conn, err := hyperspace.Connect(passcode)
if err != nil {
    log.Fatal(err)
}
defer conn.Close()

// Use conn as an io.ReadWriteCloser
conn.Write([]byte("Hello, peer!"))
Example (PasscodeEncoding)

ExamplePasscodeEncoding demonstrates encoding functions.

package main

import (
	"fmt"
	"log"

	"codeberg.org/OperatorFoundation/hyperspace"
)

func main() {
	// Create a passcode
	sharedSecret := make([]byte, 32)
	for i := range sharedSecret {
		sharedSecret[i] = byte(i)
	}

	passcode, _ := hyperspace.DerivePasscode(sharedSecret)

	// Z-base-32 encoding (what hyperbeam expects)
	z32 := hyperspace.PasscodeToZ32(passcode)
	fmt.Printf("z-base-32: %s\n", z32)

	// Hex encoding (for debugging)
	hex := hyperspace.PasscodeToHex(passcode)
	fmt.Printf("hex: %s\n", hex)

	// Decode z-base-32 back to bytes
	decoded, err := hyperspace.Z32ToPasscode(z32)
	if err != nil {
		log.Fatal(err)
	}
	_ = decoded
}

Index

Examples

Constants

View Source
const PasscodeContext = "hyperspace-v1"

PasscodeContext is the domain separation string for passcode derivation. This prevents cross-protocol attacks if the same X25519 keys are used for multiple purposes.

View Source
const PasscodeSize = 32

PasscodeSize is the size of the derived passcode in bytes (32 bytes = SHA-256 output).

Variables

View Source
var (
	ErrInvalidKeySize    = fmt.Errorf("key must be %d bytes", curve25519.PointSize)
	ErrHyperbeamNotFound = fmt.Errorf("hyperbeam not found - install with: npm install -g hyperbeam")
)

Errors returned by the package.

Functions

func ComputeSharedSecret

func ComputeSharedSecret(privateKey, peerPublicKey []byte) ([]byte, error)

ComputeSharedSecret performs X25519 key exchange between a private key and a peer's public key.

func DefaultNodePath

func DefaultNodePath() string

DefaultNodePath returns the default path for Node.js installation. This is useful for debugging installation issues.

func DerivePasscode

func DerivePasscode(sharedSecret []byte) ([]byte, error)

DerivePasscode derives a 32-byte passcode from an X25519 shared secret. The passcode is: SHA-256(sharedSecret || context)

This passcode can be used directly with Hyperbeam.

Example

ExampleDerivePasscode demonstrates passcode derivation.

package main

import (
	"fmt"
	"log"

	"codeberg.org/OperatorFoundation/hyperspace"
)

func main() {
	// X25519 keys (32 bytes each)
	myPrivateKey := make([]byte, 32)
	myPrivateKey[0] = 1 // Non-zero for valid key
	theirPublicKey := make([]byte, 32)
	theirPublicKey[0] = 2

	// Derive passcode
	passcode, err := hyperspace.DerivePasscodeFromKeys(myPrivateKey, theirPublicKey)
	if err != nil {
		log.Fatal(err)
	}

	// Convert to z-base-32 for hyperbeam
	z32 := hyperspace.PasscodeToZ32(passcode)
	fmt.Printf("Passcode: %s\n", z32)

}
Output:
Passcode: wtb1teqygth1xoni8cdssiu39ypb7n9i5pabjepuofde88qoccto

func DerivePasscodeFromKeys

func DerivePasscodeFromKeys(myPrivateKey, theirPublicKey []byte) ([]byte, error)

DerivePasscodeFromKeys derives a passcode directly from X25519 keypairs. This is a convenience function that combines ComputeSharedSecret and DerivePasscode.

func EnsureHyperbeam

func EnsureHyperbeam() error

EnsureHyperbeam ensures hyperbeam is installed, installing if necessary.

func HyperbeamPath

func HyperbeamPath() (string, error)

HyperbeamPath returns the path to the hyperbeam executable. It first checks the HYPERBEAM_PATH environment variable, then searches the PATH.

func InstallHyperbeam

func InstallHyperbeam() error

InstallHyperbeam attempts to install hyperbeam globally via npm. Returns an error if npm is not available or installation fails.

func IsHyperbeamInstalled

func IsHyperbeamInstalled() bool

IsHyperbeamInstalled returns true if hyperbeam is available.

func NodePath

func NodePath() (string, error)

NodePath returns the path to node, or an error if not found.

func NpmPath

func NpmPath() (string, error)

NpmPath returns the path to npm, or an error if not found.

func PasscodeToHex

func PasscodeToHex(passcode []byte) string

PasscodeToHex converts a passcode to a hexadecimal string.

func PasscodeToZ32

func PasscodeToZ32(passcode []byte) string

PasscodeToZ32 encodes a passcode to z-base-32 format. Hyperbeam expects z-base-32 encoded passphrases. z-base-32 uses the alphabet: ybndrfg8ejkmcpqxot1uwisza345h769

func Z32ToPasscode

func Z32ToPasscode(z32 string) ([]byte, error)

Z32ToPasscode decodes a z-base-32 string to a passcode.

Types

type ConnectOptions

type ConnectOptions struct {
	// Env is additional environment variables.
	Env []string
}

ConnectOptions provides optional configuration for connections.

type Connection

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

Connection represents an active Hyperbeam connection.

func Connect

func Connect(passcode string) (*Connection, error)

Connect establishes a P2P connection via Hyperbeam using the given passcode. The passcode should be a z-base-32 encoded string (use PasscodeToZ32).

This function spawns a hyperbeam process and returns a Connection that implements io.ReadWriteCloser.

func ConnectWithKeys

func ConnectWithKeys(myPrivateKey, theirPublicKey []byte) (*Connection, error)

ConnectWithKeys derives a passcode from X25519 keys and establishes a connection. This is a convenience function that combines key exchange, passcode derivation, and connection.

Example

ExampleConnectWithKeys demonstrates basic connection flow.

package main

import (
	"fmt"
	"log"

	"codeberg.org/OperatorFoundation/hyperspace"
)

func main() {
	// In a real application, these would come from the keychain:
	// myPrivateKey: from keychain.Store.LoadSecret(myKeyID)
	// theirPublicKey: from keychain.AddressBook.GetContactPublicKeys(contactID)

	// For demonstration, generate test keys
	myPrivateKey := make([]byte, 32)
	theirPublicKey := make([]byte, 32)
	for i := range myPrivateKey {
		myPrivateKey[i] = byte(i)
		theirPublicKey[i] = byte(i + 1)
	}

	// Check if hyperbeam is installed
	if !hyperspace.IsHyperbeamInstalled() {
		fmt.Println("Installing hyperbeam...")
		if err := hyperspace.InstallHyperbeam(); err != nil {
			log.Fatal(err)
		}
	}

	// Connect using keys directly
	conn, err := hyperspace.ConnectWithKeys(myPrivateKey, theirPublicKey)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	// Use the connection
	_, _ = conn.Write([]byte("Hello, peer!"))
}

func ConnectWithPasscode

func ConnectWithPasscode(passcode string, opts *ConnectOptions) (*Connection, error)

ConnectWithPasscode establishes a P2P connection via Hyperbeam. If opts is nil, default options are used.

func (*Connection) Close

func (c *Connection) Close() error

Close closes the connection and terminates the hyperbeam process.

func (*Connection) Process

func (c *Connection) Process() *exec.Cmd

Process returns the underlying hyperbeam process. This is exposed for advanced use cases.

func (*Connection) Read

func (c *Connection) Read(p []byte) (n int, err error)

Read reads data from the connection.

func (*Connection) SetOnClose

func (c *Connection) SetOnClose(fn func())

SetOnClose sets a callback to be called when the connection closes.

func (*Connection) Stderr

func (c *Connection) Stderr() io.Reader

Stderr returns a reader for the hyperbeam process's stderr output. Useful for debugging connection issues.

func (*Connection) Write

func (c *Connection) Write(p []byte) (n int, err error)

Write writes data to the connection.

Jump to

Keyboard shortcuts

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