ML-DSA Message Verification
One boolean, and it can be false for the wrong reason
- message
- signature
- public_bytes
- public_key
- context
- verify_status
Every signature workflow ends here. Someone hands you a message, a signature, and their public key; you want to know whether the signature is genuine. This node answers with verify_status - a plain BOOLEAN, true or false, nothing else. That's the correct interface for the job and also the reason it's easy to misread.
What goes in
Four required inputs, and the ordering of importance is: message, signature, sig_alg, key_source.
message and signature are both BYTESLIKE and both forced to sockets - no typing hex into a widget. Whatever byte string was signed is what has to arrive here, byte for byte. sig_alg has to be the same ML-DSA parameter set the signer used (ML-DSA-44, -65 or -87); the node does not sniff it from the key, and a wrong pick is one of the ways you get a false that isn't a forgery.
key_source is a boolean whose published labels are actively misleading, so read this before you click it. On, the label says From Private Bytes - but what it actually does is use a public key object you wired into the optional public_key input. Off, the label says From Loaded Key - and it actually loads raw public key bytes from the optional public_bytes input. The labels look like they were copied from the signing node and never fixed. The behaviour is sane; the words are swapped. Remember it as: on = object, off = bytes.
context, optional and up to 255 bytes, must match what the signer used exactly. If the signer bound a signature to a phase label and you pass nothing, you get a false and no explanation. It's the single most common "but it's the right key" head-scratch in this whole scheme.
Why you can get false instead of an error
The implementation wraps the library's verify call and catches both InvalidSignature and ValueError, returning False for either. The InvalidSignature case is the one you want: bytes were wrong, signature doesn't hold. The ValueError case is the trap - that's also what you get from structurally wrong input, so a mangled key, a truncated signature, or a parameter-set mismatch all collapse into the same quiet false.
Which means false means "not verified", not "forged". When debugging, don't start by suspecting an attacker; start by suspecting a mismatch. Check, in this order: same sig_alg at both ends, same context, message bytes identical (a trailing newline from a text widget is the classic), and the public key actually derived from the private key that signed. Three of those four are configuration, not cryptography.
And one hard failure you can distinguish: if key_source is on with nothing wired into public_key, there's no key object to call verify on and the node errors out instead of returning false. Red border means wiring, plain false means bytes.
Installing it
Same pack, same drill. Manager, search ComfyUI ARG Toolkit, install, restart, refresh the browser - or:
cd ComfyUI/custom_nodes
git clone https://github.com/AzelusLightvale/ComfyUI-ARG-Toolkit
pip install -r requirements.txt
The ML-DSA nodes live or die on the cryptography version - the pack pins 50.0.2, and ML-DSA support is recent enough that an environment where something else pulled an older cryptography is the first suspect if the whole Modern/Asymmetric category misbehaves. The requirements file also pins torch and torchaudio, which this pack doesn't use; letting pip re-resolve torch in a working ComfyUI venv is how people lose an evening. Manager installs into the same shared interpreter, so it's not magically safer - it's just consistent.
Where the boolean goes
verify_status is the kind of output that's more useful than it looks. Drop it into anything downstream that takes a BOOLEAN - a primitive you can display, a gate on a branch - and the whole thing becomes a puzzle check: correct signature lights up true, everything else is false. It pairs naturally with the pack's other verification-style nodes for multi-stage ARG puzzles, where the answer to stage one decides whether stage two runs.
Two notes on living with it. First, verification is pure computation on small inputs, so it's cheap and fast - no reason to avoid re-running it. Second, the node holds no state: no key is cached, nothing is remembered between runs. Every queue is a fresh "did these three byte strings agree", which is exactly what you want from a verifier and a fact worth internalising before you build logic that assumes otherwise.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| sig_alg | COMBO | ML-DSA-65 | The algorithm to use for the public key. |
| key_source | BOOLEAN | The source of the public key to be used to verify the signature of the message | |
| message | BYTESLIKE | — | |
| signature | BYTESLIKE | — | |
| public_bytesopt | BYTESLIKE | — | |
| public_keyopt | KEYOBJ | Only applicable if key_source is 'From Loaded Key'. | |
| contextopt | BYTESLIKE | Optional context string, up to 255 bytes. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| verify_status | BOOLEAN | — |