[2024/2/17 update]
credo-ts libは、openid-credential-offerに含まれる"credentials":["OpenBadgeCredential"]をもとにissuerのmetadataを検索する。なのでmetadataに含まれる値、すなわち"credentials":["OpenBadgeCredentialJwt"]に変更
はじめに
最近EUDI walletの話をよく聞くようになってきた。EUDIとは欧州デジタルIDウォレットのことらしい(参考)。IDウォレットではOpenID4VCI/OpenID4VP+SIOPv2を使って、VCの受け渡しをするようである。
図にすると以下のような感じなる(引用元:globalPlatform)
このOpenID4VCIを試す事ができるものがないか探してみると「Sphereon Wallet」を見つけることが出来た。さらに都合のいいことにios app storeで配付されていた(https://github.com/Sphereon-Opensource/ssi-mobile-wallet)。
またOpenID4VCI/OpenID4VPをしゃべるdemoサーバも用意されていて、以下のサイトで試す事が出来る。
このプロトコルの中身を見ながら、最終的には簡易的なopenID4VCIをしゃべるサーバをnodejsで作ってみようと思う。
OpenID4VCIの動き
1) QRコードを発行
demoサーバにブラウザでアクセスし、[manually fill out details]を選択して、適当なfirstname/lastname, emailを入力してcontinueをクリック。その後select credentials画面が出るので「University degree」を選択すると、下記のようなQRコードが発行される
QRコードの中身は以下のようになっている。
openid-credential-offer://?credential_offer={
"grants":{
"urn:ietf:params:oauth:grant-type:pre-authorized_code":{
"pre-authorized_code":"AZcbrjCMa9StPQxVQRQAf",
"user_pin_required":false
}
},
"credentials":["OpenBadgeCredential"],
"credential_issuer":"https://ssi.sphereon.com/pf3"
}
2) Credential Issuer Metadata の用意
QRコードを読み取ったスマホは、issuerのMetadata情報(issuerはどのURLでCredentialsを発行していて、どこでtokenをもらえるかなどが書かれたJSON)を取得しにくる。demoサーバの場合、https://ssi.sphereon.com/pf3/.well-known/openid-credential-issue
がそのURLである。以下のようなjsonが用意されている。
{
"credential_issuer":"https://ssi.sphereon.com/pf3",
"credential_endpoint": "https://ssi.sphereon.com/pf3/credentials",
"token_endpoint": "https://ssi.sphereon.com/pf3/token",
"display":[{name":"Sphereon","description":"Sphereon JFF Plugfest3 Issuer"}],
"credentials_supported":[
{..}
{..}
{
"display":[
{
"name":"Example University Degree",
"description":"JFF Plugfest 3 OpenBadge (JWT)",
"text_color":"#FFFFFF",
"background_color":"#1763c1",
"logo":{
"url":"https://w3c-ccg.github.io/vc-ed/plugfest-1-2022/images/JFF_LogoLockup.png",
"alt_text":"Red, magenta and yellow vertical lines with 3 black dots and the text JFF, depicting the Jobs For the Future logo."
}
},
{
"locale":"en-US",
"name":"Example University Degree",
"description":"JFF Plugfest 3 OpenBadge (JWT)"",
"text_color":"#FFFFFF",
"background_color":"#1763c1",
"logo":{
"url":"https://w3c-ccg.github.io/vc-ed/plugfest-1-2022/images/JFF_LogoLockup.png",
"alt_text":"Red, magenta and yellow vertical lines with 3 black dots and the text JFF, depicting the Jobs For the Future logo."
}
}
],
"id":"OpenBadgeCredentialJwt",
"types":["VerifiableCredential","OpenBadgeCredential"],
"format":"jwt_vc_json",
"cryptographic_binding_methods_supported":["did:key"],
"cryptographic_suites_supported":["EdDSA"]
}
],
"credential_supplier_config":{
"templates_base_dir":"templates/sphereon",
"template_mappings":[
{..},
{..},
{
"credential_types":["OpenBadgeCredential"],
"template_path":"OpenBadgeCredential.hbs",
"format":"jwt_vc_json"
}
]
}
};
3) tokenを発行
QRコードに書かれたpre-authorized_code
を用いて、スマホはdemoサーバからtokenを取得する。curlで書くと以下になる
curl https://ssi.sphereon.com/pf3/token -X POST -H "Content-Type: application/json" -d @data1.txt
{
"client_id": "sphereon:ssi-wallet",
"grant_type": "urn:ietf:params:oauth:grant-type:pre-authorized_code",
"pre-authorized_code": "AZcbrjCMa9StPQxVQRQAf"
}
POSTに成功すると、以下のような値が返ってくる
{
"access_token":"eyJ0eXAiOiJKV1....6Vg8v4xrWbxA",
"token_type":"bearer",
"expires_in":300,
"c_nonce":"48aa8701-dbec-4e56-a49e-2d9bf83f7ec3",
"c_nonce_expires_in":300000,
"authorization_pending":false,
"interval":300000
}
access_tokeの中身は、以下のようなJWTになっている。
{"typ":"JWT", "alg":"ES256K"}.
{
"iat":1704349765484, "exp":300,
"iss":"https://ssi.sphereon.com/pf3",
"preAuthorizedCode":"AZcbrjCMa9StPQxVQRQAf"
}.signature
4) VCを発行
スマホは、metadata情報にkhttps://ssi.sphereon.com/pf3/credentials
に、3)で取得したaccess_tokenを用いてアクセスすることでVCを取得することができる。curlで書くと以下になる。
curl https://ssi.sphereon.com/pf3/credentials -X POST -H "Content-Type: application/json" -H "authorization: Bearer eyJ0eXAiOiJKV1....6Vg8v4xrWbxA" -d @data2.txt
{
"types": [ "OpenBadgeCredential" ],
"format": "jwt_vc_json",
"proof": {
"proof_type": "jwt",
"jwt": "eyJhbGciOiJFUzI1Nksi....7GootXw"
}
}
このdata2.txtに書かれているjwtをbase64デコードすると中身は以下の通りである。token取得時にdemoサーバから発行されたc_nonceや、issuer endpoint:https://ssi.sphereon.com/pf3
を用いて生成されている。
{
"alg":"ES256K",
"typ":"openid4vci-proof+jwt",
"kid":"did:key:zQ3shdST2uDCn8eNRhTNYs3Yh6dDx9aYQSV7gYyFfhc9kCxnm"}.
{
"iat":1704349849,"exp":1704353449,
"aud":"https://ssi.sphereon.com/pf3",
"nonce":"48aa8701-dbec-4e56-a49e-2d9bf83f7ec3",
"iss":"sphereon:ssi-wallet",
"jti":"C289A6F6-7731-49B6-BC4A-CF8247369D2D"
}.signature
POSTが成功すると、VC credentialが入ったJSONが返ってくる。
{
"credential":"eyJhbGciOiJFUzI1NiIsInR5c....kX8s64CusI6Tz-ZtJkQ",
"format":"jwt_vc_json",
"c_nonce":"1fbd4fa8-cd62-4b90-a07f-9201d97f9951",
"c_nonce_expires_in":300000
}
credentialの中身をデコードすると、以下のようになっている。
{"alg":"ES256","typ":"JWT"}.
{
"exp":1704954740,
"vc":{
"@context":["https://www.w3.org/2018/credentials/v1","https://purl.imsglobal.org/spec/ob/v3p0/context.json"],
"type":["VerifiableCredential","OpenBadgeCredential"],
"credentialSubject":{
"type":["AchievementSubject"],
"achievement":{
"id":"urn:uuid:ac254bd5-8fad-4bb1-9d29-efd938536926",
"type":["Achievement"],
"name":"JFF x vc-edu PlugFest 3 Interoperability",
"description":".........<snip>.......",
"criteria":{
"type":"Criteria",
"narrative":".........<snip>......."
},
"image":{
"id":"https://w3c-ccg.github.io/vc-ed/plugfest-3-2023/images/JFF-VC-EDU-PLUGFEST3-badge-image.png",
"type":"Image"
}
},
"id":"did:key:zQ3shdST2uDCn8eNRhTNYs3Yh6dDx9aYQSV7gYyFfhc9kCxnm"
}
},
"@context":["https://www.w3.org/2018/credentials/v1","https://purl.imsglobal.org/spec/ob/v3p0/context.json"],
"type":["VerifiableCredential","OpenBadgeCredential"],
"expirationDate":"2024-01-11T06:32:20.016Z",
"name":"JFF x vc-edu PlugFest 3 Interoperability",
"issuer":{
"type":["Profile"],
"name":"Jobs for the Future (JFF)",
"url":"https://www.jff.org/",
"image":"https://w3c-ccg.github.io/vc-ed/plugfest-1-2022/images/JFF_LogoLockup.png",
"id":"did:jwk:eyJhbGciOiJ....M4In0"
},
"credentialSubject":{
"type":["AchievementSubject"],
"achievement":{
"id":"urn:uuid:ac254bd5-8fad-4bb1-9d29-efd938536926",
"type":["Achievement"],
"name":"JFF x vc-edu PlugFest 3 Interoperability",
"description":".........<snip>.......",
"criteria":{
"type":"Criteria",
"narrative":".........<snip>......."
},
"image":{
"id":"https://w3c-ccg.github.io/vc-ed/plugfest-3-2023/images/JFF-VC-EDU-PLUGFEST3-badge-image.png",
"type":"Image"
}
},
"id":"did:key:zQ3shdST2uDCn8eNRhTNYs3Yh6dDx9aYQSV7gYyFfhc9kCxnm"
},
"issuanceDate":"2024-01-04T06:32:20.016Z",
"sub":"did:key:zQ3shdST2uDCn8eNRhTNYs3Yh6dDx9aYQSV7gYyFfhc9kCxnm",
"nbf":1704349940,
"iss":"did:jwk:eyJhbGciOiJ....M4In0"
}.signature
簡易サーバで動作確認
demoサーバ:https://ssi.sphereon.com/demo/issuer/
の動きをnodejsで実装し、SSI sphereon walletにVCを発行してみる
1. index.html, server.js, package.jsを以下のフォルダ構成のように配置する
├── server.js
├── package.json
└── views/
├── index.html
index.htmlのコードを見る
<!DOCTYPE html>
<html lang='en'>
<head><meta charset='utf-8'></head>
<body>
<div align=center>
<div id="title">[ngrok openBadge issue]</div><br>
<div id="parameter">University:<input type="text" id="university" value="xx University"><br>
achievement:<input type="text" id="achievement" value="Math."><br>
<input type="button" value="発行" onclick="issuerQR()"/><br>
<p><div id=qrmsg></div>
<p><div id=qr></div>
</div>
</body>
<script type="text/javascript">
function getData(url, type, cb){
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
switch(xhr.readyState){
case 4:
if (xhr.status == 200||xhr.status ==304){
cb(xhr.response)
}
}
};
xhr.open("GET", url, false);
xhr.setRequestHeader('Content-Type', type);
xhr.send('');
}
function sendData(url, data, type, cb){
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
switch(xhr.readyState){
case 4:
if (xhr.status == 200||xhr.status ==304){
console.log("send success");
cb(xhr.response)
}
}
};
xhr.open("POST", url, false);
xhr.setRequestHeader('Content-Type', type);
xhr.send(data);
}
function issuerQR() {
let setting={
"clientName": "ngrok openBadge issue",
"type": "openBadge",
"claims": {"university": document.getElementById('university').value, "achievement": document.getElementById('achievement').value}
}
sendData("./.issuer_init", JSON.stringify(setting), 'application/json',
function(data){
target = document.getElementById("qr");
target.innerHTML = "<img src="+JSON.parse(data).qrcode+" />";
let interval_id = setInterval(
function(){
console.log("request check status");
istatusCheck(interval_id, JSON.parse(data).sess);
}, 3000);
}
);
}
function istatusCheck(clearid, sess){
getData("./.istatus/"+sess, 'application/json',
function(data){
console.log("status:"+JSON.parse(data).status);
if (JSON.parse(data).status == "request_retrieved"){
let msg = "QR code sccand. waiting..";
if (typeof JSON.parse(data).pin !=="undefined"){ msg +="PIN:"+JSON.parse(data).pin;}
document.getElementById("qrmsg").innerHTML =msg;
}else if(JSON.parse(data).status == "issuance_successful"){
document.getElementById("qrmsg").innerHTML = "finish";
document.getElementById("qr").innerHTML = "";
clearInterval(clearid);
}
}
);
}
</script>
</html>
server.jsのコードを見る
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const http = require('http');
const QRCode = require('qrcode');
const crypto = require('crypto');
const elliptic = require('elliptic');
const didJWT = require('did-jwt');
const fs = require('fs');
const uuid = require('uuid');
const base58 = require('bs58');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use('/', express.static(__dirname + '/views/'));
//*******setting********
const port=8888;
let hostname = '127.0.0.1'+":"+port;
if (process.argv.length > 2){ hostname = process.argv[2];}
//**********************
//*******global variable*******
let DID="did:web:"+hostname;
let task= new Array();
let taskstatus= new Array();
let storageJson=[];
let didwebkey, didkey, didjwk;
const main = async()=>{
didwebkey = await init(hostname);
didkey = a_did(didwebkey, "key");
didjwk = a_did(didwebkey, "jwk");
console.log("\n " + DID)
console.log(" " + didkey)
console.log(" " + didjwk+"\n")
}
main();
//******************************
//*******web server*******
const httpServer = http.createServer(app);
const server = httpServer.listen(port, () => {
console.log('\n web page start: listening on port %s...', server.address().port);
});
app.get('/.*', async function(req,res){
console.log("\n====GET====");
console.log("cmd:"+JSON.stringify(req.params));
console.log("query:"+JSON.stringify(req.query));
console.log("headers:"+JSON.stringify(req.headers));
console.log(req.body);
console.log("====GET====");
let ret="";
let cmd = req.params[0].split('/');
if (cmd[0] =="pf" && cmd[1] ==".well-known" && cmd[2] == "openid-credential-issuer"){
console.log("access to openid-credential-issuer")
let _metadata = await metadata(hostname, "");
res.send(_metadata);
} else if (cmd[0] =="istatus"){
ret={"status":taskstatus[cmd[1]].vcstatus};
res.send(JSON.stringify(ret));
}else{
res.send(ret);
}
});
app.post('/.*', async function(req,res){
console.log("\n====POST====");
console.log("cmd:"+JSON.stringify(req.params));
console.log("query:"+JSON.stringify(req.query));
console.log("headers:"+JSON.stringify(req.headers));
console.log(req.body);
console.log("====POST====");
let ret="";
let cmd = req.params[0].split('/');
if (cmd[0] =="issuer_init"){
let preAuthorizedCode = await issuerInit(req.body);
let offer = {
"grants":{
"urn:ietf:params:oauth:grant-type:pre-authorized_code":{
"pre-authorized_code":preAuthorizedCode,
"user_pin_required":false
}
},
"credentials":["OpenBadgeCredentialJwt"],
"credential_issuer":"https://"+hostname+"/.pf"
}
let inviteUrl = "openid-credential-offer://?credential_offer="+encodeURIComponent(JSON.stringify(offer));
QRCode.toDataURL(inviteUrl, function (err, url) {
let sendData = {"qrcode":url, "sess":task[preAuthorizedCode].sess, "url":task[preAuthorizedCode].serviceEndpoint};
res.send(sendData);
});
}else if(cmd[0] == "pf" && cmd[1]=="token"){
let token={};
let authcode = req.body['pre-authorized_code'];
if (typeof task[authcode] !== "undefined"){
console.log("authenticated!!");
task[authcode].client_id = req.body.client_id;
task[authcode].nonce = uuid.v4();
let atoken = await makeToken(authcode, 300);
token = {
"access_token":atoken,
"token_type":"bearer",
"expires_in":300,
"c_nonce":task[authcode].nonce,
"c_nonce_expires_in":300000,
"authorization_pending":false,
"interval":300000
}
res.send(JSON.stringify(token))
}else{
console.log("unauthenticated!!:"+ req.body['pre-authorized_code']);
res.send(JSON.stringify(token))
}
}else if (cmd[0] == "pf" && cmd[1]=="credentials"){
let credJson={}
let auth = req.headers.authorization;
let token = didJWT.decodeJWT(auth.split(" ")[1]);
if (typeof task[token.payload.preAuthorizedCode] !== "undefined"){
let _jwt = didJWT.decodeJWT(req.body.proof.jwt);
if (task[token.payload.preAuthorizedCode].nonce == _jwt.payload.nonce
&& task[token.payload.preAuthorizedCode].serviceEndpoint == _jwt.payload.aud){
let _kid = _jwt.header.kid;
let cred = await makecred(
_kid, uuid.v4(),
Math.floor( new Date().getTime() / 1000 ) , 30*24*60*60, // from now to 1 month
task[token.payload.preAuthorizedCode].sess
);
let credJson = {
"credential":cred,
"format":"jwt_vc_json",
"c_nonce":uuid.v4(),
"c_nonce_expires_in":300000
}
taskstatus[task[token.payload.preAuthorizedCode].sess].vcstatus="issuance_successful";
res.send(JSON.stringify(credJson))
}else{
console.log("bad nonce or bad aud")
res.send(JSON.stringify(credJson))
}
}else{
console.log("unauthentication: bad token")
res.send(JSON.stringify(credJson))
}
}else{
res.send(null);
}
});
//////////////////////
// support function
///////////////////////
async function issuerInit(_body){
let preAuthorizedCode = base58.encode(crypto.randomBytes(18));
console.log(preAuthorizedCode)
let sess = crypto.randomBytes(8).toString("hex")
let serviceEndpoint = "https://"+hostname+"/.pf";
task[preAuthorizedCode]= {
"claims": _body.claims,
"preAuthorizedCode":preAuthorizedCode,
"serviceEndpoint":serviceEndpoint,
"isskey": didwebkey,
"sess": sess
};
taskstatus[sess]={"vcstatus":"wait", "claims":_body.claims};
return preAuthorizedCode;
}
async function metadata(_hostname, _sess){
let _endpoint = "https://"+_hostname+"/.pf";
let ret = {
"credential_issuer":_endpoint,
"credential_endpoint": _endpoint + "/credentials",
"token_endpoint": _endpoint + "/token",
"display":[{"name":"ngrok:"+hostname,"description":"ngrok Issuer:"+hostname}],
"credentials_supported":[{
"display":[
{
"name":"xx university",
"description":"OpenBadge (JWT)",
"text_color":"#FFFFFF",
"background_color":"#1763c1",
"logo":{
"url":"https://w3c-ccg.github.io/vc-ed/plugfest-1-2022/images/JFF_LogoLockup.png",
"alt_text":"Red, magenta and yellow vertical lines with 3 black dots and the text JFF, depicting the Jobs For the Future logo."
}
},
{
"locale":"en-US",
"name":"xx university",
"description":"OpenBadge (JWT)",
"text_color":"#FFFFFF",
"background_color":"#1763c1",
"logo":{
"url":"https://w3c-ccg.github.io/vc-ed/plugfest-1-2022/images/JFF_LogoLockup.png",
"alt_text":"Red, magenta and yellow vertical lines with 3 black dots and the text JFF, depicting the Jobs For the Future logo."
}
}
],
"id":"OpenBadgeCredentialJwt",
"types":["VerifiableCredential","OpenBadgeCredential"],
"format":"jwt_vc_json",
"cryptographic_binding_methods_supported":["did:key"],
"cryptographic_suites_supported":["EdDSA"]
}],
"credential_supplier_config":{
"templates_base_dir":"templates/sphereon",
"template_mappings":[
{
"credential_types":["OpenBadgeCredential"],
"template_path":"OpenBadgeCredential.hbs",
"format":"jwt_vc_json"
}
]
}
};
return ret;
}
async function makecred(_subdidkey, _uuid, _nbf, _exp, _sess){
const signer = didJWT.ES256KSigner(didJWT.hexToBytes(Buffer.from(didwebkey.privateJwk.d, 'base64').toString('hex')))
let _body = {
"vc":await makevc(_subdidkey, _uuid, taskstatus[_sess].claims.achievement),
"@context":["https://www.w3.org/2018/credentials/v1","https://purl.imsglobal.org/spec/ob/v3p0/context.json"],
"type":["VerifiableCredential","OpenBadgeCredential"],
"name": taskstatus[_sess].claims.university +"'s openBadge",
"issuer":{
"type":["Profile"],
"name":"Jobs",
"url":"https://www.jff.org/",
"image":"https://w3c-ccg.github.io/vc-ed/plugfest-1-2022/images/JFF_LogoLockup.png",
"id":didjwk
},
"credentialSubject":{
"type":["AchievementSubject"],
"achievement":{
"id":"urn:uuid:"+_uuid,
"type":["Achievement"],
"name":taskstatus[_sess].claims.university +"'s openBadge",
"description":"demo",
"criteria":{
"type":"Criteria",
"narrative":"..."
},
"image":{
"id":"https://w3c-ccg.github.io/vc-ed/plugfest-3-2023/images/JFF-VC-EDU-PLUGFEST3-badge-image.png",
"type":"Image"
}
},
"id": _subdidkey
},
"sub": _subdidkey,
"iss": didjwk,
"nbf": _nbf
}
let jwt = await didJWT.createJWT(
_body,
{ issuer: didjwk, expiresIn:_exp, signer },
{ alg: 'ES256K' , typ:'JWT'}
)
return jwt
}
async function makevc(_subdidkey, _uuid, name){
let _vc = {
"@context":["https://www.w3.org/2018/credentials/v1","https://purl.imsglobal.org/spec/ob/v3p0/context.json"],
"type":["VerifiableCredential","OpenBadgeCredential"],
"credentialSubject":{
"type":["AchievementSubject"],
"achievement":{
"id":"urn:uuid:"+_uuid,
"type":["Achievement"],
"name": name,
"image":{
"id":"https://w3c-ccg.github.io/vc-ed/plugfest-3-2023/images/JFF-VC-EDU-PLUGFEST3-badge-image.png",
"type":"Image"
}
},
"id":_subdidkey
}
};
return _vc;
}
async function init(_hostname){
let _did = "did:web:"+_hostname;
let _key = await loadDid(_did);
if (_key ==null){
console.log("\n this "+_did+"'s key not registered")
let _key = await createkey();
await saveDid(_did, _key);
return _key;
}else{
console.log("\n this "+_did+"'s key is registered")
return _key;
}
}
async function loadDid(_did){
let key = null;
try{
storageJson = JSON.parse(fs.readFileSync('./storage.json'));
storageJson.forEach((item) =>{
if (item.did == _did) key = item.key
})
return key;
}catch(err){
return null;
}
}
async function saveDid(_did, _key){
storageJson.push({did:_did, key:_key})
try{
fs.writeFileSync("./storage.json", JSON.stringify(storageJson))
}catch(err){
console.error(err);
}
}
async function createkey(){
const key = crypto.randomBytes(32).toString("hex");
const ec = new elliptic.ec('secp256k1');
const prv = ec.keyFromPrivate(key, 'hex');
const pub = prv.getPublic();
const ecjwk = {
kty:"EC",
crv:"secp256k1",
"x":pub.x.toBuffer().toString('base64'),
"y":pub.y.toBuffer().toString('base64')
};
const ecjwkpri = {
kty:"EC",
crv:"secp256k1",
"x":pub.x.toBuffer().toString('base64'),
"y":pub.y.toBuffer().toString('base64'),
"d":prv.getPrivate().toBuffer().toString('base64')
};
let _key={"publicJwk":ecjwk,"privateJwk":ecjwkpri};
return _key;
}
function a_did(key, method){
let ret = "";
if (method == "key"){
if (key.publicJwk.kty == "EC" && key.publicJwk.crv=="secp256k1"){
let yhex = Buffer.from(key.publicJwk.y, 'base64').toString('hex')
if ((parseInt(yhex.slice( -1 ), 16) %2)==1){
parity_flag = "03";
}else{
parity_flag = "02";
}
let tmp = "E701" + parity_flag + Buffer.from(key.publicJwk.x, 'base64').toString('hex');
ret = "did:key:z"+base58.encode(Buffer.from(tmp, 'hex'));
}
}else if(method == "jwk"){
if (key.publicJwk.kty == "EC" && key.publicJwk.crv=="secp256k1"){
let jwt = {"alg":"ES256K","use":"sig","kty":"EC","crv":"secp256k1","x":key.publicJwk.x.replaceAll("=",""),"y":key.publicJwk.y.replace("=", "")}
ret = "did:jwk:"+Buffer.from(JSON.stringify(jwt)).toString('base64').replaceAll("=", "");
}
}else{
}
return ret;
}
async function makeToken(authcode, exp){
const signer = didJWT.ES256KSigner(didJWT.hexToBytes(Buffer.from(task[authcode].isskey.privateJwk.d, 'base64').toString('hex')))
let jwt = await didJWT.createJWT(
{ preAuthorizedCode: authcode},
{ issuer: task[authcode].serviceEndpoint, expiresIn:exp, signer },
{ alg: 'ES256K' , typ:'JWT'}
)
return jwt
}
package.jsonのコードを見る
{
"dependencies": {
"body-parser": "^1.20.2",
"bs58": "^5.0.0",
"did-jwt": "^7.4.7",
"elliptic": "^6.5.4",
"express": "^4.18.2",
"qrcode": "^1.5.3",
"uuid": "^9.0.1"
}
}
2. 関連モジュールをインストールする
> npm install
3. サーバに割り当てるドメイン名を取得
> ngrok http 8888
Region United States (us)
Web Interface http://127.0.0.1:4040
Forwarding http://57dd-180-53-77-xxx.ngrok-free.app -> http://localhost:8888
Forwarding https://57dd-180-53-77-xxx.ngrok-free.app -> http://localhost:8888
4. 取得したドメイン名を引数に、サーバを起動する
> nodejs server.js 57dd-180-53-77-xxx.ngrok-free.app
web page start: listening on port 8888...
this did:web:6f3d-153-219-137-227.ngrok-free.app's key is registered
did:web:57dd-180-53-77-xxx.ngrok-free.app
did:key:zQ3shq4taAWbUYmvaf4gNmfR9ZtJ1wN8nauwRxnacLQarYTCp
did:jwk:eyJhbGciOiJFUzI1NksiLCJ1c2UiOiJzaWciLCJrdHkiOiJFQyIsImNydiI6InNlY3AyNTZrMSIsIngiOiJtdTR4b0JHV0lmd0VvYkoyN3krcitMYjhCSklqaC9kU1pYRmlRaEN6clIwIiwieSI6InJ5Nkh5QmVqNENOVllveGZZYmQrOW1ibVZWU0VySEdDUUQ1NnJ5TFFrenMifQ