Note – this document is still work in progress and subject to change (based on received feedback and further findings / implementation insights).
As stated in the Linked Data Proofs specification:
A proof chain is useful when the same data needs to be signed by multiple entities and the order of when the proofs occurred matters, such as in the case of a notary counter-signing a proof that had been created on a document.
Unfortunately, the approach for expressing a "proof chains" described in the Linked Data specification is not suitable for usage with Verifiable Credentials, as pointed out in the following Github Issues [1, 2].
This document attempts to address this issue by defining a new Linked Data Proof type, as per the received suggestion(s)[1, 2].
This document defines a new Linked Data Proof type – ChainedProof2021
, which can be used to describe a signature which is generated / verified based on an existing proof node (i.e. Linked Data Proof), as well as the original signed document (indirectly, via signing the proofValue
of the referenced proof).
It should be possible to chain multiple such signatures together, to achieve the structure illustrated below:
The directed arrows illustrate a dependency (in terms of the signed content), i.e. Proof 1 is generated over the normalized contents of the Credential and the Proof 1 options. In case the credential or the signature options (e.g. created
) are modified, the signature is invalidated.
Proof 2 (a Chained Proof) is generated over the normalized contents of the Proof 1 node and the Proof 2 options. In case the contents the two proof objects are tampered with in any way (e.g. the created
date is tampered with), the signature is invalidated. Furthermore, in this case, if the contents of the Credential are tampered with, Proof 1 will be invalidated, and by extension Proof 2 will be invalidated as well
A proof node of the ChainedProof2021
type is expected to contain a proofValue
field (e.g. containing a digital signature), which can be verified using the referenced verificationMethod
. This is a common pattern with various Linked Data Proof types.
Unlike a regular Linked Data Proof, the signature associated with the proofValue
field is not generated based on the contents of the Linked Data Document, but rather based on the contents of a referenced Linked Data Proof node (also associated with the document). The exact process is defined in section 2.3. Modification To Algorithms.
The following section outlines the terms used with a ChainedProof2021
proof type.
This property is required, and MUST be set to ChainedProof2021
.
This is a standard property used to identify the type of linked data proof that is attached to a linked data document.
{
"type": "ChainedProof2021",
"verificationMethod": "did:example:employee#keys1",
"chainSignatureSuite": "Ed25519Signature2018",
"created": "2010-02-01T12:00:00Z",
"proofPurpose": "assertionMethod"
"previousProof": {
"type": "Ed25519Signature2018",
"created": "2010-01-01T19:23:24Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "did:example:issuer#keys1"
},
"proofValue": "eyJhbGc...kvBg"
}
This property is required.
This is a standard property used to timestamp the creation of the new proof. The string value of an ISO8601 combined date and time string generated by the Proof Algorithm.
{
"type": "ChainedProof2021",
"verificationMethod": "did:example:employee#keys1",
"chainSignatureSuite": "Ed25519Signature2018",
"created": "2010-02-01T12:00:00Z",
"proofPurpose": "assertionMethod"
"previousProof": {
"type": "Ed25519Signature2018",
"created": "2010-01-01T19:23:24Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "did:example:issuer#keys1"
},
"proofValue": "eyJhbGc...kvBg"
}
This property is required.
This is a custom property used to uniquely reference / identify an existing proof node associated with a linked data document. In case the proof node referenced by this value is missing or tampered with, the verification of the proof value associated with the ChainedProof2021
must fail.
The value associated with this property must be an object with the following properties:
type
of the referenced proofcreated
field of the referenced proofverificationMethod
(e.g. DID URL) associated with the previous proofThese properties were selected because they are all required by the Linked Data Proofs specification, and can be expected to be present in all Linked Data Proof nodes, regardless of their type. These properties should be enough to uniquely identify / reference any other proofs associated with the document.
{
"type": "ChainedProof2021",
"verificationMethod": "did:example:employee#keys1",
"chainSignatureSuite": "Ed25519Signature2018",
"created": "2010-02-01T12:00:00Z",
"proofPurpose": "assertionMethod"
"previousProof": {
"type": "Ed25519Signature2018",
"created": "2010-01-01T19:23:24Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "did:example:issuer#keys1"
},
"proofValue": "eyJhbGc...kvBg"
}
In case the reference is still ambigous (i.e. two or more proof nodes match the provided type, created date, verification method and proof purpose) the signature generation / verification process must fail.
Note – the approach described above is based on the assumption that proof nodes do not contain an identifier (i.e. @id
property), which prevents us from directly referencing them in the previousProof
field. This assumption was also mentioned in the associated issue. In case a more elegant mechanism for uniquely referencing another existing blank node exist, they should be considered.
This property is required. The associated value MUST be a signature suite defined in the Linked Data Cryptographic Suite Registry.
In the current design, the ChainedProof2021
proof type does not enforce the usage of a specific cryptographic suite for generating the associated proofValue
.
Instead, the definition of the signature suite is delegated to the proof type defined in the chainSignatureSuite
property. This enables a certain degree of flexibility, i.e. signers can use proof types they support (e.g. Ed25519Signature2018, JsonWebSignature2020, etc.) in combination with this proof type.
{
"type": "ChainedProof2021",
"verificationMethod": "did:example:employee#keys1",
"chainSignatureSuite": "Ed25519Signature2018",
"created": "2010-02-01T12:00:00Z",
"proofPurpose": "assertionMethod"
"previousProof": {
"type": "Ed25519Signature2018",
"created": "2010-01-01T19:23:24Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "did:example:issuer#keys1"
},
"proofValue": "eyJhbGc...kvBg"
}
This property is required.
The specific intent for the proof, the reason why an entity created it (as defined here). Allowed values are defined here.
{
"type": "ChainedProof2021",
"verificationMethod": "did:example:employee#keys1",
"chainSignatureSuite": "Ed25519Signature2018",
"created": "2010-02-01T12:00:00Z",
"proofPurpose": "assertionMethod"
"previousProof": {
"type": "Ed25519Signature2018",
"created": "2010-01-01T19:23:24Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "did:example:issuer#keys1"
},
"proofValue": "eyJhbGc...kvBg"
}
This property is required.
A linked data document featuring a ChainedProof2021
Linked Data Proof MUST contain a proofValue
attribute with value defined by the signing algorithm described in the specification of the used chainedProofType
.
{
"type": "ChainedProof2021",
"verificationMethod": "did:example:employee#keys1",
"chainSignatureSuite": "Ed25519Signature2018",
"created": "2010-02-01T12:00:00Z",
"proofPurpose": "assertionMethod"
"previousProof": {
"type": "Ed25519Signature2018",
"created": "2010-01-01T19:23:24Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "did:example:issuer#keys1"
},
"proofValue": "eyJhbGc...kvBg"
}
In the example above, because the Ed25519Signature2018
signature suite is used to generate the proofValue
associated with the "chain tip", the resulting proofValue
field contains a detached JWS.
A ChainedProof2021
proof can be generated only in combination with another existing proof
node (referenced via the previousProof
value). A Linked Data Proof node which satisfies the constraints expressed in the previousProof
property (evaluated based on the rules defined in section 2.3.3.) is required as an additional input. In case the Linked Data Proof node referenced by the previousProof
entry is missing, the process must exit with an error.
Because of this special property, modifications to the Proof Generation and Proof Verification algorithms are required.
As stated in section 11.1 of the Linked Data Proofs specification, the expected inputs for this algorithm are the linked data document to be signed (referred to as document), proof options for the proof being generated (referred to as options), as well as a private key, referred to as privateKey.
Because the signature included in a ChainedProof2021
node is not generated over the contents of the document, but rather over the contents of another proof node, the referenced proof node must be passed as an adittional input as well (referred to as previousProof).
previousProof
property as the document
argument, the options for the ChainedProof2021
as proof options
, as well as the canonicalization and message digest algorithms defined by the selected chainSignatureSuite
.chainSignatureSuite
value of the options argument). The resulting string is the proofValue
.ChainedProof2021
linked data proof, using the appropriate type
(ChainedProof2021
) and proofValue
values as well as all of the data in the proof options (e.g. created, and if given, any additional proof options such as domain).I.e. the signature is generated over the following content:
[Hash proof options 2 || Hash proof 1]
Furthermore, given an example with N chained proofs, the signature of proof N is is generated over the following content:
[Hash proof options N || Hash proof N-1]
Where ||
denotes concatenation.
This algorithm takes a signed linked data document, signed document and outputs a true or false value based on whether or not the digital proof on signed document was verified.
As stated in the relevant specification section, the expected inputs for this algorithm is the signed linked data document (referred to as signed document) to be verified.
In order to verify a ChainedProof2021
the following steps need to be taken:
ChainedProof2021
proof node of the default graph of signed document. Confirm that the linked data document that describes the public key specifies its owner and that its owner's URL identifier can be dereferenced to reveal a bi-directional link back to the key. Ensure that the key's owner is a trusted entity before proceeding to the next step.ChainedProof2021
, and the proof node referenced by the previousProof
property of the Chained Proof).previousProof
according to the Proof Verification Algorithm defined by the corresponding Proof Type. In case the referenced signature is not valid, the process must terminate with an error.document
argument, the options for the ChainedProof2021
as proof options
, as well as the canonicalization and message digest algorithms defined by the selected chainSignatureSuite
.previousProof
optionsGiven two inputs:
previousProof
entry extracted from a ChaiendProof2021
Linked Data Proof.proof
property of the Signed Linked Data Document)The process described here must output one Linked Data Proof object (selected based on the previousProof
argument), or return an error (in case zero, or more than two Linked Data Proofs match the previousProof
).
In order to find the appropriate Linked Data Proof to return, each entry in the proof
set is matched against the options in the previousProof
object. The type
, created
, verificationMethod
and proofPurpose
must match exactly.
In case any optional values (e.g. domain
) are present in the previousProof
entry, they must also be present in the returned Linked Data Proof.
A proof chain is useful when the same data needs to be signed by multiple entities and the order of the proofs matters, such as in the case of a notary counter-signing a proof that had been created on a document.
Given a Verifiable Credential with the following associated Ed25519SignatureSuite2018
proof:
{
"type": "Ed25519Signature2018",
"created": "2016-10-23T05:50:16Z",
"verificationMethod": "did:example:employer#keys1",
"proofPurpose": "assertionMethod",
"jws" : "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..AUQ3AJ23WM5vMOWNtYKuqZBekRAOUibOMH9XuvOd39my1sO-X9R4QyAXLD2ospssLvIuwmQVhJa-F0xMOnkvBg"
}
The signature suite defined in this document can be used to generate a chained proof referencing the original signature. The second proof would look as follows:
{
"type": "ChainedProof2021",
// The creator of the embedded signature
"verificationMethod": "did:example:employee#keys1",
// When was the chained proof created
"created": "2010-02-01T12:00:00Z",
"proofPurpose": "assertionMethod",
// To avoid ambiguity, the Signature Suite used to created the proofValue is explicitly mentioned.
"chainSignatureSuite": "Ed25519Signature2019",
// Uniquely identifies the previous link in the chain
"previousProof": {
"type": "Ed25519Signature2018",
"created": "2010-01-01T19:23:24Z",
"verificationMethod": "did:example:employer#keys1",
"proofPurpose": "assertionMethod",
},
"proofValue": "eyJhbGc...kvBg"
}
Both proofs can be attached to the verifiable credential as follows:
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "http://example.edu/credentials/1872",
"type": ["VerifiableCredential", "EmploymentContractCredential"],
"issuer": "did:example:employer",
"issuanceDate": "2010-01-01T19:23:24Z",
"credentialSubject": {
"id": "did:example:employee",
"professorAt": {
"id": "did:example:university",
"name": [{
"value": "Example University",
"lang": "en"
}]
}
},
"proof": [
// Employer signature, e.g. university representative
{
"type": "Ed25519Signature2018",
"created": "2010-01-01T19:23:24Z",
"verificationMethod": "did:example:employer#key1",
"proofPurpose": "assertionMethod",
"jws" : "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..AUQ3AJ23WM5vMOWNtYKuqZBekRAOUibOMH9XuvOd39my1sO-X9R4QyAXLD2ospssLvIuwmQVhJa-F0xMOnkvBg"
},
{
"type": "ChainedProof2021",
"verificationMethod": "did:example:employee#keys1",
"chainedProofType": "Ed25519Signature2018",
"created": "2010-02-01T12:00:00Z",
"proofPurpose": "assertionMethod"
"previousProof": {
"type": "Ed25519Signature2018",
"created": "2010-01-01T19:23:24Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "did:example:issuer#keys1"
},
// ProofValue is a detached JWS, as defined by the used Ed25519Signature2018 suite.
"proofValue": "eyJhbGc...kvBg"
},
// A further example for a ChainedProof entry "built" on a previous ChainedProof property
{
"type": "ChainedProof2021",
"verificationMethod": "did:example:hr-department#keys1",
"chainedProofType": "RsaSignature2018",
"created": "2010-03-01T12:00:00Z",
"proofPurpose": "assertionMethod"
"previousProof": {
"type": "ChainedProof2021",
"created": "2010-02-01T12:00:00Z",
"verificationMethod": "did:example:employee#keys1",
"proofPurpose": "assertionMethod",
},
// ProofValue is a detached JWS, as defined by the used RsaSignature2018 suite.
"proofValue": "eyJhGbc...Gbg"
}]
}