{"record":{"id":"4b9f3621c8e66786","repo":"HelloZeroNet/ZeroNet","slug":"valid-signs-s-s","errorCode":null,"errorMessage":"Valid signs: %s/%s","messagePattern":"Valid signs: (.+?)/(.+?)","errorType":"validation","errorClass":"VerifyError","httpStatus":null,"severity":"error","filePath":"src/Content/ContentManager.py","lineNumber":998,"sourceCode":"                    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:\n                self.log.warning(\"%s: verify sign error: %s\" % (inner_path, Debug.formatException(err)))\n                raise err\n\n        else:  # Check using sha512 hash\n            file_info = self.getFileInfo(inner_path)\n            if file_info:\n                if CryptHash.sha512sum(file) != file_info.get(\"sha512\", \"\"):\n                    raise VerifyError(\"Invalid hash\")\n\n                if file_info.get(\"size\", 0) != file.tell():\n                    raise VerifyError(\n                        \"File size does not match %s <> %s\" %","sourceCodeStart":980,"sourceCodeEnd":1016,"githubUrl":"https://github.com/HelloZeroNet/ZeroNet/blob/454c0b2e7e000fda7000cba49027541fbf327b96/src/Content/ContentManager.py#L980-L1016","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["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]","Lower signs_required in the content rules if fewer signatures are intended (requires re-sign/signers_sign update)","Ensure no field changed after signatures were collected — sign_content is derived from the content itself, so any edit invalidates all signs","Confirm signer addresses are in the valid_signers list; signatures from non-listed addresses don't count"],"exampleFix":"// before: only 1 of 2 required signatures\n{\"signs\": {\"1SignerA\": \"sigA\"}}  // signs_required: 2\n// after: second signer adds their signature\n{\"signs\": {\"1SignerA\": \"sigA\", \"1SignerB\": \"sigB\"}}","handlingStrategy":"validation","validationCode":"from Crypt import CryptBitcoin\ncontent = json.load(open('content.json'))\nsign_content = content_manager.signContentJson(content)  # reconstruct signed body\nvalid = sum(1 for addr, sig in content.get('signs', {}).items()\n            if CryptBitcoin.verify(sign_content, addr, sig))\nrequired = content_manager.getSignsRequired(inner_path, content)\nassert valid >= required, f'only {valid}/{required} valid signatures'","typeGuard":null,"tryCatchPattern":"from Content.ContentManager import VerifyError\ntry:\n    site.content_manager.isModified(inner_path, file)\nexcept VerifyError as e:\n    if str(e).startswith('Valid signs:'):\n        collect_missing_signatures()  # ask co-signers to re-sign current content\n    else:\n        raise","preventionTips":["Freeze content edits until all required signers have signed","Re-collect signatures after ANY content change","Keep signs_required no higher than the number of active co-signers"],"tags":["zeronet","multisig","signature","content-verification"],"backgroundTag":"insufficient-signatures","analyzedSha":"454c0b2e7e000fda7000cba49027541fbf327b96","analyzedAt":"2026-09-02T19:46:57.278Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}