Skip to content

Script Signing & Verification

NeuroScript signing provides cryptographic verification of transform scripts, ensuring they haven't been tampered with and come from trusted sources.

Availability note: Signed-binary verification is only available in builds that bundle Signing/root_keys.json and have a verified keyset loaded. If either piece is missing, the app surfaces signing as unavailable and signed-binary-only preset imports stay blocked.

What is Script Signing?

Script signing uses Ed25519 digital signatures to verify:

  • Authenticity - Script comes from a trusted publisher (Neurode developers)
  • Integrity - Script hasn't been modified since signing
  • Provenance - Full audit trail of script origin

Signed artifacts (.nssigned files) contain:

  • NeuroScript source code
  • Ed25519 signature
  • Signing key ID (kid)
  • Metadata (name, version, creation date)
  • Content-type and checksums

Why Script Signing Matters

Security

Unsigned scripts can:

  • Execute arbitrary MIDI transforms
  • Access register state
  • Modify routing behavior

Signed scripts prevent:

  • Malicious code injection - Attacker can't modify bundled scripts
  • Supply chain attacks - Tampered presets detected and rejected
  • Accidental corruption - File damage caught before execution

Trust

Bundled presets can ship as Neurode-signed artifacts:

  • ✅ Verified when the build has bundled root keys and a verified keyset
  • ✅ Rejected as signed binaries when verification fails
  • ✅ May still offer editable source or another allowed fallback, depending on how the preset was packaged

User presets are unsigned by default:

  • ⚠️ No verification (trust yourself)
  • ⚠️ Can be modified freely
  • ⚠️ Shareable with others (but unverified)

Signature Verification Process

When loading a .nssigned file:

  1. Extract payload - Separate source code from signature
  2. Load root keys - Read the bundled root-key resource for this build
  3. Load keyset - Use the current cached keyset or refresh it from DNS
  4. Verify keyset signature - Ensure the keyset is signed by a trusted root key
  5. Find signing key - Match key ID (kid) from artifact
  6. Verify artifact signature - Check Ed25519 signature
  7. Check validity period - Ensure key is active and not expired
  8. Check digest - Verify payload hasn't been tampered with
  9. Extract source - Return NeuroScript code for compilation

Any verification failure → Reject the signed binary path and show an error. If the preset also includes editable source or another policy-allowed fallback, the app can use that instead.

Signature Policies

Control how unsigned scripts are handled:

Require signatures for bundled presets only

  • ✅ Bundled presets must be signed
  • ✅ User presets can be unsigned
  • ✅ Shared presets can be unsigned

To enable:

  1. Settings → Security
  2. Script Signing → ON (default)
  3. Allow Unsigned Binaries → OFF (default)

Result: Maximum safety without blocking user creativity.

Strict Policy

Require signatures for all presets

  • ✅ Bundled presets must be signed
  • ❌ User presets must be signed (rejects unsigned)
  • ❌ Shared presets must be signed (rejects unsigned)

To enable:

  1. Settings → Security
  2. Script Signing → ON
  3. Allow Unsigned Binaries → OFF
  4. Trusted Publishers → Neurode only

Result: Maximum security, requires signing all custom scripts.

Permissive Policy

Allow all presets regardless of signature

  • ⚠️ Bundled presets verified but not required
  • ⚠️ User presets always allowed
  • ⚠️ Shared presets always allowed

To enable:

  1. Settings → Security
  2. Allow Unsigned Binaries → ON

Result: No protection, use at your own risk.

Signed Artifact Format

File Extension

.nssigned - Signed NeuroScript artifact (CBOR-encoded)

Structure

┌─────────────────────────────────┐
│ CBOR Map                        │
├─────────────────────────────────┤
│ "u" → Unsigned payload (CBOR)   │
│ "s" → Signature (Ed25519, bytes)│
│ "k" → Key ID (string)           │
└─────────────────────────────────┘

Unsigned Payload Map:
├── "kind" → "neuroscript-source"
├── "name" → "Octave Doubler"
├── "version" → "1.0.0"
├── "contentType" → "text/neuroscript"
├── "createdAt" → timestamp
├── "payload" → NeuroScript source (bytes)
└── "digest" → SHA-256 of payload

Encoding: CBOR (Concise Binary Object Representation) Signature: Ed25519 (32-byte public key, 64-byte signature) Digest: SHA-256 (32-byte hash of payload)

Creating Signed Artifacts

Using neuroscript-sign CLI (developers only):

bash
# Sign a NeuroScript file
neuroscript-sign \
  --input my-transform.ns \
  --output my-transform.nssigned \
  --key-id "neurode-2026-01" \
  --private-key ~/.neurode/signing-key.pem

# Verify a signed artifact
neuroscript-sign --verify my-transform.nssigned

Requirements:

  • Private signing key (Ed25519)
  • Key ID from keyset
  • Access to signing infrastructure (Neurode developers only)

User presets typically don't need signing - just use .ns files directly.

Keyset Distribution

Signing keys are distributed via DNS TXT records.

DNS-Based Keyset

Domain: _neurode-keyset.neurode.ca

TXT record format:

ndks1:cboru:<base64url-encoded-keyset>

Keyset contents:

  • Sequence number (version)
  • Validity period (notBefore, expiresAt)
  • List of signing keys with:
    • Key ID (kid)
    • Public key (Ed25519, 32 bytes)
    • Permitted artifact kinds
    • Status (active, retired, revoked)
    • Validity window

Keyset signature:

  • Signed by a bundled root key loaded from Signing/root_keys.json
  • Prevents malicious keyset injection
  • Ensures keyset authenticity

Keyset Refresh

Automatic refresh:

  • On app launch
  • Again using the DNS TTL after a successful fetch
  • After failures, the app retries on a shorter backoff

Fallback to cache:

  • Last-known-good keyset cached locally
  • Used when DNS lookup fails
  • Persisted in ~/Library/Application Support/NeurodeMIDI/keyset.cbor

Manual refresh:

  • Settings → Security → Refresh Keyset

Key Rotation

Keys have limited validity periods for security:

Typical lifecycle:

  1. Active (365 days) - Used for signing new artifacts
  2. Retired (730 days) - Old artifacts still valid, no new signatures
  3. Revoked - Immediately invalid, all artifacts rejected

Why rotation:

  • Limit blast radius if key is compromised
  • Migrate to newer cryptographic standards
  • Retire keys that may have been exposed

User impact:

  • Bundled presets resign periodically with active keys
  • Old presets remain valid during retired period
  • No action required - handled automatically

Troubleshooting

Signature Verification Failed

Problem: Loading .nssigned file shows "Signature verification failed".

Solutions:

  1. Refresh keyset - Settings → Security → Refresh Keyset
  2. Check network - DNS lookup may have failed
  3. Verify file integrity - File may be corrupted (redownload)
  4. Check date/time - System clock affects validity window
  5. Allow unsigned - Temporarily enable unsigned binaries (Settings → Security)

Keyset Missing or Expired

Problem: All signed presets fail with "Keyset missing" or "Keyset not valid".

Solutions:

  1. Connect to internet - App needs DNS access to fetch keyset
  2. Check DNS - Verify _neurode-keyset.neurode.ca resolves
  3. Refresh keyset - Force refresh in Settings → Security
  4. Check system date - Incorrect date/time breaks validity checks
  5. Check root-key availability - If Settings reports missing root keys, use a build that bundles signing material or temporarily disable signing verification

Key Not Active / Key Expired

Problem: Preset signed with retired or expired key.

Solutions:

  1. Update bundled presets - Reinstall app or update to latest version
  2. Re-sign user preset - Use neuroscript-sign CLI with active key
  3. Allow unsigned - Disable signature requirement (Settings → Security)
  4. Report issue - Bundled presets should never use expired keys

Unsigned Binary Blocked

Problem: User preset won't load, shows "Unsigned binary rejected".

Solutions:

  1. Allow unsigned binaries - Settings → Security → Allow Unsigned Binaries → ON
  2. Sign the preset - Use neuroscript-sign CLI to create .nssigned
  3. Use bundled alternative - Check if similar preset exists in Presets Browser

Security Considerations

What Signing Does NOT Protect Against

  • Logic bugs - Signed script can still have incorrect behavior
  • Resource exhaustion - Signed script can still hit instruction limits
  • Malicious signed scripts - If attacker obtains signing key
  • Side-channel attacks - Timing, power analysis (not applicable to MIDI transforms)

Signing verifies authenticity, not correctness or safety.

Threat Model

Signing protects against:

  • Tampered bundled presets - Attacker modifies shipped examples
  • Supply chain attacks - Attacker replaces legitimate presets
  • File corruption - Accidental damage to preset files

