This specification describes a Data Integrity Cryptosuite for use when generating a digital signature using the Elliptic Curve Digital Signature Algorithm (ECDSA).
The Working Group is actively seeking implementation feedback for this specification. In order to exit the Candidate Recommendation phase, the Working Group has set the requirement of at least two independent implementations for each mandatory feature in the specification. For details on the conformance testing process, see the test suites listed in the implementation report.
This specification defines a cryptographic suite for the purpose of creating, and verifying proofs for ECDSA signatures in conformance with the Data Integrity [[VC-DATA-INTEGRITY]] specification. ECDSA signatures are specified in [[FIPS-186-5]] with elliptic curves P-256 and P-384 specified in [[NIST-SP-800-186]]. [[FIPS-186-5]] includes the deterministic ECDSA algorithm which is also specified in [[RFC6979]].
This specification uses either the RDF Dataset Canonicalization Algorithm [[RDF-CANON]] or the JSON Canonicalization Scheme [[RFC8785]] to transform the input document into its canonical form. It uses one of two mechanisms to digest and sign: SHA-256 [[RFC6234]] as the message digest algorithm and ECDSA with Curve P-256 as the signature algorithm, or SHA-384 [[RFC6234]] as the message digest algorithm and ECDSA with Curve P-384 as the signature algorithm.
The elliptic curves P-256 and P-384 of [[NIST-SP-800-186]] are referred to as secp256r1 and secp384r1 respectively in [[SECG2]]. In addition, this notation is sometimes used in ECDSA software libraries.
Terminology used throughout this document is defined in the Terminology section of the [[[VC-DATA-INTEGRITY]]] specification.
A conforming proof is any concrete expression of the data model that complies with the normative statements in this specification. Specifically, all relevant normative statements in Sections [[[#data-model]]] and [[[#algorithms]]] of this document MUST be enforced.
A conforming processor is any algorithm realized as software and/or hardware that generates or consumes a [=conforming proof=]. Conforming processors MUST produce errors when non-conforming documents are consumed.
This document contains examples of JSON and JSON-LD data. Some of these examples are invalid JSON, as they include features such as inline comments (`//`) explaining certain portions and ellipses (`...`) indicating the omission of information that is irrelevant to the example. Such parts need to be removed if implementers want to treat the examples as valid JSON or JSON-LD.
The following sections outline the data model that is used by this specification to express verification methods, such as cryptographic public keys, and data integrity proofs, such as digital signatures.
These verification methods are used to verify Data Integrity Proofs [[VC-DATA-INTEGRITY]] produced using Elliptic Curve cryptographic key material that is compliant with [[FIPS-186-5]]. The encoding formats for these key types are provided in this section. Lossless cryptographic key transformation processes that result in equivalent cryptographic key material MAY be used during the processing of digital signatures.
The Multikey format, as defined in [[VC-DATA-INTEGRITY]], is used to express public keys for the cryptographic suites defined in this specification.
The `publicKeyMultibase` property represents a Multibase-encoded Multikey expression of a P-256 or P-384 public key.
The Multikey encoding of a P-256 public key MUST start with the two-byte prefix `0x8024` (the varint expression of `0x1200`) followed by the 33-byte compressed public key data. The resulting 35-byte value MUST then be encoded using the base-58-btc alphabet, according to the Multibase section in the [[VC-DATA-INTEGRITY]] specification, and then prepended with the base-58-btc Multibase header (`z`).
The encoding of a P-384 public key MUST start with the two-byte prefix `0x8124` (the varint expression of `0x1201`) followed by the 49-byte compressed public key data. The resulting 51-byte value is then encoded using the base-58-btc alphabet, according to the Multibase section in the [[VC-DATA-INTEGRITY]] specification, and then prepended with the base-58-btc Multibase header (`z`). Any other encodings MUST NOT be allowed.
Developers are advised to not accidentally publish a representation of a private key. Implementations of this specification will raise errors in the event of a Multicodec value other than `0x1200` or `0x1201` being used in a `publicKeyMultibase` value.
{ "id": "https://example.com/issuer/123#key-0", "type": "Multikey", "controller": "https://example.com/issuer/123", "publicKeyMultibase": "zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv" }
{ "id": "https://example.com/issuer/123#key-0", "type": "Multikey", "controller": "https://example.com/issuer/123", "publicKeyMultibase": "z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJ Uz2sG9FE42shbn2xkZJh54" }
{ "@context": [ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/multikey/v1" ], "id": "did:example:123", "verificationMethod": [{ "id": "https://example.com/issuer/123#key-1", "type": "Multikey", "controller": "https://example.com/issuer/123", "publicKeyMultibase": "zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv" }, { "id": "https://example.com/issuer/123#key-2", "type": "Multikey", "controller": "https://example.com/issuer/123", "publicKeyMultibase": "z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJ Uz2sG9FE42shbn2xkZJh54" }], "authentication": [ "did:example:123#key-1" ], "assertionMethod": [ "did:example:123#key-2" ], "capabilityDelegation": [ "did:example:123#key-2" ], "capabilityInvocation": [ "did:example:123#key-2" ] }
The `secretKeyMultibase` property represents a Multibase-encoded Multikey expression of a P-256 or P-384 secret key (also sometimes referred to as a private key).
The encoding of a P-256 secret key MUST start with the two-byte prefix `0x8626` (the varint expression of `0x1306`) followed by the 32-byte secret key data. The 34-byte value MUST then be encoded using the base-58-btc alphabet, according to the Multibase section in the [[VC-DATA-INTEGRITY]] specification, and then prepended with the base-58-btc Multibase header (`z`). Any other encodings MUST NOT be allowed.
The encoding of a P-384 secret key is the two-byte prefix `0x8726` (the varint expression of `0x1307`) followed by the 48-byte secret key data. The 50-byte value MUST then be encoded using the base-58-btc alphabet, according to the Multibase section in the [[VC-DATA-INTEGRITY]] specification, and then prepended with the base-58-btc Multibase header (`z`). Any other encodings MUST NOT be allowed.
Developers are advised to prevent accidental publication of a representation of a secret key, and to not export the `secretKeyMultibase` property by default, when serializing key pairs as Multikey.
This section details the proof representation formats that are defined by this specification.
A proof contains the attributes specified in the Proofs section of [[VC-DATA-INTEGRITY]] with the following restrictions.
The `type` property of the proof MUST be `DataIntegrityProof`.
The `cryptosuite` property of the proof MUST be `ecdsa-rdfc-2019` or `ecdsa-jcs-2019`.
The value of the `proofValue` property of the proof MUST be an ECDSA signature produced according to [[FIPS-186-5]] and SHOULD use the deterministic ECDSA signature variant, produced according to [[FIPS-186-5]] using the curves and hashes as specified in section [[[#algorithms]]], encoded according to section 7 of [[RFC4754]] (sometimes referred to as the IEEE P1363 format), and encoded using the base-58-btc header and alphabet as described in the Multibase section of [[VC-DATA-INTEGRITY]].
{ "@context": [ {"myWebsite": "https://vocabulary.example/myWebsite"}, "https://www.w3.org/ns/credentials/v2" ], "myWebsite": "https://hello.world.example/", "proof": { "type": "DataIntegrityProof", "cryptosuite": "ecdsa-rdfc-2019", "created": "2023-02-24T23:36:38Z", "verificationMethod": "https://vc.example/issuers/5678#zDnaepBuvsQ8cpsWrVKw8 fbpGpvPeNSjVPTWoq6cRqaYzBKVP", "proofPurpose": "assertionMethod", "proofValue": "z2iAR3F2Sk3mWfYyrinKzSQpSbvfxnz9kkv7roxxumB5RZDP9JUw5QAXuchUd huiwE18hyyZTjiEreKmhH3oj9Q8" } }
The following section describes multiple Data Integrity cryptographic suites that utilize the Elliptic Curve Digital Signature Algorithm (ECDSA) [[FIPS-186-5]]. When generating ECDSA signatures, the deterministic ECDSA algorithm variant SHOULD be used.
Implementations SHOULD fetch and cache verification method information as early as possible when adding or verifying proofs. Parameters passed to functions in this section use information from the verification method — such as the public key size — to determine function parameters — such as the cryptographic hashing algorithm.
When the RDF Dataset Canonicalization Algorithm [[RDF-CANON]] is used with ECDSA algorithms, the cryptographic hashing function that is passed to the algorithm MUST be determined by the size of the associated public key. For P-256 keys, SHA-2 with 256 bits of output is utilized. For P-384 keys, SHA-2 with 384-bits of output is utilized.
When the RDF Dataset Canonicalization Algorithm [[RDF-CANON]] is used, implementations of that algorithm will detect dataset poisoning by default, and abort processing upon detection.
This algorithm is used to configure a cryptographic suite to be used by the Add Proof and Verify Proof functions in [[[VC-DATA-INTEGRITY]]]. The algorithm takes an options object ([=map=] |options|) as input and returns a [=data integrity cryptographic suite instance|cryptosuite instance=] ([=struct=] |cryptosuite|).
The `ecdsa-rdfc-2019` cryptographic suite takes an input document, canonicalizes the document using the Universal RDF Dataset Canonicalization Algorithm [[RDF-CANON]], and then cryptographically hashes and signs the output resulting in the production of a data integrity proof. The algorithms in this section also include the verification of such a data integrity proof.
The following algorithm specifies how to create a [=data integrity proof=] given an unsecured data document. Required inputs are an unsecured data document ([=map=] |unsecuredDocument|), and a set of proof options ([=map=] |options|). A [=data integrity proof=] ([=map=]), or an error, is produced as output.
The following algorithm specifies how to verify a [=data integrity proof=] given an secured data document. Required inputs are an secured data document ([=map=] |securedDocument|). This algorithm returns a verification result, which is a [=struct=] whose [=struct/items=] are:
The following algorithm specifies how to transform an unsecured input document into a transformed document that is ready to be provided as input to the hashing algorithm in Section [[[#hashing-ecdsa-rdfc-2019]]].
Required inputs to this algorithm are an unsecured data document (|unsecuredDocument|) and transformation options (|options|). The transformation options MUST contain a type identifier for the cryptographic suite (|type|) and a cryptosuite identifier (|cryptosuite|). A transformed data document is produced as output. Whenever this algorithm encodes strings, it MUST use UTF-8 encoding.
The following algorithm specifies how to cryptographically hash a transformed data document and proof configuration into cryptographic hash data that is ready to be provided as input to the algorithms in Section [[[#proof-serialization-ecdsa-rdfc-2019]]] or Section [[[#proof-verification-ecdsa-rdfc-2019]]]. One must use the hash algorithm appropriate in security level to the curve used, i.e., for curve P-256 one uses SHA-256 and for curve P-384 one uses SHA-384.
The required inputs to this algorithm are a transformed data document (|transformedDocument|) and canonical proof configuration (|canonicalProofConfig|). A single hash data value represented as series of bytes is produced as output.
The following algorithm specifies how to generate a proof configuration from a set of proof options that is used as input to the proof hashing algorithm.
The required inputs to this algorithm are proof options (|options|). The proof options MUST contain a type identifier for the cryptographic suite (|type|) and MUST contain a cryptosuite identifier (|cryptosuite|). A proof configuration object is produced as output.
The following algorithm specifies how to serialize a digital signature from a set of cryptographic hash data. This algorithm is designed to be used in conjunction with the algorithms defined in the Data Integrity [[VC-DATA-INTEGRITY]] specification, Section 4: Algorithms. Required inputs are cryptographic hash data (|hashData|) and proof options (|options|). The proof options MUST contain a type identifier for the cryptographic suite (|type|) and MAY contain a cryptosuite identifier (|cryptosuite|). A single digital proof value represented as series of bytes is produced as output.
The following algorithm specifies how to verify a digital signature from a set of cryptographic hash data. This algorithm is designed to be used in conjunction with the algorithms defined in the Data Integrity [[VC-DATA-INTEGRITY]] specification, Section 4: Algorithms. Required inputs are cryptographic hash data (|hashData|), a digital signature (|proofBytes|) and proof options (|options|). A verification result represented as a boolean value is produced as output.
The `ecdsa-jcs-2019` cryptographic suite takes an input document, canonicalizes the document using the JSON Canonicalization Scheme [[RFC8785]], and then cryptographically hashes and signs the output resulting in the production of a data integrity proof. The algorithms in this section also include the verification of such a data integrity proof.
The following algorithm specifies how to create a [=data integrity proof=] given an unsecured data document. Required inputs are an unsecured data document ([=map=] |unsecuredDocument|), and a set of proof options ([=map=] |options|). A [=data integrity proof=] ([=map=]), or an error, is produced as output.
The following algorithm specifies how to verify a [=data integrity proof=] given an secured data document. Required inputs are an secured data document ([=map=] |securedDocument|). This algorithm returns a [=verification result=]:
The following algorithm specifies how to transform an unsecured input document into a transformed document that is ready to be provided as input to the hashing algorithm in Section [[[#hashing-ecdsa-jcs-2019]]].
Required inputs to this algorithm are an unsecured data document (|unsecuredDocument|) and transformation options (|options|). The transformation options MUST contain a type identifier for the cryptographic suite (|type|) and a cryptosuite identifier (|cryptosuite|). A transformed data document is produced as output. Whenever this algorithm encodes strings, it MUST use UTF-8 encoding.
The following algorithm specifies how to cryptographically hash a transformed data document and proof configuration into cryptographic hash data that is ready to be provided as input to the algorithms in Section [[[#proof-serialization-ecdsa-jcs-2019]]] or Section [[[#proof-verification-ecdsa-jcs-2019]]]. One must use the hash algorithm appropriate in security level to the curve used, i.e., for curve P-256 one uses SHA-256, and for curve P-384 one uses SHA-384.
The required inputs to this algorithm are a transformed data document (|transformedDocument|) and a canonical proof configuration (|canonicalProofConfig|). A single hash data value represented as series of bytes is produced as output.
The following algorithm specifies how to generate a proof configuration from a set of proof options that is used as input to the proof hashing algorithm.
The required inputs to this algorithm are proof options (|options|). The proof options MUST contain a type identifier for the cryptographic suite (|type|) and MUST contain a cryptosuite identifier (|cryptosuite|). A proof configuration object is produced as output.
The following algorithm specifies how to serialize a digital signature from a set of cryptographic hash data. This algorithm is designed to be used in conjunction with the algorithms defined in the Data Integrity [[VC-DATA-INTEGRITY]] specification, Section 4: Algorithms. Required inputs are cryptographic hash data (|hashData|) and proof options (|options|). The proof options MUST contain a type identifier for the cryptographic suite (|type|) and MAY contain a cryptosuite identifier (|cryptosuite|). A single digital proof value represented as series of bytes is produced as output.
The following algorithm specifies how to verify a digital signature from a set of cryptographic hash data. This algorithm is designed to be used in conjunction with the algorithms defined in the Data Integrity [[VC-DATA-INTEGRITY]] specification, Section 4: Algorithms. Required inputs are cryptographic hash data (|hashData|), a digital signature (|proofBytes|), and proof options (|options|). A verification result represented as a boolean value is produced as output.
The Working Group is seeking implementer feedback on these generalized selective disclosure functions as well as horizonal security review on the features from parties at W3C and IETF. Those reviews might result in significant changes to these functions, migration of these functions to the core Data Integrity specification (for use by other cryptographic suites), or the removal of the algorithm from the specification during the Candidate Recommendation phase.
The following section contains a set of functions that are used throughout cryptographic suites that perform selective disclosure.
The following algorithm canonicalizes an array of N-Quad strings and replaces any blank node identifiers in the canonicalized result using a label map factory function, |labelMapFactoryFunction|. The required inputs are an array of N-Quad strings (|nquads|), and a label map factory function (|labelMapFactoryFunction|). Any custom options can also be passed. An N-Quads representation of the canonicalNQuads as an array of N-Quad strings, with the replaced blank node labels, and a map from the old blank node IDs to the new blank node IDs, labelMap, is produced as output.
The following algorithm canonicalizes a JSON-LD document and replaces any blank node identifiers in the canonicalized result using a label map factory function, |labelMapFactoryFunction|. The required inputs are a JSON-LD document (|document|) and a label map factory function (|labelMapFactoryFunction|). Additional custom options (such as a document loader) can also be passed. An N-Quads representation of the canonicalNQuads as an array of N-Quad strings, with the replaced blank node labels, and a map from the old blank node IDs to the new blank node IDs, labelMap, is produced as output.
The following algorithm creates a label map factory function that uses an input label map to replace canonical blank node identifiers with another value. The required input is a label map, |labelMap|. A function, labelMapFactoryFunction, is produced as output.
The following algorithm creates a label map factory function that uses an HMAC to replace canonical blank node identifiers with their encoded HMAC digests. The required input is an HMAC (previously initialized with a secret key), |HMAC|. A function, labelMapFactoryFunction, is produced as output.
A different primitive could be created that returned a label map factory function that would instead sort the resulting HMAC digests and assign labels in the produced label map using a prefix and integers based on their sorted order. This primitive might be useful for selective disclosure schemes, such as BBS, that favor unlinkability over minimizing unrevealed data leakage.
The following algorithm replaces all blank node identifiers in an array of N-Quad strings with custom scheme URNs. The required inputs are an array of N-Quad strings (|inputNQuads|) and a URN scheme (|urnScheme|). An array of N-Quad strings, skolemizedNQuads, is produced as output. This operation is intended to be reversible through the use of the algorithm in Section [[[#deskolemizenquads]]].
s1.replace(/(_:([^\s]+))/g, '<urn:custom-scheme:$2>')
.
The following algorithm replaces all custom scheme URNs in an array of N-Quad statements with a blank node identifier. The required inputs are an array of N-Quad strings (|inputNQuads|) and a URN scheme (|urnScheme|). An array of N-Quad strings, deskolemizedNquads, is produced as output. This operation is intended to reverse use of the algorithm in Section [[[#deskolemizenquads]]].
s1.replace(/(<urn:custom-scheme:([^>]+)>)/g, '_:$2').
.
The following algorithm replaces all blank node identifiers in an expanded JSON-LD document with custom-scheme URNs, including assigning such URNs to blank nodes that are unlabeled. The required inputs are an expanded JSON-LD document (|expanded|), a custom URN scheme (|urnScheme|), a UUID string or other comparably random string (|randomString|), and reference to a shared integer (|count|). Any additional custom options (such as a document loader) can also be passed. It produces the expanded form of the skolemized JSON-LD document (|skolemizedExpandedDocument| as output. The skolemization used in this operation is intended to be reversible through the use of the algorithm in Section [[[#todeskolemizednquads]]].
The following algorithm replaces all blank node identifiers in a compact JSON-LD document with custom-scheme URNs. The required inputs are a compact JSON-LD document (|document|) and a custom URN scheme (|urnScheme|). The |document| is assumed to use only one @context property at the top level of the document. Any additional custom options (such as a document loader) can also be passed. It produces both an expanded form of the skolemized JSON-LD document (|skolemizedExpandedDocument| and a compact form of the skolemized JSON-LD document (|skolemizedCompactDocument|) as output. The skolemization used in this operation is intended to be reversible through the use of the algorithm in Section [[[#todeskolemizednquads]]].
The following algorithm converts a skolemized JSON-LD document, such as one created using the algorithm in Section [[[#skolemizecompactjsonld]]], to an array of deskolemized N-Quads. The required input is a JSON-LD document, skolemizedDocument. Additional custom options (such as a document loader) can be passed. An array of deskolemized N-Quad strings (|deskolemizedNQuads|) is produced as output.
The following algorithm converts a JSON Pointer [[RFC6901]] to an array of paths into a JSON tree. The required input is a JSON Pointer string (|pointer|). An array of paths (paths) is produced as output.
The following algorithm creates an initial selection (a fragment of a JSON-LD document) based on a JSON-LD object. This is a helper function used within the algorithm in Section [[[#selectjsonld]]]. The required input is a JSON-LD object (|source|). A JSON-LD document fragment object (|selection|) is produced as output.
The following algorithm selects a portion of a compact JSON-LD document using paths parsed from a parsed JSON Pointer. This is a helper function used within the algorithm in Section [[[#selectjsonld]]]. The required inputs are an array of paths (|paths|) parsed from a JSON Pointer, a compact JSON-LD document (|document|), a selection document (|selectionDocument|) to be populated, and an array of arrays (|arrays|) for tracking selected arrays. This algorithm produces no output; instead it populates the given |selectionDocument| with any values selected via |paths|.
The following algorithm selects a portion of a compact JSON-LD document using an array of JSON Pointers. The required inputs are an array of JSON Pointers (|pointers|) and a compact JSON-LD document (|document|). The |document| is assumed to use a JSON-LD context that aliases `@id` and `@type` to `id` and `type`, respectively, and to use only one `@context` property at the top level of the document. A new JSON-LD document that represents a selection (selectionDocument) of the original JSON-LD document is produced as output.
The following algorithm relabels the blank node identifiers in an array of N-Quad strings using a blank node label map. The required inputs are an array of N-Quad strings (|nquads|) and a blank node label map (|labelMap|). An array of N-Quad strings with relabeled blank node identifiers (|relabeledNQuads|) is produced as output.
The following algorithm selects a portion of a skolemized compact JSON-LD document using an array of JSON Pointers, and outputs the resulting canonical N-Quads with any blank node labels replaced using the given label map. The required inputs are an array of JSON Pointers (|pointers|), a skolemized compact JSON-LD document (|skolemizedCompactDocument|), and a blank node label map (|labelMap|). Additional custom options (such as a document loader) can be passed. The |document| is assumed to use a JSON-LD context that aliases `@id` and `@type` to `id` and `type`, respectively, and to use only one `@context` property at the top level of the document. An object containing the new JSON-LD document that represents a selection of the original JSON-LD document (|selectionDocument|), an array of deskolemized N-Quad strings (|deskolemizedNQuads|), and an array of canonical N-Quads with replacement blank node labels (|nquads|) is produced as output.
The following algorithm is used to output canonical N-Quad strings that match custom selections of a compact JSON-LD document. It does this by canonicalizing a compact JSON-LD document (replacing any blank node identifiers using a label map) and grouping the resulting canonical N-Quad strings according to the selection associated with each group. Each group will be defined using an assigned name and array of JSON pointers. The JSON pointers will be used to select portions of the skolemized document, such that the output can be converted to canonical N-Quads to perform group matching.
The required inputs are a compact JSON-LD document (|document|), a label map factory function (|labelMapFactoryFunction|), and a map of named group definitions (|groupDefinitions|). Additional custom options (such as a document loader) can be passed. The |document| is assumed to use a JSON-LD context that aliases `@id` and `@type` to `id` and `type`, respectively, and to use only one `@context` property at the top level of the document. An object containing the created groups (|groups|), the skolemized compact JSON-LD document (|skolemizedCompactDocument|), the skolemized expanded JSON-LD document (|skolemizedExpandedDocument|), the deskolemized N-Quad strings (|deskolemizedNQuads|), the blank node label map (|labelMap|), and the canonical N-Quad strings |nquads|, is produced as output.
The following algorithm cryptographically hashes an array of mandatory to disclose N-Quads using a provided hashing API. The required input is an array of mandatory to disclose N-Quads (|mandatory|) and a hashing function (|hasher|). A cryptographic hash (mandatoryHash) is produced as output.
The Working Group is seeking implementer feedback on these cryptographic suite functions as well as horizonal security review on the feature from parties at W3C and IETF. Those reviews might result in significant changes to these algorithms, or the removal of the algorithms from the specification during the Candidate Recommendation phase.
This section contains subalgorithms that are useful to the `ecdsa-sd-2023` cryptographic suite.
The following algorithm serializes the data that is to be signed by the private key associated with the base proof verification method. The required inputs are the proof options hash (|proofHash|), the proof-scoped multikey-encoded public key (|publicKey|), and the mandatory hash (|mandatoryHash|). A single sign data value, represented as series of bytes, is produced as output.
The following algorithm serializes the base proof value, including the base signature, public key, HMAC key, signatures, and mandatory pointers. The required inputs are a base signature |baseSignature|, a public key |publicKey|, an HMAC key |hmacKey|, an array of |signatures|, and an array of |mandatoryPointers|. A single base proof string value is produced as output.
The following algorithm parses the components of an `ecdsa-sd-2023` selective disclosure base proof value. The required inputs are a proof value (|proofValue|). A single object parsed base proof, containing five elements, using the names `baseSignature`, `publicKey`, `hmacKey`, `signatures`, and `mandatoryPointers`, is produced as output.
The following algorithm creates data to be used to generate a derived proof. The inputs include a JSON-LD document (|document|), an ECDSA-SD base proof (|proof|), an array of JSON pointers to use to selectively disclose statements (|selectivePointers|), and any custom JSON-LD API options, such as a document loader). A single object, disclosure data, is produced as output, which contains the "baseSignature", "publicKey", "signatures" for "filteredSignatures", "labelMap", "mandatoryIndexes", and "revealDocument" fields.
The following algorithm compresses a label map. The required inputs are label map (|labelMap|). The output is a compressed label map.
The following algorithm decompresses a label map. The required input is a compressed label map (|compressedLabelMap|). The output is a decompressed label map.
The following algorithm serializes a derived proof value. The required inputs are a base signature (|baseSignature|), public key (|publicKey|), an array of signatures (|signatures|), a label map (|labelMap|), and an array of mandatory indexes (|mandatoryIndexes|). A single derived proof value, serialized as a byte string, is produced as output.
The following algorithm parses the components of the derived proof value. The required inputs are a derived proof value (|proofValue|). A A single derived proof value value object is produced as output, which contains a set to five elements, using the names "baseSignature", "publicKey", "signatures", "labelMap", and "mandatoryIndexes".
The following algorithm creates the data needed to perform verification of an ECDSA-SD-protected [=verifiable credential=]. The inputs include a JSON-LD document (|document|), an ECDSA-SD disclosure proof (|proof|), and any custom JSON-LD API options, such as a document loader. A single verify data object value is produced as output containing the following fields: "baseSignature", "proofHash", "publicKey", "signatures", "nonMandatory", and "mandatoryHash".
The Working Group is seeking implementer feedback on this cryptographic suite as well as horizonal security review on the feature from parties at W3C and IETF. Those reviews might result in significant changes to this algorithm, or the removal of the algorithm from the specification during the Candidate Recommendation phase.
The `ecdsa-sd-2023` cryptographic suite takes an input document, canonicalizes the document using the Universal RDF Dataset Canonicalization Algorithm [[RDF-CANON]], and then cryptographically hashes and signs the output resulting in the production of a data integrity proof. The algorithms in this section also include the verification of such a data integrity proof.
The following algorithm specifies how to create a [=data integrity proof=] given an unsecured data document. Required inputs are an unsecured data document ([=map=] |unsecuredDocument|), and a set of proof options ([=map=] |options|). A [=data integrity proof=] ([=map=]), or an error, is produced as output.
The following algorithm specifies how to transform an unsecured input document into a transformed document that is ready to be provided as input to the hashing algorithm in Section [[[#base-proof-hashing-ecdsa-sd-2023]]].
Required inputs to this algorithm are an unsecured data document (|unsecuredDocument|) and transformation options (|options|). The transformation options MUST contain a type identifier for the cryptographic suite (|type|), a cryptosuite identifier (|cryptosuite|), and a verification method (|verificationMethod|). The transformation options MUST contain an array of mandatory JSON pointers (|mandatoryPointers|) and MAY contain additional options, such as a JSON-LD document loader. A transformed data document is produced as output. Whenever this algorithm encodes strings, it MUST use UTF-8 encoding.
The following algorithm specifies how to cryptographically hash a transformed data document and proof configuration into cryptographic hash data that is ready to be provided as input to the algorithms in Section [[[#base-proof-serialization-ecdsa-sd-2023]]].
The required inputs to this algorithm are a transformed data document (|transformedDocument|) and canonical proof configuration (|canonicalProofConfig|). A hash data value represented as an object is produced as output.
The following algorithm specifies how to generate a proof configuration from a set of proof options that is used as input to the base proof hashing algorithm.
The required inputs to this algorithm are proof options (|options|). The proof options MUST contain a type identifier for the cryptographic suite (|type|) and MUST contain a cryptosuite identifier (|cryptosuite|). A proof configuration object is produced as output.
The following algorithm specifies how to create a base proof; called by an issuer of an ECDSA-SD-protected Verifiable Credential. The base proof is to be given only to the holder, who is responsible for generating a derived proof from it, exposing only selectively disclosed details in the proof to a verifier. This algorithm is designed to be used in conjunction with the algorithms defined in the Data Integrity [[VC-DATA-INTEGRITY]] specification, Section 4: Algorithms. Required inputs are cryptographic hash data (|hashData|) and proof options (|options|). The proof options MUST contain a type identifier for the cryptographic suite (|type|) and MAY contain a cryptosuite identifier (|cryptosuite|). A single digital proof value represented as series of bytes is produced as output.
The following algorithm creates a selective disclosure derived proof; called by a holder of an `ecdsa-sd-2023`-protected [=verifiable credential=]. The derived proof is to be given to the [=verifier=]. The inputs include a JSON-LD document (|document|), an ECDSA-SD base proof (|proof|), an array of JSON pointers to use to selectively disclose statements (|selectivePointers|), and any custom JSON-LD API options, such as a document loader. A single selectively revealed document value, represented as an object, is produced as output.
The following algorithm attempts verification of an `ecdsa-sd-2023` derived proof. This algorithm is called by a verifier of an ECDSA-SD-protected [=verifiable credential=]. The inputs include a JSON-LD document (|document|), an ECDSA-SD disclosure proof (|proof|), and any custom JSON-LD API options, such as a document loader. This algorithm returns a [=verification result=]:
Before reading this section, readers are urged to familiarize themselves with general security advice provided in the Security Considerations section of the Data Integrity specification.
The integrity and authenticity of a secured document that is protected by this cryptographic suite is dependent on a number of factors including the following:
In the following sections, we review these important points and direct the reader to additional information.
The ECDSA signature scheme has the EUF-CMA (existential unforgeability under chosen message attacks) security property. This property guarantees that any efficient adversary who has the public key pk of the signer and received an arbitrary number of signatures on messages of its choice (in an adaptive manner) cannot output a valid signature for a new message (except with negligible probability).
SUF-CMA (strong unforgeability under chosen message attacks) is a stronger notion than EUF-CMA. It guarantees that for any efficient adversary who has the public key pk of the signer and received an arbitrary number of signatures on messages of its choice, it cannot output a new valid signature pair for a new message nor a new signature for an old message (except with negligible probability). ECDSA signature scheme does not have the SUF-CMA property, while other schemes such as EdDSA [[FIPS-186-5]] do.
Per [[NIST-SP-800-57-Part-1]] in the absence of large scale quantum computers a security strength level of 128 bits requires a key size of approximately 256 bits while a security strength level of 192 bits requires a key size of 384 bits. [[NIST-SP-800-186]] recommendations includes curves P-256 and P-384 at these respective security strength levels.
The ECDSA algorithm as detailed in [[FIPS-186-5]] states: "A new secret random number k, 0 < k < n, shall be generated prior to the generation of each digital signature for use during the signature generation process." The failure to properly generate this k value has lead to some highly publicized integrity breaches in widely deployed systems. To counter this problem, a hash-based method of determining the secret number k, called deterministic ECDSA, is given in [[FIPS-186-5]] and [[RFC6979]].
Verification of a ECDSA signature is independent of the method of generating k. Hence it is generally recommended to use deterministic ECDSA unless other requirements dictate otherwise. For example, using different k values results in different signature values for the same document which might be a desirable property in some privacy enhancing situations.
The security of the ECDSA algorithm is dependent on the quality and protection of its private signing key. Guidance in the management of cryptographic keys is a large subject and the reader is referred to [[NIST-SP-800-57-Part-1]] for more extensive recommendations and discussion. As strongly recommended in both [[FIPS-186-5]] and [[NIST-SP-800-57-Part-1]], an ECDSA private signing key is not to be used for any other purpose than ECDSA signatures.
ECDSA private signing keys and public verification keys are strongly advised to have limited cryptoperiods [[NIST-SP-800-57-Part-1]], where a cryptoperiod is "the time span during which a specific key is authorized for use by legitimate entities or the keys for a given system will remain in effect." [[NIST-SP-800-57-Part-1]] gives extensive guidance on cryptoperiods for different key types under different situations and generally recommends a 1-3 year cryptoperiod for a private signing key.
To deal with potential private key compromises, [[NIST-SP-800-57-Part-1]] gives recommendations for protective measures, harm reduction, and revocation. Although we have been emphasizing the security of the private signing key, assurance of public key validity is highly recommended on all public keys before using them, per [[NIST-SP-800-57-Part-1]].
Ensuring that cryptographic suites are versioned and tightly scoped to a very small set of possible key types and signature schemes (ideally one key type and size and one signature output type) is a design goal for most Data Integrity cryptographic suites. Historically, this has been done by defining both the key type and the cryptographic suite that uses the key type in the same specification. The downside of doing so, however, is that there might be a proliferation of different key types in multikey that result in different cryptosuites defining the same key material differently. For example, one cryptosuite might use compressed Curve P-256 keys while another uses uncompressed values. If that occurs, it will harm interoperability. It will be important in the coming months to years to ensure that this does not happen by fully defining the multikey format in a separate specification so cryptosuite specifications, such as this one, can refer to the multikey specification, thus reducing the chances of multikey type proliferation and improving the chances of maximum interoperability for the multikey format.
Before reading this section, readers are urged to familiarize themselves with general privacy advice provided in the Privacy Considerations section of the Data Integrity specification.
The following section describes privacy considerations that developers implementing this specification should be aware of in order to avoid violating privacy assumptions.
All test vectors are produced using deterministic ECDSA. The implementation was validated against the test vectors in [[RFC6979]].
The signer needs to generate a private/public key pair with the private key used for signing and the public key made available for verification. The representation of the public key, and the representation of the private key, are shown below.
Signing begins with a credential without an attached proof, which is converted to canonical form, which is then hashed, as shown in the following three examples.
The next step is to take the proof options document, convert it to canonical form, and obtain its hash, as shown in the next three examples.
Finally, we concatenate the hash of the proof options followed by the hash of the credential without proof, use the private key with the combined hash to compute the ECDSA signature, and then base-58-btc encode the signature.
Assemble the signed credential with the following two steps:
proofValue
field with the previously computed base-58-btc
value to the proof options document.
proof
field of the credential to the augmented proof
option document.
The signer needs to generate a private/public key pair with the private key used for signing and the public key made available for verification. The representation of the public key, and the representation of the private key, are shown below.
Signing begins with a credential without an attached proof, which is converted to canonical form, and then hashed, as shown in the following three examples.
The next step is to take the proof options document, convert it to canonical form, and obtain its hash, as shown in the next three examples.
Finally, we concatenate the hash of the proof options followed by the hash of the credential without proof, use the private key with the combined hash to compute the ECDSA signature, and then base-58-btc encode the signature.
Assemble the signed credential with the following two steps:
proofValue
field with the previously computed base-58-btc
value to the proof options document.
proof
field of the credential to the augmented proof
option document.
The signer needs to generate a private/public key pair with the private key used for signing and the public key made available for verification. The representation of the public key, and the representation of the private key, are shown below.
Signing begins with a credential without an attached proof, which is converted to canonical form, which is then hashed, as shown in the following three examples.
The next step is to take the proof options document, convert it to canonical form, and obtain its hash, as shown in the next three examples.
Finally, we concatenate the hash of the proof options followed by the hash of the credential without proof, use the private key with the combined hash to compute the ECDSA signature, and then base-58-btc encode the signature.
Assemble the signed credential with the following two steps:
proofValue
field with the previously computed base-58-btc
value to the proof options document.
proof
field of the credential to the augmented proof
option document.
The signer needs to generate a private/public key pair with the private key used for signing and the public key made available for verification. The representation of the public key, and the representation of the private key, are shown below.
Signing begins with a credential without an attached proof, which is converted to canonical form, which is then hashed, as shown in the following three examples.
The next step is to take the proof options document, convert it to canonical form, and obtain its hash, as shown in the next three examples.
Finally, we concatenate the hash of the proof options followed by the hash of the credential without proof, use the private key with the combined hash to compute the ECDSA signature, and then base-58-btc encode the signature.
Assemble the signed credential with the following two steps:
proofValue
field with the previously computed base-58-btc
value to the proof options document.
proof
field of the credential to the augmented proof
option document.
To demonstrate selective disclosure features including mandatory disclosure, selective disclosure, and overlap between mandatory and selective disclosure requires an input credential document with more content than previous test vectors. To avoid excessively long test vectors the starting document test vector is based on a purely ficticious windsurfing (sailing) competition scenario. In addition we break the test vectors into two groups based on those that would be generated by the issuer (base proof) and those that would be generated by the holder (derived proof).
In order to add a selective disclosure base proof to a document the issuer needs the following cryptographic key material:
The key material used for generating the add base proof test vectors is shown below. Multibase representation is use for the P-256 key pairs and the HMAC key is given as a hexadecimal string.
In our scenario a sailor is registering with a race organizer for a series of windsurfing races to be held over a number of days on Maui. The organizer will inspect the sailors equipment to certify that what has been declared is accurate. The sailors unsigned equipment inventory is shown below.
In addition to let other sailors know what kinds of equipment their competitors maybe sailing on it is mandatory that each sailor disclose the year of their most recent windsurfing board and full details on two of their sails. Note that all sailors are identified by a sail number that is printed on all their equipment. This mandatory information is specified via an array of JSON pointers as shown below.
The result of applying the above JSON pointers to the sailors equipment document is shown below.
Transformation of the unsigned document begins with canonicalizing the document as shown below.
To prevent possible information leakage from the ordering of the blank node ids these are processed through a PRF, i.e., the HMAC to give the canonized HMAC document shown below. This represents an ordered list of statements that will be subject to mandatory and selective disclosure, i.e., it is from this list that statements are grouped.
The above canonical document gets grouped in to mandatory and non-mandatory statements. The final output of the selective disclosure transformation process is shown below. Each statement is now grouped as mandatory and non-mandatory and its index in the previous list of statements is remembered.
The next step is to create the base proof configuration and canonicalize it. This is shown in the following two examples.
In the hashing step we compute the SHA-256 hash of the canonicalized proof options to produce the `proofHash` and we compute the SHA-256 hash of the join of all the mandatory nquads to produce the `mandatoryHash`. These are shown below in hexadecimal format.
We compute the `baseSignature` over the concatenation of the `proofHash`, `proofPublicKey`, and `mandatoryHash` using the issuers long term `privateKey`. We compute the `signatures` array by signing each non-mandatory nquad using the per `proofPrivateKey`. These signatures, the `proofPublicKey`, and `mandatoryPointers` which are fed to the final serialization step are shown below.
Finally, the values above are run through the algorithm of Section [[[#serializebaseproofvalue]]] to produce the `proofValue` which is used in the signed based document shown below.
In order to create a derived proof a holder starts with a signed document containing a base proof. The base document we will use for these test vectors is the final example from Section [[[#base-proof]]] above. The first step is to run the algorithm of Section [[[#parsebaseproofvalue]]] to recover `baseSignature`, `publicKey`, `hmacKey`, `signatures`, and `mandatoryPointers` as shown below.
Next, the holder needs to indicate what, if anything else, they wish to reveal to the verifiers by specifying JSON pointers for selective disclosure. In our windsurfing competition scenario a sailor (the holder) has just completed their first day of racing and wishes to reveal to the general public (the verifiers) all the details of the windsurfing boards they used in the competition. These are shown below. Note that this slightly overlaps with the mandatory disclosed information which included only the year of their most recent board.
To produce the `revealDocument`, i.e., the unsigned document that will eventually be signed and sent to the verifier, we append the selective pointers to the mandatory pointers and input these combined pointers along with the document without proof to the algorithm of Section [[[#selectjsonld]]] to give the result shown below.
Now that we know what the revealed document looks like, we need to furnish appropriately updated information to the verifier on which statements are mandatory, the signatures for the selected non-mandatory statements, and the mapping between canonical blank node ids for the reveal document and a subset of the HMAC blank node ids. Running step 6 of the [[[#createdisclosuredata]]] yields an abundance of information about various statement groups relative to the original document. Below we show a portion of the indexes for those groups.
The verifier needs to be able to aggregate and hash the mandatory statements. To enable this we furnish them with a list of indexes of the mandatory statements adjusted to their positions in the reveal document. In the previous example the `combinedIndexes` show the indexes of all the original nquads (statements) that make up the reveal document, in order. To come up with the adjusted mandatory indexes shown below we obtain the index of each of original mandatory indexes relative to the `combinedIndexes` as shown below.
We have to furnish the verifier with a list of signatures for those selective statements (nquads) that are not mandatory. The original list of signatures corresponds to every non-mandatory statement and the indexes of these in the original document are given above. We now compute a list of adjusted signature indexes by computing the index of each selective index in the non-mandatory index list, ignoring any selective index not present in the list. We then use the adjusted signature indexes to obtain the filtered signature list. These lists are shown below.
The last important piece of disclosure data is a mapping of canonical blank node ids to HMAC based ids, the `labelMap`, computed according to Section [[[#createdisclosuredata]]] steps 12-14. This is shown below along with the rest of the disclosure data minus the reveal document.
Finally using the disclosure data above with the algorithm of Section [[[#serializederivedproofvalue]]] we obtain the signed derived (reveal) document shown below.
This section contains the substantive changes that have been made to this specification over time.
Changes since the First Public Working Draft: