Rust and WASM did-method-plc tools and structures

Quick Start: plc-audit WASM Implementation#

What Was Created#

A JavaScript implementation of the plc-audit binary that uses WebAssembly for all cryptographic operations. This provides the same functionality as the Rust binary but runs in JavaScript/Node.js environments.

Files Created#

  1. wasm/plc-audit.js - JavaScript CLI tool (10KB)
  2. wasm/README.md - Comprehensive documentation
  3. wasm/build.sh - Build script for WASM module
  4. wasm/pkg/ - Generated WASM module directory (created by build)
    • atproto_plc_bg.wasm - WebAssembly binary (374KB)
    • atproto_plc.js - JavaScript bindings (39KB)
    • atproto_plc.d.ts - TypeScript definitions

WASM Bindings Enhanced#

Enhanced src/wasm.rs with new exports:

  • WasmVerifyingKey - For creating verifying keys from did:key strings
  • WasmOperation.prev() - Get previous operation CID
  • WasmOperation.signature() - Get operation signature
  • WasmOperation.rotationKeys() - Get rotation keys
  • WasmOperation.verify() - Verify signatures
  • WasmOperation.verifyWithKeyIndex() - Verify and return which key signed

How to Build#

Prerequisites#

# Install wasm-pack (one-time setup)
cargo install wasm-pack

# Ensure wasm32 target is available
rustup target add wasm32-unknown-unknown

Build the WASM Module#

cd wasm
./build.sh

This will:

  1. Check prerequisites
  2. Compile Rust to WebAssembly
  3. Generate JavaScript bindings
  4. Optimize the WASM binary
  5. Output to wasm/pkg/

How to Use#

Basic Usage#

cd wasm

# Validate a DID (standard output)
node plc-audit.js did:plc:z72i7hdynmk6r22z27h6tvur

# Output:
# 🔍 Fetching audit log for: did:plc:z72i7hdynmk6r22z27h6tvur
#    Source: https://plc.directory
#
# 📊 Audit Log Summary:
#    Total operations: 4
#    Genesis operation: bafyreigp6shzy6dlcxuowwoxz7u5nemdrkad2my5zwzpwilcnhih7bw6zm
#    Latest operation: bafyreifn4pkect7nymne3sxkdg7tn7534msyxcjkshmzqtijmn3enyxm3q
#
# 🔐 Validating operation chain...
# ✅ Validation successful!
#
# 📄 Final DID State:
#    Rotation keys: 2
#      [0] did:key:zQ3shhCGUqDKjStzuDxPkTxN6ujddP4RkEKJJouJGRRkaLGbg
#      [1] did:key:zQ3shpKnbdPx3g3CmPf5cRVTPe1HtSwVn5ish3wSnDPQCbLJK

Verbose Mode#

Show detailed cryptographic validation steps:

node plc-audit.js did:plc:z72i7hdynmk6r22z27h6tvur --verbose

Output includes:

  • All operations with CIDs and timestamps
  • Step 1: Chain linkage validation (prev reference checking)
  • Step 2: Cryptographic signature validation
    • Genesis rotation key extraction
    • Per-operation signature verification
    • Which specific rotation key verified each signature
    • Rotation key changes tracked

Quiet Mode#

For scripts and automation:

node plc-audit.js did:plc:z72i7hdynmk6r22z27h6tvur --quiet

# Output: ✅ VALID
# Exit code: 0 (success) or 1 (failure)

Custom PLC Directory#

node plc-audit.js did:plc:example --plc-url https://custom.plc.directory

Integration Examples#

Use in Node.js Application#

import { WasmDid, WasmOperation, WasmVerifyingKey } from './pkg/atproto_plc.js';

// Parse and validate a DID
const did = new WasmDid('did:plc:ewvi7nxzyoun6zhxrhs64oiz');
console.log('Valid DID:', did.did);

// Fetch and validate audit log
const response = await fetch(`https://plc.directory/${did.did}/log/audit`);
const auditLog = await response.json();

// Parse operations
const operations = auditLog.map(entry => ({
  operation: WasmOperation.fromJson(JSON.stringify(entry.operation)),
  cid: entry.cid,
}));

// Validate chain linkage
for (let i = 1; i < operations.length; i++) {
  const prev = operations[i].operation.prev();
  if (prev !== operations[i - 1].cid) {
    throw new Error('Chain linkage broken at operation ' + i);
  }
}

// Validate signatures
const rotationKeys = operations[0].operation.rotationKeys();
const verifyingKeys = rotationKeys.map(k => WasmVerifyingKey.fromDidKey(k));

for (let i = 1; i < operations.length; i++) {
  const keyIndex = operations[i].operation.verifyWithKeyIndex(verifyingKeys);
  console.log(`Operation ${i} verified with key ${keyIndex}`);
}

Use as Executable Script#

Add shebang to make it executable:

chmod +x plc-audit.js
./plc-audit.js did:plc:z72i7hdynmk6r22z27h6tvur

Performance Comparison#

Metric Rust Binary WASM/JavaScript
Validation Time ~100-200ms ~200-500ms
Binary Size 1.5MB (native) 374KB (WASM) + 39KB (JS)
Startup Time ~10ms ~50-100ms (WASM init)
Memory Usage ~5MB ~10MB
Platform Compiled per-platform Universal

Architecture#

The implementation divides responsibilities between JavaScript and WASM:

JavaScript Layer (plc-audit.js)#

  • Command-line argument parsing (using Node.js util.parseArgs)
  • HTTP requests to plc.directory (using fetch)
  • Console output formatting with emojis
  • Control flow and orchestration
  • Error handling and reporting

WASM Layer (Rust compiled to WebAssembly)#

  • DID parsing and validation
  • Operation parsing from JSON
  • Cryptographic signature verification
    • P-256 (secp256r1) ECDSA
    • secp256k1 ECDSA
  • Rotation key management
  • All security-critical operations

Troubleshooting#

Build Issues#

Error: wasm-pack is not installed

cargo install wasm-pack

Error: wasm32-unknown-unknown target not found

rustup target add wasm32-unknown-unknown

Error: wasm-opt bulk memory operations

  • Already fixed in Cargo.toml with wasm-opt = ["-O", "--enable-bulk-memory"]

Runtime Issues#

Error: Module not found

# Ensure WASM module is built
ls wasm/pkg/atproto_plc_bg.wasm

# If missing, rebuild
cd wasm && ./build.sh

Error: WasmDid.parse is not a function

  • Use new WasmDid(didString) constructor syntax instead

Error: Cannot find module

  • Ensure you're running from the wasm/ directory
  • Or use full paths: node /path/to/wasm/plc-audit.js ...

Node.js Version#

Requires Node.js v18 or later for:

  • Native fetch support
  • util.parseArgs support
  • ES modules support

Check version:

node --version  # Should be v18.0.0 or higher

Next Steps#

Key Differences from Rust Binary#

Identical#

  • ✅ Validation logic and cryptography
  • ✅ Command-line interface and arguments
  • ✅ Output format and messages
  • ✅ Error handling and exit codes

Different#

  • 🔄 2-3x slower performance (acceptable for most use cases)
  • 🔄 Requires Node.js runtime (vs standalone binary)
  • 🔄 Smaller total size (413KB vs 1.5MB)
  • 🔄 Universal (no per-platform compilation)

License#

Dual-licensed under MIT or Apache-2.0, same as the parent library.