eddsa-rdfc-2022
and eddsa-jcs-2022
suites and has simple and enhanced test vectors as well as test vectors for proof sets and chains.ecdsa-rdfc-2019
, ecdsa-jcs-2019
, and ecdsa-sd-2023
over curves P-256 and P-384 and test vectors.Suppose that we have two community hiking clubs: The Arcata Tall Trees Hiking Club and the White Mountain Old Trees Hiking Club Both clubs exists to promote the “great outdoors”, hiking safety, and comraderie.
Students make up their own club to build over a semester. Example: Bay Area Windsurf Foiling Club.
To make the club interesting from a VC perspective the club should certify knowledge, skills, or something else about a member. What they choose to certify is up to the club/student.
An example of a real club is the Cal Sailing Club, a university club that morphed into a community club.
Consider a wind sport club. It might offer activities in a number of disciplines: (a) dinghy sailing, (b) keel boat sailing, (c) windsurfing, (d) windsurf foiling, (e) wing foiling.
We could have a separate credential for each discipline. Some disciplines may overlap or feed into each other (windsurfing -> windsurf foiling).
Now there are also a number of things a club might need to know about you that do not need to be in a verifiable credential such as (a) your phone number, (b) your address, (c) your emergency contact information, (d) student status, etc…
{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://www.mysailclub.org/ns/credentials" // Our context will go here
],
"id": "http://university.example/credentials/58473", // optional, id of the VC
"name": "The name of this credential", // optional
"description": "A description of the credential", // optional
"type": ["VerifiableCredential", "WaterClubCredential"], // required
"issuer": { // required, Specify the issuer nicely
"id": "https://mysailclub.org", // club IRI (but also URL)
"name": "My Wind Sports Club", // issuer name
"description": "A public or school based wind sports club." // optional issuer desc
},
"validFrom": "2010-01-01T00:00:00Z", // optional
"validUntil": "2020-01-01T19:23:24Z", // optional
"credentialSubject": { // required
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21", // optional, id of the subject
// Here is where all the custom stuff will go
}
}
{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://bawfc.grotto-networking.com/cred/foilcontext1"],
"name": "Bay Area Windsurf Foiling Credential",
"description": "Denotes membership, knowledge, and skills in windsurf foiling",
"type": ["VerifiableCredential", "WindFoilCredential"],
"issuer": {
"id": "https://bawfc.grotto-networking.com",
"name": "Bay Area Windsurf Foiling Club",
"description": "Not a real club, but real information!"
},
"credentialSubject": {
"type": ["Member", "WindsurfFoiler"],
"membership": {"start": "2025-03-01T00:00:00Z", "end": "2025-10-31T00:00:00Z"},
"knowledge": [
{"topic": "DockEtiquette", "date": "1984-01-01T00:00:00Z"},
{"topic": "TidesAndCurrents", "date": "2010-01-01T00:00:00Z"},
{"topic": "WindPatterns", "date": "2010-01-01T00:00:00Z"},
{"topic": "LandMarksBuoys","date": "2010-01-01T00:00:00Z"},
{"topic": "BigBoatsFerries", "date": "2010-01-01T00:00:00Z"}],
"skills": [
{"skill": "Rigging", "date": "2010-01-01T00:00:00Z"},
{"skill": "FoilSetup", "date": "2017-01-01T00:00:00Z"}],
"distanceAchievements": [
{"landmark": "TI", "date": "2010-01-01T00:00:00Z"},
{"landmark": "R2", "date": "2011-01-01T00:00:00Z"}]
}
}
JSON is great for modeling almost any data structure imaginable. However most applications only wants certain types of data in a particular format, i.e., a small subset of possible JSON structures and content. Typically the structure and type of data is specified by a schema of some kind.
From JSON-Schema
{
"$id": "https://grotto-networking.com/simple-credential.schema.json",
"title": "UnsignedCredential",
"description": "A basic credential validator, data model v1.1 or v2.0",
"type": "object",
"properties": {
"@context": {
"type": "array",
"items": {"type": ["string", "object"]}
},
"id": {"type": "string", "format": "uri"},
"type": {"type": ["string", "array"]},
"credentialSubject": {"type": ["object", "array"]},
"issuer": {
"anyOf": [{"type": "string", "format": "uri"},
{"type": "object",
"properties": {
"id": {"type": "string", "format": "uri"}
},
"required": ["id"]}
]
},
"credentialStatus": {
"type": "object",
"properties": {
"id": {"type": "string", "format": "uri"},
"type": {"type": ["string", "array"]
}},
"required": ["type"]
}
},
"required": ["@context", "type", "credentialSubject", "issuer"]
}
To actually perform checks of JSON document against a JSON-Schema, optimized code libraries are used. I use and would have my students use npm: AJV.
AJV NPM page
What do the date fields mean in the credential? What do the distanceAchievements mean?
"credentialSubject": {
"type": ["Member", "WindsurfFoiler"],
"membership": {"start": "2025-03-01T00:00:00Z", "end": "2025-10-31T00:00:00Z"},
"knowledge": [
{"topic": "DockEtiquette", "date": "1984-01-01T00:00:00Z"},
{"topic": "TidesAndCurrents", "date": "2010-01-01T00:00:00Z"},
{"topic": "WindPatterns", "date": "2010-01-01T00:00:00Z"},
{"topic": "LandMarksBuoys","date": "2010-01-01T00:00:00Z"},
{"topic": "BigBoatsFerries", "date": "2010-01-01T00:00:00Z"}],
"skills": [
{"skill": "Rigging", "date": "2010-01-01T00:00:00Z"},
{"skill": "FoilSetup", "date": "2017-01-01T00:00:00Z"}],
"distanceAchievements": [
{"landmark": "TI", "date": "2010-01-01T00:00:00Z"},
{"landmark": "R2", "date": "2011-01-01T00:00:00Z"}]
}
Distances relative to Berkeley
https://schema.org/givenName
for givenName.@context
document@context
: https://w3id.org/security/multikey/v1https://w3id.org/security
resolves to the Security Vocabulary where you can actually read the definition of the term.{
"@context": {
"@protected": true,
"Member": {
"@id": "https://bawfc.grotto-networking.com/cred/vocab#Member",
"@context": {
"@protected": true,
"membership": {
"@id": "https://bawfc.grotto-networking.com/cred/vocab#membership",
"@context": {
"@protected": true,
"start": {
"@id": "https://bawfc.grotto-networking.com/cred/vocab#start",
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
},
"end": {
"@id": "https://bawfc.grotto-networking.com/cred/vocab#end",
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
}
}
}
}
},
"WindsurfFoiler": {
"@id": "https://bawfc.grotto-networking.com/cred/vocab#WindsurfFoiler",
"@context": [
{
"knowledge": {
"@id": "https://bawfc.grotto-networking.com/cred/vocab#knowledge",
"@container": "@set",
"@context": {
"@protected": true,
"topic": "https://bawfc.grotto-networking.com/cred/vocab#topic",
"date": {
"@id": "https://bawfc.grotto-networking.com/cred/vocab#date",
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
}
}
}
},
{
"skills": {
"@id": "https://bawfc.grotto-networking.com/cred/vocab#skills",
"@container": "@set",
"@context": {
"@protected": true,
"skill": "https://bawfc.grotto-networking.com/cred/vocab#skill",
"date": {
"@id": "https://bawfc.grotto-networking.com/cred/vocab#date",
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
}
}
}
},
{
"distanceAchiements": {
"@id": "https://bawfc.grotto-networking.com/cred/vocab#distanceAchievements",
"@container": "@set",
"@context": {
"@protected": true,
"landmark": "https://bawfc.grotto-networking.com/cred/vocab#landmark",
"date": {
"@id": "https://bawfc.grotto-networking.com/cred/vocab#date",
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
}
}
}
}
]
}
}
}
context
document, and a vocabulary
document!# WindsurfFoiler
This **type** information more specific to the discipline of windsurf foiling which can be considered a sub-discipline of windsurfing, which can be considered a sub-discipline of sailing.
## knowledge
*knowledge* is a set of understandings about various *topics*.
### topic
A *topic* is a single string identitying a particular area of knowledge. Currently defined topic strings include:
* **DockEtiquette**: Be polite and don't leave your gear on the dock for any longer than necessary. Know hazards of gear and equipment around other dock users.
* **TidesAndCurrents**: We don't like to run around or get stuck in the mud. Know the advantages and disadvantages of "flood" and "ebb" tides.
* **WindPatterns**: Know the common wind directions and patterns. Know what a "fade" is. Know what "banding" is and why its not a good thing. Never go out in easterlies!
* **LandMarksBuoys**: Know the common landmarks and buoys so you can communicate effectively with your fellow sailors.
* **BigBoatsFerries**: Let's stay alive people! Know the "big boat" traffic lanes in the bay. Know that they cannot stop (well yes, they can in a mile or two!). Know the fast ferry routes and why you always need to pay attention when crossing (yes, they are really fast).
### knowledgeDate
When was this knowledge *last* demonstrated, i.e., tested.
Canonicalization choice?
@context
and RDFC takes full advantage of that.NIST SP 800-57 lists 19 different types of cryptographic keys. For issuing credentials for out club we will only be concerned with their first two types:
A digital signature algorithm, such as EdDSA, will provide function (or two) for generating the private and public portions of the key pair. This can be as simple as shown below.
You may see a number of different formats for the same key information.
{
// Hex representation typical used in signature spec.
privateKeyHex: "4b49e4ec275c7590d39672508b2717c2dc1dc015e50a5d064732742413c810e0",
publicKeyHex: "419d02f127a4703ee3e09d071a1ddf2bd8be891558c5023e64232dc7f255eda4",
// Multibase used in lots of higher level specifications
publicKeyMultibase: "z6MkisPQbQ4toWkXzY5C1Da76Ghc64DVe5ZU7DkMDjRtvqNj",
privateKeyMultibase: "z3u2WHhiUqnajJreS8TQw2M57nMfjr7pD7pbme2M66U6BBgb",
// did:key used in test vectors and interop testing
didKey: "did:key:z6MkisPQbQ4toWkXzY5C1Da76Ghc64DVe5ZU7DkMDjRtvqNj"
}
From VC-DI-EdDSA types of signature security
EUF-CMA (existential unforgeability under chosen message attacks) is usually the minimal security property required of a signature scheme. It 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): \(\{m_i, \sigma_i\}_{i=1}^N\), cannot output a valid signature \(\sigma^∗\) for a new message \(m^∗ \notin \{m_i\}_{i=1}^N\) (except with negligible probability). In case the attacker outputs a valid signature on a new message: \((m^∗, \sigma^∗)\), it is called an existential forgery.
The public portion needs to be distributed in a way so that a verifier will have a high assurance that the public key is actually for our club and not an imposter.
References:
did:key
DID methoddid:web
DID methoddid:key
direct distribution and testingWith the did:key
DID method the value of key is embedded in the DID method, e.g., did:key:z6MkisPQbQ4toWkXzY5C1Da76Ghc64DVe5ZU7DkMDjRtvqNj
contains an encoded verion of the actual public key value.
did:key
example useTest vector from VC-DI-EdDSA
{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://www.w3.org/ns/credentials/examples/v2"
],
"id": "urn:uuid:58172aac-d8ba-11ed-83dd-0b3aef56cc33",
"type": [
"VerifiableCredential",
"AlumniCredential"
],
"name": "Alumni Credential",
"description": "A minimum viable example of an Alumni Credential.",
"issuer": "https://vc.example/issuers/5678",
"validFrom": "2023-01-01T00:00:00Z",
"credentialSubject": {
"id": "did:example:abcdefgh",
"alumniOf": "The School of Examples"
},
"proof": {
"type": "DataIntegrityProof",
"cryptosuite": "eddsa-rdfc-2022",
"created": "2023-02-24T23:36:38Z",
"verificationMethod": "did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2#z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2",
"proofPurpose": "assertionMethod",
"proofValue": "z2YwC8z3ap7yx1nZYCg4L3j3ApHsF8kgPdSb5xoS1VR7vPG3F561B52hYnQF9iseabecm3ijx4K1FBTQsCZahKZme"
}
}
In previous courses the students would have
BAWFC Certification
did:web
since we have HTTPSdid:web
is to resolve to a DID document.{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/multikey/v1"
],
"id": "did:web:bawfc.grotto-networking.com:signpubkey",
"assertionMethod": [
{
"id": "did:web:bawfc.grotto-networking.com:signpubkey#vm",
"type": "Multikey",
"controller": "did:web:bawfc.grotto-networking.com:signpubkey",
"publicKeyMultibase": "z6MkssZC7JGm9aBNsB5VbAWpHAcD3MNFi2anUbNY65g8vXAB"
}
]
}
did:web
If our DID is did:web:bawfc.grotto-networking.com:signpubkey
then the above DID document would be obtain by getting the page: https://bawfc.grotto-networking.com/signpubkey/did.json.
Note that in the resolved URL for the DID document that https
is used. This implies that the full power of HTTPS guarantees the authenticity of the DID document as comming from our club’s website.
proof
proof
Annotated fields based on VC Data Integrity 1.0
{
"proof": {
"id": "whatever...", // Optional
"type": "DataIntegrityProof", // Required and this value only
"proofPurpose": "assertionMethod", // Required, use this value for a VC
// verificationMethod should "point" to the public key for the signature
"verificationMethod": "https://di.example/issuer#z6MkjLrk3gKS2nnkeWcmcxiZPGskmesDpuwRBorgHxUXfxnG",
"cryptosuite": "eddsa-rdfc-2022", // Required, choose as appropriate
"created": "2023-03-05T19:23:24Z", // Optional
"expires": "2026-03-05T19:23:24Z", // Optional
"domain": "somekind of string", // Optional
"challenge": "string that SHOULD be included if a domain is specified", // Optional
"proofValue": "encoded cryptographic stuff", // Computed by cryptosuite!!!
"previousProof": "id of another proof", // Optional, sets and chains
"nonce": "string thingy" // Optional
}
}
proof
object except proofValue can be considered either proof metadata or proof options.{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://bawfc.grotto-networking.com/cred/foilcontext1"
],
"name": "Bay Area Windsurf Foiling Credential",
"description": "Denotes membership, knowledge, and skills in windsurf foiling",
"type": [
"VerifiableCredential",
"WindFoilCredential"
],
"issuer": {
"id": "https://bawfc.grotto-networking.com",
"name": "Bay Area Windsurf Foiling Club",
"description": "Not a real club, but real information!"
},
"credentialSubject": {
"type": ["Member", "WindsurfFoiler"],
"membership": {"start": "2025-03-01T00:00:00Z", "end": "2025-10-31T00:00:00Z"},
"knowledge": [
{"topic": "DockEtiquette", "date": "1984-01-01T00:00:00Z"},
{"topic": "TidesAndCurrents", "date": "2010-01-01T00:00:00Z"},
{"topic": "WindPatterns", "date": "2010-01-01T00:00:00Z"},
{"topic": "LandMarksBuoys", "date": "2010-01-01T00:00:00Z"},
{"topic": "BigBoatsFerries", "date": "2010-01-01T00:00:00Z"}
],
"skills": [
{"skill": "Rigging", "date": "2010-01-01T00:00:00Z"},
{"skill": "FoilSetup", "date": "2017-01-01T00:00:00Z"}
],
"distanceAchiements": [
{"landmark": "TI", "date": "2010-01-01T00:00:00Z"},
{"landmark": "R2", "date": "2011-01-01T00:00:00Z"}
]
},
"proof": {
"type": "DataIntegrityProof",
"cryptosuite": "eddsa-rdfc-2022",
"verificationMethod": "did:key:z6MkfQxm1VBtUV7mAkzEkeM2xFxAb1rhZT5MaJmnkp4f8FDL#z6MkfQxm1VBtUV7mAkzEkeM2xFxAb1rhZT5MaJmnkp4f8FDL",
"proofPurpose": "assertionMethod",
"proofValue": "z6gFSUSasdoCMqqHXpHx1bsqg84qp7JEGi5GePxGkJitv3u6jqSMKQhotHpvhUwJZ2mHX3xHVYjgCW7H243RY9Ec"
}
}
src/EdDSAKeyGen.js
: creates private/public key pair. Puts them into multiple convenient formats in allKeys.json
file; creates the did.json
file with public key for did:web
method.src/SignCredential.js
: Define proof options in here, uses `allKeys.json
for private key, produces signed credential based on specified input credential.scr/VerifyCredential.js
: Verifies signed credential. Currently did:key
verification only.documentLoader.js
: Loads up local versions of VC DM 2.0 context and our club context.