Signing does NOT protect against:

  • User-written malicious scripts - User creates harmful transform
  • Compromised signing key - Attacker obtains private key
  • DNS hijacking - Attacker controls DNS records (keyset signature prevents this)

For maximum protection:

  • Keep signing keys offline (cold storage)
  • Use hardware security modules (HSM) for signing
  • Monitor keyset DNS for unauthorized changes
  • Revoke keys immediately if compromise suspected

Privacy

Signature verification does NOT:

  • Phone home or track usage
  • Send telemetry about loaded presets
  • Identify users or devices

DNS queries for keyset are anonymous:

  • No user identifiers in queries
  • Standard DNS traffic
  • No correlation with device or user

Advanced: Custom Signing Infrastructure

Organizations can deploy their own signing infrastructure for internal use:

Custom Root Keys

In app bundle (developers only):

swift
import NeurodeMIDICore

// Configure custom root keys at app launch (on MainActor)
NeuroscriptSigningConfiguration.shared.updateRootKeys([
    NeuroscriptRootKey(
        kid: "mycompany-root-2026",
        publicKey: Data(/* Ed25519 public key bytes */)
    )
])

Custom DNS Domain

Point to internal DNS:

swift
// Override keyset DNS domain (compile-time change required)
let keyset = try store.updateFromDNSText(
    resolver.resolve("_neuroscript-keys.mycompany.internal")
)

Offline Signing

For air-gapped environments:

  1. Generate keyset offline - Create valid keyset CBOR
  2. Sign keyset with root key - Offline Ed25519 signing
  3. Distribute keyset manually - Copy to devices via USB/network
  4. Import keyset - Load from file instead of DNS

Use cases:

  • Military/defense applications
  • Highly regulated industries
  • Air-gapped studio environments
  • Presets Browser - Where signed presets are loaded (see Presets Browser)
  • NeuroScript - Language that gets signed (see NeuroScript Guide)
  • Security Settings - Where signature policies are configured (see Settings)

Best Practices

For Users

Recommended settings:

  • ✅ Script Signing: ON
  • ✅ Allow Unsigned Binaries: OFF (for bundled), ON (for user)
  • ✅ Refresh keyset monthly

When sharing presets:

  • ⚠️ Don't trust unsigned presets from strangers
  • ✅ Verify source code before loading
  • ✅ Test in isolated environment first

For Developers

Signing presets for distribution:

  1. Test thoroughly - Ensure script works correctly
  2. Sign with active key - Use current kid from keyset
  3. Verify signature - Test loading before distributing
  4. Document purpose - Add comments explaining behavior
  5. Version artifacts - Track changes with semantic versioning

Key management:

  1. Generate offline - Private keys never touch network
  2. Backup securely - Encrypted backup in multiple locations
  3. Rotate annually - Fresh keys limit compromise window
  4. Revoke if compromised - Immediately invalidate exposed keys

For Organizations

Deploy custom infrastructure:

  1. Generate root key pair - Ed25519, offline generation
  2. Embed root public key - Compile into custom app build
  3. Set up keyset DNS - Internal DNS server for keyset distribution
  4. Automate signing - CI/CD pipeline signs artifacts on release
  5. Monitor keyset - Alert on unauthorized DNS changes

Audit trail:

  1. Log all signatures - Who signed what, when
  2. Track key usage - Which artifacts use which keys
  3. Monitor verification - Failed verifications may indicate attack
  4. Review periodically - Ensure signing practices remain secure

Technical Details

Cryptography:

  • Signature: Ed25519 (Curve25519-based)
  • Key size: 32 bytes (public), 64 bytes (private + public)
  • Signature size: 64 bytes
  • Digest: SHA-256 (32 bytes)

Performance:

  • Verification: <1ms per artifact (negligible overhead)
  • Keyset refresh: 100-500ms (DNS lookup + verification)
  • Signing: <1ms per artifact (not done on device)

Standards:

  • CBOR: RFC 8949 (Concise Binary Object Representation)
  • Ed25519: RFC 8032 (EdDSA signature scheme)
  • SHA-256: FIPS 180-4 (Secure Hash Standard)
  • Base64URL: RFC 4648 (URL-safe encoding)

Implementation:

  • CryptoKit (Apple's cryptography framework)
  • SwiftCBOR (CBOR encoding/decoding)
  • Foundation (networking, caching)

Built with ❤️ for musicians