did-sdk-js
Version:
js sdk for did and vc according to mcps did spec
560 lines (559 loc) • 22.7 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.VerifiableCredentialCipher = exports.VerifiableCredential = exports.Proof = exports.Revocation = exports.CredentialSubject = exports.VCType = void 0;
const crypto = require("../crypto");
const utils_1 = require("../utils");
const errors_1 = require("../errors");
const common_1 = require("./claims/common");
const did_1 = require("../did");
const cipher_1 = require("./cipher");
const registration_1 = require("./claims/registration");
const bs58 = require('bs58');
var VCType;
(function (VCType) {
VCType["vcTypeProof"] = "ProofClaim";
VCType["vcTypeProfile"] = "ProfileClaim";
})(VCType = exports.VCType || (exports.VCType = {}));
class CredentialSubject {
constructor() {
this.id = [];
this.claims = [];
}
}
exports.CredentialSubject = CredentialSubject;
class Revocation {
constructor() {
this.endpoint = "";
}
}
exports.Revocation = Revocation;
class Proof {
constructor() {
this.nonce = "";
this.creator = "";
this.created = "";
}
genNonce() {
this.nonce = utils_1.Utils.getRandomString(Proof.nonceLen);
}
signVerify(vcID, publicKey) {
return __awaiter(this, void 0, void 0, function* () {
let keyGen = crypto.keyGenerator(crypto.getAlgoTypeByVerifyType(this.type));
let signData = vcID + this.nonce;
return yield keyGen.signVerify(signData, this.signatureValue, publicKey.publicKeyHex);
});
}
}
exports.Proof = Proof;
Proof.nonceLen = 16;
class VerifiableCredential {
constructor() {
this.context = [VerifiableCredential.w3cContextVC];
this.id = "";
this.type = "";
this.issuer = [];
this.validFrom = "";
this.validUntil = "";
this.proof = [];
}
addContext(context) {
if (utils_1.Utils.isUndefined(this.context)) {
this.context = [context];
}
else {
this.context.push(context);
}
}
setID() {
this.id = this.genID();
}
setType(type) {
this.type = type;
}
addIssuer(issuer) {
if (utils_1.Utils.isUndefined(this.issuer)) {
this.issuer = issuer;
}
else {
this.issuer = this.issuer.concat(issuer);
}
}
setValidFrom(time) {
if (time == null) {
this.validFrom = utils_1.Utils.dateToStr(new Date());
}
else {
this.validFrom = time;
}
}
setValidUntil(time) {
this.validUntil = time;
}
addProof(proof) {
if (utils_1.Utils.isUndefined(this.proof)) {
this.proof = [proof];
}
else {
this.proof.push(proof);
}
}
setCredentialSubject(sub) {
this.credentialSubject = sub;
}
toString() {
// TODO: 优化context 替换方法
let str = JSON.stringify(this, utils_1.Utils.replacerTrimEmpty).replace("context", "@context");
let obj = JSON.parse(str);
// sort by key
obj = utils_1.Utils.sortObject(obj);
return JSON.stringify(obj);
}
copy() {
return utils_1.Utils.clone(this);
}
// TODO: 优化实现
static parseFrom(vcStr) {
let vc = new VerifiableCredential();
vc.parseFrom(vcStr);
return vc;
}
parseFrom(vcStr) {
vcStr = vcStr.replace("@context", "context");
let vcOjb = JSON.parse(vcStr);
let vc = Object.assign(this, vcOjb);
// TODO: 优化
// 内部嵌套的object单独处理
if (!utils_1.Utils.isNull(vc.credentialSubject)) {
let subject = Object.assign(new CredentialSubject(), vc.credentialSubject);
if (!utils_1.Utils.isNull(subject.claims)) {
for (let i = 0; i < subject.claims.length; i++) {
// subject.claims[i][]
if (!utils_1.Utils.isUndefined(subject.claims[i].meta) && !utils_1.Utils.isUndefined(subject.claims[i].meta.type)) {
let claimType = subject.claims[i].meta.type;
let claimObj = registration_1.newClaim(claimType);
if (claimObj == null) {
throw new errors_1.SdkError("no claim registered for " + claimType);
}
subject.claims[i] = claimObj.parseFrom(JSON.stringify(subject.claims[i]));
// subject.claims[i] = Object.assign(claimObj, subject.claims[i])
}
else {
throw new errors_1.SdkError("no meta type field");
}
}
}
vc.credentialSubject = subject;
}
if (!utils_1.Utils.isNull(vc.proof)) {
for (let i = 0; i < vc.proof.length; i++) {
vc.proof[i] = Object.assign(new Proof(), vc.proof[i]);
}
}
return vc;
}
genID() {
/*
id的生成需保证唯一性,可采用如下步骤生成:
1. 上述VC的整体内容去除id字段和proof字段以及其他空值字段得到VC_1,如果存在credentialSubject.claims.privateData字段,则进行下一步,否则跳到第7步
2. 将credentialSubject.claims.privateData字段内的所有属性分别以"key=value"的形式序列化为字符串data_str_n(如果value为json结构,需保证json结构的有序性)
3. 将第2步每个属性得到的字符串,做base64(sha256(data_str_n)),得到一个hash列表[hash_1, hash_2, ..]
4. 将第3步得到的hash列表做升序排序后,拼接成一个字符串hash_str="hash_1hash_2"
5. 将第4步拼接的字符串做base64(sha256(hash_str))得到data_hash_str
6. 将第5步得到的data_hash_str替换第1步中VC_1中的credentialSubject.claims.privateHash字段,并清除credentialSubject.claims.privateData字段,得到新的VC_2
7. 将VC_2中json字段按key升序排序后,序列化为字符串,保证序列化后一致性,得到字符串id_str
8. 将id_str做base58(sha256(id_str)),得到id_hash_str记为最终的VC的id
*/
let vcCopy = this.copy();
vcCopy.genIdPrepare();
if (!utils_1.Utils.isNull(vcCopy.credentialSubject)) {
if (!utils_1.Utils.isNull(vcCopy.credentialSubject.claims)) {
for (let i = 0; i < vcCopy.credentialSubject.claims.length; i++) {
vcCopy.credentialSubject.claims[i].preparePrivateHash();
}
}
}
let vcStr = vcCopy.toString();
return bs58.encode(utils_1.Utils.sha256(vcStr)).toString();
}
checkEmpty() {
if (utils_1.Utils.isNull(this.context)) {
return new errors_1.SdkError("context is null");
}
if (utils_1.Utils.isNull(this.id)) {
return new errors_1.SdkError("id is null");
}
if (utils_1.Utils.isNull(this.issuer)) {
return new errors_1.SdkError("issuer is null");
}
if (utils_1.Utils.isNull(this.credentialSubject)) {
return new errors_1.SdkError("credential subject is null");
}
return null;
}
checkType() {
if (this.type == VCType.vcTypeProof || this.type == VCType.vcTypeProfile) {
return null;
}
return new errors_1.SdkError("type is not supported");
}
checkTime() {
if (this.validFrom == "") {
return new errors_1.SdkError("valid from is null");
}
let err = utils_1.Utils.checkTimeFormat(this.validFrom);
if (err != null) {
return err;
}
if (!utils_1.Utils.isNull(this.validUntil)) {
err = utils_1.Utils.checkTimeFormat(this.validUntil);
if (err != null) {
return err;
}
}
return null;
}
checkId() {
let expectId = this.genID();
if (this.id != expectId) {
return new errors_1.SdkError("id is err, expect: " + expectId + " get:" + this.id);
}
return null;
}
checkCredentialSubject() {
if (utils_1.Utils.isNull(this.credentialSubject)) {
return new errors_1.SdkError("credentialSubject is null");
}
if (utils_1.Utils.isNull(this.credentialSubject.id)) {
return new errors_1.SdkError("credentialSubject id is null");
}
if (utils_1.Utils.isNull(this.credentialSubject.claims)) {
return new errors_1.SdkError("credentialSubject claims is null");
}
for (let i in this.credentialSubject.claims) {
let err = this.credentialSubject.claims[i].validateBasic();
if (err != null) {
return err;
}
}
return null;
}
validateBasic() {
let err = this.checkEmpty();
if (err != null) {
return err;
}
err = this.checkType();
if (err != null) {
return err;
}
err = this.checkTime();
if (err != null) {
return err;
}
err = this.checkId();
if (err != null) {
return err;
}
err = this.checkCredentialSubject();
if (err != null) {
return err;
}
return null;
}
static creatVC(issuers, subjectIds, claims, validYears) {
if (utils_1.Utils.isNull(issuers) || utils_1.Utils.isNull(subjectIds) || utils_1.Utils.isNull(validYears)) {
throw new errors_1.SdkError("param is null");
}
let vc = new VerifiableCredential();
vc.setType(VCType.vcTypeProof);
let curDate = new Date();
vc.setValidFrom(utils_1.Utils.dateToStr(curDate));
curDate.setFullYear(curDate.getFullYear() + validYears);
vc.setValidUntil(utils_1.Utils.dateToStr(curDate));
vc.addIssuer(issuers);
let subject = new CredentialSubject();
subject.id = subjectIds;
subject.claims = claims;
vc.setCredentialSubject(subject);
vc.setID();
let err = vc.validateBasic();
if (err != null) {
throw err;
}
return vc;
}
genProof(issuerDoc, issuerPrivateKey, useNonce) {
return __awaiter(this, void 0, void 0, function* () {
let proof = new Proof();
let publicKey = issuerDoc.getPublicKeyByKeyHex(issuerPrivateKey.publicKeyHex);
if (publicKey == null) {
throw new errors_1.SdkError("get no pubkey for " + issuerPrivateKey.publicKeyHex);
}
if (!issuerDoc.hasAuthenticationPerm(publicKey)) {
throw new errors_1.SdkError("pubkey" + issuerPrivateKey.publicKeyHex + "has no perm for authentication");
}
let keyGen = crypto.keyGenerator(issuerPrivateKey.algo);
let signData = this.id;
if (useNonce) {
proof.genNonce();
signData = signData + proof.nonce;
}
let signValue = yield keyGen.sign(signData, issuerPrivateKey.privateKeyHex);
proof.type = crypto.getVerifyTypeByAlgo(issuerPrivateKey.algo);
proof.created = utils_1.Utils.getCurTimeStr();
proof.creator = did_1.DIDUrl.makeKeyUrl(issuerDoc.id, publicKey.id);
proof.signatureValue = signValue;
return proof;
});
}
issue(issuerDoc, issuerPrivateKey) {
return __awaiter(this, void 0, void 0, function* () {
let err = this.validateBasic();
if (err != null) {
throw err;
}
let proof = yield this.genProof(issuerDoc, issuerPrivateKey, true);
this.addProof(proof);
});
}
signVerifySingle(issuerDoc) {
return __awaiter(this, void 0, void 0, function* () {
if (utils_1.Utils.isNull(this.proof)) {
return new errors_1.SdkError("proof is null");
}
let err = this.validateBasic();
if (err != null) {
return err;
}
let isIssuer = false;
for (let i in this.issuer) {
if (this.issuer[i] == issuerDoc.id) {
isIssuer = true;
break;
}
}
if (!isIssuer) {
return new errors_1.SdkError("not issuer");
}
for (let i in this.proof) {
if (this.proof[i].creator.startsWith(issuerDoc.id)) {
let publicKey = issuerDoc.getPublicKeyByKeyUrl(this.proof[i].creator);
if (publicKey == null) {
return new errors_1.SdkError("not found public key for: " + this.proof[i].creator);
}
if (!issuerDoc.hasAuthenticationPerm(publicKey)) {
return new errors_1.SdkError(this.proof[i].creator + " no perm for authentication");
}
if (!(yield this.proof[i].signVerify(this.id, publicKey))) {
return new errors_1.SdkError("proof sign verify fail");
}
}
}
return null;
});
}
makeSecretKey(randomKey, dstDoc, publicKeyId = "") {
return __awaiter(this, void 0, void 0, function* () {
let didSecret = [];
for (let pi in dstDoc.publicKey) {
if (publicKeyId.length > 0 && dstDoc.publicKey[pi].id != publicKeyId) {
continue;
}
let keyStr = yield dstDoc.publicKey[pi].encrypt(randomKey);
let secretKey = new common_1.SecretKey();
secretKey.pubKeyHex = dstDoc.publicKey[pi].publicKeyHex;
secretKey.pubKeyId = dstDoc.publicKey[pi].id;
secretKey.type = dstDoc.publicKey[pi].type;
secretKey.key = keyStr;
didSecret.push(secretKey);
}
return didSecret;
});
}
encrypt(level, dstDocs) {
return __awaiter(this, void 0, void 0, function* () {
let vcCipher = VerifiableCredentialCipher.parseFrom(this.toString());
if (vcCipher.validateBasic()) {
throw new errors_1.SdkError("validate basic fail");
}
let randomKey = crypto.Aes.genRandomKey(level);
for (let i in vcCipher.credentialSubject.claims) {
let err = vcCipher.credentialSubject.claims[i].privateDataEncrypt(randomKey);
if (err != null) {
throw err;
}
vcCipher.credentialSubject.claims[i].removePrivateData();
}
// 添加加密信息;默认保存所有公钥加密的key
for (let i in dstDocs) {
// let didSecret = await this.makeSecretKey(randomKey, dstDocs[i])
// for (let pi in dstDocs[i].publicKey) {
// let keyStr = await dstDocs[i].publicKey[pi].encrypt(randomKey)
// let secretKey = new SecretKey()
// secretKey.pubKeyHex = dstDocs[i].publicKey[pi].publicKeyHex
// secretKey.pubKeyId = dstDocs[i].publicKey[pi].id
// secretKey.type = dstDocs[i].publicKey[pi].type
// secretKey.key = keyStr
// didSecret.push(secretKey)
// }
// @ts-ignore
vcCipher.cipherInfo.secretKey[dstDocs[i].id] = yield this.makeSecretKey(randomKey, dstDocs[i]);
}
return vcCipher;
});
}
genIdPrepare() {
this.id = "";
this.proof = [];
}
// 根据原凭证(隐私信息已加密),从最小化披露凭证验证披露内容的正确性
verifyMinDisclosure(disclosedClaims) {
let err = this.validateBasic();
if (null != err) {
return err;
}
let vcCopy = this.copy();
vcCopy.genIdPrepare();
if (!utils_1.Utils.isNull(vcCopy.credentialSubject)) {
if (!utils_1.Utils.isNull(vcCopy.credentialSubject.claims)) {
if (vcCopy.credentialSubject.claims.length != disclosedClaims.length) {
return new errors_1.SdkError("claim num does not match, disclosure:" + disclosedClaims.length +
" origin:" + vcCopy.credentialSubject.claims.length);
}
for (let i = 0; i < vcCopy.credentialSubject.claims.length; i++) {
if (vcCopy.credentialSubject.claims[i].type() != disclosedClaims[i].type) {
return new errors_1.SdkError("claim type does not match, disclosure:" + disclosedClaims[i].type +
" origin:" + vcCopy.credentialSubject.claims[i].type() + " index: " + i);
}
vcCopy.credentialSubject.claims[i].preparePrivateHashFromDiscloseData(disclosedClaims[i]);
}
}
}
let vcStr = vcCopy.toString();
let newId = bs58.encode(utils_1.Utils.sha256(vcStr)).toString();
if (newId != this.id) {
return new errors_1.SdkError("id does not match, expect: " + this.id + " get: " + newId);
}
return null;
}
}
exports.VerifiableCredential = VerifiableCredential;
VerifiableCredential.w3cContextVC = "https://www.w3.org/2018/credentials/v1";
class VerifiableCredentialCipher extends VerifiableCredential {
constructor() {
super(...arguments);
this.cipherInfo = new cipher_1.CipherInfo();
}
static parseFrom(vcStr) {
let vc = new VerifiableCredentialCipher();
vc.parseFrom(vcStr);
return vc;
}
validateBasic() {
let err = this.checkEmpty();
if (err != null) {
return err;
}
err = this.checkType();
if (err != null) {
return err;
}
err = this.checkTime();
if (err != null) {
return err;
}
err = this.checkCredentialSubject();
if (err != null) {
return err;
}
return null;
}
copy() {
return utils_1.Utils.clone(this);
}
decrypt(did, didPrivateKey) {
return __awaiter(this, void 0, void 0, function* () {
let err = this.validateBasic();
if (err != null) {
throw err;
}
let secretKey = this.getSecretKey(did, didPrivateKey.publicKeyHex);
if (null == secretKey) {
throw new errors_1.SdkError("no secretKey found for " + did);
}
let randomKey = yield this.decryptRandomKey(secretKey, didPrivateKey);
if (randomKey.length == 0) {
throw new errors_1.SdkError("not found secretkey for pubkey: " + didPrivateKey.publicKeyHex);
}
let vc = this.decryptFromRandomKey(randomKey);
err = vc.validateBasic();
if (err != null) {
throw err;
}
return vc;
});
}
decryptFromSecretKey(secretKey, didPrivateKey) {
return __awaiter(this, void 0, void 0, function* () {
let randomKey = yield this.decryptRandomKey(secretKey, didPrivateKey);
return this.decryptFromRandomKey(randomKey);
});
}
decryptFromRandomKey(randomKey) {
if (randomKey.length == 0) {
throw new errors_1.SdkError("randomKey is null");
}
let vcCopy = this.copy();
for (let i in vcCopy.credentialSubject.claims) {
let err = vcCopy.credentialSubject.claims[i].privateDataDecrypt("", randomKey);
if (err != null) {
throw err;
}
vcCopy.credentialSubject.claims[i].removePrivateCipher();
}
vcCopy.cipherInfo = null;
return VerifiableCredential.parseFrom(vcCopy.toString());
}
decryptRandomKey(secretKey, didPrivateKey) {
return __awaiter(this, void 0, void 0, function* () {
if (secretKey.pubKeyHex != didPrivateKey.publicKeyHex) {
throw new errors_1.SdkError("public key does not match");
}
let keyGen = crypto.keyGenerator(didPrivateKey.algo);
return yield keyGen.decrypt(Buffer.from(secretKey.key, 'base64').toString("hex"), didPrivateKey.privateKeyHex);
});
}
getSecretKey(did, publicKeyHex) {
if (null == this.cipherInfo) {
// throw new SdkError("no cipherInfo in vc")
return null;
}
let secretKeys = this.cipherInfo.secretKey[did];
if (utils_1.Utils.isUndefined(secretKeys)) {
// throw new SdkError("no cipher key for " + did)
return null;
}
for (let i in secretKeys) {
if (secretKeys[i].pubKeyHex == publicKeyHex) {
return secretKeys[i];
}
}
return null;
}
genIdPrepare() {
this.id = "";
this.proof = [];
this.cipherInfo = null;
}
}
exports.VerifiableCredentialCipher = VerifiableCredentialCipher;
//# sourceMappingURL=vc.js.map