阅读此文,需要具备一定的DID数字身份体系知识
目前只关注Blockchain部署方式下VP的生成过程
签发者基础身份信息:
Weid:
did:weid:iot:0x670f39b3dd92ae1cb97bc137793635c06cfed76f
Private_key: 55908972729025521152709411706112459391833725585437529733832811281080145383459
Public_key:
1049546905065573960760084439650965135523067151041694071311599899979899899017785831423352665548046543544539176746548789086180279253285007496367545719488819
1、参数解析基础校验分装
// 获取到所有的VC凭证
credentialListNode = functionArgNode.get(WeIdentityParamKeyConstant.CREDENTIAL_LIST);
// 获取到所有可披露策略
presentationPolicyENode = functionArgNode.get(WeIdentityParamKeyConstant.PRESENTATION_POLICY_E);
2、参数解析
获取签发者weid
// 获取 transactionArg 入参内容
JsonNode txnArgNode = new ObjectMapper().readTree(createPresentationFuncArgs.getTransactionArg());
// 获取 transactionArg 中 invokerWeId 签名者weid
JsonNode keyIndexNode = txnArgNode.get(WeIdentityParamKeyConstant.KEY_INDEX);
3、寻址及转化
//根据签发者weid获取到私钥文件名及存储地址
//文件名为 weid 去除前缀 did:weid:iot:后的字符串
String fileName = weId.substring(weId.lastIndexOf(":") + 1);
// check the default passphrase 从application配置文件中获取default.passphrase=private_key
String passphrase = PropertiesUtil.getProperty("default.passphrase");
// admin的私钥单独存储需特殊处理 文件名为private_key 路径为 ./
// 其他的私钥 文件名为weid 去除前缀 did:weid:iot:后的字符串 路径为 ./keys/
if (fileName.equalsIgnoreCase(passphrase)) {
fileName = WeIdentityParamKeyConstant.DEFAULT_PRIVATE_KEY_FILE_NAME;
path = ADMIN_PRIVKEY_PATH;
}
4、从私钥中获取公钥
DataToolUtils.publicKeyStrFromPrivate(new BigInteger(privateKey))
5、从公钥中获取weid(keyWeId)
convertPublicKeyToWeId(publicKey);
其中在convertPublicKeyToWeId 函数中调用address = DataToolUtils.addressFromPublic(new BigInteger(publicKey));这里address = 0x670f39b3dd92ae1cb97bc137793635c06cfed76f即为存储的文件名
private static String buildWeIdByAddress(String address) {
if (StringUtils.isEmpty(getChainId())) {
throw new WeIdBaseException("the chain Id is illegal.");
}
StringBuffer weId = new StringBuffer();
weId.append(WeIdConstant.WEID_PREFIX)// did:weid:
.append(getChainId())// iot 在weidentity配置文件中设置的chain.id
.append(WeIdConstant.WEID_SEPARATOR); // did:weid:
if (!StringUtils.contains(address, WeIdConstant.HEX_PREFIX)) {
weId.append(WeIdConstant.HEX_PREFIX);
}
weId.append(address);
return weId.toString();
}
6、身份校验
检查通过weid拿到的私钥重构的地址和weid中指向的地址是否匹配
函数:validatePrivateKeyWeIdMatches
检查weId是否与私钥匹配。
参数:
privateKey—私钥
weId——weId
返回:
匹配为True,不匹配为false
public static boolean validatePrivateKeyWeIdMatches(WeIdPrivateKey privateKey, String weId) {
boolean isMatch = false;
try {
//通过weid拿到的私钥重构的地址
String address1 = DataToolUtils.addressFromPrivate(new BigInteger(privateKey.getPrivateKey()));
//weid中指向的地址
String address2 = WeIdUtils.convertWeIdToAddress(weId);
if (address1.equals(address2)) {
isMatch = true;
}
} catch (Exception e) {
logger.error("Validate private key We Id matches failed. Error message :{}", e);
return false;
}
return isMatch;
}
7、从传入的私钥构建默认的身份验证weIdAuthentication
weIdAuthentication包含三项:WeId、AuthenticationMethodId、PrivateKey
其中WeId由经由4、5两步获取,AuthenticationMethodId由下列公式生成:
setAuthenticationMethodId=keyWeId + "#keys-" + 公钥
(公钥获取方式:DataToolUtils.publicKeyStrFromPrivate(new BigInteger(privateKey))));
8、生成challenge
指定一个随机的字母数字nonce, WeIdentity DID所有者将签署一个包含nonce的凭证,以证明该WeIdentity DID的所有权。依赖方应在挑战中包含随机字母数字(即nonce),以防止重放攻击。这也被称为动态挑战。(后文中会用到)
入参为weid和当前时间的毫秒数
challenge = Challenge.create(WeId, currentTimeMillis)
//这里的seed就是currentTimeMillis
//生成随机种子=当前时间戳+uuid(字符串拼接)
String randomSeed = seed + DataToolUtils.getUuId32()
//字符集将此randomSeed编码为字节序列,生成新的字节数组存储到Seed中。
random.setSeed(randomSeed.getBytes());
byte[] bytes = new byte[15];
random.nextBytes(bytes);
//经由base64转码封装成nonce
String nonce = Base64.encodeBase64String(bytes);
Challenge challenge = new Challenge();
challenge.setNonce(nonce);
challenge.setWeId(userWeId);
return challenge;
9、封装vp前参数校验
(1)校验PolicyPublisherWeId边界、链信息、格式合法性(WeIdUtils.isWeIdValid)
(2)遍历并校验原始校凭证:
校验Issuer边界、链信息、格式合法性(WeIdUtils.isWeIdValid)
校验凭证过期时间expirationDate
validDateExpired(args.getIssuanceDate(), args.getExpirationDate());
校验凭证内容:credentialId非空且为有效uuid、content和type非空、proof校验等
(3)根据原始证书和claimPolicy去创建选择性披露凭证
获取vc列表中的cptId组装成list,获取策略主键组装成Set(策略主键为cptId);
策略预处理:获取盐值map(来源于policy)、claim map(来源于VC)及disclosure map来源于policy)(待处理的入参fieldsToBeDisclosed转换为map)进行校验
策略校验:逐一校验vc中的claim字段与policy中的fieldsToBeDisclosed是否匹配(注意:字段策略可缺失的情况默认补全(0)即为披露,但不能全部缺失)
加盐处理:对不披露的字段进行加盐处理,具体运算规则如下: DataToolUtils.hash(String.valueOf(field) + String.valueOf(salt));
DataToolUtils.hash使用的方法为:Keccak-256 hash最终返回16进制编码的字符串。
Vp主要由verifiableCredential和proof组成,至此,原始凭证已处理完毕封装到verifiableCredential中。
10、组装vp及签名
这里我们注重关注proof的生成,首先看一个标准的vp proof
"proof": {
"created": 1711530265,//创建时间戳
"nonce": "q3MxHERSFqoFd6BA2h6R",
"signatureValue": "oEvoNZeFYNTdvC6PsBxEQUsdTk4eWJ2G3Z/yf/7Rwb8MJKbh3A0iPgSk38niHRzgls5FzsETa+QRoU49owLoBgE=",
"type": "Secp256k1",//签名算法
"verificationMethodId":// keyWeId + "#keys-" + 公钥;
"did:weid:iot:0x670f39b3dd92ae1cb97bc137793635c06cfed76f#keys- 1049546905065573960760084439650965135523067151041694071311599899979899899017785831423352665548046543544539176746548789086180279253285007496367545719488819"
},
verifySignatureFromWeId 入参:presentationE(为:VP中 proof置空的内容)、VP中的签名、签发者链上的公钥
DataToolUtils.verifySignatureFromWeId(presentationE.toRawData(), signature, weIdDocument, null);
注意我们看toRawData这段源码:
public String toRawData() {
PresentationE presentation = DataToolUtils.clone(this);
presentation.proof = null;
return DataToolUtils.serialize(presentation);
}
proof主要有created(创建时间戳)、nonce(8中已生成)、signatureValue、type(即签名类型,weid中固定为Secp256k1)、verificationMethodId(7中已生成,生成格式为:keyWeId + "#keys-" + 公钥)
PresentationE presentation) {
String proofType = CredentialProofType.ECDSA.getTypeName();
//这里PROOF_TYPE用的是Secp256k1作为签名算法
presentation.putProofValue(ParamKeyConstant.PROOF_TYPE, proofType);
//获取当前时间戳
Long proofCreated = DateUtils.getNoMillisecondTimeStamp();
presentation.putProofValue(ParamKeyConstant.PROOF_CREATED, proofCreated);
//封装数组
String methodId = weIdAuthentication.getAuthenticationMethodId();
presentation.putProofValue(ParamKeyConstant.PROOF_VERIFICATION_METHOD_ID, methodId);
presentation.putProofValue(ParamKeyConstant.PROOF_NONCE, challenge.getNonce());
//签名使用Secp256k1 sign to Signature.其中签名的范围包含:proofType、proofCreated、AuthenticationMethodId以及nonce
String signature = DataToolUtils.SigBase64Serialization(
DataToolUtils.signToRsvSignature(presentation.toRawData(),
weIdAuthentication.getWeIdPrivateKey().getPrivateKey())
);;
presentation.putProofValue(ParamKeyConstant.PROOF_SIGNATURE, signature);
}
至此VP的签发已完成。