UNPKG

did-sdk-js

Version:

js sdk for did and vc according to mcps did spec

560 lines (559 loc) 22.7 kB
"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