Skip to content

Instantly share code, notes, and snippets.

@Wind4Greg
Created February 28, 2023 18:38
Show Gist options
  • Save Wind4Greg/a601a4617fa2963da2bcce39303283e3 to your computer and use it in GitHub Desktop.
Save Wind4Greg/a601a4617fa2963da2bcce39303283e3 to your computer and use it in GitHub Desktop.
Creating a EdDSA signed VC
/*
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