diff --git a/docs/src/architecture/08_concepts/signed_doc/.pages b/docs/src/architecture/08_concepts/signed_doc/.pages index ec6e58bbdb5..2f72c50dd2c 100644 --- a/docs/src/architecture/08_concepts/signed_doc/.pages +++ b/docs/src/architecture/08_concepts/signed_doc/.pages @@ -7,3 +7,4 @@ nav: - Document Form Templates: form_templates.md - form_template_elements - Document Presentation Templates: presentation_template.md + - key-derivation diff --git a/docs/src/architecture/08_concepts/signed_doc/cddl/section_ref.cddl b/docs/src/architecture/08_concepts/signed_doc/cddl/section_ref.cddl index 602767751a1..427ec6c50a1 100644 --- a/docs/src/architecture/08_concepts/signed_doc/cddl/section_ref.cddl +++ b/docs/src/architecture/08_concepts/signed_doc/cddl/section_ref.cddl @@ -5,4 +5,5 @@ section_ref = json_pointer ; RFC6901 Standard JSON Pointer +; See: https://datatracker.ietf.org/doc/html/rfc6901 json_pointer = text diff --git a/docs/src/architecture/08_concepts/signed_doc/cddl/signed_document.cddl b/docs/src/architecture/08_concepts/signed_doc/cddl/signed_document.cddl index 947b56a8db7..2650553304b 100644 --- a/docs/src/architecture/08_concepts/signed_doc/cddl/signed_document.cddl +++ b/docs/src/architecture/08_concepts/signed_doc/cddl/signed_document.cddl @@ -33,7 +33,7 @@ COSE_Document_Header_Map = { ; COSE Standard headers used by a Document COSE_Document_Standard_Headers = ( 3 => media_type - ?"content-encoding" => http_content_encoding + "content-encoding" => http_content_encoding ) ; Supported Content Media Types. @@ -113,6 +113,7 @@ cid = #6.42(bytes) section_ref = json_pointer ; RFC6901 Standard JSON Pointer +; See: https://datatracker.ietf.org/doc/html/rfc6901 json_pointer = text ; Allowed Collaborators on the next subsequent version of a document. diff --git a/docs/src/architecture/08_concepts/signed_doc/diagrams/all.dot b/docs/src/architecture/08_concepts/signed_doc/diagrams/all.dot index 23b8b10e393..a48854baeff 100644 --- a/docs/src/architecture/08_concepts/signed_doc/diagrams/all.dot +++ b/docs/src/architecture/08_concepts/signed_doc/diagrams/all.dot @@ -581,7 +581,7 @@ digraph "All" { - +
content typeUndefinedapplication/json
diff --git a/docs/src/architecture/08_concepts/signed_doc/diagrams/all.dot.svg b/docs/src/architecture/08_concepts/signed_doc/diagrams/all.dot.svg index f12f6fafbb9..13cdebe3a78 100644 --- a/docs/src/architecture/08_concepts/signed_doc/diagrams/all.dot.svg +++ b/docs/src/architecture/08_concepts/signed_doc/diagrams/all.dot.svg @@ -692,7 +692,7 @@ content type -Undefined +application/json type diff --git a/docs/src/architecture/08_concepts/signed_doc/diagrams/contest_delegation.dot b/docs/src/architecture/08_concepts/signed_doc/diagrams/contest_delegation.dot index 5e672a15caf..c658c09b181 100644 --- a/docs/src/architecture/08_concepts/signed_doc/diagrams/contest_delegation.dot +++ b/docs/src/architecture/08_concepts/signed_doc/diagrams/contest_delegation.dot @@ -57,7 +57,7 @@ Relationships" - +
content typeUndefinedapplication/json
diff --git a/docs/src/architecture/08_concepts/signed_doc/docs/brand_parameters.md b/docs/src/architecture/08_concepts/signed_doc/docs/brand_parameters.md index bbe3a606730..5912b69e3a0 100644 --- a/docs/src/architecture/08_concepts/signed_doc/docs/brand_parameters.md +++ b/docs/src/architecture/08_concepts/signed_doc/docs/brand_parameters.md @@ -158,7 +158,7 @@ Revoked documents are flagged as no longer valid, and should not be displayed. As a special case, if the revocations are set to `true` then all versions of the document are revoked, including the latest document. -In this case, when the latest document is revoked, the payload may be empty. +In this case, when the latest document is revoked, the payload may be `nil`. Any older document that has [`revocations`](../metadata.md#revocations) set to `true` is always to be filtered and its payload is to be assumed to be invalid. diff --git a/docs/src/architecture/08_concepts/signed_doc/docs/campaign_parameters.md b/docs/src/architecture/08_concepts/signed_doc/docs/campaign_parameters.md index 0009dc515ff..26a482d6c90 100644 --- a/docs/src/architecture/08_concepts/signed_doc/docs/campaign_parameters.md +++ b/docs/src/architecture/08_concepts/signed_doc/docs/campaign_parameters.md @@ -159,7 +159,7 @@ Revoked documents are flagged as no longer valid, and should not be displayed. As a special case, if the revocations are set to `true` then all versions of the document are revoked, including the latest document. -In this case, when the latest document is revoked, the payload may be empty. +In this case, when the latest document is revoked, the payload may be `nil`. Any older document that has [`revocations`](../metadata.md#revocations) set to `true` is always to be filtered and its payload is to be assumed to be invalid. diff --git a/docs/src/architecture/08_concepts/signed_doc/docs/category_parameters.md b/docs/src/architecture/08_concepts/signed_doc/docs/category_parameters.md index 94def30d664..bd7cf0c738f 100644 --- a/docs/src/architecture/08_concepts/signed_doc/docs/category_parameters.md +++ b/docs/src/architecture/08_concepts/signed_doc/docs/category_parameters.md @@ -159,7 +159,7 @@ Revoked documents are flagged as no longer valid, and should not be displayed. As a special case, if the revocations are set to `true` then all versions of the document are revoked, including the latest document. -In this case, when the latest document is revoked, the payload may be empty. +In this case, when the latest document is revoked, the payload may be `nil`. Any older document that has [`revocations`](../metadata.md#revocations) set to `true` is always to be filtered and its payload is to be assumed to be invalid. diff --git a/docs/src/architecture/08_concepts/signed_doc/docs/contest_delegation.md b/docs/src/architecture/08_concepts/signed_doc/docs/contest_delegation.md index 40b42a7594c..c006d96b528 100644 --- a/docs/src/architecture/08_concepts/signed_doc/docs/contest_delegation.md +++ b/docs/src/architecture/08_concepts/signed_doc/docs/contest_delegation.md @@ -15,7 +15,7 @@ Multiple Delegations must be published if there are multiple Contests within a Brand/Campaign or Category. This is because different Contests may have different rules. -And not all Representatives will choose to nominate +And not all Representatives will choose to (or be able to) nominate for every Contest. A Representative ***MAY NOT*** delegate to a different Representative @@ -23,6 +23,39 @@ for any contest they have nominated for. They ***MAY*** however nominate a Representative in any contest they have not nominated for. +A Representative is NOT required to delegate to themselves in a contest they are nominated for, +and in fact, any self-delegation is invalid and ignored. +A Representative has an implicit 100% voting power delegation to themselves in any contest +they are nominated. +The MAY not vote personally, and if they do, that vote will have Zero (0) voting power. +100% of their voting power is assigned to their delegate vote and can not be split in any way. + +A voter MAY choose multiple delegates for a contest, in this case they are listed in priority +order from highest priority to lowest. +Priority only affects two aspects of the delegation. + +1. Any residual voting power after it is split among all delegates is given to the highest + priority delegate (first). +2. If there is not enough voting power to distribute, then its distributed from highest + priority to lowest. + This may mean that low priority delegates get zero voting power. + +An example: If a Voter has 100 raw voting power, after quadratic scaling, they have 10. +If they delegated to 15 delegates equally, then only the first 10 would get 1 voting power +each. +Voting power is not fractionally assigned. + +The payload MAY contain a [json][RFC8259] document which consists of a single array which can adjust +the ratio of the delegation. +Voting power is divided based on the weight of a single delegate over the sum of all +weights of all delegates. +This is performed with integer division. +As a special condition, 0 or any negative value is equivalent to a weight of 1. +As explained above, if there is not enough voting power to distribute, low priority reps +will receive 0 voting power from the delegation. +And if there is any residual after integer division its applied to the representative +with the highest priority. + ```graphviz dot contest_delegation.dot.svg @@ -37,6 +70,12 @@ have not nominated for. * The [`parameters`](../metadata.md#parameters) metadata *MUST* point to the same Contest as the Nomination of the Representative. * The 'ref' metadata field MUST point to a valid 'Representative Nomination'. + IF there are multiple representatives, then any which are not pointing + to a valid `Representative Nomination` are excluded. + The nomination is only invalid if ALL references `Representative Nomination` + references are invalid. + This is to prevent a Representative changing their nomination invalidating a + delegation with multiple representatives. * The payload MUST be nil. A Representative *MUST* Delegate to their latest Nomination for a Category, @@ -74,7 +113,8 @@ considered. ## [COSE Header Parameters][RFC9052-HeaderParameters] -No Headers are defined for this document. +* [content type](../spec.md#content-type) = `application/json` +* [content-encoding](../spec.md#content-encoding) = `[br]` ## Metadata @@ -134,6 +174,7 @@ The document version must always be >= the document ID. | --- | --- | | Required | yes | | Format | [Document Reference](../metadata.md#document-reference) | +| Multiple References | True | | Valid References | [Rep Nomination](rep_nomination.md) | Reference to a Linked Document or Documents. @@ -182,7 +223,7 @@ Revoked documents are flagged as no longer valid, and should not be displayed. As a special case, if the revocations are set to `true` then all versions of the document are revoked, including the latest document. -In this case, when the latest document is revoked, the payload may be empty. +In this case, when the latest document is revoked, the payload may be `nil`. Any older document that has [`revocations`](../metadata.md#revocations) set to `true` is always to be filtered and its payload is to be assumed to be invalid. @@ -231,10 +272,83 @@ hierarchy they are all valid. ## Payload -There is no payload. - -This document has no payload. -It must be encoded as a [CBOR][RFC8949] `null (0xf6)`. +The Payload is a [JSON][RFC8259] Document, and must conform to this schema. + +It consists of an array which defines the weights to be applied to the chosen delegations. + +Each valid delegate gets the matching weight from this array. +The total voting power is split proportionally based on these weights over the +valid drep nominations. + +### Schema + + +??? abstract "Schema: Payload [JSON][RFC8259] Schema" + + The Payload is a [JSON][RFC8259] Document, and must conform to this schema. + + It consists of an array which defines the weights to be applied to the chosen delegations. + + Each valid delegate gets the matching weight from this array. + The total voting power is split proportionally based on these weights over the + valid drep nominations. + + + ```json + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maintainers": [ + { + "name": "Catalyst Team", + "url": "https://projectcatalyst.io/" + } + ], + "title": "Contest Delegation Schema", + "description": "Structure of the payload of a Contest Delegation.", + "type": "object", + "properties": { + "weights": { + "description": "List of weights to apply to each delegate.\nThis list is in the same order as the delegate references.\nIf there are fewer entries than delegates, then the missing weights are set to `1`.\nIf there are more weights, then the extra weights are ignored. If the payload is missing, OR the array is empty, then the weights assigned is `1`.", + "items": { + "exclusiveMinimum": 0, + "type": "integer" + }, + "minItems": 0, + "type": "array" + } + }, + "additionalProperties": false, + "required": [ + "weights" + ], + "x-changelog": { + "2025-03-01": [ + "First Version Created." + ] + } + } + ``` + + +### Example + +??? example "Example: Three Delegation Weights" + + If there are only 1 delegation, then the weights do not matter. + If there are two, then the first delegate has a weight of 10/30, and the second has 20/30. + If there are 5, then the weights are: `[10,20,30,1,1]` + + ```json + { + "weights": [ + 10, + 20, + 30 + ] + } + ``` + + ## Signers @@ -264,9 +378,13 @@ New versions of this document may be published by: * First Published Version +#### 0.1.2 (2025-09-04) + +* Allow Multi Delegation + [CBOR-TAG-42]: https://github.com/ipld/cid-cbor/ [RFC9052-HeaderParameters]: https://www.rfc-editor.org/rfc/rfc8152#section-3.1 [CC-BY-4.0]: https://creativecommons.org/licenses/by/4.0/legalcode [IPFS-CID]: https://docs.ipfs.tech/concepts/content-addressing/#what-is-a-cid [RFC9562-V7]: https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-7 -[RFC8949]: https://www.rfc-editor.org/rfc/rfc8949.html +[RFC8259]: https://www.rfc-editor.org/rfc/rfc8259.html diff --git a/docs/src/architecture/08_concepts/signed_doc/docs/contest_parameters.md b/docs/src/architecture/08_concepts/signed_doc/docs/contest_parameters.md index d7e3e90564a..180a9e77bd7 100644 --- a/docs/src/architecture/08_concepts/signed_doc/docs/contest_parameters.md +++ b/docs/src/architecture/08_concepts/signed_doc/docs/contest_parameters.md @@ -159,7 +159,7 @@ Revoked documents are flagged as no longer valid, and should not be displayed. As a special case, if the revocations are set to `true` then all versions of the document are revoked, including the latest document. -In this case, when the latest document is revoked, the payload may be empty. +In this case, when the latest document is revoked, the payload may be `nil`. Any older document that has [`revocations`](../metadata.md#revocations) set to `true` is always to be filtered and its payload is to be assumed to be invalid. diff --git a/docs/src/architecture/08_concepts/signed_doc/docs/proposal.md b/docs/src/architecture/08_concepts/signed_doc/docs/proposal.md index e1b75d34295..41779f6c9a8 100644 --- a/docs/src/architecture/08_concepts/signed_doc/docs/proposal.md +++ b/docs/src/architecture/08_concepts/signed_doc/docs/proposal.md @@ -167,7 +167,7 @@ Revoked documents are flagged as no longer valid, and should not be displayed. As a special case, if the revocations are set to `true` then all versions of the document are revoked, including the latest document. -In this case, when the latest document is revoked, the payload may be empty. +In this case, when the latest document is revoked, the payload may be `nil`. Any older document that has [`revocations`](../metadata.md#revocations) set to `true` is always to be filtered and its payload is to be assumed to be invalid. diff --git a/docs/src/architecture/08_concepts/signed_doc/docs/proposal_comment.md b/docs/src/architecture/08_concepts/signed_doc/docs/proposal_comment.md index 10b52d51b7b..12fea8f6c90 100644 --- a/docs/src/architecture/08_concepts/signed_doc/docs/proposal_comment.md +++ b/docs/src/architecture/08_concepts/signed_doc/docs/proposal_comment.md @@ -202,7 +202,7 @@ Revoked documents are flagged as no longer valid, and should not be displayed. As a special case, if the revocations are set to `true` then all versions of the document are revoked, including the latest document. -In this case, when the latest document is revoked, the payload may be empty. +In this case, when the latest document is revoked, the payload may be `nil`. Any older document that has [`revocations`](../metadata.md#revocations) set to `true` is always to be filtered and its payload is to be assumed to be invalid. diff --git a/docs/src/architecture/08_concepts/signed_doc/docs/rep_nomination.md b/docs/src/architecture/08_concepts/signed_doc/docs/rep_nomination.md index cf6e7199c25..88f43b21837 100644 --- a/docs/src/architecture/08_concepts/signed_doc/docs/rep_nomination.md +++ b/docs/src/architecture/08_concepts/signed_doc/docs/rep_nomination.md @@ -212,7 +212,7 @@ Revoked documents are flagged as no longer valid, and should not be displayed. As a special case, if the revocations are set to `true` then all versions of the document are revoked, including the latest document. -In this case, when the latest document is revoked, the payload may be empty. +In this case, when the latest document is revoked, the payload may be `nil`. Any older document that has [`revocations`](../metadata.md#revocations) set to `true` is always to be filtered and its payload is to be assumed to be invalid. diff --git a/docs/src/architecture/08_concepts/signed_doc/docs/rep_profile.md b/docs/src/architecture/08_concepts/signed_doc/docs/rep_profile.md index 4200afdc8c7..bc87626366a 100644 --- a/docs/src/architecture/08_concepts/signed_doc/docs/rep_profile.md +++ b/docs/src/architecture/08_concepts/signed_doc/docs/rep_profile.md @@ -126,7 +126,7 @@ Revoked documents are flagged as no longer valid, and should not be displayed. As a special case, if the revocations are set to `true` then all versions of the document are revoked, including the latest document. -In this case, when the latest document is revoked, the payload may be empty. +In this case, when the latest document is revoked, the payload may be `nil`. Any older document that has [`revocations`](../metadata.md#revocations) set to `true` is always to be filtered and its payload is to be assumed to be invalid. diff --git a/docs/src/architecture/08_concepts/signed_doc/form_template_elements/drop_down_single_select.md b/docs/src/architecture/08_concepts/signed_doc/form_template_elements/drop_down_single_select.md index 9a41885de8d..0275ba3b439 100644 --- a/docs/src/architecture/08_concepts/signed_doc/form_template_elements/drop_down_single_select.md +++ b/docs/src/architecture/08_concepts/signed_doc/form_template_elements/drop_down_single_select.md @@ -165,70 +165,66 @@ Each item in the array MUST be unique. Items string - - Content Media Type - text/plain - Example - enum: ["option 1", "option 2", "option 3"] + enum: ["option 1", "option 2", "option 3"] title
The label attached to the field. Required - yes + yes Type - string + string Content Media Type - text/plain + text/plain Example - title: "Selector" + title: "Selector" x-guidance
Long form Markdown formatted description to give guidance about how the field is to be completed. Required - optional + optional Type - string + string Content Media Type - text/markdown; template=handlebars + text/markdown; template=handlebars Example - x-guidance: "It is recommended that a good choice be made.\nA bad choice could effect prospects of success.\nA good choice could improve them.\nSo make a good choice." + x-guidance: "It is recommended that a good choice be made.\nA bad choice could effect prospects of success.\nA good choice could improve them.\nSo make a good choice." x-icon
The name of the Icon to display with the field. Required - optional + optional Type - string + string Choices - Icons + Icons Example - x-icon: "emoji-happy" + x-icon: "emoji-happy" diff --git a/docs/src/architecture/08_concepts/signed_doc/form_template_elements/radio_button_select.md b/docs/src/architecture/08_concepts/signed_doc/form_template_elements/radio_button_select.md index 0d8303051a8..b4bccf9d804 100644 --- a/docs/src/architecture/08_concepts/signed_doc/form_template_elements/radio_button_select.md +++ b/docs/src/architecture/08_concepts/signed_doc/form_template_elements/radio_button_select.md @@ -166,70 +166,66 @@ Each item in the array MUST be unique. Items string - - Content Media Type - text/plain - Example - enum: ["Hot FM", "AM Stereo (but not really)", "Silence"] + enum: ["Hot FM", "AM Stereo (but not really)", "Silence"] title
The label attached to the field. Required - yes + yes Type - string + string Content Media Type - text/plain + text/plain Example - title: "Radio Selector" + title: "Radio Selector" x-guidance
Long form Markdown formatted description to give guidance about how the field is to be completed. Required - optional + optional Type - string + string Content Media Type - text/markdown; template=handlebars + text/markdown; template=handlebars Example - x-guidance: "Video killed the radio star." + x-guidance: "Video killed the radio star." x-icon
The name of the Icon to display with the field. Required - optional + optional Type - string + string Choices - Icons + Icons Example - x-icon: "bottom-rail-toggle" + x-icon: "bottom-rail-toggle" diff --git a/docs/src/architecture/08_concepts/signed_doc/form_template_elements/single_grouped_tag_selector.md b/docs/src/architecture/08_concepts/signed_doc/form_template_elements/single_grouped_tag_selector.md index e89d525b6f0..fd63f692f65 100644 --- a/docs/src/architecture/08_concepts/signed_doc/form_template_elements/single_grouped_tag_selector.md +++ b/docs/src/architecture/08_concepts/signed_doc/form_template_elements/single_grouped_tag_selector.md @@ -148,7 +148,7 @@ Parameters Required - excluded + yes Type @@ -156,7 +156,7 @@ Parameters Items - optional_property_type=None description='\tAn array of grouped tag objects, of which one can be selected.\n\tEach object MUST have the form:\n\t\n\tjson\n\t"properties": {\n\t\t"group": {\n\t\t\t"$ref": "$def/tagGroup",\n\t\t\t"const": <group name string>\n\t\t},\n\t\t"tag": {\n\t\t\t"$ref": "$def/tagSelection",\n\t\t\t"enum": [\n\t\t\t\t<tag 1 string>,\n\t\t\t\t<tag 2 string>,\n\t\t\t\t...\n\t\t\t]\n\t\t}\n\t}\n\t' required=<OptionalField.excluded: 'excluded'> type='object' items=None choices=None format=None content_media_type=None pattern=None min_length=None minimum=None maximum=None example=None element_name='Unknown' property_type='object' name='Unknown' + optional_property_type=None description='\tAn array of grouped tag objects, of which one can be selected.\n\tEach object MUST have the form:\n\t\n\tjson\n\t"properties": {\n\t\t"group": {\n\t\t\t"$ref": "$def/tagGroup",\n\t\t\t"const": <group name string>\n\t\t},\n\t\t"tag": {\n\t\t\t"$ref": "$def/tagSelection",\n\t\t\t"enum": [\n\t\t\t\t<tag 1 string>,\n\t\t\t\t<tag 2 string>,\n\t\t\t\t...\n\t\t\t]\n\t\t}\n\t}\n\t' required=<OptionalField.required: 'yes'> type='object' items=None choices=None format=None content_media_type=None pattern=None min_length=None minimum=None maximum=None example=None element_name='Unknown' property_type='object' name='Unknown' title
The label attached to the field. diff --git a/docs/src/architecture/08_concepts/signed_doc/form_template_elements/single_select.md b/docs/src/architecture/08_concepts/signed_doc/form_template_elements/single_select.md index 07983dc3085..cd8d4b56b37 100644 --- a/docs/src/architecture/08_concepts/signed_doc/form_template_elements/single_select.md +++ b/docs/src/architecture/08_concepts/signed_doc/form_template_elements/single_select.md @@ -165,70 +165,66 @@ Each item in the array MUST be unique. Items string - - Content Media Type - text/plain - Example - enum: ["option 1", "option 2", "option 3"] + enum: ["option 1", "option 2", "option 3"] title
The label attached to the field. Required - yes + yes Type - string + string Content Media Type - text/plain + text/plain Example - title: "Single Selector" + title: "Single Selector" x-guidance
Long form Markdown formatted description to give guidance about how the field is to be completed. Required - optional + optional Type - string + string Content Media Type - text/markdown; template=handlebars + text/markdown; template=handlebars Example - x-guidance: "It is recommended that a good choice be made.\nA bad choice could effect prospects of success.\nA good choice could improve them.\nSo make a good choice." + x-guidance: "It is recommended that a good choice be made.\nA bad choice could effect prospects of success.\nA good choice could improve them.\nSo make a good choice." x-icon
The name of the Icon to display with the field. Required - optional + optional Type - string + string Choices - Icons + Icons Example - x-icon: "emoji-happy" + x-icon: "emoji-happy" diff --git a/docs/src/architecture/08_concepts/signed_doc/spec.md b/docs/src/architecture/08_concepts/signed_doc/spec.md index 70becaacbde..75934760fcb 100644 --- a/docs/src/architecture/08_concepts/signed_doc/spec.md +++ b/docs/src/architecture/08_concepts/signed_doc/spec.md @@ -391,7 +391,7 @@ Supported HTTP Encodings of the Payload Required - optional + yes Is the field required? diff --git a/specs/Earthfile b/specs/Earthfile index 5c479c78e09..deeb6005c87 100644 --- a/specs/Earthfile +++ b/specs/Earthfile @@ -10,8 +10,8 @@ IMPORT ../docs AS docs builder: FROM python-ci+python-base - # Eval V3 currently broken with our specifications. - ENV CUE_EXPERIMENT=evalv3=0 + # Always use Eval V3, as early evaluators have bugs that mask specifications issues. + ENV CUE_EXPERIMENT=evalv3=1 DO cue+INSTALL # Copy all the source we need to build the docs @@ -23,12 +23,20 @@ src: SAVE ARTIFACT signed_doc.json +GEN_JSON: + FUNCTION + ARG output=../signed_doc.json + RUN cd definitions; \ + cue vet -Evc -s ./signed_docs/docs:signed_docs && \ + cue export -f -s ./signed_docs/docs:signed_docs --out json | jq -S > $output + + # Regenerate the json spec file. regenerate-json: FROM +src + # Make sure keys are sorted so its both reproducible, AND diffs easily. - RUN cd definitions; \ - cue export -f -s ./signed_docs/docs:signed_docs --out json | jq -S > ../signed_doc.json + DO +GEN_JSON SAVE ARTIFACT --keep-ts signed_doc.json @@ -38,10 +46,6 @@ regenerate: COPY +regenerate-json/signed_doc.json . - # Make sure keys are sorted so its both reproducible, AND diffs easily. - RUN cd definitions; \ - cue export -f -s ./signed_docs/docs:signed_docs --out json | jq -S > ../signed_doc.json - RUN cd generators; \ uv run docs -g -o "../../gen" ../signed_doc.json @@ -64,11 +68,16 @@ regenerate-local: # Check the the generated signed docs documentation matches the generated signed docs data check: FROM +src - RUN cue fmt --check --diff -s --files ./definitions + RUN cue fmt --check -s --files ./definitions # RUN cd definitions; \ # cue vet ./cddl - RUN cd definitions; \ - cue vet ./signed_docs/docs:signed_docs ../signed_doc.json + + # This does not work anymore. Looks like a bug in the validator. + # so regenerate and compare the output. + #RUN cd definitions; \ + # cue vet ./signed_docs/docs:signed_docs ../signed_doc.json + DO +GEN_JSON --output=../signed_doc.check.json + RUN diff signed_doc.json signed_doc.check.json # Get the current docs COPY --keep-ts docs+generated-pages/signed_doc /docs diff --git a/specs/Justfile b/specs/Justfile index b6ea2e8cf0d..6499888c409 100644 --- a/specs/Justfile +++ b/specs/Justfile @@ -34,7 +34,7 @@ regenerate: # Validate the generated signed_docs.json is correct against the cue schema. validate: # Generate the intermediate compiled schema data. - cd definitions; CUE_EXPERIMENT=evalv3=0 cue vet ./signed_docs/docs:signed_docs ../signed_doc.json + cd definitions; cue vet ./signed_docs/docs:signed_docs ../signed_doc.json # cd definitions; cue vet ./signed_docs/docs:signed_docs ../signed_doc.json # Check the Model is valid. cd generators; uv run validator ../signed_doc.json diff --git a/specs/definitions/cddl/common.cue b/specs/definitions/cddl/common.cue new file mode 100644 index 00000000000..40d2964e7fb --- /dev/null +++ b/specs/definitions/cddl/common.cue @@ -0,0 +1,87 @@ +// Common CDDL Definitions +// +// CDDL Definitions +package cddl + +import ( + docs "github.com/input-output-hk/catalyst-libs/specs/documentation" + "github.com/input-output-hk/catalyst-libs/specs/media_types" + "strings" +) + +// Formatted content strings to use in CDDL Definition. +cddlContentTypes: "\"\(strings.Join(media_types.allContentTypes, "\" /\n \""))\"" + +// Formatted CoAP content string to use in CDDL Definition. +cddlCoapTypes: "\(strings.Join(media_types.allCoapTypesStr, " / "))" + +// Documentation links we embed inside CDDL descriptions. +documentation: links: docs.links + +cddlDefinitions: #cddlDefinitions & { + uuid_v7: { + def: "#6.37(bytes .size 16)" + description: """ + Version 7 UUID + See: \(documentation.links."RFC9562-V7") + \(documentation.links."CBOR-TAG-37") + """ + comment: "UUIDv7" + } + uuid_v4: { + def: "#6.37(bytes .size 16)" + description: """ + Version 4 UUID + See: \(documentation.links."RFC9562-V4") + \(documentation.links."CBOR-TAG-37") + """ + comment: "UUIDv4" + } + blake2b_256: { + def: "bytes .size 32" + description: "Blake2b Hash (256 bits)" + comment: "Blake2B-256" + } + cid: { + def: "#6.42(bytes)" + description: """ + IPLD content identifier. + Also known as an IPFS CID + See: \(documentation.links."IPFS-CID") + \(documentation.links."CBOR-TAG-42") + """ + comment: """ + IPLD content identifier + TODO: add size limits if possible + """ + } + json_pointer: { + def: "text" + comment: """ + RFC6901 Standard JSON Pointer + See: \(documentation.links."RFC6901") + """ + } + media_type: { + def: """ + ( + (uint .eq (\(cddlCoapTypes))) / + (tstr .eq ( + \(cddlContentTypes) + )) + ) + """ + comment: """ + Supported Content Media Types. + If the Media Type is supported by COAP, then the `uint` CoAP encoded + version of the media type must be used, in preference to the string. + """ + } + http_content_encoding: { + def: """ + tstr .eq "br" + """ + comment: "Supported Content Encoding Types" + } + +} diff --git a/specs/definitions/cddl/contest_ballot_payload.cue b/specs/definitions/cddl/contest_ballot_payload.cue new file mode 100644 index 00000000000..12ca910a79f --- /dev/null +++ b/specs/definitions/cddl/contest_ballot_payload.cue @@ -0,0 +1,367 @@ +// CDDL Definitions +// +// Contest Choice Payload V2 CDDL Specification +@extern(embed) + +package cddl + +cddlDefinitions: { + "contest-ballot-payload": { + requires: [ + "document_ref", + "choices", + "column-proof", + "matrix-proof", + "voter-choice", + ] + def: """ + { + + "\(requires[0])" => \(requires[1]), + ? "\(requires[2])" : \(requires[2]), + ? "\(requires[3])" : \(requires[3]), + ? "\(requires[4])" : \(requires[4]), + } + """ + description: """ + Catalyst Vote Payload data object. + + A vote payload that can hold both encrypted or unencrypted votes. + """ + comment: """ + Catalyst Vote Payload data object. + """ + examples: [ + { + title: "Example Encrypted Contest Ballot Payload." + description: """ + Example Shows: + + * Three Proposals + * Two Encrypted Choices + * Row Proofs for each proposal. + * `aes-crt-encrypted-choices` which reflects the choices. + + The Contest Private Key was: 0x1234562343.... + The Contest Public Key was: 0x1324354235... + The AES encryption key for the `aes-crt-encrypted-choices` is 0x123456789... + """ + example: _ @embed(file=examples/contest_ballot_payload_encrypted.cbor,type=binary) + }, + { + title: "Example Clear Ballot Payload." + description: """ + Example Shows: + + * Three Proposals + * Two Choices + """ + example: _ @embed(file=examples/contest_ballot_payload_clear.cbor,type=binary) + }, + ] + } + + choices: { + requires: [ + "clear-choices", + "elgamal-ristretto255-encrypted-choices", + ] + def: """ + [ 0, \(requires[0]) ] / + [ 1, \(requires[1]) ] + """ + description: """ + Choices are an array of encrypted or unencrypted choices. + """ + comment: """ + Voters Choices. + """ + } + + "clear-choices": { + requires: [ + "clear-choice", + ] + def: """ + ( +clear-choice ) + """ + description: """ + A Choice Selection (clear/unencrypted). + + This can be a positive or negative integer, and is + constrained by the parameters of the contest. + """ + comment: """ + Universal Unencrypted Choice + """ + } + + "clear-choice": { + requires: [] + def: """ + int + """ + description: """ + An Choice Selection (clear/unencrypted). + + This can be a positive or negative integer, and is + constrained by the parameters of the contest. + """ + comment: """ + Universal Unencrypted Choice + """ + } + + "elgamal-ristretto255-encrypted-choices": { + requires: [ + "elgamal-ristretto255-encrypted-choice", + "row-proof", + ] + def: """ + ( + [+ \(requires[0])], + ? \(requires[1]) + ) + """ + description: """ + Encrypted Choices are a Vector (list) of encrypted items. + The size of the vector will depend on the cryptography used, + and the number of choices. + + Typically, (but optionally) it has a proof attached which proves something + about the encrypted choices, without disclosing their contents. + + For example, a ZKProof that there is only a single `1` in the choices, + and all the rest are `0`. + + The size/contents of the proof depend on what is being proved, and the + cryptography underlying the proof. + """ + comment: """ + Universal Encrypted Choices + """ + } + + "elgamal-ristretto255-encrypted-choice": { + requires: [ + "elgamal-ristretto255-group-element", + ] + def: """ + [ + c1: \(requires[0]), + c2: \(requires[0]), + ] + """ + description: """ + The elgamal encrypted ciphertext `(c1, c2)`. + """ + comment: """ + Elgamal encrypted ciphertext. + """ + } + + "elgamal-ristretto255-group-element": { + requires: [] + def: """ + bytes .size 32 + """ + description: """ + An individual elgamal ristretto255 group element. + """ + comment: """ + Elgamal group element that composes the elgamal cipher text. + """ + } + + "row-proof": { + requires: [ + "zkproof-elgamal-ristretto255-unit-vector-with-single-selection", + ] + def: """ + [0, \(requires[0]) ] + """ + description: """ + A proof that the choices conform to a required set of properties. + It is defined by the configured cryptography used for encrypted choices. + This format is universal over all encrypted choice encoding. + """ + comment: """ + Universal Encrypted Row Proof + """ + } + + "zkproof-elgamal-ristretto255-unit-vector-with-single-selection": { + requires: [ + "zkproof-elgamal-ristretto255-unit-vector-with-single-selection-item", + "zkproof-ed25519-scalar", + ] + def: """ + ( [ [ +\(requires[0]) ], \(requires[1]) ) + """ + description: """ + ??? + """ + } + + "zkproof-elgamal-ristretto255-unit-vector-with-single-selection-item": { + requires: [ + "zkproof-elgamal-announcement", + "elgamal-ristretto255-encrypted-choice", + "zkproof-ed25519-r-response", + ] + def: """ + ( \(requires[0]), ~\(requires[1]), \(requires[2]) ) + """ + description: """ + ??? + """ + } + + "zkproof-elgamal-announcement": { + requires: ["zkproof-elgamal-group-element"] + def: """ + ( \(requires[0]), \(requires[0]), \(requires[0]) ) + """ + description: """ + ??? + """ + } + + "zkproof-elgamal-group-element": { + requires: [] + def: """ + bytes .size 32 + """ + description: """ + ??? + """ + } + + "zkproof-ed25519-r-response": { + requires: ["zkproof-ed25519-scalar"] + def: """ + ( \(requires[0]), \(requires[0]), \(requires[0]) ) + """ + description: """ + ??? + """ + } + + "zkproof-ed25519-scalar": { + requires: [] + def: """ + bytes .size 32 + """ + description: """ + ??? + """ + } + + "column-proof": { + requires: [] + def: """ + [ uint, [ +undefined ] ] + """ + description: """ + Proof that values in a column have a required arrangement. + This is similar to the `row-proof` but for all values in a + single column. + It is an array that matches the length of `choices`. + If it is a different length, then it is invalid. + + Currently there are no `column-proof` defined, this value is + a placeholder for documentation purposes only. + + It is NOT to be implemented. + `column-proof` should be assumed to be missing until such time + as a concrete `column-proof` is defined. + + Similar to `row-proof` there can be multiple column-proofs defined which prove + certain characteristics of the encrypted column values. + They are identified by the unsigned integer starting the proof. + """ + } + + "matrix-proof": { + requires: [] + def: """ + [ uint, undefined ] + """ + description: """ + Proof that values in the matrix of all columns and rows have a required arrangement. + This is similar to the `row-proof` and `column-proof` but for all values in a + ballot taken together. + + There is a single `matrix-proof` but it may be chosen from a pre-defined + known set of valid proofs. + + Currently there are no `matrix-proof` defined, this value is + a placeholder for documentation purposes only. + + It is NOT to be implemented. + `matrix-proof` should be assumed to be missing until such time + as a concrete `matrix-proof` is defined. + + Similar to `row-proof` and `column-proof` there can be multiple matrix-proofs defined + which prove certain characteristics of the encrypted column values. + They are identified by the unsigned integer starting the proof. + """ + } + + "voter-choice": { + requires: ["aes-crt-encrypted-choices"] + def: """ + [ 0, /(requires[0]) ] + """ + description: """ + This is an encrypted payload that a voter, and ONLY the voter can decrypt. + It allows the voter to recover their choices without needing to decrypt the + encrypted votes used in the tally. + + There is no way to associate this data with the encrypted choices directly, but + it is created by the voter from the same data used to create the choices. + """ + } + + "aes-crt-encrypted-choices": { + requires: ["aes-crt-encrypted-block"] + def: """ + +/(requires[0]) + """ + description: """ + Choices are constructed as a CBOR multidimensional array of the form: + `[ +[+choice] ]` + reflecting the choices in the rows and columns as present 1:1 in the encrypted + choices. + + This data is then compressed using `brotli` compression, and the result is encrypted + using AES-CTR and encoded as a sequence of blocks here. + + Data needs to be pre-compressed before encryption as encryption will make the data + incompressible. + + The Encryption Key is to be derived from the Voters catalyst key-chain and not to be + published. + Derivation *MUST* take include the contest Document ID and Version, so that the same + encryption key is never used twice for different contests, but can still be re-derived + by a voter that holds their catalyst key-chain recovery keys. + """ + } + + "aes-crt-encrypted-block": { + requires: [] + def: """ + bytes .size 16 + """ + description: """ + AES-CTR encrypted data. + The Nonce/IV is the UUIDv7 `document_ver`. + This is the correct size, and has the necessary randomness properties. + The first block uses the `document_ver` the second `document_ver+1` and so on. + The document_ver is interpreted as a Big Endian 128bit integer for the purpose + of the addition. + + As the CTR is predictable, the blocks can be decrypted in parallel for maximum performance. + """ + } + +} diff --git a/specs/definitions/signed_docs/cose_signed_doc_cddl_defs.cue b/specs/definitions/cddl/cose_signed_doc_cddl_defs.cue similarity index 88% rename from specs/definitions/signed_docs/cose_signed_doc_cddl_defs.cue rename to specs/definitions/cddl/cose_signed_doc_cddl_defs.cue index 94d78fec069..13e63696622 100644 --- a/specs/definitions/signed_docs/cose_signed_doc_cddl_defs.cue +++ b/specs/definitions/cddl/cose_signed_doc_cddl_defs.cue @@ -1,20 +1,9 @@ // Signed Document Definitions // // COSE Signed Document CDDL Definitions -package signed_docs +package cddl -import ( - "strings" - "github.com/input-output-hk/catalyst-libs/specs/media_types" -) - -// Formatted content strings to use in CDDL Definition. -_cddlContentTypes: "\"\(strings.Join(cose.headers."content type".value, "\" /\n \""))\"" - -// Formatted CoAP content string to use in CDDL Definition. -_cddlCoapTypes: "\(strings.Join(media_types.allCoapTypesStr, " / "))" - -cddlDefinitions: #cddlDefinitions & { +cddlDefinitions: { signed_document: { requires: ["COSE_Sign"] def: "\(requires[0])" diff --git a/specs/definitions/cddl/defs.cue b/specs/definitions/cddl/defs.cue new file mode 100644 index 00000000000..85fe325b57b --- /dev/null +++ b/specs/definitions/cddl/defs.cue @@ -0,0 +1,25 @@ +// Signed Document Definitions +// +// CDDL Definitions +package cddl + +import ( + Eg "github.com/input-output-hk/catalyst-libs/specs/generic:examples" +) + +// List of cddl definitions, cddl_type_name: cddl_definition +#cddlDefinitions: { + [string]: { + def: string + requires: [...#cddlTypesConstraint] | *[] + description?: string // Description - multiline + comment?: string // Single line comments are displayed after a definition. Multiline comments, before. + examples?: Eg.#list + } +} + +#cddlTypes: [ + for k, _ in cddlDefinitions {k}, +] + +#cddlTypesConstraint: or(#cddlTypes) diff --git a/specs/definitions/cddl/examples/contest_ballot_payload_clear.cbor b/specs/definitions/cddl/examples/contest_ballot_payload_clear.cbor new file mode 100644 index 00000000000..67f25b9480b --- /dev/null +++ b/specs/definitions/cddl/examples/contest_ballot_payload_clear.cbor @@ -0,0 +1 @@ +Replace this file with CBOR Encoded example clear ballot. diff --git a/specs/definitions/cddl/examples/contest_ballot_payload_encrypted.cbor b/specs/definitions/cddl/examples/contest_ballot_payload_encrypted.cbor new file mode 100644 index 00000000000..8187b855b6b --- /dev/null +++ b/specs/definitions/cddl/examples/contest_ballot_payload_encrypted.cbor @@ -0,0 +1 @@ +Replace this file with CBOR Encoded example encrypted ballot. diff --git a/specs/definitions/signed_docs/cddl_defs.cue b/specs/definitions/cddl/signed_doc.cue similarity index 68% rename from specs/definitions/signed_docs/cddl_defs.cue rename to specs/definitions/cddl/signed_doc.cue index 014872fb442..ade3661a0ce 100644 --- a/specs/definitions/signed_docs/cddl_defs.cue +++ b/specs/definitions/cddl/signed_doc.cue @@ -1,48 +1,15 @@ -// Signed Document Definitions +// Common CDDL Definitions // // CDDL Definitions -package signed_docs - -// List of cddl definitions, cddl_type_name: cddl_definition -#cddlDefinitions: { - [string]: { - def: string - requires: [...#cddlTypesConstraint] | *[] - description?: string // Description - multiline - comment?: string // Single line comments are displayed after a definition. Multiline comments, before. - } -} +package cddl cddlDefinitions: #cddlDefinitions & { - uuid_v7: { - def: "#6.37(bytes .size 16)" - description: """ - Version 7 UUID - See: \(documentation.links."RFC9562-V7") - \(documentation.links."CBOR-TAG-37") - """ - comment: "UUIDv7" - } - uuid_v4: { - def: "#6.37(bytes .size 16)" - description: """ - Version 4 UUID - See: \(documentation.links."RFC9562-V4") - \(documentation.links."CBOR-TAG-37") - """ - comment: "UUIDv4" - } document_type: { def: "[ 1* \(requires[0]) ]" requires: ["uuid_v4"] description: "Unique Document Type Identifier" comment: "Document Type" } - blake2b_256: { - def: "bytes .size 32" - description: "Blake2b Hash (256 bits)" - comment: "Blake2B-256" - } document_id: { def: "\(requires[0])" requires: ["uuid_v7"] @@ -99,10 +66,6 @@ cddlDefinitions: #cddlDefinitions & { ] comment: "Reference to a single Signed Document" } - json_pointer: { - def: "text" - comment: "RFC6901 Standard JSON Pointer" - } section_ref: { def: "\(requires[0])" requires: ["json_pointer"] @@ -118,21 +81,6 @@ cddlDefinitions: #cddlDefinitions & { requires: ["document_ver"] comment: "List of revoked versions of this document." } - media_type: { - def: """ - ( - (uint .eq (\(_cddlCoapTypes))) / - (tstr .eq ( - \(_cddlContentTypes) - )) - ) - """ - comment: """ - Supported Content Media Types. - If the Media Type is supported by COAP, then the `uint` CoAP encoded - version of the media type must be used, in preference to the string. - """ - } http_content_encoding: { def: """ tstr .eq "br" @@ -182,9 +130,3 @@ cddlDefinitions: #cddlDefinitions & { } } - -#cddlTypes: [ - for k, _ in cddlDefinitions {k}, -] - -#cddlTypesConstraint: or(#cddlTypes) diff --git a/specs/definitions/documentation/links.cue b/specs/definitions/documentation/links.cue index 6e2676455c2..e37ffb5e6d9 100644 --- a/specs/definitions/documentation/links.cue +++ b/specs/definitions/documentation/links.cue @@ -43,6 +43,7 @@ links: #docLinks & { CSS: "https://www.w3.org/Style/CSS/" "text/plain": "https://www.rfc-editor.org/rfc/rfc2046.html" "text/css": "https://www.rfc-editor.org/rfc/rfc2318.html" + RFC6901: "https://datatracker.ietf.org/doc/html/rfc6901" } // Constrains the URLs being linked to be unique diff --git a/specs/definitions/form_template/elements/drop_down_single_select.cue b/specs/definitions/form_template/elements/drop_down_single_select.cue index 26fa3de825c..b3eb034ab41 100644 --- a/specs/definitions/form_template/elements/drop_down_single_select.cue +++ b/specs/definitions/form_template/elements/drop_down_single_select.cue @@ -37,7 +37,7 @@ dictionary: dropDownSingleSelect: { No value that is not in the array may be listed or presented. Each item in the array **MUST** be unique. """ - contentMediaType: definition.contentMediaType + items: contentMediaType: definition.contentMediaType example: [ "option 1", "option 2", diff --git a/specs/definitions/form_template/elements/radio_button_select.cue b/specs/definitions/form_template/elements/radio_button_select.cue index 6351e35541a..bf9be66e8cc 100644 --- a/specs/definitions/form_template/elements/radio_button_select.cue +++ b/specs/definitions/form_template/elements/radio_button_select.cue @@ -38,7 +38,7 @@ dictionary: radioButtonSelect: { No value that is not in the array may be listed or presented. Each item in the array **MUST** be unique. """ - contentMediaType: definition.contentMediaType + items: contentMediaType: definition.contentMediaType example: [ "Hot FM", "AM Stereo (but not really)", diff --git a/specs/definitions/form_template/elements/single_select.cue b/specs/definitions/form_template/elements/single_select.cue index ce682211536..21d6d0b7c64 100644 --- a/specs/definitions/form_template/elements/single_select.cue +++ b/specs/definitions/form_template/elements/single_select.cue @@ -37,7 +37,7 @@ dictionary: singleSelect: { No value that is not in the array may be listed or presented. Each item in the array **MUST** be unique. """ - contentMediaType: definition.contentMediaType + items: contentMediaType: definition.contentMediaType example: [ "option 1", "option 2", diff --git a/specs/definitions/form_template/parameters.cue b/specs/definitions/form_template/parameters.cue index e064d765474..6a1d324b138 100644 --- a/specs/definitions/form_template/parameters.cue +++ b/specs/definitions/form_template/parameters.cue @@ -24,7 +24,7 @@ import ( #parameter: { property?: #properties // Name of the property, IF its not the same as the parameter. description: string - required: optional.#field + required: optional.#field_default_yes // The following constrain the value of the parameter // within a template. diff --git a/specs/definitions/generic/examples.cue b/specs/definitions/generic/examples.cue new file mode 100644 index 00000000000..73ea6679f5f --- /dev/null +++ b/specs/definitions/generic/examples.cue @@ -0,0 +1,19 @@ +package examples + +import ( + "list" +) + +// Individual Payload Example +#item: { + // Title of the example + title: string + // Expanded description of what the example shows. + description: string + // Example data value. + example: _ +} + +// A List of examples. (each must be unique) +#list: list.UniqueItems +#list: [...#item] | *[] diff --git a/specs/definitions/generic/optional.cue b/specs/definitions/generic/optional.cue index 0118616db30..fb28777cbed 100644 --- a/specs/definitions/generic/optional.cue +++ b/specs/definitions/generic/optional.cue @@ -1,9 +1,11 @@ package optional -#field_without_default: +#field: "yes" | "optional" | "excluded" // Is a field Required, Optional or Excluded/Unused -#field: #field_without_default | *"excluded" +#field_default_yes: #field | *"yes" +#field_default_optional: #field | *"optional" +#field_default_excluded: #field | *"excluded" diff --git a/specs/definitions/media_types/content.cue b/specs/definitions/media_types/content.cue index db957594688..81dc64b464c 100644 --- a/specs/definitions/media_types/content.cue +++ b/specs/definitions/media_types/content.cue @@ -3,6 +3,7 @@ package media_types import ( "list" + "github.com/input-output-hk/catalyst-libs/specs/regex" ) // Content Type name : Description @@ -126,3 +127,18 @@ allCoapTypes: list.Sort([ allCoapTypesStr: [...string] allCoapTypesStr: [for v in allCoapTypes {"\(v)"}] + +jsonContentTypes: list.UniqueItems +jsonContentTypes: list.Sort([ + for k, _ in contentTypes if k =~ regex.def.jsonContentType.pattern {k}, +], list.Ascending) + +cborContentTypes: list.UniqueItems +cborContentTypes: list.Sort([ + for k, _ in contentTypes if k =~ regex.def.cborContentType.pattern {k}, +], list.Ascending) + +cddlContentTypes: list.UniqueItems +cddlContentTypes: list.Sort([ + for k, _ in contentTypes if k =~ regex.def.cddlContentType.pattern {k}, +], list.Ascending) diff --git a/specs/definitions/regex/def.cue b/specs/definitions/regex/def.cue index 0faac0c80c1..5c582a1152d 100644 --- a/specs/definitions/regex/def.cue +++ b/specs/definitions/regex/def.cue @@ -47,6 +47,24 @@ def: #def & { A name where every word starts with a capital letter. """ } + jsonContentType: { + pattern: #"^application\/(?:json|[a-z0-9!#$&^_.+-]+\+json)(?:\s*;\s*[^=]+=[^;]+)*$"# + description: """ + Matches any known json content type. + """ + } + cborContentType: { + pattern: #"^application\/(?:cbor|[a-z0-9!#$&^_.+-]+\+cbor)(?:\s*;\s*[^=]+=[^;]+)*$"# + description: """ + Matches any known cbor content type. + """ + } + cddlContentType: { + pattern: #"^application\/(?:cddl|[a-z0-9!#$&^_.+-]+\+cddl)(?:\s*;\s*[^=]+=[^;]+)*$"# + description: """ + Matches any known cddl content type. + """ + } } // Every definition above MUST have at least one test below @@ -86,6 +104,17 @@ positive_match: "A Title" =~ def.titleCaseName.pattern positive_match: "A Title Case" =~ def.titleCaseName.pattern positive_match: "A Title Case Name" =~ def.titleCaseName.pattern +positive_match: "application/json" =~ def.jsonContentType.pattern +positive_match: "application/json; charset=UTF-8" =~ def.jsonContentType.pattern +positive_match: "application/schema+json; profile=\"http://example.org/schema\"" =~ def.jsonContentType.pattern +positive_match: "application/ld+json; charset=utf-8; foo=bar" =~ def.jsonContentType.pattern + +positive_match: "application/cbor" =~ def.cborContentType.pattern +positive_match: "application/ce+cbor; foo=bar" =~ def.cborContentType.pattern + +positive_match: "application/cddl" =~ def.cddlContentType.pattern +positive_match: "application/schema+cddl; charset=utf-8" =~ def.cddlContentType.pattern + // Negative match (where possible to test) negative_match: false @@ -123,3 +152,12 @@ negative_match: "a" =~ def.titleCaseName.pattern negative_match: "A.Title" =~ def.titleCaseName.pattern negative_match: "A title Case" =~ def.titleCaseName.pattern negative_match: "A Title Case-name" =~ def.titleCaseName.pattern + +negative_match: "application/cbor" =~ def.jsonContentType.pattern +negative_match: "application/cddl; charset=UTF-8" =~ def.jsonContentType.pattern + +negative_match: "application/json" =~ def.cborContentType.pattern +negative_match: "application/ce+cddl; foo=bar" =~ def.cborContentType.pattern + +negative_match: "application/cbor" =~ def.cddlContentType.pattern +negative_match: "application/schema+json; charset=utf-8" =~ def.cddlContentType.pattern diff --git a/specs/definitions/signed_doc_types/types.cue b/specs/definitions/signed_doc_types/types.cue index e304aad9dbb..52e4ed23288 100644 --- a/specs/definitions/signed_doc_types/types.cue +++ b/specs/definitions/signed_doc_types/types.cue @@ -28,6 +28,8 @@ allDocTypes: { "Rep Nomination": "bf9abd97-5d1f-4429-8e80-740fea371a9c" "Rep Nomination Form Template": "431561a5-9c2b-4de1-8e0d-78eb4887e35d" "Contest Delegation": "764f17fb-cc50-4979-b14a-b213dbac5994" + //"Contest Ballot": "de1284b8-8533-4f7a-81cc-ff4bde5ef8d0" + //"Contest Ballot Register": "58608925-bda3-47df-b39a-ae0d0a1dd6ed" //"Rep Profile Moderation Action": "0e20010b-eeaf-4938-a7ee-ceb3df9e8af6" // speculative //"Rep Nomination Moderation Action": "d27ecb44-bd4d-42bb-9273-5e5433cdfdb6" // speculative } diff --git a/specs/definitions/signed_docs/cose_headers.cue b/specs/definitions/signed_docs/cose_headers.cue index 9119e1e61f3..742bda6c7fe 100644 --- a/specs/definitions/signed_docs/cose_headers.cue +++ b/specs/definitions/signed_docs/cose_headers.cue @@ -51,8 +51,7 @@ cose: headerFormats: #metadataFormats & { coseLabel: int | string description: string format: #coseHeaderTypesConstraint - //required: "yes" | "optional" | "excluded" - required: optional.#field_without_default + required: optional.#field_default_yes if required != "excluded" { if format == "Media Type" { @@ -74,14 +73,12 @@ _coseHeaders: #coseHeaders & { "content type": #coseField & { coseLabel: 3 format: "Media Type" - required: _ | *"yes" description: "Media Type/s allowed in the Payload" } // Documents Used content encodings "content-encoding": #coseField & { coseLabel: "content-encoding" format: "HTTP Content Encoding" - required: _ | *"optional" description: """ Supported HTTP Encodings of the Payload. If no compression or encoding is used, then this field must not be present. @@ -94,7 +91,6 @@ _coseSignatureHeaders: #coseHeaders & { kid: #coseField & { coseLabel: 4 format: "Catalyst ID" - required: "yes" description: """ Catalyst ID URI identifying the Public Key. diff --git a/specs/definitions/signed_docs/docs/contest_ballot.cue.off b/specs/definitions/signed_docs/docs/contest_ballot.cue.off new file mode 100644 index 00000000000..93289eed12e --- /dev/null +++ b/specs/definitions/signed_docs/docs/contest_ballot.cue.off @@ -0,0 +1,102 @@ +@extern(embed) + +package signed_docs + +import ( + "github.com/input-output-hk/catalyst-libs/specs/cddl" +) + + +docs: "Contest Ballot": { + description: """ + An individual Ballot cast in a Contest by a registered user. + + Each ballot contains choices for all possible proposals eligible for the + contest. + + Multiple contest ballots can be cast by the same registered user in a contest, but + only the latest (by its document_version) will be counted. + + The reason the ballot is cast in a contest is because there may be multiple contests in + a campaign, and they may be attached to either the brand, campaign or category level. + Each level, (for example category) can in-theory have multiple contests. + + Only eligible users can cast ballots in the respective contest. + """ + validation: """ + * The `parameters` metadata *MUST* point to the Contest the ballot is being cast in. + * The 'ref' metadata fields within the ballot payload (not the headers) must point to + ALL the proposals eligible to be chosen in the contest. + """ + business_logic: { + front_end: """ + * Always cast a ballot for all proposals in the contest. + * Any proposal not explicitely selected by a user must have the default selection applied. + Typically, this would be `abstain`. + * The voter signs this document to confirm their ballot. + * Ballots can not be cast outside the time allowed for the casting of ballots. + * The `document_id` and `document+ver` must be within the time of allowed casting + of ballots. Any document_id of document_ver outside this time are invalid and will + not be counted. + """ + back_end: """ + * Verifies that the Contest is valid, and that the ballot is cast in the appropriate + time frame, and has a valid `document_id` and `document_ver` in that range. + * Verify the payload lists all the eligible proposals which can be chosen in the contest. + * Verify the proofs in the payload are correct. + """ + } + + metadata: { + ref: { + required: "yes" + type: "Rep Nomination" + multiple: true + } + parameters: { + required: "yes" + type: "Contest Parameters" + linked_refs: [ + "ref", + ] + } + revocations: required: "optional" + } + + headers: "content type": value: "application/cbor" + + payload: { + description: """ + The Payload is a JSON Document, and must conform to this schema. + + It consists of an array which defines the weights to be applied to the chosen delegations. + + Each valid delegate gets the matching weight from this array. + The total voting power is split proportionally based on these weights over the + valid drep nominations. + """ + schema: "contest-ballot-payload" + examples: cddl.cddlDefinitions["\(schema)"].examples + } + + signers: roles: user: [ + "Registered", + ] + authors: "Neil McAuliffe": "neil.mcauliffe@iohk.io" + versions: [ + { + version: "0.01" + modified: "2025-06-19" + changes: """ + * First Published Version + """ + }, + { + version: "0.1.2" + modified: "2025-09-04" + changes: """ + * Allow Multi Delegation + """ + }, + ] +} diff --git a/specs/definitions/signed_docs/docs/contest_ballot_register.cue.off b/specs/definitions/signed_docs/docs/contest_ballot_register.cue.off new file mode 100644 index 00000000000..25e0f0f1754 --- /dev/null +++ b/specs/definitions/signed_docs/docs/contest_ballot_register.cue.off @@ -0,0 +1,117 @@ +@extern(embed) + +package signed_docs + +docs: "Contest Ballot Register": { + description: """ + Periodically as ballots are collected, a summary of all newly collected ballots will be + published in a `Contest Ballot Register` document. + This document forms part of the bulletin boards complete Contest Ballot Register. + + These documents are chained to each other, and the final document is specified as final + in the `chain` metadata. + + Typically each `Contest Ballot Register` document is made immutable by referencing it on + the blockchain most applicable to the Contest. + + Different blockchains will have different mechanisms for referencing the individual + `Contest Ballot Register` documents. + + For example, Cardano will encode a `document_ref` in metadata, signed by the ballot box + operator. + + The blockchain record must be as close in time as practically possible to the creation of + the `Contest Ballot Register` document. + """ + validation: """ + * The `parameters` metadata *MUST* point to the Contest the ballot is being cast in. + * The 'ref' metadata fields reference the Contest Ballots collected in the proceeding + period by the ballot box. + These are sorted from earliest `document_id`:`document_ver` regardless of the time + the individual ballot was received by the ballot box. + * Ballot boxes will not accept ballots whose `document_id`:`document_ver` fall outside + the boundaries of the contest, or are not close in time to when the ballot box + received the ballot. + """ + business_logic: { + front_end: """ + * Always cast a ballot for all proposals in the contest. + * Any proposal not explicitely selected by a user must have the default selection applied. + Typically, this would be `abstain`. + * The voter signs this document to confirm their ballot. + * Ballots can not be cast outside the time allowed for the casting of ballots. + * The `document_id` and `document+ver` must be within the time of allowed casting + of ballots. Any document_id of document_ver outside this time are invalid and will + not be counted. + """ + back_end: """ + * Verifies that the Contest is valid, and that the ballot is cast in the appropriate + time frame, and has a valid `document_id` and `document_ver` in that range. + * Verify the payload lists all the eligible proposals which can be chosen in the contest. + * Verify the proofs in the payload are correct. + """ + } + + metadata: { + ref: { + required: "yes" + type: "Rep Nomination" + multiple: true + } + parameters: { + required: "yes" + type: "Contest Parameters" + linked_refs: [ + "ref", + ] + } + revocations: required: "optional" + } + + headers: "content type": value: "application/json" + + payload: { + description: """ + The Payload is a JSON Document, and must conform to this schema. + + It consists of an array which defines the weights to be applied to the chosen delegations. + + Each valid delegate gets the matching weight from this array. + The total voting power is split proportionally based on these weights over the + valid drep nominations. + """ + schema: _ @embed(file="payload_schemas/contest_delegation.schema.json") + examples: [ + { + title: "Three Delegation Weights" + description: """ + If there are only 1 delegation, then the weights do not matter. + If there are two, then the first delegate has a weight of 10/30, and the second has 20/30. + If there are 5, then the weights are: `[10,20,30,1,1]` + """ + example: _ @embed(file="payload_schemas/contest_delegation.example.json") + } + ] + } + + signers: roles: user: [ + "Registered", + ] + authors: "Neil McAuliffe": "neil.mcauliffe@iohk.io" + versions: [ + { + version: "0.01" + modified: "2025-06-19" + changes: """ + * First Published Version + """ + }, + { + version: "0.1.2" + modified: "2025-09-04" + changes: """ + * Allow Multi Delegation + """ + }, + ] +} diff --git a/specs/definitions/signed_docs/docs/contest_delegation.cue b/specs/definitions/signed_docs/docs/contest_delegation.cue index 5c3a65a2e3c..9aa1b054986 100644 --- a/specs/definitions/signed_docs/docs/contest_delegation.cue +++ b/specs/definitions/signed_docs/docs/contest_delegation.cue @@ -17,18 +17,57 @@ docs: "Contest Delegation": { Contests within a Brand/Campaign or Category. This is because different Contests may have different rules. - And not all Representatives will choose to nominate + And not all Representatives will choose to (or be able to) nominate for every Contest. A Representative ***MAY NOT*** delegate to a different Representative for any contest they have nominated for. They ***MAY*** however nominate a Representative in any contest they have not nominated for. + + A Representative is NOT required to delegate to themselves in a contest they are nominated for, + and in fact, any self-delegation is invalid and ignored. + A Representative has an implicit 100% voting power delegation to themselves in any contest + they are nominated. + The MAY not vote personally, and if they do, that vote will have Zero (0) voting power. + 100% of their voting power is assigned to their delegate vote and can not be split in any way. + + A voter MAY choose multiple delegates for a contest, in this case they are listed in priority + order from highest priority to lowest. + Priority only affects two aspects of the delegation. + + 1. Any residual voting power after it is split among all delegates is given to the highest + priority delegate (first). + 2. If there is not enough voting power to distribute, then its distributed from highest + priority to lowest. + This may mean that low priority delegates get zero voting power. + + An example: If a Voter has 100 raw voting power, after quadratic scaling, they have 10. + If they delegated to 15 delegates equally, then only the first 10 would get 1 voting power + each. + Voting power is not fractionally assigned. + + The payload MAY contain a json document which consists of a single array which can adjust + the ratio of the delegation. + Voting power is divided based on the weight of a single delegate over the sum of all + weights of all delegates. + This is performed with integer division. + As a special condition, 0 or any negative value is equivalent to a weight of 1. + As explained above, if there is not enough voting power to distribute, low priority reps + will receive 0 voting power from the delegation. + And if there is any residual after integer division its applied to the representative + with the highest priority. """ validation: """ * The `parameters` metadata *MUST* point to the same Contest as the Nomination of the Representative. * The 'ref' metadata field MUST point to a valid 'Representative Nomination'. + IF there are multiple representatives, then any which are not pointing + to a valid `Representative Nomination` are excluded. + The nomination is only invalid if ALL references `Representative Nomination` + references are invalid. + This is to prevent a Representative changing their nomination invalidating a + delegation with multiple representatives. * The payload MUST be nil. A Representative *MUST* Delegate to their latest Nomination for a Category, @@ -57,6 +96,7 @@ docs: "Contest Delegation": { ref: { required: "yes" type: "Rep Nomination" + multiple: true } parameters: { required: "yes" @@ -67,12 +107,31 @@ docs: "Contest Delegation": { } revocations: required: "optional" } + + headers: "content type": value: "application/json" + payload: { description: """ - There is no payload. - """ + The Payload is a JSON Document, and must conform to this schema. - nil: true + It consists of an array which defines the weights to be applied to the chosen delegations. + + Each valid delegate gets the matching weight from this array. + The total voting power is split proportionally based on these weights over the + valid drep nominations. + """ + schema: _ @embed(file="payload_schemas/contest_delegation.schema.json") + examples: [ + { + title: "Three Delegation Weights" + description: """ + If there are only 1 delegation, then the weights do not matter. + If there are two, then the first delegate has a weight of 10/30, and the second has 20/30. + If there are 5, then the weights are: `[10,20,30,1,1]` + """ + example: _ @embed(file="payload_schemas/contest_delegation.example.json") + }, + ] } signers: roles: user: [ @@ -87,5 +146,12 @@ docs: "Contest Delegation": { * First Published Version """ }, + { + version: "0.1.2" + modified: "2025-09-04" + changes: """ + * Allow Multi Delegation + """ + }, ] } diff --git a/specs/definitions/signed_docs/docs/payload_schemas/contest_delegation.example.json b/specs/definitions/signed_docs/docs/payload_schemas/contest_delegation.example.json new file mode 100644 index 00000000000..1620985377f --- /dev/null +++ b/specs/definitions/signed_docs/docs/payload_schemas/contest_delegation.example.json @@ -0,0 +1,7 @@ +{ + "weights": [ + 10, + 20, + 30 + ] +} \ No newline at end of file diff --git a/specs/definitions/signed_docs/docs/payload_schemas/contest_delegation.schema.json b/specs/definitions/signed_docs/docs/payload_schemas/contest_delegation.schema.json new file mode 100644 index 00000000000..6e49605627e --- /dev/null +++ b/specs/definitions/signed_docs/docs/payload_schemas/contest_delegation.schema.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Contest Delegation Schema", + "description": "Structure of the payload of a Contest Delegation.", + "maintainers": [ + { + "name": "Catalyst Team", + "url": "https://projectcatalyst.io/" + } + ], + "x-changelog": { + "2025-03-01": [ + "First Version Created." + ] + }, + "type": "object", + "additionalProperties": false, + "properties": { + "weights": { + "type": "array", + "description": "List of weights to apply to each delegate.\nThis list is in the same order as the delegate references.\nIf there are fewer entries than delegates, then the missing weights are set to `1`.\nIf there are more weights, then the extra weights are ignored. If the payload is missing, OR the array is empty, then the weights assigned is `1`.", + "items": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "minItems": 0 + } + }, + "required": [ + "weights" + ] +} \ No newline at end of file diff --git a/specs/definitions/signed_docs/docs/proposal_comment.cue b/specs/definitions/signed_docs/docs/proposal_comment.cue index d85f6154c26..92ea3dd6f56 100644 --- a/specs/definitions/signed_docs/docs/proposal_comment.cue +++ b/specs/definitions/signed_docs/docs/proposal_comment.cue @@ -6,6 +6,8 @@ import ( // Proposal Document Definition +// TODO: Proposers that sign this are proposer to proposer comments for collaboration. + docs: #DocumentDefinitions & { "Proposal Comment": { description: """ diff --git a/specs/definitions/signed_docs/metadata.cue b/specs/definitions/signed_docs/metadata.cue index facc0709293..3ffc598ec02 100644 --- a/specs/definitions/signed_docs/metadata.cue +++ b/specs/definitions/signed_docs/metadata.cue @@ -8,6 +8,7 @@ import ( "list" "github.com/input-output-hk/catalyst-libs/specs/generic:optional" "github.com/input-output-hk/catalyst-libs/specs/signed_doc_types" + CDDL "github.com/input-output-hk/catalyst-libs/specs/cddl" ) // Metadata Formats. @@ -15,7 +16,7 @@ import ( #metadataFormats: { [string]: { description: string - cddl: #cddlTypesConstraint + cddl: CDDL.#cddlTypesConstraint } } @@ -95,7 +96,7 @@ _allMetadataNames: or([ // Definition of a metadata field. #metadataField: { // Is the field required to be present. - required: optional.#field + required: optional.#field_default_excluded // Format of the field. format: #metadataTypesConstraint | *#metadataTypes[0] @@ -235,7 +236,7 @@ _allMetadataNames: or([ As a special case, if the revocations are set to `true` then all versions of the document are revoked, including the latest document. - In this case, when the latest document is revoked, the payload may be empty. + In this case, when the latest document is revoked, the payload may be `nil`. Any older document that has `revocations` set to `true` is always to be filtered and its payload is to be assumed to be invalid. diff --git a/specs/definitions/signed_docs/payload.cue b/specs/definitions/signed_docs/payload.cue index d0e90b66151..df93e901ebf 100644 --- a/specs/definitions/signed_docs/payload.cue +++ b/specs/definitions/signed_docs/payload.cue @@ -1,33 +1,44 @@ package signed_docs import ( - "list" + Eg "github.com/input-output-hk/catalyst-libs/specs/generic:examples" + "github.com/input-output-hk/catalyst-libs/specs/cddl" + "github.com/input-output-hk/catalyst-libs/specs/regex" ) -// Individual Payload Example -#payloadExample: { - // Title of the example - title: string - // Expanded description of what the example shows. +// Payload definition +#payload: { + // Description of the payload description: string - // Example data that matches the payload schema. - example: _ + + // Is the Payload allowed to be nil? + // This DOES NOT preclude there being a payload also defined. + // For example when `revocations` is `true` then the payload may be `nil`. + nil: true | *false } // Payload definition -_payload: { - // Description of the payload - description: string - // Is the Payload nil? - nil: bool | *false - - // Only have these when the payload isn't nil. - if !nil { - // Optional fixed schema for the payload. - // A URI or inline JSON Schema that the payload must validate against. - schema?: _ - // Examples of the schema. - examples?: list.UniqueItems - examples?: [...#payloadExample] | *[] - } +#payload_json: { + // Extends #payload + #payload + + // Optional fixed schema for the payload. + // A URI or inline JSON Schema that the payload must validate against. + // Can't work out a way to validated json schema constraint here, + // but is validated by the documentation generator. + schema?: _ | =~regex.def.httpsUrl.pattern + // Examples of the schema. + examples?: Eg.#list +} + +// Payload definition for cbor payloads +#payload_cbor: { + // Extends #payload + #payload + + // CBOR payloads must have a CDDL Schema defined. + schema?: cddl.#cddlTypesConstraint + + // Examples of the schema. + examples?: Eg.#list } diff --git a/specs/definitions/signed_docs/signed_doc.cue b/specs/definitions/signed_docs/signed_doc.cue index f5ee0e99944..f2be40bb985 100644 --- a/specs/definitions/signed_docs/signed_doc.cue +++ b/specs/definitions/signed_docs/signed_doc.cue @@ -8,6 +8,9 @@ import ( "github.com/input-output-hk/catalyst-libs/specs/form_template/elements:form_template" "github.com/input-output-hk/catalyst-libs/specs/presentation_template/definedCards:presentation_template" "github.com/input-output-hk/catalyst-libs/specs/signed_doc_types" + "github.com/input-output-hk/catalyst-libs/specs/media_types" + "github.com/input-output-hk/catalyst-libs/specs/cddl" + "list" ) // Document Type must be a valid UUIDv4 @@ -38,18 +41,28 @@ import ( notes: [...string] | *[] - if payload.nil { - headers: "content type": required: "excluded" - headers: "content-encoding": required: "excluded" - } - headers: _coseHeaders // The Metadata fields in this document (non cose standard) metadata: #metadata // Requirements for the document payload. - payload: _payload + //payload: #payload + + // IF there is no defined content, then the payload MUST allow `nil` + if headers."content type".required == "excluded" { + payload: #payload + payload: nil: true + } // Would be nice if `cuelang` had `else` + if headers."content type".required != "excluded" { + //payload: #payload_json + if list.Contains( media_types.jsonContentTypes, headers."content type".value) { + payload: #payload_json + } + //if list.Contains( media_types.cborContentTypes, headers."content type".value) { + // payload: #payload_cbor + //} + } // Required/Allowed Signers of a document signers: _allowedSigners @@ -89,3 +102,5 @@ presentationTemplate: { cards: presentation_template.allCards schema: presentation_template.presentationTemplate } + +cddlDefinitions: cddl.cddlDefinitions diff --git a/specs/generators/packages/spec/src/spec/cddl/definition.py b/specs/generators/packages/spec/src/spec/cddl/definition.py index 704b11ae6e0..b308749c60a 100644 --- a/specs/generators/packages/spec/src/spec/cddl/definition.py +++ b/specs/generators/packages/spec/src/spec/cddl/definition.py @@ -5,6 +5,8 @@ from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, RootModel +from spec.example import CborExample + class CDDLDefinition(BaseModel): """CDDL Definition Deserialized Specification.""" @@ -13,6 +15,7 @@ class CDDLDefinition(BaseModel): requires: list[str] description: str | None = Field(default=None) comment: str = Field(default_factory=str) + examples: list[CborExample] = Field(default_factory=CborExample.default) _name: str = PrivateAttr(default="Unknown") diff --git a/specs/generators/packages/spec/src/spec/example.py b/specs/generators/packages/spec/src/spec/example.py new file mode 100644 index 00000000000..03c243a2dc9 --- /dev/null +++ b/specs/generators/packages/spec/src/spec/example.py @@ -0,0 +1,75 @@ +"""Payload Specification.""" + +import json +import textwrap +from typing import Any + +from pydantic import Base64Bytes, BaseModel, ConfigDict + + +class JsonExample(BaseModel): + """An Example of the payload.""" + + title: str + description: str + example: dict[str, Any] + + model_config = ConfigDict(extra="forbid") + + @classmethod + def default(cls) -> list["JsonExample"]: + """Return Default list.""" + return [] + + def __str__(self) -> str: + """Get the example properly formatted as markdown.""" + example = json.dumps(self.example, indent=2, sort_keys=True) + textwrap.indent(example, " ") + + return f""" + + +??? example "Example: {self.title}" + +{textwrap.indent(self.description, " ")} + + ```json +{textwrap.indent(example, " ")} + ``` + + +""".strip() + + +class CborExample(BaseModel): + """An Example of the payload.""" + + title: str + description: str + example: Base64Bytes + + model_config = ConfigDict(extra="forbid") + + @classmethod + def default(cls) -> list["CborExample"]: + """Return Default list.""" + return [] + + def __str__(self) -> str: + """Get the example properly formatted as markdown.""" + example = json.dumps(self.example, indent=2, sort_keys=True) + textwrap.indent(example, " ") + + return f""" + + +??? example "Example: {self.title}" + +{textwrap.indent(self.description, " ")} + + ```json +{textwrap.indent(example, " ")} + ``` + + +""".strip() diff --git a/specs/generators/packages/spec/src/spec/payload.py b/specs/generators/packages/spec/src/spec/payload.py index 97cc86b93e0..1b66328b677 100644 --- a/specs/generators/packages/spec/src/spec/payload.py +++ b/specs/generators/packages/spec/src/spec/payload.py @@ -1,7 +1,6 @@ """Payload Specification.""" import json -import textwrap import urllib import urllib.request from typing import Any @@ -10,44 +9,12 @@ import rich from pydantic import BaseModel, ConfigDict, Field, HttpUrl +from spec.example import JsonExample + DRAFT7_SCHEMA = "https://json-schema.org/draft-07/schema" DRAFT202012_SCHEMA = "https://json-schema.org/draft/2020-12/schema" -class PayloadExample(BaseModel): - """An Example of the payload.""" - - title: str - description: str - example: dict[str, Any] - - model_config = ConfigDict(extra="forbid") - - @classmethod - def default(cls) -> list["PayloadExample"]: - """Return Default list.""" - return [] - - def __str__(self) -> str: - """Get the example properly formatted as markdown.""" - example = json.dumps(self.example, indent=2, sort_keys=True) - textwrap.indent(example, " ") - - return f""" - - -??? example "Example: {self.title}" - -{textwrap.indent(self.description, " ")} - - ```json -{textwrap.indent(example, " ")} - ``` - - -""".strip() - - class SchemaValidationError(Exception): """Something is wrong with payload schema validation.""" @@ -58,7 +25,7 @@ class Payload(BaseModel): description: str nil: bool doc_schema: HttpUrl | dict[str, Any] | None = Field(default=None, alias="schema") - examples: list[PayloadExample] = Field(default_factory=PayloadExample.default) + examples: list[JsonExample] = Field(default_factory=JsonExample.default) model_config = ConfigDict(extra="forbid") diff --git a/specs/generators/packages/spec/src/spec/signed_doc.py b/specs/generators/packages/spec/src/spec/signed_doc.py index 44a4ee37440..62ff2c76ae4 100644 --- a/specs/generators/packages/spec/src/spec/signed_doc.py +++ b/specs/generators/packages/spec/src/spec/signed_doc.py @@ -6,7 +6,9 @@ import typing from pathlib import Path -from pydantic import BaseModel, ConfigDict, Field, PrivateAttr +from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, ValidationError +from rich.console import Console +from rich.table import Table from spec.authors import Authors from spec.cddl.cose import CoseDefinitions @@ -43,6 +45,46 @@ class SignedDoc(BaseModel): model_config = ConfigDict(extra="forbid") + @staticmethod + def validation_error(err: ValidationError) -> None: + """Print validation errors nicely when they occur (helper).""" + table = Table( + title=f"{err.error_count()} Locations where Schema Data does not match the {err.title} Model.", + caption="Model does not match Schema and needs updating.", + min_width=120, + expand=True, + ) + table.add_column("Key", style="yellow", overflow="fold") + table.add_column("Error", style="red") + table.add_column("Input", no_wrap=True, max_width=30, style="grey37") + table.add_column("Context", style="green") + + error_links: dict[str, str] = {} + errors = err.errors() + errors.sort(key=lambda x: [x["loc"], x["type"]]) + for error in errors: + error_links[error["msg"]] = error["url"] # type: ignore # noqa: PGH003 + + loc: list[str] = [] + for x in error["loc"]: + if isinstance(x, int): + loc.append(f"[{x}]") + else: + loc.append(f"{x}") + + table.add_row( + ".".join(loc), + f"{error['type']}: {error['msg']}", + str(error["input"]).splitlines()[0], + ", ".join(f"{k}={v}" for k, v in (error.get("ctx") or {}).items()), + ) + + console = Console(width=120, force_terminal=True) + console.print(table) + + for msg, url in error_links.items(): + console.print(f"* {msg} : {url}") + @classmethod def load(cls, spec_file: str) -> typing.Self: """Initialize the Signed Document Specification.""" diff --git a/specs/generators/pages/signed_doc/.pages.jinja b/specs/generators/pages/signed_doc/.pages.jinja index ec6e58bbdb5..2f72c50dd2c 100644 --- a/specs/generators/pages/signed_doc/.pages.jinja +++ b/specs/generators/pages/signed_doc/.pages.jinja @@ -7,3 +7,4 @@ nav: - Document Form Templates: form_templates.md - form_template_elements - Document Presentation Templates: presentation_template.md + - key-derivation diff --git a/specs/generators/pages/signed_doc/key-derivation/.pages.jinja b/specs/generators/pages/signed_doc/key-derivation/.pages.jinja new file mode 100644 index 00000000000..5544bbcd087 --- /dev/null +++ b/specs/generators/pages/signed_doc/key-derivation/.pages.jinja @@ -0,0 +1,5 @@ +title: Key Derivation +arrange: + - Catalyst Hierarchical Deterministic Key Derivation: hd-key-derivation.md + - Ed25519 Document Signing Key Derivation: ed25519-document-signing-key-derivation.md + - AES256 Document Encryption Key Derivation: aes256-document-encryption-key-derivation.md diff --git a/specs/generators/pages/signed_doc/key-derivation/aes256-document-encryption-key-derivation.md.jinja b/specs/generators/pages/signed_doc/key-derivation/aes256-document-encryption-key-derivation.md.jinja new file mode 100644 index 00000000000..6e8e8e25cdc --- /dev/null +++ b/specs/generators/pages/signed_doc/key-derivation/aes256-document-encryption-key-derivation.md.jinja @@ -0,0 +1,107 @@ +--- +Title: AES256 Document Encryption Keys Derivation +Category: Catalyst +Status: Proposed +Authors: + - Steven Johnson +Implementors: + - Catalyst Fund 14 +Discussions: [] +Created: 2024-11-29 +License: CC-BY-4.0 +--- + +## Abstract + +Defines how AES256 Document Encryption Keys are derived using [Catalyst HD Key Derivation](./hd-key-derivation.md). +It goes on to explain how they are used. + +## Motivation: why is this CIP necessary? + +Users in Catalyst may wish to embed encrypted data within a signed document that only they can read. +For this purpose we utilize AES256. +This is a symmetric encryption algorithm, so only the user who encrypted the data can decrypt it +unless they share their key. + +To do this securely, we need a method to derive AES256 keys that are unique to each document. + +## Specification + +The process of deriving an encryption key, and then using it follows the process: + +1. Derive the Root Master Key (Never used for encryption itself). +2. Derive a PER Document AES256 key securely from the Root Master Key. +3. Encrypt the data using the PER Document AES256 Key. + +### Deriving the Root Master Encryption Key from the Seed phrase. + +We re-utilize the ED25519 Key derivation function, even though we will not use this for ED25519 signatures. + +For reference, see [Catalyst HD Key Derivation](./hd-key-derivation.md). + +Once derived, this will give us a 96 byte Extended Private Key, which will be used directly as a 96 byte +Root Master Key. + +#### `usage'` + +The AES256 Root Master Key is derived with `usage'` set to 1. + +#### `role` + +Role maps 1:1 to the role the user will be under when using the key, and this maps to their on-chain registration. +The registered public key for the Role, MUST match the derived key or documents will not be accepted as +valid. + +#### `index` + +Index maps 1:1 to the key rotation currently used for the role, and this maps to their on-chain registration. +The registered public key for the Role+Rotation, MUST match the derived key or documents will not be accepted as +valid. + +### Deriving the Per Document AES256 Encryption Key + +We utilize Blake3 Key Derivation to produce the AES256 Encryption Key from the Root Master Key. + +The simplified pseudo-code of the algorithm is as follows. + +``` +seed_phrase: list[str] = ["abandon", "abandon", "abandon", ... "zoo"]; +key_material: bytes[96] = path_derivation("m/508'/139'/1'/{role}/{index}", seed_phrase); +context: bytes[] = cbor_encoded([document_type,document_id,document_ver]); +aes_256_key: bytes[32] = blake3_derive_key(context, key_material); +``` + +1. The seed_phrase is turned into 96 bytes of `key material` as discussed above. +2. A CBOR encoded array is created for the `context`, which contains the `document_type` UUIDv4, + the `document_id` UUIDv7 and the `document_ver` UUIDv7. +3. A aes256 key is derived using the blake3 `derive_key` function, using the `context` and `key_material`. + +This creates a *UNIQUE* encryption key for encrypting any content in the one document. + +Having derived the Private signing key, the public key can be obtained and posted on chain in an RBAC registration for the role. +The private key can then be used to authoritatively sign documents for that registration under that role. + + +## Reference Implementation + +The first implementation will be Catalyst Voices. + +*TODO: Generate a set of test vectors which conform to this specification.* + +## Rationale: how does this CIP achieve its goals? + +By leveraging known working Key Derivation techniques and simply modifying the path we inherit the properties of those methods. + +## Path to Active + +### Acceptance Criteria + +Working Implementation before Fund 14. + +### Implementation Plan + +Fund 16 project catalyst will deploy this scheme for Key derivation. + +## Copyright + +This document is licensed under [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/legalcode). diff --git a/specs/generators/pages/signed_doc/key-derivation/ed25519-document-signing-key-derivation.md.jinja b/specs/generators/pages/signed_doc/key-derivation/ed25519-document-signing-key-derivation.md.jinja new file mode 100644 index 00000000000..05ffccce3dd --- /dev/null +++ b/specs/generators/pages/signed_doc/key-derivation/ed25519-document-signing-key-derivation.md.jinja @@ -0,0 +1,81 @@ +--- +Title: ED25519 Document Signature Keys Derivation +Category: Catalyst +Status: Proposed +Authors: + - Steven Johnson +Implementors: + - Catalyst Fund 14 +Discussions: [] +Created: 2024-11-29 +License: CC-BY-4.0 +--- + +## Abstract + +Defines how Document Signature Keys are derived using [Catalyst HD Key Derivation](./hd-key-derivation.md). + +## Motivation: why is this CIP necessary? + +Users in Catalyst are required to sign various documents with various authorities. +This is used as a way to authenticate not just the user acted to sign the document, but that they +knowingly acted in the capacity of the role they are registered under. + +This helps clearly delineate actions, and also helps with organizational keys where certain +parties may be trusted with a derived key for one role, but not others. + +For example, an organization may internally delegate writing and submitting of proposals to one person, +but they do not also want to give that person the capability to vote on a proposal. + +This scheme allows for that segregation of roles and responsibilities. + +## Specification + +For reference, see [Catalyst HD Key Derivation](./hd-key-derivation.md). +This document defines how ED25519 document signing keys are derived from the master seed phrase. + +### `usage'` + +The ED25519 private signing key is derived with `usage'` set to 0. + +### `role` + +Role maps 1:1 to the role the user will be under when using the key, and this maps to their on-chain registration. +The registered public key for the Role, MUST match the derived key or documents will not be accepted as +valid. + +### `index` + +Index maps 1:1 to the key rotation currently used for the role, and this maps to their on-chain registration. +The registered public key for the Role+Rotation, MUST match the derived key or documents will not be accepted as +valid. + +## Usage + +Having derived the Private signing key, the public key can be obtained and posted on chain in an RBAC registration for the role. +The private key can then be used to authoritatively sign documents for that registration under that role. + + +## Reference Implementation + +The first implementation will be Catalyst Voices. + +*TODO: Generate a set of test vectors which conform to this specification.* + +## Rationale: how does this CIP achieve its goals? + +By leveraging known working Key Derivation techniques and simply modifying the path we inherit the properties of those methods. + +## Path to Active + +### Acceptance Criteria + +Working Implementation before Fund 14. + +### Implementation Plan + +Fund 14 project catalyst deployed this scheme for Key derivation.> + +## Copyright + +This document is licensed under [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/legalcode). diff --git a/specs/generators/pages/signed_doc/key-derivation/hd-key-derivation.md.jinja b/specs/generators/pages/signed_doc/key-derivation/hd-key-derivation.md.jinja new file mode 100644 index 00000000000..c12d504c73e --- /dev/null +++ b/specs/generators/pages/signed_doc/key-derivation/hd-key-derivation.md.jinja @@ -0,0 +1,129 @@ +--- +Title: Catalyst HD Key Derivation for Off Chain Keys +Category: Catalyst +Status: Proposed +Authors: + - Steven Johnson +Implementors: + - Catalyst Fund 14 +Discussions: [] +Created: 2024-11-29 +License: CC-BY-4.0 +--- + +## Abstract + +Project Catalyst uses off chain keys, as a proxy for on-chain keys. +These keys need to be derived similar to the keys controlled by a wallet. +This document defines the Derivation path. + +## Motivation: why is this CIP necessary? + +A user will need a number of self generated and controlled signature and other keys. +They will need to be able to recover them from a known seed phrase, and also to roll them over. + +This allows users to replace keys, and have them fully recoverable. +Which they may have to do if: + +* Their keys are lost, and the account has to be recovered, or moved to a different device. +* Their keys are compromised (or suspected to be compromised), and they have to be replaced. + +The keys are not controlled by a Blockchain wallet. +They are agnostic of any blockchain. +So, Project Catalyst must implement similar mechanisms as the wallets to safely derive keys for its use. + +## Specification + +For reference, see [CIP-1852]. +This document is a modified implementation of this specification. + +The basic structure of the Key Derivation path shall be: + +```text +m / purpose' / type' / usage' / role / index +``` + +### `purpose'` + +Defines the purpose of the key, a distinct value from the one chosen for cardano is used to +prevent collision with keys derived by wallets if the same seed phrase were to be used. +Cardano uses a notable year that aligns with Cardano ecosystem philosophy, +we maintain that practice but choose an alternative notable year. + +* Value: `508` +* Name in [CIP-1852]: `purpose'` +* Hardened: YES +* Represents: Taken from year 508 BCE, the first known instance of democracy in human history. + *"The Athenian Revolution, a revolt that overthrew the aristocratic oligarchy and established a participatory democracy in Athens"*. + +### `type'` + +Defines the type of the key, a distinct value from the one chosen for cardano is used to +prevent collision with keys derived by wallets if the same seed phrase were to be used. +Cardano uses a notable year that aligns with Cardano ecosystem philosophy, +we maintain that practice but choose an alternative notable year. + +* Value: `139` +* Name in [CIP-1852]: `coin_type'` +* Hardened: YES +* Represents: Taken from the year 139 BCE, the first known instance of secret voting. + *"A secret ballot is instituted for Roman citizens, who mark their vote on a tablet and place it in an urn."* + +### `usage'` + +Defines how the derived key will be used. +This occupies the same position as `account'` in [CIP-1852]. + +* Value: positive integer (0-n) +* Name in [CIP-1852]: `account'` +* Hardened: YES + +| `usage'` | Name | +| 0 | [ED25519 Document Signing Key](./ed25519-document-signing-key-derivation.md) | +| 1 | Used to derive a Root Symmetric encryption key used for encrypting data within a document. | +| 2+ | Currently undefined. | + +### `role` + +The role in the derivation maps 1:1 with the role number in the RBAC registration the key will be used for. + +* Value: positive integer (0-n) +* Name in [CIP-1852]: `role` +* Hardened: NO + +### `index` + +The sequentially derived key in a sequence, starting at 0. +Each new key for the same role just increments `index`. +This is mapped 1:1 to the `rotation` field in a Catalyst ID. + +* Value: positive integer (0-n) +* Name in [CIP-1852]: `index` +* Hardened: NO + +## Reference Implementation + +The first implementation will be for [ED25519 Document Signing Key](./ed25519-document-signing-key-derivation.md) in Catalyst Voices. + +*TODO: Generate a set of test vectors which conform to this specification.* + +## Rationale: how does this CIP achieve its goals? + +By leveraging known working Key Derivation techniques and simply modifying the path we inherit the properties of those methods. + +## Path to Active + +### Acceptance Criteria + +Working Implementation before Fund 14. + +### Implementation Plan + +Fund 14 project catalyst will deploy this scheme for Key derivation.> + +## Copyright + +This document is licensed under [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/legalcode). + +[CIP-1852]: https://cips.cardano.org/cip/CIP-1852 +[historical dates]: https://www.oxfordreference.com/display/10.1093/acref/9780191737152.timeline.0001 diff --git a/specs/generators/src/docs/main.py b/specs/generators/src/docs/main.py index 29e1fb2a916..1dfd99f6a2c 100755 --- a/specs/generators/src/docs/main.py +++ b/specs/generators/src/docs/main.py @@ -6,6 +6,7 @@ from pathlib import Path import rich +from pydantic import ValidationError from rich_argparse import RichHelpFormatter from docs.doc_index import DocIndex @@ -70,7 +71,14 @@ def main() -> None: args = parse_args() # Get the compiled documentation json file - spec = SignedDoc.load(args.spec) + try: + spec = SignedDoc.load(args.spec) + except ValidationError as e: + SignedDoc.validation_error(e) + sys.exit(1) + except Exception: # noqa: BLE001 + rich.get_console().print_exception(show_locals=True) + sys.exit(1) # We start out hoping everything is OK. good = True diff --git a/specs/generators/src/validator/main.py b/specs/generators/src/validator/main.py index 51ef671b6c0..a6b0fb55d16 100644 --- a/specs/generators/src/validator/main.py +++ b/specs/generators/src/validator/main.py @@ -5,7 +5,6 @@ import rich from pydantic import ValidationError -from rich.table import Table from rich_argparse import RichHelpFormatter from spec.signed_doc import SignedDoc @@ -32,37 +31,7 @@ def main() -> None: _spec = SignedDoc.load(args.spec) rich.print("Architectural Specifications Validated Successfully.") except ValidationError as exc: - table = Table( - title=f"{exc.error_count()} Locations where Schema Data does not match the {exc.title} Model.", - caption="Model does not match Schema and needs updating.", - ) - table.add_column("Key", no_wrap=True, style="yellow") - table.add_column("Error", no_wrap=True, style="red") - table.add_column("Input", no_wrap=True, max_width=30, style="grey37") - - error_links: dict[str, str] = {} - errors = exc.errors() - errors.sort(key=lambda x: [x["loc"], x["type"]]) - for error in errors: - error_links[error["msg"]] = error["url"] # type: ignore # noqa: PGH003 - - loc: list[str] = [] - for x in error["loc"]: - if isinstance(x, int): - loc.append(f"[{x}]") - else: - loc.append(f"{x}") - - table.add_row( - ".".join(loc), - error["msg"], - str(error["input"]).splitlines()[0], - ) - rich.print(table) - - for msg, url in error_links.items(): - rich.print(f"* {msg} : {url}") - + SignedDoc.validation_error(exc) sys.exit(1) except Exception: # noqa: BLE001 diff --git a/specs/signed_doc.json b/specs/signed_doc.json index 5c80b8dc3f2..190f90d6f5d 100644 --- a/specs/signed_doc.json +++ b/specs/signed_doc.json @@ -94,6 +94,18 @@ "COSE_Generic_Headers" ] }, + "aes-crt-encrypted-block": { + "def": "bytes .size 16", + "description": "AES-CTR encrypted data.\nThe Nonce/IV is the UUIDv7 `document_ver`.\nThis is the correct size, and has the necessary randomness properties.\nThe first block uses the `document_ver` the second `document_ver+1` and so on.\nThe document_ver is interpreted as a Big Endian 128bit integer for the purpose\nof the addition.\n\nAs the CTR is predictable, the blocks can be decrypted in parallel for maximum performance.", + "requires": [] + }, + "aes-crt-encrypted-choices": { + "def": "+/(requires[0])", + "description": "Choices are constructed as a CBOR multidimensional array of the form:\n`[ +[+choice] ]`\nreflecting the choices in the rows and columns as present 1:1 in the encrypted\nchoices.\n\nThis data is then compressed using `brotli` compression, and the result is encrypted \nusing AES-CTR and encoded as a sequence of blocks here.\n\nData needs to be pre-compressed before encryption as encryption will make the data\nincompressible.\n\nThe Encryption Key is to be derived from the Voters catalyst key-chain and not to be\npublished.\nDerivation *MUST* take include the contest Document ID and Version, so that the same\nencryption key is never used twice for different contests, but can still be re-derived\nby a voter that holds their catalyst key-chain recovery keys.", + "requires": [ + "aes-crt-encrypted-block" + ] + }, "blake2b_256": { "comment": "Blake2B-256", "def": "bytes .size 32", @@ -113,12 +125,35 @@ "document_ref" ] }, + "choices": { + "comment": "Voters Choices.", + "def": "[ 0, clear-choices ] /\n[ 1, elgamal-ristretto255-encrypted-choices ]", + "description": "Choices are an array of encrypted or unencrypted choices.", + "requires": [ + "clear-choices", + "elgamal-ristretto255-encrypted-choices" + ] + }, "cid": { "comment": "IPLD content identifier\nTODO: add size limits if possible", "def": "#6.42(bytes)", "description": "IPLD content identifier.\nAlso known as an IPFS CID\nSee: https://docs.ipfs.tech/concepts/content-addressing/#what-is-a-cid\n https://github.com/ipld/cid-cbor/", "requires": [] }, + "clear-choice": { + "comment": "Universal Unencrypted Choice", + "def": "int", + "description": "An Choice Selection (clear/unencrypted).\n\nThis can be a positive or negative integer, and is\nconstrained by the parameters of the contest.", + "requires": [] + }, + "clear-choices": { + "comment": "Universal Unencrypted Choice", + "def": "( +clear-choice )", + "description": "A Choice Selection (clear/unencrypted).\n\nThis can be a positive or negative integer, and is\nconstrained by the parameters of the contest.", + "requires": [ + "clear-choice" + ] + }, "collaborators": { "comment": "Allowed Collaborators on the next subsequent version of a document.", "def": "[ * catalyst_id_kid ]", @@ -126,6 +161,35 @@ "catalyst_id_kid" ] }, + "column-proof": { + "def": "[ uint, [ +undefined ] ]", + "description": "Proof that values in a column have a required arrangement.\nThis is similar to the `row-proof` but for all values in a \nsingle column.\nIt is an array that matches the length of `choices`.\nIf it is a different length, then it is invalid.\n\nCurrently there are no `column-proof` defined, this value is\na placeholder for documentation purposes only.\n\nIt is NOT to be implemented.\n`column-proof` should be assumed to be missing until such time\nas a concrete `column-proof` is defined.\n\nSimilar to `row-proof` there can be multiple column-proofs defined which prove\ncertain characteristics of the encrypted column values.\nThey are identified by the unsigned integer starting the proof.", + "requires": [] + }, + "contest-ballot-payload": { + "comment": "Catalyst Vote Payload data object.", + "def": "{\n\t+ \"document_ref\" => choices,\n\t? \"column-proof\" : column-proof,\n\t? \"matrix-proof\" : matrix-proof,\n\t? \"voter-choice\" : voter-choice,\n}", + "description": "Catalyst Vote Payload data object.\n\nA vote payload that can hold both encrypted or unencrypted votes.", + "examples": [ + { + "description": "Example Shows:\n\n* Three Proposals\n* Two Encrypted Choices\n* Row Proofs for each proposal.\n* `aes-crt-encrypted-choices` which reflects the choices.\n\nThe Contest Private Key was: 0x1234562343....\nThe Contest Public Key was: 0x1324354235...\nThe AES encryption key for the `aes-crt-encrypted-choices` is 0x123456789...", + "example": "UmVwbGFjZSB0aGlzIGZpbGUgd2l0aCBDQk9SIEVuY29kZWQgZXhhbXBsZSBlbmNyeXB0ZWQgYmFsbG90Lgo=", + "title": "Example Encrypted Contest Ballot Payload." + }, + { + "description": "Example Shows:\n\n* Three Proposals\n* Two Choices", + "example": "UmVwbGFjZSB0aGlzIGZpbGUgd2l0aCBDQk9SIEVuY29kZWQgZXhhbXBsZSBjbGVhciBiYWxsb3QuCg==", + "title": "Example Clear Ballot Payload." + } + ], + "requires": [ + "document_ref", + "choices", + "column-proof", + "matrix-proof", + "voter-choice" + ] + }, "document_id": { "comment": "Document ID", "def": "uuid_v7", @@ -173,6 +237,29 @@ "uuid_v7" ] }, + "elgamal-ristretto255-encrypted-choice": { + "comment": "Elgamal encrypted ciphertext.", + "def": "[\n c1: elgamal-ristretto255-group-element, \n c2: elgamal-ristretto255-group-element,\n]", + "description": "The elgamal encrypted ciphertext `(c1, c2)`.", + "requires": [ + "elgamal-ristretto255-group-element" + ] + }, + "elgamal-ristretto255-encrypted-choices": { + "comment": "Universal Encrypted Choices", + "def": "( \n [+ elgamal-ristretto255-encrypted-choice], \n ? row-proof \n)", + "description": "Encrypted Choices are a Vector (list) of encrypted items.\nThe size of the vector will depend on the cryptography used, \nand the number of choices.\n\nTypically, (but optionally) it has a proof attached which proves something\nabout the encrypted choices, without disclosing their contents.\n\nFor example, a ZKProof that there is only a single `1` in the choices, \nand all the rest are `0`.\n\nThe size/contents of the proof depend on what is being proved, and the \ncryptography underlying the proof.", + "requires": [ + "elgamal-ristretto255-encrypted-choice", + "row-proof" + ] + }, + "elgamal-ristretto255-group-element": { + "comment": "Elgamal group element that composes the elgamal cipher text.", + "def": "bytes .size 32", + "description": "An individual elgamal ristretto255 group element.", + "requires": [] + }, "height": { "comment": "The consecutive sequence number of the current document \nin the chain.\nThe very first document in a sequence is numbered `0` and it\n*MUST ONLY* increment by one for each successive document in\nthe sequence.\n\nThe FINAL sequence number is encoded with the current height\nsequence value, negated. \n\nFor example the following values for height define a chain\nthat has 5 documents in the sequence 0-4, the final height \nis negated to indicate the end of the chain:\n`0, 1, 2, 3, -4`\n\nNo subsequent document can be chained to a sequence that has\na final chain height.", "def": "int", @@ -184,10 +271,15 @@ "requires": [] }, "json_pointer": { - "comment": "RFC6901 Standard JSON Pointer", + "comment": "RFC6901 Standard JSON Pointer\nSee: https://datatracker.ietf.org/doc/html/rfc6901", "def": "text", "requires": [] }, + "matrix-proof": { + "def": "[ uint, undefined ]", + "description": "Proof that values in the matrix of all columns and rows have a required arrangement.\nThis is similar to the `row-proof` and `column-proof` but for all values in a \nballot taken together.\n\nThere is a single `matrix-proof` but it may be chosen from a pre-defined\nknown set of valid proofs.\n\nCurrently there are no `matrix-proof` defined, this value is\na placeholder for documentation purposes only.\n\nIt is NOT to be implemented.\n`matrix-proof` should be assumed to be missing until such time\nas a concrete `matrix-proof` is defined.\n\nSimilar to `row-proof` and `column-proof` there can be multiple matrix-proofs defined \nwhich prove certain characteristics of the encrypted column values.\nThey are identified by the unsigned integer starting the proof.", + "requires": [] + }, "media_type": { "comment": "Supported Content Media Types.\nIf the Media Type is supported by COAP, then the `uint` CoAP encoded\nversion of the media type must be used, in preference to the string.", "def": "(\n (uint .eq (0 / 50 / 60 / 20000)) / \n (tstr .eq (\n \"application/cbor\" /\n \"application/cddl\" /\n \"application/json\" /\n \"application/schema+json\" /\n \"text/css; charset=utf-8\" /\n \"text/css; charset=utf-8; template=handlebars\" /\n \"text/html; charset=utf-8\" /\n \"text/html; charset=utf-8; template=handlebars\" /\n \"text/markdown; charset=utf-8\" /\n \"text/markdown; charset=utf-8; template=handlebars\" /\n \"text/plain; charset=utf-8\" /\n \"text/plain; charset=utf-8; template=handlebars\"\n ))\n)", @@ -200,6 +292,14 @@ "document_ver" ] }, + "row-proof": { + "comment": "Universal Encrypted Row Proof", + "def": "[0, zkproof-elgamal-ristretto255-unit-vector-with-single-selection ]", + "description": "A proof that the choices conform to a required set of properties.\nIt is defined by the configured cryptography used for encrypted choices.\nThis format is universal over all encrypted choice encoding.", + "requires": [ + "zkproof-elgamal-ristretto255-unit-vector-with-single-selection" + ] + }, "section_ref": { "comment": "Reference to a section in a referenced document.", "def": "json_pointer", @@ -226,6 +326,54 @@ "def": "#6.37(bytes .size 16)", "description": "Version 7 UUID\nSee: https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-7\n https://github.com/lucas-clemente/cbor-specs/blob/master/uuid.md", "requires": [] + }, + "voter-choice": { + "def": "[ 0, /(requires[0]) ]", + "description": "This is an encrypted payload that a voter, and ONLY the voter can decrypt.\nIt allows the voter to recover their choices without needing to decrypt the\nencrypted votes used in the tally.\n\nThere is no way to associate this data with the encrypted choices directly, but\nit is created by the voter from the same data used to create the choices.", + "requires": [ + "aes-crt-encrypted-choices" + ] + }, + "zkproof-ed25519-r-response": { + "def": "( zkproof-ed25519-scalar, zkproof-ed25519-scalar, zkproof-ed25519-scalar )", + "description": "???", + "requires": [ + "zkproof-ed25519-scalar" + ] + }, + "zkproof-ed25519-scalar": { + "def": "bytes .size 32", + "description": "???", + "requires": [] + }, + "zkproof-elgamal-announcement": { + "def": "( zkproof-elgamal-group-element, zkproof-elgamal-group-element, zkproof-elgamal-group-element )", + "description": "???", + "requires": [ + "zkproof-elgamal-group-element" + ] + }, + "zkproof-elgamal-group-element": { + "def": "bytes .size 32", + "description": "???", + "requires": [] + }, + "zkproof-elgamal-ristretto255-unit-vector-with-single-selection": { + "def": "( [ [ +zkproof-elgamal-ristretto255-unit-vector-with-single-selection-item ], zkproof-ed25519-scalar )", + "description": "???", + "requires": [ + "zkproof-elgamal-ristretto255-unit-vector-with-single-selection-item", + "zkproof-ed25519-scalar" + ] + }, + "zkproof-elgamal-ristretto255-unit-vector-with-single-selection-item": { + "def": "( zkproof-elgamal-announcement, ~elgamal-ristretto255-encrypted-choice, zkproof-ed25519-r-response )", + "description": "???", + "requires": [ + "zkproof-elgamal-announcement", + "elgamal-ristretto255-encrypted-choice", + "zkproof-ed25519-r-response" + ] } }, "contentTypes": { @@ -357,7 +505,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -405,7 +553,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -449,7 +597,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "optional", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -530,7 +678,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -574,7 +722,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "excluded", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -658,7 +806,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -705,7 +853,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "optional", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -786,7 +934,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -835,7 +983,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "excluded", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -919,7 +1067,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -966,7 +1114,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "optional", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -1047,7 +1195,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -1096,7 +1244,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "excluded", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -1177,7 +1325,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -1226,7 +1374,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "excluded", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -1288,20 +1436,24 @@ "back_end": "* Verifies that the voter and Representative are valid and registered for the category.\n* Records the delegation of voting power from the voter to the Representative.", "front_end": "* Allows a voter to select a Representative from a list of eligible candidates for a category.\n* The voter signs this document to confirm their delegation choice." }, - "description": "Delegation by a Registered User to a Representative for\na contest.\n\nThis delegation allows votes cast by the Representative\nto use the voting power of the delegating User, in addition\nto their own personal voting power and that of all other Users \nwho delegate to the same Representative.\n\nDelegation is for a specific Contest.\nMultiple Delegations must be published if there are multiple\nContests within a Brand/Campaign or Category.\n\nThis is because different Contests may have different rules.\nAnd not all Representatives will choose to nominate\nfor every Contest.\n\nA Representative ***MAY NOT*** delegate to a different Representative\nfor any contest they have nominated for.\nThey ***MAY*** however nominate a Representative in any contest they\nhave not nominated for.", + "description": "Delegation by a Registered User to a Representative for\na contest.\n\nThis delegation allows votes cast by the Representative\nto use the voting power of the delegating User, in addition\nto their own personal voting power and that of all other Users \nwho delegate to the same Representative.\n\nDelegation is for a specific Contest.\nMultiple Delegations must be published if there are multiple\nContests within a Brand/Campaign or Category.\n\nThis is because different Contests may have different rules.\nAnd not all Representatives will choose to (or be able to) nominate\nfor every Contest.\n\nA Representative ***MAY NOT*** delegate to a different Representative\nfor any contest they have nominated for.\nThey ***MAY*** however nominate a Representative in any contest they\nhave not nominated for.\n\nA Representative is NOT required to delegate to themselves in a contest they are nominated for,\nand in fact, any self-delegation is invalid and ignored.\nA Representative has an implicit 100% voting power delegation to themselves in any contest \nthey are nominated.\nThe MAY not vote personally, and if they do, that vote will have Zero (0) voting power.\n100% of their voting power is assigned to their delegate vote and can not be split in any way.\n\nA voter MAY choose multiple delegates for a contest, in this case they are listed in priority \norder from highest priority to lowest.\nPriority only affects two aspects of the delegation.\n\n1. Any residual voting power after it is split among all delegates is given to the highest \n priority delegate (first).\n2. If there is not enough voting power to distribute, then its distributed from highest \n priority to lowest.\n This may mean that low priority delegates get zero voting power.\n\nAn example: If a Voter has 100 raw voting power, after quadratic scaling, they have 10.\nIf they delegated to 15 delegates equally, then only the first 10 would get 1 voting power\neach.\nVoting power is not fractionally assigned.\n\nThe payload MAY contain a json document which consists of a single array which can adjust \nthe ratio of the delegation.\nVoting power is divided based on the weight of a single delegate over the sum of all \nweights of all delegates.\nThis is performed with integer division.\nAs a special condition, 0 or any negative value is equivalent to a weight of 1.\nAs explained above, if there is not enough voting power to distribute, low priority reps \nwill receive 0 voting power from the delegation.\nAnd if there is any residual after integer division its applied to the representative \nwith the highest priority.", "draft": false, "headers": { "content type": { "coseLabel": 3, "description": "Media Type/s allowed in the Payload", "format": "Media Type", - "required": "excluded" + "required": "yes", + "value": "application/json" }, "content-encoding": { "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "excluded" + "required": "yes", + "value": [ + "br" + ] } }, "metadata": { @@ -1338,7 +1490,7 @@ "description": "Reference to a Linked Document or Documents. \nThis is the primary hierarchical reference to a related document.\t\t\t\n\nIf a reference is defined as required, there must be at least 1 reference specified.\nSome documents allow multiple references, and they are documented as required.\n\nThe document reference serves two purposes:\n \n1. It ensures that the document referenced by an ID/Version is not substituted.\n\tIn other words, that the document intended to be referenced, is actually referenced.\n2. It Allows the document to be unambiguously located in decentralized storage systems.\n\nThere can be any number of Document Locations in any reference.\nThe currently defined locations are:\n\n* `cid` : A CBOR Encoded IPLD Content Identifier ( AKA an IPFS CID ).\n* Others may be added when further storage mechanisms are defined.\n\nThe document location does not guarantee that the document is actually stored.\nIt only defines that if it were stored, this is the identifier\nthat is required to retrieve it.\nTherefore it is required that Document References\nare unique and reproducible, given a documents contents.", "format": "Document Reference", "linked_refs": null, - "multiple": false, + "multiple": true, "required": "yes", "type": "Rep Nomination", "validation": "The following must be true for a valid reference:\n\n* The Referenced Document **MUST** Exist\n* Every value in the `document_locator` must consistently reference the exact same document.\n* The `document_id` and `document_ver` **MUST** match the values in the referenced document." @@ -1350,7 +1502,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "optional", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -1382,8 +1534,53 @@ }, "notes": [], "payload": { - "description": " There is no payload.", - "nil": true + "description": "The Payload is a JSON Document, and must conform to this schema.\n\nIt consists of an array which defines the weights to be applied to the chosen delegations.\n\nEach valid delegate gets the matching weight from this array.\nThe total voting power is split proportionally based on these weights over the\nvalid drep nominations.", + "examples": [ + { + "description": "If there are only 1 delegation, then the weights do not matter.\nIf there are two, then the first delegate has a weight of 10/30, and the second has 20/30.\nIf there are 5, then the weights are: `[10,20,30,1,1]`", + "example": { + "weights": [ + 10, + 20, + 30 + ] + }, + "title": "Three Delegation Weights" + } + ], + "nil": false, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "description": "Structure of the payload of a Contest Delegation.", + "maintainers": [ + { + "name": "Catalyst Team", + "url": "https://projectcatalyst.io/" + } + ], + "properties": { + "weights": { + "description": "List of weights to apply to each delegate.\nThis list is in the same order as the delegate references.\nIf there are fewer entries than delegates, then the missing weights are set to `1`.\nIf there are more weights, then the extra weights are ignored. If the payload is missing, OR the array is empty, then the weights assigned is `1`.", + "items": { + "exclusiveMinimum": 0, + "type": "integer" + }, + "minItems": 0, + "type": "array" + } + }, + "required": [ + "weights" + ], + "title": "Contest Delegation Schema", + "type": "object", + "x-changelog": { + "2025-03-01": [ + "First Version Created." + ] + } + } }, "signers": { "roles": { @@ -1396,12 +1593,17 @@ } }, "type": "764f17fb-cc50-4979-b14a-b213dbac5994", - "validation": "* The `parameters` metadata *MUST* point to the same Contest as the \n\tNomination of the Representative.\n* The 'ref' metadata field MUST point to a valid 'Representative Nomination'.\n* The payload MUST be nil.\n\nA Representative *MUST* Delegate to their latest Nomination for a Category,\notherwise their Nomination is invalid.\n\nThis is because Delegation points to a *SPECIFIC* Nomination, and it\n*MUST* be the latest for the Representative on the Contest.\nAs the Nomination contains information that the User relies on\nwhen choosing to delegate, changing that information could have a \nreal and detrimental result in the Delegation choice.\nTherefore, for a Delegation to be valid, it *MUST* point to the\nlatest Nomination for a Representative.\n\nPublishing a newer version of the Nomination Document to a specific contest will\ninvalidate all pre-existing delegations, and all voters will need\nto re-delegate to affirm the delegates latest nomination.\n\nA Voter may withdraw their Delegation by revoking all delegation documents.\n`revocations` must be set to `true` to withdraw a delegation, OR\na later contest delegation may change the delegated representative without\nfirst revoking the prior delegation, as only the latest delegation is\nconsidered.", + "validation": "* The `parameters` metadata *MUST* point to the same Contest as the \n\tNomination of the Representative.\n* The 'ref' metadata field MUST point to a valid 'Representative Nomination'.\n IF there are multiple representatives, then any which are not pointing\n to a valid `Representative Nomination` are excluded. \n The nomination is only invalid if ALL references `Representative Nomination` \n references are invalid.\n This is to prevent a Representative changing their nomination invalidating a\n delegation with multiple representatives.\n* The payload MUST be nil.\n\nA Representative *MUST* Delegate to their latest Nomination for a Category,\notherwise their Nomination is invalid.\n\nThis is because Delegation points to a *SPECIFIC* Nomination, and it\n*MUST* be the latest for the Representative on the Contest.\nAs the Nomination contains information that the User relies on\nwhen choosing to delegate, changing that information could have a \nreal and detrimental result in the Delegation choice.\nTherefore, for a Delegation to be valid, it *MUST* point to the\nlatest Nomination for a Representative.\n\nPublishing a newer version of the Nomination Document to a specific contest will\ninvalidate all pre-existing delegations, and all voters will need\nto re-delegate to affirm the delegates latest nomination.\n\nA Voter may withdraw their Delegation by revoking all delegation documents.\n`revocations` must be set to `true` to withdraw a delegation, OR\na later contest delegation may change the delegated representative without\nfirst revoking the prior delegation, as only the latest delegation is\nconsidered.", "versions": [ { "changes": "* First Published Version", "modified": "2025-06-19", "version": "0.01" + }, + { + "changes": "* Allow Multi Delegation", + "modified": "2025-09-04", + "version": "0.1.2" } ] }, @@ -1424,7 +1626,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -1475,7 +1677,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "optional", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -1556,7 +1758,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -1607,7 +1809,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "excluded", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -1688,7 +1890,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -1739,7 +1941,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "excluded", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -1905,7 +2107,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -1958,7 +2160,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "optional", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -2042,7 +2244,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -2102,7 +2304,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "optional", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -2181,7 +2383,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -2232,7 +2434,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "excluded", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -2313,7 +2515,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -2364,7 +2566,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "excluded", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -2445,7 +2647,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -2492,7 +2694,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "excluded", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -2566,7 +2768,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -2622,7 +2824,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "excluded", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -2767,7 +2969,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -2819,7 +3021,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "optional", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -2893,7 +3095,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -2940,7 +3142,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "excluded", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -3028,7 +3230,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -3079,7 +3281,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "optional", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -3153,7 +3355,7 @@ "coseLabel": "content-encoding", "description": "Supported HTTP Encodings of the Payload.\nIf no compression or encoding is used, then this field must not be present.", "format": "HTTP Content Encoding", - "required": "optional", + "required": "yes", "value": [ "br" ] @@ -3202,7 +3404,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "excluded", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted." @@ -3316,6 +3518,7 @@ "Mustache": "https://mustache.github.io/mustache.5.html", "RFC3629": "https://datatracker.ietf.org/doc/html/rfc3629", "RFC3986": "https://datatracker.ietf.org/doc/html/rfc3986", + "RFC6901": "https://datatracker.ietf.org/doc/html/rfc6901", "RFC7932": "https://www.rfc-editor.org/rfc/rfc7932", "RFC8259": "https://www.rfc-editor.org/rfc/rfc8259.html", "RFC8610": "https://www.rfc-editor.org/rfc/rfc8610", @@ -3631,7 +3834,6 @@ "type": "string" }, "enum": { - "contentMediaType": "text/plain", "description": "Sorted array of string values from which a single value can be selected.\nValues must be presented in the order they appear in the array.\nNo value that is not in the array may be listed or presented.\nEach item in the array **MUST** be unique.", "example": [ "option 1", @@ -3639,6 +3841,7 @@ "option 3" ], "items": { + "contentMediaType": "text/plain", "description": "An element of the Enum.", "required": "yes", "type": "string" @@ -5289,7 +5492,6 @@ "type": "string" }, "enum": { - "contentMediaType": "text/plain", "description": "Sorted array of string values from which a single value can be selected.\nValues must be presented in the order they appear in the array.\nNo value that is not in the array may be listed or presented.\nEach item in the array **MUST** be unique.", "example": [ "option 1", @@ -5297,6 +5499,7 @@ "option 3" ], "items": { + "contentMediaType": "text/plain", "description": "An element of the Enum.", "required": "yes", "type": "string" @@ -5915,7 +6118,6 @@ "type": "string" }, "enum": { - "contentMediaType": "text/plain", "description": "Sorted array of string values from which a single value can be selected.\nValues must be presented in the order they appear in the array.\nNo value that is not in the array may be listed or presented.\nEach item in the array **MUST** be unique.", "example": [ "Hot FM", @@ -5923,6 +6125,7 @@ "Silence" ], "items": { + "contentMediaType": "text/plain", "description": "An element of the Enum.", "required": "yes", "type": "string" @@ -6886,11 +7089,11 @@ "description": "A set of tags with a group selector.", "items": { "description": "\tAn array of grouped tag objects, of which one can be selected.\n\tEach object *MUST* have the form:\n\t\n\t```json\n\t\"properties\": {\n\t\t\"group\": {\n\t\t\t\"$ref\": \"$def/tagGroup\",\n\t\t\t\"const\": \n\t\t},\n\t\t\"tag\": {\n\t\t\t\"$ref\": \"$def/tagSelection\",\n\t\t\t\"enum\": [\n\t\t\t\t,\n\t\t\t\t,\n\t\t\t\t...\n\t\t\t]\n\t\t}\n\t}\n\t```", - "required": "excluded", + "required": "yes", "type": "object" }, "property": "oneOf", - "required": "excluded", + "required": "yes", "type": "array" }, "title": { @@ -8263,7 +8466,6 @@ "type": "string" }, "enum": { - "contentMediaType": "text/plain", "description": "Sorted array of string values from which a single value can be selected.\nValues must be presented in the order they appear in the array.\nNo value that is not in the array may be listed or presented.\nEach item in the array **MUST** be unique.", "example": [ "option 1", @@ -8271,6 +8473,7 @@ "option 3" ], "items": { + "contentMediaType": "text/plain", "description": "An element of the Enum.", "required": "yes", "type": "string" @@ -8788,7 +8991,7 @@ "validation": "In addition to the validation performed for `Document Reference` type fields, \nThe `ref` of the `reply` document must be the same as\nthe original comment document." }, "revocations": { - "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be empty.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", + "description": "A document may include a list of any prior versions which are considered to be revoked.\nOnly the revocation list in the latest version of the document applies.\nRevoked documents are flagged as no longer valid, and should not be displayed.\nAs a special case, if the revocations are set to `true` then all versions of the document\nare revoked, including the latest document.\n\nIn this case, when the latest document is revoked, the payload may be `nil`.\nAny older document that has `revocations` set to `true` is always to be filtered\nand its payload is to be assumed to be invalid.\n\nThis allows for an entire document and any/all published versions to be revoked.\nA new version of the document that is published after this, may reinstate prior\ndocument versions, by not listing them as revoked. \nHowever, any document where revocations was set `true` can never be reinstated.", "format": "Version Revocations", "required": "excluded", "validation": "If the field is `true` the payload may be absent or invalid.\nSuch documents may never be submitted."