Created
February 28, 2023 18:38
-
-
Save Wind4Greg/a601a4617fa2963da2bcce39303283e3 to your computer and use it in GitHub Desktop.
Creating a EdDSA signed VC
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Steps to create a signed verifiable credential in the *DataIntegrityProof* | |
representation. | |
*/ | |
import jsonld from 'jsonld'; | |
import { base58btc } from "multiformats/bases/base58"; | |
import * as ed from '@noble/ed25519'; | |
import { sha256 } from '@noble/hashes/sha256'; | |
import { bytesToHex, concatBytes } from '@noble/hashes/utils'; | |
import { vcv2 } from './credv2.js'; // Used to bring in context | |
// Set up a document loader so we don't have to go to the net | |
const CONTEXTS = { | |
"https://www.w3.org/ns/credentials/v2": { | |
"@context": vcv2 // Local copy | |
} | |
}; | |
// grab the built-in Node.js doc loader | |
const nodeDocumentLoader = jsonld.documentLoaders.node(); | |
// change the default document loader | |
const customLoader = async (url, options) => { | |
if (url in CONTEXTS) { | |
return { | |
contextUrl: null, // this is for a context via a link header | |
document: CONTEXTS[url], // this is the actual document that was loaded | |
documentUrl: url // this is the actual context URL after redirects | |
}; | |
} | |
return nodeDocumentLoader(url); | |
}; | |
jsonld.documentLoader = customLoader; | |
const keyPair = { | |
publicKeyMultibase: "z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2", | |
privateKeyMultibase: "z3u2en7t5LR2WtQH5PfFqMqwVHBeXouLzo6haApm8XHqvjxq" | |
}; | |
let document = { | |
"@context": [ | |
"https://www.w3.org/ns/credentials/v2", | |
{ | |
"AlumniCredential": "https://schema.org#AlumniCredential", | |
"alumniOf": "https://schema.org#alumniOf" | |
}, | |
], | |
"id": "http://example.edu/credentials/1872", | |
"type": [ | |
"VerifiableCredential", | |
"AlumniCredential" | |
], | |
"issuer": "https://example.edu/issuers/565049", | |
"issuanceDate": "2010-01-01T19:23:24Z", | |
"credentialSubject": { | |
"id": "https://example.edu/students/alice", | |
"alumniOf": "Example University" | |
} | |
}; | |
document = { | |
"@context": [ | |
"https://www.w3.org/ns/credentials/v2", | |
{"UniversityDegreeCredential": "https://example.org/examples#UniversityDegreeCredential"} | |
], | |
"id": "http://example.edu/credentials/3732", | |
"type": [ | |
"VerifiableCredential", | |
"UniversityDegreeCredential" | |
], | |
"issuer": "https://example.edu/issuers/565049", | |
"issuanceDate": "2010-01-01T00:00:00Z", | |
"credentialSubject": { | |
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21", | |
"degree": { | |
"type": "BachelorDegree", | |
"name": "Bachelor of Science and Arts" | |
} | |
} | |
}; | |
// Canonize the document | |
let cannon = await jsonld.canonize(document); | |
console.log("Canonized unsigned document:") | |
console.log(cannon); | |
// Hash canonized document | |
let docHash = sha256(cannon); // @noble/hash will convert string to bytes via UTF-8 | |
console.log("Hash of canonized document in hex:") | |
console.log(bytesToHex(docHash)); | |
// Set proof options per draft | |
let proofConfig = {}; | |
proofConfig.type = "DataIntegrityProof"; | |
proofConfig.cryptosuite = "eddsa-2022"; | |
proofConfig.created = "2023-02-24T23:36:38Z"; | |
proofConfig.verificationMethod = "https://example.edu/issuers/565049#" + keyPair.publicKeyMultibase; | |
proofConfig.proofPurpose = "assertionMethod"; | |
proofConfig["@context"] = document["@context"]; // Missing from draft!!! | |
// canonize the proof config | |
let proofCanon = await jsonld.canonize(proofConfig); | |
console.log("Proof Configuration Canonized:"); | |
console.log(proofCanon); | |
// Hash canonized proof config | |
let proofHash = sha256(proofCanon); // @noble/hash will convert string to bytes via UTF-8 | |
console.log("Hash of canonized proof in hex:") | |
console.log(bytesToHex(proofHash)); | |
// Combine hashes | |
let combinedHash = concatBytes(proofHash, docHash); // Hash order different from draft | |
// Sign | |
let privKey = base58btc.decode(keyPair.privateKeyMultibase); | |
privKey = privKey.slice(2, 34); // only want the first 2-34 bytes | |
console.log(`Secret key length ${privKey.length}, value in hex:`); | |
let signature = await ed.sign(combinedHash, privKey); | |
console.log("Computed Signature from private key:"); | |
console.log(base58btc.encode(signature)); | |
// Verify (just to see we have a good private/public pair) | |
let pbk = base58btc.decode(keyPair.publicKeyMultibase); | |
pbk = pbk.slice(2, pbk.length); // First two bytes are multi-format indicator | |
console.log(`Public Key hex: ${bytesToHex(pbk)}, Length: ${pbk.length}`); | |
let result = await ed.verify(signature, combinedHash, pbk); | |
console.log(`Signature verified: ${result}`); | |
// Construct Signed Document | |
let signedDocument = Object.assign({}, document); | |
delete proofConfig['@context']; | |
signedDocument.proof = proofConfig; | |
signedDocument.proof.proofValue = base58btc.encode(signature); | |
console.log(JSON.stringify(signedDocument, null, 2)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment