ML-DSA Private Key Bytes
A post-quantum private key, generated inside your node graph
- seed_bytes
- private_bytes
- private_key
Somebody hid a message in a generated image and told you to prove it's theirs. Or you're the one hiding it and you'd rather the recipient could tell a real signature from a forged one. Either way you need a keypair, and this node is where the private half comes from.
ML-DSA is the NIST post-quantum signature standard - FIPS 204, the thing that replaced Dilithium in the final name and is meant to survive a large quantum computer. Python's cryptography library ships it now, and the ARG Toolkit wraps it, so yes, you can mint lattice-based key material from a 2019-era node graph. That's a genuinely strange sentence, and also the whole pitch of this pack.
What it actually does
The node has one job: give you an ML-DSA private key in two shapes at once. private_bytes is the serialized form - bytes you can base64, hex, stash in an image, or type back in later. private_key is the live key object on a KEYOBJ wire, which is what the signing node and the public-key node actually want.
How it gets the key depends on the key_source boolean. On (Fresh Key) it calls the library's generate() and you get entropy from the OS. Off (Seed Bytes) it derives the key deterministically from seed_bytes, which is wired in and has to be exactly 32 bytes. Seeded generation is the interesting mode for puzzle work: same seed, same key, every run, so a workflow someone else runs reproduces your keypair exactly.
The other three widgets control how the bytes come out. sig_alg picks ML-DSA-44, -65 or -87 - the three FIPS 204 parameter sets, roughly "small", "default", and "paranoid", where the private key alone runs 2560, 4032 and 4896 bytes respectively. encoding is PEM, DER or Raw, and formatting is PKCS8 or Raw. Unless you have a reason, leave PEM + PKCS8: it's the only combination that's a text blob you can copy-paste, and the size numbers above are for the raw key, not the wrappers.
The one trap worth knowing before you run it
encryption defaults to Best Available, which reads like your key is protected. Look at what the node does with it: it only encrypts when the mode is Best Available and an encryption_password was supplied. Empty password means NoEncryption, quietly, with no warning. So the default pair - "Best Available" plus a blank password field - writes your private key out in the clear. If the key matters, type a password; if it doesn't, set None and stop pretending.
And it matters more than usual here because of the pack itself. The README is upfront that this started as an amateur project, that its tests cover "the functions and most common use cases" rather than edge cases, and that chunks of it were written with LLM assistance. For a puzzle/ARG toy that's fine. For protecting a real secret, use openssl and let the graph consume the output.
Installing it
ComfyUI Manager, search ComfyUI ARG Toolkit, install, restart, hard-refresh the browser. Manually it's the usual:
cd ComfyUI/custom_nodes
git clone https://github.com/AzelusLightvale/ComfyUI-ARG-Toolkit
cd ComfyUI-ARG-Toolkit
pip install -r requirements.txt
The requirements file is the part to be careful with. It pins cryptography==50.0.2 - the ML-DSA nodes are only as good as that library version, and an older cryptography in your environment is the most likely reason the whole Modern/Asymmetric category imports badly. It also lists torch==2.14.1 and torchaudio==2.11.0, and this pack does nothing with either. Installing requirements into a working ComfyUI venv is exactly how people end up with a surprise torch rebuild and a broken CUDA setup - the ecosystem-wide dependency problem, not this author's fault. Windows portable users: use the embedded python, and consider installing the two things you actually need (cryptography, plus whatever bytes helpers you use) rather than the full list. Prefer Manager, which at least routes the install the same way you installed everything else.
Wiring it up
Generate here, wire private_key into ML-DSASignature to sign, or into ML-DSA Public Key Bytes (with key_source on, meaning "derive the public half from the key object") to hand out a verifier key. If you'd rather serialize it yourself, the private_bytes output is the thing to base64 and post somewhere.
One workflow-shaped gotcha: a Fresh Key private key is regenerated whenever the node actually re-executes - change any widget, or a fresh queue after the cache drops, and you have a new keypair, which means every signature you made with the old one is now unverifiable against the new public key. If you're iterating on a puzzle, seed the key, or capture the bytes once and feed them back in. The node is not an output node, so ComfyUI will happily cache it - and just as happily throw the cached value away when something upstream changes.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| sig_alg | COMBO | ML-DSA-65 | The algorithm to use for the private key. |
| key_source | BOOLEAN | true | The source to generate the private key. |
| encoding | COMBO | PEM | 3 options: PEM, DER, Raw |
| formatting | COMBO | PKCS8 | 2 options: PKCS8, Raw |
| encryption | COMBO | Best Available | 2 options: Best Available, None |
| encryption_passwordopt | STRING | The password to use to encrypt the private key. Required if encryption is used. | |
| seed_bytesopt | BYTESLIKE | The seed bytes to use for generating the private key only when the source is 'Seed Bytes'. Has to be 32 bytes. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| private_bytes | BYTESLIKE | — |
| private_key | KEYOBJ | — |