@krebitdao/reputation-passport
Version:
Krebit SDK for Verified Credentials
140 lines (139 loc) • 5.93 kB
JavaScript
import 'isomorphic-fetch';
import { createHash } from 'crypto';
import { TileDocument } from '@ceramicnetwork/stream-tile';
import Ajv from 'ajv';
import { EIP712VC, getEIP712Credential, getKrebitCredentialTypes } from '@krebitdao/eip712-vc';
import { base64 } from 'ethers/lib/utils.js';
import { schemas } from '../schemas/index.js';
import { lib } from '../lib/index.js';
import { config } from '../config/index.js';
const currentConfig = config.get();
// utility to create an ordered array of the given input (of the form [[key:string, value:string], ...])
const objToSortedArray = (obj) => {
const keys = Object.keys(obj).sort();
return keys.reduce((out, key) => {
out.push([key, obj[key]]);
return out;
}, []);
};
// Generate a hash like SHA256(DID+PII), where PII is the (deterministic) JSON representation
// of the PII object after transforming it to an array of the form [[key:string, value:string], ...]
// with the elements sorted by key
// This hash can be used to de-duplicate provider verifications without revealing PII
export const hashClaimValue = (props) => {
return base64.encode(createHash('sha256')
.update(props.did, 'utf-8')
.update(JSON.stringify(objToSortedArray(props.value)))
.digest());
};
export const validateSchema = async (props) => {
const { idx, claim } = props;
let schema;
if (claim.typeSchema.startsWith('ceramic://')) {
const stream = await TileDocument.load(idx.ceramic, claim.typeSchema);
schema = stream.content;
}
else if (claim.typeSchema.startsWith('krebit://')) {
const krebitSchema = claim.typeSchema.substring(claim.typeSchema.lastIndexOf('/') + 1);
console.log('schema: ', schemas.claims[krebitSchema]);
schema = schemas.claims[krebitSchema];
}
else if (claim.typeSchema.startsWith('https://')) {
const response = await fetch(claim.typeSchema);
schema = await response.json();
}
const ajv = new Ajv();
const validateSchema = ajv.compile(schema);
const validSchema = validateSchema(claim.value);
if (!validSchema) {
console.error('the schema should be: ', JSON.stringify(validateSchema.errors));
throw new Error('Claim value does not match typeSchema: ' +
validateSchema.errors.toString());
}
return validSchema;
};
export const issueCredential = async (props) => {
const { wallet, idx, claim } = props;
if (!wallet) {
throw new Error('Wallet not defined');
}
if (!idx) {
throw new Error('IDX not defined');
}
const issuerAddres = await wallet.getAddress();
// 5 min ago (there is a delay on the blockchain time)
const issuanceDate = Date.now() - 1000 * 60 * 5;
const expirationDate = new Date(claim?.expirationDate);
if (!expirationDate) {
throw new Error('No expiration date defined');
}
const lit = new lib.Lit();
if (typeof claim.value === 'object') {
if (claim.encrypt == 'hash') {
claim['value'] = hashClaimValue({ did: idx.id, value: claim.value });
claim['encrypted'] = 'hash';
}
else if (claim.encrypt == 'lit') {
let unifiedAccessControlConditions = lit.getOwnsAddressCondition(claim.ethereumAddress);
if (claim.shareEncryptedWith) {
unifiedAccessControlConditions = unifiedAccessControlConditions?.concat(lit.getShareWithCondition(claim.shareEncryptedWith));
}
let encryptedContent = await lit.encrypt(JSON.stringify(claim.value), unifiedAccessControlConditions, wallet);
if (!encryptedContent) {
throw new Error('Problem creating encryptedContent');
}
const stream = await TileDocument.create(idx.ceramic, unifiedAccessControlConditions);
claim['value'] = JSON.stringify({
...encryptedContent,
unifiedAccessControlConditions: stream.id.toUrl()
});
claim['encrypted'] = 'lit';
}
else {
claim['value'] = JSON.stringify(claim.value);
claim['encrypted'] = 'none';
}
}
delete claim.encrypt;
const credential = {
'@context': [
'https://www.w3.org/2018/credentials/v1',
'https://w3id.org/security/suites/eip712sig-2021'
],
type: ['VerifiableCredential']?.concat(claim.type, ...claim.tags),
id: claim.id,
issuer: {
id: idx.id,
ethereumAddress: issuerAddres
},
credentialSubject: {
...claim,
id: claim.did,
trust: claim.trust ? claim.trust : 100,
stake: claim.stake ? claim.stake : 1,
price: claim.price ? claim.price : 0,
nbf: Math.floor(issuanceDate / 1000),
exp: Math.floor(expirationDate.getTime() / 1000)
},
credentialSchema: {
id: 'https://github.com/KrebitDAO/eip712-vc',
type: 'Eip712SchemaValidator2021'
},
issuanceDate: new Date(issuanceDate).toISOString(),
expirationDate: claim.expirationDate
};
console.debug('Credential: ', credential);
const eip712credential = getEIP712Credential(credential);
console.debug('eip712credential: ', eip712credential);
const krebitTypes = getKrebitCredentialTypes();
const eip712_vc = new EIP712VC(schemas.krbToken[currentConfig.network].domain);
const verifiableCredential = await eip712_vc.createEIP712VerifiableCredential(eip712credential, krebitTypes, async () => {
return await wallet._signTypedData(schemas.krbToken[currentConfig.network].domain, krebitTypes, eip712credential);
});
const w3Credential = {
...credential,
proof: verifiableCredential.proof
};
console.log('W3C Verifiable Credential: ', w3Credential);
return w3Credential;
};