{"record":{"id":"e4bcbc21db423bb1","repo":"HelloZeroNet/ZeroNet","slug":"invalid-signers-sign","errorCode":null,"errorMessage":"Invalid signers_sign!","messagePattern":"Invalid signers_sign!","errorType":"validation","errorClass":"VerifyError","httpStatus":null,"severity":"critical","filePath":"src/Content/ContentManager.py","lineNumber":986,"sourceCode":"                sign_content = json.dumps(new_content, sort_keys=True)  # Dump the json to string to remove whitepsace\n\n                # Fix float representation error on Android\n                modified = new_content[\"modified\"]\n                if config.fix_float_decimals and type(modified) is float and not str(modified).endswith(\".0\"):\n                    modified_fixed = \"{:.6f}\".format(modified).strip(\"0.\")\n                    sign_content = sign_content.replace(\n                        '\"modified\": %s' % repr(modified),\n                        '\"modified\": %s' % modified_fixed\n                    )\n\n                if signs:  # New style signing\n                    valid_signers = self.getValidSigners(inner_path, new_content)\n                    signs_required = self.getSignsRequired(inner_path, new_content)\n\n                    if inner_path == \"content.json\" and len(valid_signers) > 1:  # Check signers_sign on root content.json\n                        signers_data = \"%s:%s\" % (signs_required, \",\".join(valid_signers))\n                        if not CryptBitcoin.verify(signers_data, self.site.address, new_content[\"signers_sign\"]):\n                            raise VerifyError(\"Invalid signers_sign!\")\n\n                    if inner_path != \"content.json\" and not self.verifyCert(inner_path, new_content):  # Check if cert valid\n                        raise VerifyError(\"Invalid cert!\")\n\n                    valid_signs = 0\n                    for address in valid_signers:\n                        if address in signs:\n                            valid_signs += CryptBitcoin.verify(sign_content, address, signs[address])\n                        if valid_signs >= signs_required:\n                            break  # Break if we has enough signs\n                    if valid_signs < signs_required:\n                        raise VerifyError(\"Valid signs: %s/%s\" % (valid_signs, signs_required))\n                    else:\n                        return self.verifyContent(inner_path, new_content)\n                else:  # Old style signing\n                    raise VerifyError(\"Invalid old-style sign\")\n\n            except Exception as err:","sourceCodeStart":968,"sourceCodeEnd":1004,"githubUrl":"https://github.com/HelloZeroNet/ZeroNet/blob/454c0b2e7e000fda7000cba49027541fbf327b96/src/Content/ContentManager.py#L968-L1004","documentation":"verifyFile raises VerifyError('Invalid signers_sign!') when the root content.json has multiple valid signers (len(valid_signers) > 1) and the 'signers_sign' field fails CryptBitcoin.verify against the site address. signers_sign is a signature (by the site's private key) over the string '<signs_required>:<signer1>,<signer2>,...' proving the owner authorized that signer list and required-signature count. A mismatch means the signer list or signs_required was tampered with, or signed with the wrong key.","triggerScenarios":"verifyFile on inner_path == 'content.json' with getValidSigners returning more than one signer; CryptBitcoin.verify('%s:%s' % (signs_required, ','.join(valid_signers)), site_address, content['signers_sign']) returns False — e.g. after editing signers or signs_required without re-generating signers_sign.","commonSituations":"Hand-editing valid_signers or signs_required in content.json without re-signing; using a private key that doesn't match the site address; multi-sig sites (shared sites like blogs-with-cert authors) where signers_sign was generated from a different signer set.","solutions":["Re-generate signers_sign with the site's private key over the exact string '<signs_required>:<signer1>,<signer2>' and update content.json","Confirm the site private key (data/<site>/private.key or users.json site entry) matches the site address","Ensure signs_required and the valid_signers list are exactly what was signed (order and formatting matter — comma-joined, no spaces)","If multi-sig is not needed, revert to a single signer so the signers_sign check is skipped"],"exampleFix":"// before: edited signers without re-signing\n{\"signers_sign\": \"OLD_SIG\", \"sign\": {\"1Old\": \"sig\"}}\n// after: regenerate signers_sign from current signer list\nfrom Crypt import CryptBitcoin\nsigners_data = \"%s:%s\" % (signs_required, \",\".join(valid_signers))\ncontent[\"signers_sign\"] = CryptBitcoin.sign(signers_data, privatekey)","handlingStrategy":"validation","validationCode":"from Crypt import CryptBitcoin\ncontent = json.load(open('content.json'))\nvalid_signers = content_manager.getValidSigners('content.json', content)\nif len(valid_signers) > 1:\n    data = \"%s:%s\" % (content_manager.getSignsRequired('content.json', content), ','.join(valid_signers))\n    assert CryptBitcoin.verify(data, site_address, content['signers_sign']), 'signers_sign mismatch — re-sign'","typeGuard":null,"tryCatchPattern":"from Content.ContentManager import VerifyError\ntry:\n    site.content_manager.isModified('content.json', file)\nexcept VerifyError as e:\n    if str(e) == 'Invalid signers_sign!':\n        resign_signers_sign(site_privatekey, content)\n    else:\n        raise","preventionTips":["Regenerate signers_sign every time signers or signs_required changes","Sign with the site's own private key, not a personal one","Keep the signer list order/format exactly as signed"],"tags":["zeronet","signature","multisig","content-verification"],"backgroundTag":"invalid-signature","analyzedSha":"454c0b2e7e000fda7000cba49027541fbf327b96","analyzedAt":"2026-09-02T19:46:57.278Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}