{"record":{"id":"308717c3bef79548","repo":"hyperledger/fabric","slug":"signature-policy-is-not-an-or-concatenation-nouto-308717","errorCode":null,"errorMessage":"signature policy is not an OR concatenation, NOutOf %d","messagePattern":"signature policy is not an OR concatenation, NOutOf (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/handlers/validation/builtin/v13/lscc_validation_logic.go","lineNumber":110,"sourceCode":"\t\t}\n\n\t\t// make sure that the signature policy is meaningful (only consists of ORs)\n\t\terr := validateSpOrConcat(newCollection.MemberOrgsPolicy.GetSignaturePolicy().Rule)\n\t\tif err != nil {\n\t\t\treturn errors.WithMessagef(err, \"collection-name: %s -- error in member org policy\", collectionName)\n\t\t}\n\t}\n\treturn nil\n}\n\n// validateSpOrConcat checks if the supplied signature policy is just an OR-concatenation of identities\nfunc validateSpOrConcat(sp *common.SignaturePolicy) error {\n\tif sp.GetNOutOf() == nil {\n\t\treturn nil\n\t}\n\t// check if N == 1 (OR concatenation)\n\tif sp.GetNOutOf().N != 1 {\n\t\treturn errors.New(fmt.Sprintf(\"signature policy is not an OR concatenation, NOutOf %d\", sp.GetNOutOf().N))\n\t}\n\t// recurse into all sub-rules\n\tfor _, rule := range sp.GetNOutOf().Rules {\n\t\terr := validateSpOrConcat(rule)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc checkForMissingCollections(newCollectionsMap map[string]*pb.StaticCollectionConfig, oldCollectionConfigs []*pb.CollectionConfig,\n) error {\n\tvar missingCollections []string\n\n\t// In the new collection config package, ensure that there is one entry per old collection. Any\n\t// number of new collections are allowed.\n\tfor _, oldCollectionConfig := range oldCollectionConfigs {","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/core/handlers/validation/builtin/v13/lscc_validation_logic.go#L92-L128","documentation":"validateSpOrConcat enforces that a collection's MemberOrgsPolicy signature policy is only a concatenation of OR rules (any-of semantics, NOutOf with N==1, possibly nested). If any NOutOf node in the policy tree has N != 1 (e.g. a 2-of-3 rule), the policy is rejected because collection membership policies must express 'any of these organizations'.","triggerScenarios":"Defining a collection whose memberOrgsPolicy signaturePolicy contains an NOutOf rule with N > 1 (or effectively 0) anywhere in the tree, submitted as part of a collection config during chaincode definition.","commonSituations":"Building the SignaturePolicyEnvelope programmatically with common.SignaturePolicy_NOutOf{N: 2, ...}; converting an endorsement policy (which often uses N-of semantics) and reusing it as a collection member policy; policy YAML/JSON with 'signedBy' counts.","solutions":["Rewrite the member orgs policy so every NOutOf node has N==1, listing organizations as OR-ed principals","Use the collections config helpers so the policy is built as an OR concatenation of the desired org MSP principals","Do not reuse chaincode endorsement policies verbatim as collection member policies; only the 'any org' subset is allowed"],"exampleFix":"// before\npolicy := &common.SignaturePolicy{Type: &common.SignaturePolicy_NOutOf{NOutOf: &common.SignaturePolicy_NOutOf{N: 2, Rules: rules}}}\n// after\npolicy := &common.SignaturePolicy{Type: &common.SignaturePolicy_NOutOf{NOutOf: &common.SignaturePolicy_NOutOf{N: 1, Rules: rules}}}","handlingStrategy":"validation","validationCode":"function validateMemberOrgsPolicy(policy) {\n  const walk = (sp) => {\n    if (sp.nOutOf) {\n      if (sp.nOutOf.n !== 1) throw new Error('member orgs policy must be an OR concatenation (nOutOf n=1)');\n      sp.nOutOf.rules.forEach(walk);\n    }\n  };\n  walk(policy);\n}","typeGuard":"function isOrConcatPolicy(sp) {\n  if (!sp || !sp.nOutOf) return true;\n  return sp.nOutOf.n === 1 && sp.nOutOf.rules.every(isOrConcatPolicy);\n}","tryCatchPattern":"try {\n  await contract.submitTransaction('DeployChaincode', ...args);\n} catch (err) {\n  if (String(err).includes('not an OR concatenation')) {\n    // rebuild memberOrgsPolicy with n=1 rules\n  }\n  throw err;\n}","preventionTips":["Never reuse endorsement policies (which use N-of semantics) as collection member policies","Build member policies only from OR-ed principal lists","Add a unit test that walks any policy tree asserting n==1 at each NOutOf node"],"tags":["hyperledger-fabric","signature-policy","private-data","validation"],"backgroundTag":"invalid-signature-policy","analyzedSha":"2736b63f8fd5932511d56fe68b7039d15977f7f6","analyzedAt":"2026-09-04T08:52:36.465Z","contentChangedAt":"2026-09-04T08:52:36.465Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}