From 37554eff7e914559653e7170071665e45d351044 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Tue, 26 Aug 2025 20:13:35 +0700 Subject: [PATCH 01/12] fix(docs): fix local builds with earthly and Just --- specs/Earthfile | 19 +++++++++++++++++++ specs/Justfile | 10 ++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/specs/Earthfile b/specs/Earthfile index e6c010f0707..2ee110aa9c2 100644 --- a/specs/Earthfile +++ b/specs/Earthfile @@ -23,10 +23,21 @@ src: SAVE ARTIFACT signed_doc.json +# 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 + + SAVE ARTIFACT --keep-ts signed_doc.json + # Regenerate the full signed document specification json file. regenerate: FROM +src + 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 @@ -37,6 +48,12 @@ regenerate: SAVE ARTIFACT --keep-ts signed_doc.json SAVE ARTIFACT --keep-ts ../gen gen +regenerate-local-json: + FROM +regenerate-json + + SAVE ARTIFACT signed_doc.json AS LOCAL signed_doc.json + + regenerate-local: FROM +regenerate @@ -48,6 +65,8 @@ regenerate-local: check: FROM +src 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 diff --git a/specs/Justfile b/specs/Justfile index 632895d4009..b6ea2e8cf0d 100644 --- a/specs/Justfile +++ b/specs/Justfile @@ -3,7 +3,7 @@ # Developer convenience functions # required to get cuelang to enable file embedding -export CUE_EXPERIMENT := "embed" +export CUE_EXPERIMENT := "evalv3=0" default: @just --list --unsorted @@ -19,16 +19,14 @@ format: # Check the signed document cue files are valid. check: format - cd definitions; cue vet ./signed_docs/docs:signed_docs -c - cd definitions; cue vet ./form_template/elements:form_template -c + earthly +check # Regenerate the full signed document specification json file. regenerate-signed-doc-json: - # Make sure keys are sorted so its both reproducible, AND diffs easily. - cd definitions; cue export -f -s ./signed_docs/docs:signed_docs --out json | jq -S > ../signed_doc.json + earthly +regenerate-local-json # Fix and Check Markdown files -regenerate: check regenerate-signed-doc-json validate && validate-docs +regenerate: earthly +regenerate-local rm -rf "../docs/src/architecture/08_concepts/signed_doc" mv temp_gen "../docs/src/architecture/08_concepts/signed_doc" From 6bb0cf8df73e3554ede978721ff8cd0a12f38a3c Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Tue, 26 Aug 2025 20:25:44 +0700 Subject: [PATCH 02/12] fix(docs): Remove CDDL from checks until we actually have it --- specs/Earthfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/Earthfile b/specs/Earthfile index 2ee110aa9c2..537a67e7c5c 100644 --- a/specs/Earthfile +++ b/specs/Earthfile @@ -65,8 +65,8 @@ regenerate-local: check: FROM +src RUN cue fmt --check -s --files ./definitions - RUN cd definitions; \ - cue vet ./cddl + # RUN cd definitions; \ + # cue vet ./cddl RUN cd definitions; \ cue vet ./signed_docs/docs:signed_docs ../signed_doc.json From cd10d414e4c293537adfda92f66a036e75000a90 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Tue, 9 Sep 2025 11:54:26 +0700 Subject: [PATCH 03/12] feat(docs): wip --- .../signed_doc/cddl/section_ref.cddl | 1 + .../signed_doc/cddl/signed_document.cddl | 1 + .../08_concepts/signed_doc/diagrams/all.dot | 22 +- .../signed_doc/diagrams/all.dot.svg | 74 ++-- .../diagrams/contest_delegation.dot | 22 +- .../signed_doc/docs/contest_delegation.md | 130 ++++++- specs/Earthfile | 5 +- specs/definitions/cddl/common.cue | 87 +++++ .../cddl/contest_ballot_payload.cue | 343 ++++++++++++++++++ .../cose_signed_doc_cddl_defs.cue | 15 +- specs/definitions/cddl/defs.cue | 20 + specs/definitions/cddl/signed_doc.cue | 132 +++++++ specs/definitions/documentation/links.cue | 1 + specs/definitions/signed_doc_types/types.cue | 2 + specs/definitions/signed_docs/cddl_defs.cue | 188 +--------- .../signed_docs/docs/contest_ballot.cue | 107 ++++++ .../docs/contest_ballot_register.cue | 107 ++++++ .../signed_docs/docs/contest_delegation.cue | 73 +++- .../contest_delegation.example.json | 7 + .../contest_delegation.schema.json | 32 ++ .../signed_docs/docs/proposal_comment.cue | 3 + specs/definitions/signed_docs/payload.cue | 36 +- specs/definitions/signed_docs/signed_doc.cue | 4 +- specs/signed_doc.json | 216 ++++++++++- 24 files changed, 1353 insertions(+), 275 deletions(-) create mode 100644 specs/definitions/cddl/common.cue create mode 100644 specs/definitions/cddl/contest_ballot_payload.cue rename specs/definitions/{signed_docs => cddl}/cose_signed_doc_cddl_defs.cue (88%) create mode 100644 specs/definitions/cddl/defs.cue create mode 100644 specs/definitions/cddl/signed_doc.cue create mode 100644 specs/definitions/signed_docs/docs/contest_ballot.cue create mode 100644 specs/definitions/signed_docs/docs/contest_ballot_register.cue create mode 100644 specs/definitions/signed_docs/docs/payload_schemas/contest_delegation.example.json create mode 100644 specs/definitions/signed_docs/docs/payload_schemas/contest_delegation.schema.json 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..885e65d63eb 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 @@ -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 ad3ad1f4eb9..a48854baeff 100644 --- a/docs/src/architecture/08_concepts/signed_doc/diagrams/all.dot +++ b/docs/src/architecture/08_concepts/signed_doc/diagrams/all.dot @@ -577,7 +577,17 @@ digraph "All" { - + + + + + + +
content typeapplication/json
+ + + + @@ -587,7 +597,7 @@ digraph "All" { -
type
+ @@ -597,7 +607,7 @@ digraph "All" { -
id
+ @@ -607,7 +617,7 @@ digraph "All" { -
ver
+ @@ -617,7 +627,7 @@ digraph "All" { -
ref
+ @@ -627,7 +637,7 @@ digraph "All" { -
revocations
+ 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 64e96455c7a..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 @@ -680,56 +680,60 @@ Contest Delegation - + - - -                     - Contest Delegation -                 + + +                     + Contest Delegation +                 - - -type -764f17fb-cc50-4979-b14a-b213dbac5994 - - -id -Document Id - - -ver -Document Ver - - -ref -Rep Nomination - - -revocations -Version Revocations - - -parameters -Contest Parameters - + + +content type +application/json + + +type +764f17fb-cc50-4979-b14a-b213dbac5994 + + +id +Document Id + + +ver +Document Ver + + +ref +Rep Nomination + + +revocations +Version Revocations + + +parameters +Contest Parameters + Contest Delegation:e->Rep Nomination:w - + 1 -* +* Contest Delegation:e->Contest Parameters:w - + 1 -* +* 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 d0f70c49e80..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 @@ -53,7 +53,17 @@ Relationships" - + + +
parameters
+ + + + + + +
content typeapplication/json
+
@@ -63,7 +73,7 @@ Relationships" -
type
+ @@ -73,7 +83,7 @@ Relationships" -
id
+ @@ -83,7 +93,7 @@ Relationships" -
ver
+ @@ -93,7 +103,7 @@ Relationships" -
ref
+ @@ -103,7 +113,7 @@ Relationships" -
revocations
+ 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 7d8d892d65c..f492c9f3aaf 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,35 @@ 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 +66,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 +109,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 +170,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. @@ -231,10 +268,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 @@ -252,7 +362,7 @@ New versions of this document may be published by: | --- | --- | | License | This document is licensed under [CC-BY-4.0] | | Created | 2024-12-27 | -| Modified | 2025-08-19 | +| Modified | 2025-09-04 | | Authors | Alex Pozhylenkov | | | Nathan Bogale | | | Neil McAuliffe | @@ -264,9 +374,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/specs/Earthfile b/specs/Earthfile index 537a67e7c5c..bfc5e2721fb 100644 --- a/specs/Earthfile +++ b/specs/Earthfile @@ -28,6 +28,7 @@ regenerate-json: FROM +src # Make sure keys are sorted so its both reproducible, AND diffs easily. 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 > ../signed_doc.json SAVE ARTIFACT --keep-ts signed_doc.json @@ -38,10 +39,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 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..8e5881f8dd1 --- /dev/null +++ b/specs/definitions/cddl/contest_ballot_payload.cue @@ -0,0 +1,343 @@ +// CDDL Definitions +// +// Contest Choice Payload V2 CDDL Specification +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. + """ + } + + 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..ade3b56fc1c --- /dev/null +++ b/specs/definitions/cddl/defs.cue @@ -0,0 +1,20 @@ +// Signed Document Definitions +// +// CDDL Definitions +package cddl + +// 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. + } +} + +#cddlTypes: [ + for k, _ in cddlDefinitions {k}, +] + +#cddlTypesConstraint: or(#cddlTypes) diff --git a/specs/definitions/cddl/signed_doc.cue b/specs/definitions/cddl/signed_doc.cue new file mode 100644 index 00000000000..ade3661a0ce --- /dev/null +++ b/specs/definitions/cddl/signed_doc.cue @@ -0,0 +1,132 @@ +// Common CDDL Definitions +// +// CDDL Definitions +package cddl + +cddlDefinitions: #cddlDefinitions & { + document_type: { + def: "[ 1* \(requires[0]) ]" + requires: ["uuid_v4"] + description: "Unique Document Type Identifier" + comment: "Document Type" + } + document_id: { + def: "\(requires[0])" + requires: ["uuid_v7"] + description: "Unique Document Identifier" + comment: "Document ID" + } + document_ver: { + def: "\(requires[0])" + requires: ["uuid_v7"] + description: "Unique Chronological Document Version Identifier" + comment: "Document Version" + } + 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 + """ + } + document_locator: { + def: """ + { + \"cid\" => \(requires[0]) + } + """ + requires: ["cid"] + comment: "Where a document can be located, must be a unique identifier." + } + document_refs: { + def: "[ 1* \(requires[0]) ]" + requires: [ + "document_ref", + ] + comment: "Reference to one or more Signed Documents" + } + document_ref: { + def: """ + [ + \(requires[0]), + \(requires[1]), + \(requires[2]) + ] + """ + requires: [ + "document_id", + "document_ver", + "document_locator", + ] + comment: "Reference to a single Signed Document" + } + section_ref: { + def: "\(requires[0])" + requires: ["json_pointer"] + comment: "Reference to a section in a referenced document." + } + collaborators: { + def: "[ * \(requires[0]) ]" + requires: ["catalyst_id_kid"] + comment: "Allowed Collaborators on the next subsequent version of a document." + } + revocations: { + def: "[ * \(requires[0]) ] / true " + requires: ["document_ver"] + comment: "List of revoked versions of this document." + } + http_content_encoding: { + def: """ + tstr .eq "br" + """ + comment: "Supported Content Encoding Types" + } + catalyst_id_kid: { + def: "bytes" + comment: "UTF8 Catalyst ID URI encoded as a bytes string." + } + revocations: { + def: "[ * document_ver ] / true " + requires: ["document_ver"] + } + chain: { + def: "[\(requires[0]), ? \(requires[1])]" + requires: [ + "height", + "document_ref", + ] + comment: """ + Reference to the previous Signed Document in a sequence. + * `\(requires[0])` is of the CURRENT block. + * `\(requires[1])` is *ONLY* omitted in the very first document in a sequence. + """ + } + height: { + def: "int" + comment: """ + The consecutive sequence number of the current document + in the chain. + The very first document in a sequence is numbered `0` and it + *MUST ONLY* increment by one for each successive document in + the sequence. + + The FINAL sequence number is encoded with the current height + sequence value, negated. + + For example the following values for height define a chain + that has 5 documents in the sequence 0-4, the final height + is negated to indicate the end of the chain: + `0, 1, 2, 3, -4` + + No subsequent document can be chained to a sequence that has + a final chain height. + """ + } + +} 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/signed_doc_types/types.cue b/specs/definitions/signed_doc_types/types.cue index e304aad9dbb..5b35e6a9ee5 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/cddl_defs.cue b/specs/definitions/signed_docs/cddl_defs.cue index 014872fb442..ba26a3bd200 100644 --- a/specs/definitions/signed_docs/cddl_defs.cue +++ b/specs/definitions/signed_docs/cddl_defs.cue @@ -3,184 +3,15 @@ // 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. - } -} - -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"] - description: "Unique Document Identifier" - comment: "Document ID" - } - document_ver: { - def: "\(requires[0])" - requires: ["uuid_v7"] - description: "Unique Chronological Document Version Identifier" - comment: "Document Version" - } - 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 - """ - } - document_locator: { - def: """ - { - \"cid\" => \(requires[0]) - } - """ - requires: ["cid"] - comment: "Where a document can be located, must be a unique identifier." - } - document_refs: { - def: "[ 1* \(requires[0]) ]" - requires: [ - "document_ref", - ] - comment: "Reference to one or more Signed Documents" - } - document_ref: { - def: """ - [ - \(requires[0]), - \(requires[1]), - \(requires[2]) - ] - """ - requires: [ - "document_id", - "document_ver", - "document_locator", - ] - comment: "Reference to a single Signed Document" - } - json_pointer: { - def: "text" - comment: "RFC6901 Standard JSON Pointer" - } - section_ref: { - def: "\(requires[0])" - requires: ["json_pointer"] - comment: "Reference to a section in a referenced document." - } - collaborators: { - def: "[ * \(requires[0]) ]" - requires: ["catalyst_id_kid"] - comment: "Allowed Collaborators on the next subsequent version of a document." - } - revocations: { - def: "[ * \(requires[0]) ] / true " - 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" - """ - comment: "Supported Content Encoding Types" - } - catalyst_id_kid: { - def: "bytes" - comment: "UTF8 Catalyst ID URI encoded as a bytes string." - } - revocations: { - def: "[ * document_ver ] / true " - requires: ["document_ver"] - } - chain: { - def: "[\(requires[0]), ? \(requires[1])]" - requires: [ - "height", - "document_ref", - ] - comment: """ - Reference to the previous Signed Document in a sequence. - * `\(requires[0])` is of the CURRENT block. - * `\(requires[1])` is *ONLY* omitted in the very first document in a sequence. - """ - } - height: { - def: "int" - comment: """ - The consecutive sequence number of the current document - in the chain. - The very first document in a sequence is numbered `0` and it - *MUST ONLY* increment by one for each successive document in - the sequence. - - The FINAL sequence number is encoded with the current height - sequence value, negated. - - For example the following values for height define a chain - that has 5 documents in the sequence 0-4, the final height - is negated to indicate the end of the chain: - `0, 1, 2, 3, -4` - - No subsequent document can be chained to a sequence that has - a final chain height. - """ - } +import ( + "github.com/input-output-hk/catalyst-libs/specs/cddl" +) +#cddlDefinitions: [string]: { + def: string + requires: [...#cddlTypesConstraint] | *[] + description?: string // Description - multiline + comment?: string // Single line comments are displayed after a definition. Multiline comments, before. } #cddlTypes: [ @@ -188,3 +19,6 @@ cddlDefinitions: #cddlDefinitions & { ] #cddlTypesConstraint: or(#cddlTypes) + +cddlDefinitions: cddl.#cddlDefinitions +cddlDefinitions: cddl.cddlDefinitions diff --git a/specs/definitions/signed_docs/docs/contest_ballot.cue b/specs/definitions/signed_docs/docs/contest_ballot.cue new file mode 100644 index 00000000000..a3e0bcb4b6d --- /dev/null +++ b/specs/definitions/signed_docs/docs/contest_ballot.cue @@ -0,0 +1,107 @@ +@extern(embed) + +package signed_docs + +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: _ @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_ballot_register.cue b/specs/definitions/signed_docs/docs/contest_ballot_register.cue new file mode 100644 index 00000000000..18b3891a786 --- /dev/null +++ b/specs/definitions/signed_docs/docs/contest_ballot_register.cue @@ -0,0 +1,107 @@ +@extern(embed) + +package signed_docs + +docs: "Contest Ballot Register": { + 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: _ @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 2d286355a03..dd057f78fad 100644 --- a/specs/definitions/signed_docs/docs/contest_delegation.cue +++ b/specs/definitions/signed_docs/docs/contest_delegation.cue @@ -17,18 +17,53 @@ 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, @@ -53,11 +88,11 @@ docs: "Contest Delegation": { """ } - //headers: "content type": value: "application/cbor" metadata: { ref: { required: "yes" type: "Rep Nomination" + multiple: true } parameters: { required: "yes" @@ -68,12 +103,31 @@ docs: "Contest Delegation": { } revocations: required: "optional" } + + headers: "content type": value: "application/json" + payload: { - description: """ - There is no payload. - """ + description: """ + 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: [ @@ -88,5 +142,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..b916da39c4b 100644 --- a/specs/definitions/signed_docs/docs/proposal_comment.cue +++ b/specs/definitions/signed_docs/docs/proposal_comment.cue @@ -6,6 +6,9 @@ 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/payload.cue b/specs/definitions/signed_docs/payload.cue index f302163be1e..d87359d277b 100644 --- a/specs/definitions/signed_docs/payload.cue +++ b/specs/definitions/signed_docs/payload.cue @@ -15,19 +15,31 @@ import ( } // Payload definition -_payload: { +#payload: { // Description of the payload description: string - // Is the Payload nil? - nil?: true - // 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] | *[] - } + // Is the Payload allowed to be nil? + nil: true | *false +} + + +// Payload definition +#payload_json: #payload & { + // Optional fixed schema for the payload. + // A URI or inline JSON Schema that the payload must validate against. + schema?: string + // Examples of the schema. + examples?: list.UniqueItems + examples?: [...#payloadExample] | *[] +} + +// Payload definition for cbor payloads +#payload_cbor: #payload & { + // CBOR payloads must have a CDDL Schema defined. + schema?: #cddlTypesConstraint + + // Examples of the schema. + examples?: list.UniqueItems + examples?: [...#payloadExample] | *[] } diff --git a/specs/definitions/signed_docs/signed_doc.cue b/specs/definitions/signed_docs/signed_doc.cue index c69892d9e78..790156ca828 100644 --- a/specs/definitions/signed_docs/signed_doc.cue +++ b/specs/definitions/signed_docs/signed_doc.cue @@ -35,7 +35,7 @@ import ( notes: [...string] | *[] - if payload.nil == _|_ { + if !payload.nil { // Fixed headers in every document with a payload. headers: _coseHeaders } @@ -44,7 +44,7 @@ import ( metadata: #metadata // Requirements for the document payload. - payload?: _payload + payload?: #payload // Required/Allowed Signers of a document signers: _allowedSigners diff --git a/specs/signed_doc.json b/specs/signed_doc.json index d5362d430ad..093fe55534a 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,11 @@ "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": [] + }, "document_id": { "comment": "Document ID", "def": "uuid_v7", @@ -173,6 +213,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 +247,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 +268,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 +302,66 @@ "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": [] + }, + "vote-payload-v2": { + "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.", + "requires": [ + "document_ref", + "choices", + "column-proof", + "matrix-proof", + "voter-choice" + ] + }, + "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": { @@ -1261,7 +1397,25 @@ "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. 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. Voting 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. Voting power is divided based on the weight of a single \ndelegate over the sum of all weights 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. And if there is any residual after integer\ndivision its applied to the representative with the highest priority.", + "headers": { + "content type": { + "coseLabel": 3, + "description": "Media Type/s allowed in the Payload", + "format": "Media Type", + "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": "optional", + "value": [ + "br" + ] + } + }, "metadata": { "chain": { "description": "An immutable link to the previous document in a chained sequence of documents.\nBecause ID/Ver only defines values for the current document, and is not intended \nby itself to prevent insertion of documents in a sequence, the `chain`\nmetadata allows for the latest document to directly point to its previous iteration.\n\nIt also aids in discoverability, where the latest document may be pinned but prior\ndocuments can be discovered automatically by following the chain.", @@ -1296,7 +1450,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." @@ -1340,8 +1494,52 @@ }, "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" + } + ], + "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": { @@ -1354,12 +1552,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\t to a valid `Representative Nomination` are excluded. \n\t The nomination is only invalid if ALL references `Representative Nomination` \n\t references are invalid.\n\t This is to prevent a Representative changing their nomination invalidating a\n\t 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" } ] }, @@ -3245,6 +3448,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", From fe8c09330ceecfc4cdc8c3002912453753bdf097 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Thu, 11 Sep 2025 19:09:30 +0700 Subject: [PATCH 04/12] fix(docs): wip, but should fix cue errors --- .../08_concepts/signed_doc/.pages | 1 + .../signed_doc/cddl/signed_document.cddl | 2 +- .../signed_doc/docs/brand_parameters.md | 2 +- .../signed_doc/docs/campaign_parameters.md | 2 +- .../signed_doc/docs/category_parameters.md | 2 +- .../signed_doc/docs/contest_delegation.md | 4 +- .../signed_doc/docs/contest_parameters.md | 2 +- .../08_concepts/signed_doc/docs/proposal.md | 2 +- .../signed_doc/docs/proposal_comment.md | 2 +- .../signed_doc/docs/rep_nomination.md | 2 +- .../signed_doc/docs/rep_profile.md | 2 +- .../drop_down_single_select.md | 30 ++-- .../radio_button_select.md | 30 ++-- .../single_grouped_tag_selector.md | 4 +- .../form_template_elements/single_select.md | 30 ++-- .../08_concepts/signed_doc/spec.md | 2 +- specs/Earthfile | 4 +- .../cddl/contest_ballot_payload.cue | 30 ++++ specs/definitions/cddl/defs.cue | 5 + .../contest_ballot_payload_clear.cbor | 1 + .../contest_ballot_payload_encrypted.cbor | 1 + .../elements/drop_down_single_select.cue | 2 +- .../elements/radio_button_select.cue | 2 +- .../form_template/elements/single_select.cue | 2 +- .../definitions/form_template/parameters.cue | 2 +- specs/definitions/generic/examples.cue | 19 +++ specs/definitions/generic/optional.cue | 6 +- specs/definitions/media_types/content.cue | 17 ++ specs/definitions/regex/def.cue | 39 +++++ specs/definitions/signed_doc_types/types.cue | 4 +- specs/definitions/signed_docs/cddl_defs.cue | 24 --- .../definitions/signed_docs/cose_headers.cue | 6 +- ...test_ballot.cue => contest_ballot.cue.off} | 19 +-- ...er.cue => contest_ballot_register.cue.off} | 34 ++-- specs/definitions/signed_docs/metadata.cue | 7 +- specs/definitions/signed_docs/payload.cue | 36 ++--- specs/definitions/signed_docs/signed_doc.cue | 27 +++- .../packages/spec/src/spec/cddl/definition.py | 3 + .../packages/spec/src/spec/example.py | 75 +++++++++ .../packages/spec/src/spec/payload.py | 39 +---- .../packages/spec/src/spec/signed_doc.py | 44 +++++- .../generators/pages/signed_doc/.pages.jinja | 1 + .../signed_doc/key-derivation/.pages.jinja | 5 + ...ocument-encryption-key-derivation.md.jinja | 107 +++++++++++++ ...9-document-signing-key-derivation.md.jinja | 81 ++++++++++ .../key-derivation/hd-key-derivation.md.jinja | 129 ++++++++++++++++ specs/generators/src/docs/main.py | 10 +- specs/generators/src/validator/main.py | 33 +--- specs/signed_doc.json | 145 ++++++++++-------- 49 files changed, 788 insertions(+), 290 deletions(-) create mode 100644 specs/definitions/cddl/examples/contest_ballot_payload_clear.cbor create mode 100644 specs/definitions/cddl/examples/contest_ballot_payload_encrypted.cbor create mode 100644 specs/definitions/generic/examples.cue delete mode 100644 specs/definitions/signed_docs/cddl_defs.cue rename specs/definitions/signed_docs/docs/{contest_ballot.cue => contest_ballot.cue.off} (85%) rename specs/definitions/signed_docs/docs/{contest_ballot_register.cue => contest_ballot_register.cue.off} (65%) create mode 100644 specs/generators/packages/spec/src/spec/example.py create mode 100644 specs/generators/pages/signed_doc/key-derivation/.pages.jinja create mode 100644 specs/generators/pages/signed_doc/key-derivation/aes256-document-encryption-key-derivation.md.jinja create mode 100644 specs/generators/pages/signed_doc/key-derivation/ed25519-document-signing-key-derivation.md.jinja create mode 100644 specs/generators/pages/signed_doc/key-derivation/hd-key-derivation.md.jinja 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/signed_document.cddl b/docs/src/architecture/08_concepts/signed_doc/cddl/signed_document.cddl index 885e65d63eb..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. 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 24f92e77f81..53848970d55 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 03796555a34..9547ed15f43 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 f2ccc2352f1..de01f8d5427 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 f492c9f3aaf..3e3c8511641 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 @@ -219,7 +219,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. @@ -362,7 +362,7 @@ New versions of this document may be published by: | --- | --- | | License | This document is licensed under [CC-BY-4.0] | | Created | 2024-12-27 | -| Modified | 2025-09-04 | +| Modified | 2025-09-08 | | Authors | Alex Pozhylenkov | | | Nathan Bogale | | | Neil McAuliffe | 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 c1cab4a9229..123cd4c9a3a 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 13f9ac6df2f..70998d96117 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 1e00f71c371..af2e7349781 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 ffa00f2a393..5effbb7cf7f 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 3fe2bf7d332..609575a58ef 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. - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + 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. - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + 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 - + @@ -156,7 +156,7 @@ Parameters - + 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. - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/docs/src/architecture/08_concepts/signed_doc/spec.md b/docs/src/architecture/08_concepts/signed_doc/spec.md index d99a4a3a3bc..d9d7260bbb3 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 - + diff --git a/specs/Earthfile b/specs/Earthfile index 20bd4c8fbd1..625fa8cd6cf 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 diff --git a/specs/definitions/cddl/contest_ballot_payload.cue b/specs/definitions/cddl/contest_ballot_payload.cue index 8e5881f8dd1..4d6068c9baf 100644 --- a/specs/definitions/cddl/contest_ballot_payload.cue +++ b/specs/definitions/cddl/contest_ballot_payload.cue @@ -1,6 +1,8 @@ // CDDL Definitions // // Contest Choice Payload V2 CDDL Specification +@extern(embed) + package cddl cddlDefinitions: { @@ -28,6 +30,34 @@ cddlDefinitions: { 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: { diff --git a/specs/definitions/cddl/defs.cue b/specs/definitions/cddl/defs.cue index ade3b56fc1c..c291b957dec 100644 --- a/specs/definitions/cddl/defs.cue +++ b/specs/definitions/cddl/defs.cue @@ -3,6 +3,10 @@ // 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]: { @@ -10,6 +14,7 @@ package cddl requires: [...#cddlTypesConstraint] | *[] description?: string // Description - multiline comment?: string // Single line comments are displayed after a definition. Multiline comments, before. + examples?: Eg.#list } } 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/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..adfba352c03 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..04c25e15ab7 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,19 @@ 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..c30296cbece 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,18 @@ 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 +153,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 5b35e6a9ee5..52e4ed23288 100644 --- a/specs/definitions/signed_doc_types/types.cue +++ b/specs/definitions/signed_doc_types/types.cue @@ -28,8 +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" + //"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/cddl_defs.cue b/specs/definitions/signed_docs/cddl_defs.cue deleted file mode 100644 index ba26a3bd200..00000000000 --- a/specs/definitions/signed_docs/cddl_defs.cue +++ /dev/null @@ -1,24 +0,0 @@ -// Signed Document Definitions -// -// CDDL Definitions -package signed_docs - -import ( - "github.com/input-output-hk/catalyst-libs/specs/cddl" -) - -#cddlDefinitions: [string]: { - def: string - requires: [...#cddlTypesConstraint] | *[] - description?: string // Description - multiline - comment?: string // Single line comments are displayed after a definition. Multiline comments, before. -} - -#cddlTypes: [ - for k, _ in cddlDefinitions {k}, -] - -#cddlTypesConstraint: or(#cddlTypes) - -cddlDefinitions: cddl.#cddlDefinitions -cddlDefinitions: cddl.cddlDefinitions diff --git a/specs/definitions/signed_docs/cose_headers.cue b/specs/definitions/signed_docs/cose_headers.cue index 9119e1e61f3..5fa89e9d95e 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 b/specs/definitions/signed_docs/docs/contest_ballot.cue.off similarity index 85% rename from specs/definitions/signed_docs/docs/contest_ballot.cue rename to specs/definitions/signed_docs/docs/contest_ballot.cue.off index a3e0bcb4b6d..93289eed12e 100644 --- a/specs/definitions/signed_docs/docs/contest_ballot.cue +++ b/specs/definitions/signed_docs/docs/contest_ballot.cue.off @@ -2,6 +2,11 @@ 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. @@ -70,18 +75,8 @@ docs: "Contest Ballot": { 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") - } - ] + schema: "contest-ballot-payload" + examples: cddl.cddlDefinitions["\(schema)"].examples } signers: roles: user: [ diff --git a/specs/definitions/signed_docs/docs/contest_ballot_register.cue b/specs/definitions/signed_docs/docs/contest_ballot_register.cue.off similarity index 65% rename from specs/definitions/signed_docs/docs/contest_ballot_register.cue rename to specs/definitions/signed_docs/docs/contest_ballot_register.cue.off index 18b3891a786..25e0f0f1754 100644 --- a/specs/definitions/signed_docs/docs/contest_ballot_register.cue +++ b/specs/definitions/signed_docs/docs/contest_ballot_register.cue.off @@ -4,24 +4,34 @@ package signed_docs docs: "Contest Ballot Register": { description: """ - An individual Ballot cast in a Contest by a registered user. + 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. - Each ballot contains choices for all possible proposals eligible for the - contest. + These documents are chained to each other, and the final document is specified as final + in the `chain` metadata. - 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. + Typically each `Contest Ballot Register` document is made immutable by referencing it on + the blockchain most applicable to the Contest. - 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. + Different blockchains will have different mechanisms for referencing the individual + `Contest Ballot Register` documents. - Only eligible users can cast ballots in the respective contest. + 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 within the ballot payload (not the headers) must point to - ALL the proposals eligible to be chosen in the contest. + * 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: """ @@ -58,7 +68,7 @@ docs: "Contest Ballot Register": { revocations: required: "optional" } - headers: "content type": value: "application/cbor" + headers: "content type": value: "application/json" payload: { 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 d87359d277b..eb399b0736e 100644 --- a/specs/definitions/signed_docs/payload.cue +++ b/specs/definitions/signed_docs/payload.cue @@ -1,45 +1,43 @@ 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. - description: string - // Example data that matches the payload schema. - example: _ -} - // Payload definition #payload: { // Description of the payload description: string // 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_json: #payload & { +#payload_json: { + // Extends #payload + #payload // Optional fixed schema for the payload. // A URI or inline JSON Schema that the payload must validate against. - schema?: string + // 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?: list.UniqueItems - examples?: [...#payloadExample] | *[] + examples?: Eg.#list } // Payload definition for cbor payloads -#payload_cbor: #payload & { +#payload_cbor: { + // Extends #payload + #payload // CBOR payloads must have a CDDL Schema defined. - schema?: #cddlTypesConstraint + schema?: cddl.#cddlTypesConstraint // Examples of the schema. - examples?: list.UniqueItems - examples?: [...#payloadExample] | *[] + examples?: Eg.#list } diff --git a/specs/definitions/signed_docs/signed_doc.cue b/specs/definitions/signed_docs/signed_doc.cue index 1bef887c534..565cc95ef4e 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 \ No newline at end of file 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..d19edb7fc05 --- /dev/null +++ b/specs/generators/pages/signed_doc/key-derivation/.pages.jinja @@ -0,0 +1,5 @@ +title: Key Derivation +arrange: + - Catalyst Herarchial 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..438483d1283 --- /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 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). 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..0293b1033a1 --- /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 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/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 195a9d24a4b..29dd4bc2d13 100644 --- a/specs/signed_doc.json +++ b/specs/signed_doc.json @@ -166,6 +166,30 @@ "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", @@ -303,18 +327,6 @@ "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": [] }, - "vote-payload-v2": { - "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.", - "requires": [ - "document_ref", - "choices", - "column-proof", - "matrix-proof", - "voter-choice" - ] - }, "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.", @@ -488,7 +500,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" ] @@ -536,7 +548,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" ] @@ -580,7 +592,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." @@ -661,7 +673,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 +717,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." @@ -789,7 +801,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" ] @@ -836,7 +848,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." @@ -917,7 +929,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 +978,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." @@ -1050,7 +1062,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" ] @@ -1097,7 +1109,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." @@ -1178,7 +1190,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" ] @@ -1227,7 +1239,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." @@ -1308,7 +1320,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" ] @@ -1357,7 +1369,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." @@ -1419,20 +1431,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. 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. Voting 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. Voting power is divided based on the weight of a single \ndelegate over the sum of all weights 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. And if there is any residual after integer\ndivision its applied to the representative with 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": { @@ -1481,7 +1497,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." @@ -1527,6 +1543,7 @@ "title": "Three Delegation Weights" } ], + "nil": false, "schema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, @@ -1604,7 +1621,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" ] @@ -1655,7 +1672,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." @@ -1736,7 +1753,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" ] @@ -1787,7 +1804,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." @@ -1868,7 +1885,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" ] @@ -1919,7 +1936,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." @@ -2085,7 +2102,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" ] @@ -2138,7 +2155,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." @@ -2222,7 +2239,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" ] @@ -2282,7 +2299,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." @@ -2361,7 +2378,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" ] @@ -2412,7 +2429,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." @@ -2493,7 +2510,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" ] @@ -2544,7 +2561,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." @@ -2625,7 +2642,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" ] @@ -2672,7 +2689,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." @@ -2746,7 +2763,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" ] @@ -2802,7 +2819,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." @@ -2947,7 +2964,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" ] @@ -2999,7 +3016,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." @@ -3073,7 +3090,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" ] @@ -3120,7 +3137,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." @@ -3208,7 +3225,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" ] @@ -3259,7 +3276,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." @@ -3333,7 +3350,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" ] @@ -3382,7 +3399,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." @@ -3812,7 +3829,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", @@ -3820,6 +3836,7 @@ "option 3" ], "items": { + "contentMediaType": "text/plain", "description": "An element of the Enum.", "required": "yes", "type": "string" @@ -5470,7 +5487,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", @@ -5478,6 +5494,7 @@ "option 3" ], "items": { + "contentMediaType": "text/plain", "description": "An element of the Enum.", "required": "yes", "type": "string" @@ -6096,7 +6113,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", @@ -6104,6 +6120,7 @@ "Silence" ], "items": { + "contentMediaType": "text/plain", "description": "An element of the Enum.", "required": "yes", "type": "string" @@ -7067,11 +7084,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": { @@ -8444,7 +8461,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", @@ -8452,6 +8468,7 @@ "option 3" ], "items": { + "contentMediaType": "text/plain", "description": "An element of the Enum.", "required": "yes", "type": "string" @@ -8969,7 +8986,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." From ac1b2a9512ac9bd7c7ddb64679f76de2f21fe459 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Thu, 11 Sep 2025 21:38:54 +0700 Subject: [PATCH 05/12] fix(docs): spelling --- specs/generators/pages/signed_doc/key-derivation/.pages.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/generators/pages/signed_doc/key-derivation/.pages.jinja b/specs/generators/pages/signed_doc/key-derivation/.pages.jinja index d19edb7fc05..5544bbcd087 100644 --- a/specs/generators/pages/signed_doc/key-derivation/.pages.jinja +++ b/specs/generators/pages/signed_doc/key-derivation/.pages.jinja @@ -1,5 +1,5 @@ title: Key Derivation arrange: - - Catalyst Herarchial Deterministic Key Derivation: hd-key-derivation.md + - 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 From e921e2ddfe46a11cdf8f31cd8e1b1c5ed0819140 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Thu, 11 Sep 2025 22:28:07 +0700 Subject: [PATCH 06/12] fix(docs): format --- .../cddl/contest_ballot_payload.cue | 24 +++++------- specs/definitions/cddl/defs.cue | 4 +- specs/definitions/generic/optional.cue | 2 +- specs/definitions/media_types/content.cue | 1 - specs/definitions/regex/def.cue | 1 - .../definitions/signed_docs/cose_headers.cue | 2 +- .../signed_docs/docs/contest_delegation.cue | 38 +++++++++---------- .../signed_docs/docs/proposal_comment.cue | 1 - specs/definitions/signed_docs/payload.cue | 9 +++-- specs/definitions/signed_docs/signed_doc.cue | 2 +- 10 files changed, 38 insertions(+), 46 deletions(-) diff --git a/specs/definitions/cddl/contest_ballot_payload.cue b/specs/definitions/cddl/contest_ballot_payload.cue index 4d6068c9baf..12ca910a79f 100644 --- a/specs/definitions/cddl/contest_ballot_payload.cue +++ b/specs/definitions/cddl/contest_ballot_payload.cue @@ -12,7 +12,7 @@ cddlDefinitions: { "choices", "column-proof", "matrix-proof", - "voter-choice" + "voter-choice", ] def: """ { @@ -56,14 +56,14 @@ cddlDefinitions: { * Two Choices """ example: _ @embed(file=examples/contest_ballot_payload_clear.cbor,type=binary) - } + }, ] } choices: { requires: [ "clear-choices", - "elgamal-ristretto255-encrypted-choices" + "elgamal-ristretto255-encrypted-choices", ] def: """ [ 0, \(requires[0]) ] / @@ -79,7 +79,7 @@ cddlDefinitions: { "clear-choices": { requires: [ - "clear-choice" + "clear-choice", ] def: """ ( +clear-choice ) @@ -95,7 +95,6 @@ cddlDefinitions: { """ } - "clear-choice": { requires: [] def: """ @@ -115,7 +114,7 @@ cddlDefinitions: { "elgamal-ristretto255-encrypted-choices": { requires: [ "elgamal-ristretto255-encrypted-choice", - "row-proof" + "row-proof", ] def: """ ( @@ -142,10 +141,9 @@ cddlDefinitions: { """ } - "elgamal-ristretto255-encrypted-choice": { requires: [ - "elgamal-ristretto255-group-element" + "elgamal-ristretto255-group-element", ] def: """ [ @@ -174,10 +172,9 @@ cddlDefinitions: { """ } - "row-proof": { requires: [ - "zkproof-elgamal-ristretto255-unit-vector-with-single-selection" + "zkproof-elgamal-ristretto255-unit-vector-with-single-selection", ] def: """ [0, \(requires[0]) ] @@ -195,7 +192,7 @@ cddlDefinitions: { "zkproof-elgamal-ristretto255-unit-vector-with-single-selection": { requires: [ "zkproof-elgamal-ristretto255-unit-vector-with-single-selection-item", - "zkproof-ed25519-scalar" + "zkproof-ed25519-scalar", ] def: """ ( [ [ +\(requires[0]) ], \(requires[1]) ) @@ -209,7 +206,7 @@ cddlDefinitions: { requires: [ "zkproof-elgamal-announcement", "elgamal-ristretto255-encrypted-choice", - "zkproof-ed25519-r-response" + "zkproof-ed25519-r-response", ] def: """ ( \(requires[0]), ~\(requires[1]), \(requires[2]) ) @@ -219,7 +216,6 @@ cddlDefinitions: { """ } - "zkproof-elgamal-announcement": { requires: ["zkproof-elgamal-group-element"] def: """ @@ -230,7 +226,6 @@ cddlDefinitions: { """ } - "zkproof-elgamal-group-element": { requires: [] def: """ @@ -369,5 +364,4 @@ cddlDefinitions: { """ } - } diff --git a/specs/definitions/cddl/defs.cue b/specs/definitions/cddl/defs.cue index c291b957dec..85fe325b57b 100644 --- a/specs/definitions/cddl/defs.cue +++ b/specs/definitions/cddl/defs.cue @@ -5,7 +5,7 @@ package cddl import ( Eg "github.com/input-output-hk/catalyst-libs/specs/generic:examples" -) +) // List of cddl definitions, cddl_type_name: cddl_definition #cddlDefinitions: { @@ -14,7 +14,7 @@ import ( requires: [...#cddlTypesConstraint] | *[] description?: string // Description - multiline comment?: string // Single line comments are displayed after a definition. Multiline comments, before. - examples?: Eg.#list + examples?: Eg.#list } } diff --git a/specs/definitions/generic/optional.cue b/specs/definitions/generic/optional.cue index adfba352c03..fb28777cbed 100644 --- a/specs/definitions/generic/optional.cue +++ b/specs/definitions/generic/optional.cue @@ -6,6 +6,6 @@ package optional "excluded" // Is a field Required, Optional or Excluded/Unused -#field_default_yes: #field | *"yes" +#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 04c25e15ab7..81dc64b464c 100644 --- a/specs/definitions/media_types/content.cue +++ b/specs/definitions/media_types/content.cue @@ -128,7 +128,6 @@ 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}, diff --git a/specs/definitions/regex/def.cue b/specs/definitions/regex/def.cue index c30296cbece..5c582a1152d 100644 --- a/specs/definitions/regex/def.cue +++ b/specs/definitions/regex/def.cue @@ -115,7 +115,6 @@ 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 diff --git a/specs/definitions/signed_docs/cose_headers.cue b/specs/definitions/signed_docs/cose_headers.cue index 5fa89e9d95e..742bda6c7fe 100644 --- a/specs/definitions/signed_docs/cose_headers.cue +++ b/specs/definitions/signed_docs/cose_headers.cue @@ -51,7 +51,7 @@ cose: headerFormats: #metadataFormats & { coseLabel: int | string description: string format: #coseHeaderTypesConstraint - required: optional.#field_default_yes + required: optional.#field_default_yes if required != "excluded" { if format == "Media Type" { diff --git a/specs/definitions/signed_docs/docs/contest_delegation.cue b/specs/definitions/signed_docs/docs/contest_delegation.cue index dd057f78fad..7f95dc1a30e 100644 --- a/specs/definitions/signed_docs/docs/contest_delegation.cue +++ b/specs/definitions/signed_docs/docs/contest_delegation.cue @@ -107,27 +107,27 @@ docs: "Contest Delegation": { headers: "content type": value: "application/json" payload: { - description: """ - The Payload is a JSON Document, and must conform to this schema. + 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. + 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") - } - ] + 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: [ diff --git a/specs/definitions/signed_docs/docs/proposal_comment.cue b/specs/definitions/signed_docs/docs/proposal_comment.cue index b916da39c4b..92ea3dd6f56 100644 --- a/specs/definitions/signed_docs/docs/proposal_comment.cue +++ b/specs/definitions/signed_docs/docs/proposal_comment.cue @@ -6,7 +6,6 @@ import ( // Proposal Document Definition - // TODO: Proposers that sign this are proposer to proposer comments for collaboration. docs: #DocumentDefinitions & { diff --git a/specs/definitions/signed_docs/payload.cue b/specs/definitions/signed_docs/payload.cue index eb399b0736e..df93e901ebf 100644 --- a/specs/definitions/signed_docs/payload.cue +++ b/specs/definitions/signed_docs/payload.cue @@ -17,27 +17,28 @@ import ( nil: true | *false } - // Payload definition #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 + schema?: _ | =~regex.def.httpsUrl.pattern // Examples of the schema. examples?: Eg.#list } // Payload definition for cbor payloads -#payload_cbor: { +#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 565cc95ef4e..f2be40bb985 100644 --- a/specs/definitions/signed_docs/signed_doc.cue +++ b/specs/definitions/signed_docs/signed_doc.cue @@ -103,4 +103,4 @@ presentationTemplate: { schema: presentation_template.presentationTemplate } -cddlDefinitions: cddl.cddlDefinitions \ No newline at end of file +cddlDefinitions: cddl.cddlDefinitions From 1f5d56796256b481ed91415216e0c6d59f328834 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Thu, 11 Sep 2025 22:37:56 +0700 Subject: [PATCH 07/12] fix(docs): markdown --- .../signed_doc/docs/contest_delegation.md | 18 +++++++++++------- .../signed_docs/docs/contest_delegation.cue | 16 ++++++++++------ specs/signed_doc.json | 2 +- 3 files changed, 22 insertions(+), 14 deletions(-) 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 9ab393a2c70..ea64ea29388 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 @@ -37,20 +37,24 @@ 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. + 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. +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. +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. +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. @@ -66,7 +70,7 @@ division its applied to the representative with the highest priority. * 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 + * 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. diff --git a/specs/definitions/signed_docs/docs/contest_delegation.cue b/specs/definitions/signed_docs/docs/contest_delegation.cue index 7f95dc1a30e..8cfc1b37c11 100644 --- a/specs/definitions/signed_docs/docs/contest_delegation.cue +++ b/specs/definitions/signed_docs/docs/contest_delegation.cue @@ -39,20 +39,24 @@ docs: "Contest 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. + 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. + 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. + 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. + 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 diff --git a/specs/signed_doc.json b/specs/signed_doc.json index 6b4b42ce036..18d94e52dc7 100644 --- a/specs/signed_doc.json +++ b/specs/signed_doc.json @@ -1436,7 +1436,7 @@ "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 (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. 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. Voting 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. Voting power is divided based on the weight of a single \ndelegate over the sum of all weights 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. And if there is any residual after integer\ndivision its applied to the representative with the highest priority.", + "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": { From d69558ac1b05ccbe865b232acb960c196e011db0 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Fri, 12 Sep 2025 18:27:53 +0700 Subject: [PATCH 08/12] fix(docs): regenerated --- .../08_concepts/signed_doc/docs/contest_delegation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ea64ea29388..9ed7d15b244 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 @@ -70,7 +70,7 @@ with the highest priority. * 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 + * 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. From 2ee389a437e07e84cd16f5e0a7cdfeafd57bed50 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Fri, 12 Sep 2025 18:28:21 +0700 Subject: [PATCH 09/12] fix(docs): make the check target work properly after `cue vet` broke --- specs/Earthfile | 22 +++++++++++++++++----- specs/Justfile | 2 +- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/specs/Earthfile b/specs/Earthfile index 625fa8cd6cf..deeb6005c87 100644 --- a/specs/Earthfile +++ b/specs/Earthfile @@ -23,13 +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 vet -Evc -s ./signed_docs/docs:signed_docs && \ - 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 @@ -64,8 +71,13 @@ check: 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 From 4e8ac979e6c6c40634effb3073110ec6ea9edf51 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Fri, 12 Sep 2025 21:18:03 +0700 Subject: [PATCH 10/12] fix(docs): markdown --- .../08_concepts/signed_doc/docs/contest_delegation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9ed7d15b244..ea64ea29388 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 @@ -70,7 +70,7 @@ with the highest priority. * 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 + * 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. From 3d21ca22a0e3bdea2734a98d4dc71d6e1899ed1b Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Fri, 12 Sep 2025 22:12:14 +0700 Subject: [PATCH 11/12] fix(docs): fix format --- .../signed_doc/docs/contest_delegation.md | 12 ++++++------ .../signed_docs/docs/contest_delegation.cue | 12 ++++++------ specs/signed_doc.json | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) 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 ea64ea29388..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 @@ -70,12 +70,12 @@ with the highest priority. * 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. + 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, diff --git a/specs/definitions/signed_docs/docs/contest_delegation.cue b/specs/definitions/signed_docs/docs/contest_delegation.cue index 8cfc1b37c11..9aa1b054986 100644 --- a/specs/definitions/signed_docs/docs/contest_delegation.cue +++ b/specs/definitions/signed_docs/docs/contest_delegation.cue @@ -62,12 +62,12 @@ docs: "Contest Delegation": { * 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. + 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, diff --git a/specs/signed_doc.json b/specs/signed_doc.json index 18d94e52dc7..190f90d6f5d 100644 --- a/specs/signed_doc.json +++ b/specs/signed_doc.json @@ -1593,7 +1593,7 @@ } }, "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 * IF there are multiple representatives, then any which are not pointing\n\t to a valid `Representative Nomination` are excluded. \n\t The nomination is only invalid if ALL references `Representative Nomination` \n\t references are invalid.\n\t This is to prevent a Representative changing their nomination invalidating a\n\t 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.", + "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", From 62cad47f7ff1dae3ae5a5405b36a1dfcda3057a8 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Wed, 24 Sep 2025 18:37:10 +0700 Subject: [PATCH 12/12] feat(docs): wip --- .../aes256-document-encryption-key-derivation.md.jinja | 6 +++--- .../ed25519-document-signing-key-derivation.md.jinja | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) 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 index 438483d1283..6e8e8e25cdc 100644 --- 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 @@ -67,8 +67,8 @@ 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) +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. @@ -100,7 +100,7 @@ Working Implementation before Fund 14. ### Implementation Plan -Fund 14 project catalyst will deploy this scheme for Key derivation.> +Fund 16 project catalyst will deploy this scheme for Key derivation. ## Copyright 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 index 0293b1033a1..05ffccce3dd 100644 --- 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 @@ -74,7 +74,7 @@ Working Implementation before Fund 14. ### Implementation Plan -Fund 14 project catalyst will deploy this scheme for Key derivation.> +Fund 14 project catalyst deployed this scheme for Key derivation.> ## Copyright
parametersItems string
Content Media Typetext/plain
Exampleenum: ["option 1", "option 2", "option 3"]enum: ["option 1", "option 2", "option 3"]
title
The label attached to the field.
Requiredyesyes
Typestringstring
Content Media Typetext/plaintext/plain
Exampletitle: "Selector"title: "Selector"
x-guidance
Long form Markdown formatted description to give guidance about how the field is to be completed.
Requiredoptionaloptional
Typestringstring
Content Media Typetext/markdown; template=handlebarstext/markdown; template=handlebars
Examplex-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.
Requiredoptionaloptional
Typestringstring
ChoicesIconsIcons
Examplex-icon: "emoji-happy"x-icon: "emoji-happy"
Items string
Content Media Typetext/plain
Exampleenum: ["Hot FM", "AM Stereo (but not really)", "Silence"]enum: ["Hot FM", "AM Stereo (but not really)", "Silence"]
title
The label attached to the field.
Requiredyesyes
Typestringstring
Content Media Typetext/plaintext/plain
Exampletitle: "Radio Selector"title: "Radio Selector"
x-guidance
Long form Markdown formatted description to give guidance about how the field is to be completed.
Requiredoptionaloptional
Typestringstring
Content Media Typetext/markdown; template=handlebarstext/markdown; template=handlebars
Examplex-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.
Requiredoptionaloptional
Typestringstring
ChoicesIconsIcons
Examplex-icon: "bottom-rail-toggle"x-icon: "bottom-rail-toggle"
Requiredexcludedyes
Type
Itemsoptional_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.
Items string
Content Media Typetext/plain
Exampleenum: ["option 1", "option 2", "option 3"]enum: ["option 1", "option 2", "option 3"]
title
The label attached to the field.
Requiredyesyes
Typestringstring
Content Media Typetext/plaintext/plain
Exampletitle: "Single Selector"title: "Single Selector"
x-guidance
Long form Markdown formatted description to give guidance about how the field is to be completed.
Requiredoptionaloptional
Typestringstring
Content Media Typetext/markdown; template=handlebarstext/markdown; template=handlebars
Examplex-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.
Requiredoptionaloptional
Typestringstring
ChoicesIconsIcons
Examplex-icon: "emoji-happy"x-icon: "emoji-happy"
Requiredoptionalyes Is the field required?