HelloZeroNet/ZeroNet · error · VerifyError

Valid signs: %s/%s

Error message

Valid signs: %s/%s

What it means

verifyFile raises VerifyError('Valid signs: %s/%s') when, after checking each valid signer's signature in content['signs'], the count of valid signatures (valid_signs) is below signs_required. Multi-signature content.json must collect enough valid signatures from the authorized signer list; failing that, the content is rejected. The message shows valid count vs required count.

Source

Thrown at src/Content/ContentManager.py:998

                    valid_signers = self.getValidSigners(inner_path, new_content)
                    signs_required = self.getSignsRequired(inner_path, new_content)

                    if inner_path == "content.json" and len(valid_signers) > 1:  # Check signers_sign on root content.json
                        signers_data = "%s:%s" % (signs_required, ",".join(valid_signers))
                        if not CryptBitcoin.verify(signers_data, self.site.address, new_content["signers_sign"]):
                            raise VerifyError("Invalid signers_sign!")

                    if inner_path != "content.json" and not self.verifyCert(inner_path, new_content):  # Check if cert valid
                        raise VerifyError("Invalid cert!")

                    valid_signs = 0
                    for address in valid_signers:
                        if address in signs:
                            valid_signs += CryptBitcoin.verify(sign_content, address, signs[address])
                        if valid_signs >= signs_required:
                            break  # Break if we has enough signs
                    if valid_signs < signs_required:
                        raise VerifyError("Valid signs: %s/%s" % (valid_signs, signs_required))
                    else:
                        return self.verifyContent(inner_path, new_content)
                else:  # Old style signing
                    raise VerifyError("Invalid old-style sign")

            except Exception as err:
                self.log.warning("%s: verify sign error: %s" % (inner_path, Debug.formatException(err)))
                raise err

        else:  # Check using sha512 hash
            file_info = self.getFileInfo(inner_path)
            if file_info:
                if CryptHash.sha512sum(file) != file_info.get("sha512", ""):
                    raise VerifyError("Invalid hash")

                if file_info.get("size", 0) != file.tell():
                    raise VerifyError(
                        "File size does not match %s <> %s" %

View on GitHub (pinned to 454c0b2e7e)

Solutions

  1. Have enough valid signers re-sign the exact current content: each signer runs CryptBitcoin.sign(sign_content, their_privatekey) and adds it to content['signs'][address]
  2. Lower signs_required in the content rules if fewer signatures are intended (requires re-sign/signers_sign update)
  3. Ensure no field changed after signatures were collected — sign_content is derived from the content itself, so any edit invalidates all signs
  4. Confirm signer addresses are in the valid_signers list; signatures from non-listed addresses don't count

Example fix

// before: only 1 of 2 required signatures
{"signs": {"1SignerA": "sigA"}}  // signs_required: 2
// after: second signer adds their signature
{"signs": {"1SignerA": "sigA", "1SignerB": "sigB"}}
Defensive patterns

Strategy: validation

Validate before calling

from Crypt import CryptBitcoin
content = json.load(open('content.json'))
sign_content = content_manager.signContentJson(content)  # reconstruct signed body
valid = sum(1 for addr, sig in content.get('signs', {}).items()
            if CryptBitcoin.verify(sign_content, addr, sig))
required = content_manager.getSignsRequired(inner_path, content)
assert valid >= required, f'only {valid}/{required} valid signatures'

Try / catch

from Content.ContentManager import VerifyError
try:
    site.content_manager.isModified(inner_path, file)
except VerifyError as e:
    if str(e).startswith('Valid signs:'):
        collect_missing_signatures()  # ask co-signers to re-sign current content
    else:
        raise

Prevention

When it happens

Trigger: isModified -> verifyFile counts CryptBitcoin.verify(sign_content, address, signs[address]) over valid_signers; fewer than getSignsRequired() signatures verify (missing signatures, signatures over stale sign_content after any content field change, or wrong signing keys).

Common situations: A co-author edited content.json (changing sign_content) so other signers' signatures no longer match; one signer hasn't re-signed after an update; signs_required raised without collecting the extra signature; signatures from addresses not in valid_signers are ignored.

Related errors


AI-assisted analysis of HelloZeroNet/ZeroNet@454c0b2e7e (2026-09-02). Data as JSON: /api/errors/4b9f3621c8e66786. Report an issue: GitHub.