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.jsonand 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:
- Extract payload - Separate source code from signature
- Load root keys - Read the bundled root-key resource for this build
- Load keyset - Use the current cached keyset or refresh it from DNS
- Verify keyset signature - Ensure the keyset is signed by a trusted root key
- Find signing key - Match key ID (kid) from artifact
- Verify artifact signature - Check Ed25519 signature
- Check validity period - Ensure key is active and not expired
- Check digest - Verify payload hasn't been tampered with
- 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:
Recommended Policy (Default)
Require signatures for bundled presets only
- ✅ Bundled presets must be signed
- ✅ User presets can be unsigned
- ✅ Shared presets can be unsigned
To enable:
- Settings → Security
- Script Signing → ON (default)
- 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:
- Settings → Security
- Script Signing → ON
- Allow Unsigned Binaries → OFF
- 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:
- Settings → Security
- 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 payloadEncoding: 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):
# 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.nssignedRequirements:
- 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:
- Active (365 days) - Used for signing new artifacts
- Retired (730 days) - Old artifacts still valid, no new signatures
- 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:
- Refresh keyset - Settings → Security → Refresh Keyset
- Check network - DNS lookup may have failed
- Verify file integrity - File may be corrupted (redownload)
- Check date/time - System clock affects validity window
- 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:
- Connect to internet - App needs DNS access to fetch keyset
- Check DNS - Verify
_neurode-keyset.neurode.caresolves - Refresh keyset - Force refresh in Settings → Security
- Check system date - Incorrect date/time breaks validity checks
- 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:
- Update bundled presets - Reinstall app or update to latest version
- Re-sign user preset - Use
neuroscript-signCLI with active key - Allow unsigned - Disable signature requirement (Settings → Security)
- Report issue - Bundled presets should never use expired keys
Unsigned Binary Blocked
Problem: User preset won't load, shows "Unsigned binary rejected".
Solutions:
- Allow unsigned binaries - Settings → Security → Allow Unsigned Binaries → ON
- Sign the preset - Use
neuroscript-signCLI to create.nssigned - 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):
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:
// 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:
- Generate keyset offline - Create valid keyset CBOR
- Sign keyset with root key - Offline Ed25519 signing
- Distribute keyset manually - Copy to devices via USB/network
- Import keyset - Load from file instead of DNS
Use cases:
- Military/defense applications
- Highly regulated industries
- Air-gapped studio environments
Related Features
- 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:
- Test thoroughly - Ensure script works correctly
- Sign with active key - Use current kid from keyset
- Verify signature - Test loading before distributing
- Document purpose - Add comments explaining behavior
- Version artifacts - Track changes with semantic versioning
Key management:
- Generate offline - Private keys never touch network
- Backup securely - Encrypted backup in multiple locations
- Rotate annually - Fresh keys limit compromise window
- Revoke if compromised - Immediately invalidate exposed keys
For Organizations
Deploy custom infrastructure:
- Generate root key pair - Ed25519, offline generation
- Embed root public key - Compile into custom app build
- Set up keyset DNS - Internal DNS server for keyset distribution
- Automate signing - CI/CD pipeline signs artifacts on release
- Monitor keyset - Alert on unauthorized DNS changes
Audit trail:
- Log all signatures - Who signed what, when
- Track key usage - Which artifacts use which keys
- Monitor verification - Failed verifications may indicate attack
- 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)
