SD-JWTをスマホで体験してみる

　最近SD-JWTの話をよく聞くようになってきた。選択的開示のためのデータフォーマット([[参考](https://qiita.com/TakahikoKawasaki/items/cd2fbbb0bfcfab729d73)])らしい。[以前試したEUDI](https://qiita.com/yk839/items/ca46f846efed9e4b4b96)で、**SD-JWTを用いたSelective Disclosureの実現を必須**としていることから注目されているようである。

　「[記事:Verifiable Credentialsの署名を壊さずに選択的開示を行うSD-JWTを体感してみる](https://idmlab.eidentity.jp/2024/01/verifiable-credentialssd-jwt.html)」によれば、Verifiable Credentailを発行するissuerが下記のようなcredentialを発行してくれるそうな。ユーザが選択開示できる対象が８個あれば、credential jwtのあとに８個分の~がつくようなフォーマットらしい。
```
eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwiYXVkIjoiczZCaGRSa3F0MyIsIm5vbmNlIjoibi0wUzZfV3pBMk1qIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE3MzU2ODk2NjEsIl9zZCI6WyI1R19rclp0TVRNUGx0QVdXTk1oZER4Sk10MTVTQTFLRmtkMmdJbFN2THRRIiwiNXA2YnQ1M0Q5cDRNZmtWWVFjaUw0cFdYQWtJanowUGJMWEdEME5IaWkydyIsIjZKdDVseHJlaVRLOG9sVFJ6eUhvSFlZUG11MEctWlZ2V3BhY1ZtWVlUOE0iLCJFdy1kS1JtQTJ1U2d3OUVtR3dOSWZRa3NQcFBJczBxQTRPRS04NkRRM19ZIiwiSHotQllQUUptSHNGbElmWl85Q0o2SWJvcWlzb1pOdHd6a2IwWjNKQWxGNCIsIkozUF9nTkEzTEVDSXpUdHRCdVg2MmNMVklDREZ6Ui1ycm9pQ1FrYkJidVEiLCJPMmJEVUhhRUxaSXN5T1hUeXV0V3VuQWdtMzdRVGtiQ1RSdzczYmk1NXhFIiwiUTR2dHJhRHZwcnFCa2pyUG9MV185RlpEZXRGS0tBR2N5YTA1ckl3cTR4ZyJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.LAlXQnPK8rOhxBhbg_FU-hiBJJqCX5CYWYTp0YcbbIxnBZPN1ZhRgE_SLh0rEg1L559GxmG1WrpGMyOl1rDoAA
~WyJ5OWVMRmI3azdXZnd4ZkllQWp5eUt3Iiwic3ViIiwiMjQ4Mjg5NzYxMDAxIl0
~WyJUNkVlZzRsaVJyN3VtOUZxOFRYekR3IiwibmFtZSIsIkphbmUgRG9lIl0
~WyJUUnRocWdUc29kZzVJbnBBR2ZvNjJnIiwiZ2l2ZW5fbmFtZSIsIkpvbmUiXQ
~WyJmRWx5VzFIdlVjX1dYTnFLeUdRcEJ3IiwiZmFtaWx5X25hbWUiLCJEb2UiXQ
~WyI1blgwRmZTNHUtVjN3aFNORUpPQndnIiwiZ2VuZGVyIiwiZmVtYWxlIl0
~WyJhSzhJYWc2dzB1Mno1WVpxMWlXOC13IiwiYmlydGhkYXRlIiwiMDAwMC0xMC0zMSJd
~WyIwR0pLdnN2aDNpbnFkeGlUM3MxbVJRIiwiZW1haWwiLCJqb25lZG9lQGV4YW1wbGUuY29tIl0
~WyJXMHpCa1lfLThqUVhNY3JnYTJTak9BIiwicGljdHVyZSIsImh0dHA6Ly9leGFtcGxlLmNvbS9qYW5lZG9lL21lLmpwZyJd
```
　この~で始まる部分はbase64でエンコードされた文字列だという話なので、それを明に書き下すと、
 ```
eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwiYXVkIjoiczZCaGRSa3F0MyIsIm5vbmNlIjoibi0wUzZfV3pBMk1qIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE3MzU2ODk2NjEsIl9zZCI6WyI1R19rclp0TVRNUGx0QVdXTk1oZER4Sk10MTVTQTFLRmtkMmdJbFN2THRRIiwiNXA2YnQ1M0Q5cDRNZmtWWVFjaUw0cFdYQWtJanowUGJMWEdEME5IaWkydyIsIjZKdDVseHJlaVRLOG9sVFJ6eUhvSFlZUG11MEctWlZ2V3BhY1ZtWVlUOE0iLCJFdy1kS1JtQTJ1U2d3OUVtR3dOSWZRa3NQcFBJczBxQTRPRS04NkRRM19ZIiwiSHotQllQUUptSHNGbElmWl85Q0o2SWJvcWlzb1pOdHd6a2IwWjNKQWxGNCIsIkozUF9nTkEzTEVDSXpUdHRCdVg2MmNMVklDREZ6Ui1ycm9pQ1FrYkJidVEiLCJPMmJEVUhhRUxaSXN5T1hUeXV0V3VuQWdtMzdRVGtiQ1RSdzczYmk1NXhFIiwiUTR2dHJhRHZwcnFCa2pyUG9MV185RlpEZXRGS0tBR2N5YTA1ckl3cTR4ZyJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.LAlXQnPK8rOhxBhbg_FU-hiBJJqCX5CYWYTp0YcbbIxnBZPN1ZhRgE_SLh0rEg1L559GxmG1WrpGMyOl1rDoAA
~base64encode(["y9eLFb7k7WfwxfIeAjyyKw","sub","248289761001"]) ---------１
~base64encode(["T6Eeg4liRr7um9Fq8TXzDw","name","Jane Doe"]) ------------２
~base64encode(["TRthqgTsodg5InpAGfo62g","given_name","Jone"]) ----------３
~base64encode(["fElyW1HvUc_WXNqKyGQpBw","family_name","Doe"]) ----------４
~base64encode(["5nX0FfS4u-V3whSNEJOBwg","gender","female"]) ------------５
~base64encode(["aK8Iag6w0u2z5YZq1iW8-w","birthdate","0000-10-31"]) -----６
~base64encode(["0GJKvsvh3inqdxiT3s1mRQ","email","jonedoe@example.com"])-７
~base64encode(["W0zBkY_-8jQXMcrga2SjOA","picture","http://example.com/janedoe/me.jpg"]) -８
 ```
 ということらしい。

　ユーザは隠したい情報、例えばgender、birthdateであれば、それに対応する５，６部分の~Wy..を消去して、相手に渡すことで選択開示が可能となる。本体のcredentialには~で始まるbase64でエンコードされた文字列のハッシュ値が埋め込まれているそうだ(墨塗り署名に似た仕掛けだなと感じた)
 
## sd-jwt形式のvcを取得するまでの流れ
### 1. QRコードをスキャン
　sd-jwtを体験できるissuerを探してみた。[animo](https://openid4vc.animo.id/)、[walt](https://portal.walt.id/)と試したものの、まだsd-jwtへの対応は出来ておらず、authlete社の[デモサイト](https://trial.authlete.net/api/offer/issue)が唯一sd-jwt形式のcredentialを発行してくれた(2024年1月時点)。
 ![authlete.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624321/c4f1ebab-6038-c4ec-9ffb-99beac0702f9.png)

  発行してもらうには、Login ID/Passwordの欄に、[デモサイトの使い方ページ](https://www.authlete.com/ja/developers/oid4vci/#41-%E4%BA%8B%E5%89%8D%E8%AA%8D%E5%8F%AF%E3%82%B3%E3%83%BC%E3%83%89%E3%83%95%E3%83%AD%E3%83%BC--%E9%8D%B5%E8%A8%BC%E6%98%8E--sd-jwt-vc)に書かれた値を入れてsubmitボタンを押すだけでOKである。押すと下記のようなQRコードが表示される。

![issue.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624321/2164b8d8-27f8-d944-7936-df7c2159f6ab.png)
### 2. Credential Issuer Metadata を取得
　QRコードを読み取ると、以下のようなCredential Offer情報が取得できる。
 ```
 openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Ftrial.authlete.net%22%2C%22credential_configurations%22%3A%5B%22IdentityCredential%22%2C%22org.iso.18013.5.1.mDL%22%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22BrSjFNeIYQJkemFOAj4nk5RrDD2lo8lNgRnXLD-o540%22%7D%7D%7D
 ```
このCredential Offerをsphereon社のライブラリoid4vci-clientに読み込ませるとpreAuthorizedCodeや、token endpointを取得してくれる

＃ライブラリの動作をみていると、.well-known/openid-credential-issuer、.well-known/oauth-authorization-server、.well-known/openid-configurationをチェックしてmetadataを生成している印象

``` javascript
 import { CredentialOfferClient, MetadataClient,} from '@sphereon/oid4vci-client';

const initiationRequestWithUrl 
   = await CredentialOfferClient.fromURI(_initiationURI);
const metadata 
   = await MetadataClient.retrieveAllMetadataFromCredentialOffer(initiationRequestWithUrl);

console.log(initiationRequestWithUrl.preAuthorizedCode)
console.log(metadata.token_endpoint)
console.log(metadata.issuer)
```

### 3.tokenの取得
 issuer metadataからtoken endpoint情報をもらい、そこにQRコード経由で取得したpreAuthorizedCodeを用いてaccess_tokenを取得する。このデモサイトからaccess_tokenを取得する場合には、application/x-www-form-urlencoded形式でpreAuthorizedCodeを送る必要がある。

``` js
  const clientId = '218232426';
  const tokenReq = "client_id="+clientId
            + "&grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code&pre-authorized_code="
            + initiationRequestWithUrl.preAuthorizedCode;
  const accessTokenResponse = await fetch(metadata.token_endpoint, {
    method: 'post',
    body: tokenReq,
    headers: {'Content-Type': 'application/x-www-form-urlencoded'}
  });
  const accessToken = await accessTokenResponse.json();

```

### 4. VCの取得
　VCを取得するには、metadata.credential_endpointに、さきほど取得したtokenをheaderにセットして依頼を行う。このとき
- vctを設定する(いままでは、credentialTypesを設定)
- formatを**vc+sd-jwt**に設定する(いままでは**jwt_vc_json**を設定)。

```js

function main(){
...
    const proofJwt = await generateKeyProof ("ES256",clientId, accessToken.c_nonce, metadata.issuer)

    const credentialReq = {
      "format": "vc+sd-jwt", // jwt_vc_json
      "vct": "https://credentials.example.com/identity_credential", // credentialTypes:..
      "proof": {
        "proof_type": "jwt",
        "jwt":proofJwt
       }
    };
    const credentialResponse = await fetch(metadata.credential_endpoint, {
      method: 'post',
      body: JSON.stringify(credentialReq),
      headers: {
      　'Content-Type': 'application/json', 
        'Authorization': 'Bearer '+accessToken.access_token}
    });
    const credential = await credentialResponse.json();
    console.log(credential);
...
}
async function generateKeyProof (_alg, _clientid,  _nonce, _aud){
  const { privateKey, publicKey } = await jose.generateKeyPair(_alg);
  let ecjwk  = await jose.exportJWK(publicKey);
  let ecjwkpri  = await jose.exportJWK(privateKey);
  let _key={"publicJwk":ecjwk,"privateJwk":ecjwkpri};
  let args = {
     "payload":{
       "iat":Math.floor(new Date().getTime() / 1000),
       "iss":_clientid,
       "nonce": _nonce,
       "aud":_aud,
     },
     "header":{
        "typ":"openid4vci-proof+jwt",
        "alg":_alg,
        "jwk":_key.publicJwk
     }
  }
  const privateKeyObj  = await jose.importJWK(ecjwkpri, _alg);
  const jwt =  await new jose.SignJWT({ ...args.payload })
    .setProtectedHeader({...args.header})
    .sign(privateKeyObj);
  return jwt;
}

```
すると、以下のようなvc+sd-jwt形式のデータが返ってくる。
```
{
  format: 'vc+sd-jwt',
  credential: 'eyJraWQiOiJKMUZ3SlA4N0M2LVFOX1dTSU9tSkFRYzZuNUNRX2JaZGFGSjVHRG5XMVJrIiwidHlwIjoidmMrc2Qtand0IiwiYWxnIjoiRVMyNTYifQ.eyJfc2QiOlsiN3V5c0ZxWWdTWDVnWmFocmh0dE9ZMXlxeGg2QUFqQ242eE1mSWdOVnZBSSIsIjh6ekJWaVc5cl8wbFY0UlJYY01rYVVCRUNlZHduYTBPY0lNdEl5UVdqc0EiLCJCeEpfU0hSZ0lDWHFyOENGd0xFZlQ2b0VVR3A0YktrRlBVazF5dnBuekNNIiwiR2s4bjQxcHZ4WndtOWNvZmtTX01BLXdrZjY2bXZPVUVFV0FWUGFaenliayIsIlE1Z3Bjc1hvSHVZa3h3VHdhVXZ4YUR3VFpiZHE5OW5qbjRjNEJfZXFaU1EiLCJWSnVrU2NGQWFxUVhaUVhLR041MVJrcWVRN3FBUDVOc1JRMkpHeGpGMFBnIiwiVm5MWGNqXzRONVdXclVhNXp4LWRPWHhOMXVFSG1JUXEwU3UyQmNZd2R2TSIsImtUcmFMajdsbGN4eGxreVdrUHV2Tm1QZDc1ZU1DTlNOM0dPelpoNnh4YkUiLCJtaG9oSGc0Sm5qQzZfbi1uVXIwbzFkdVlPb2xISGFuc0RjTU9WYnIycTZvIiwib1E0b1IxZURmc1JQR0FSeHpxNkNsa0ZSbzc5dkQyRWJ6cVNYVUNUTDhUbyIsInFHel9pRlNKLVJKbG96NGdBUDZqOW5sekpCYW9Id0JsS0I2LVBSVlBXd1EiLCJ2eFVPalJnTmh4Vk1Hb292MHVFck4zbUtxdUxKN2pCR1RtdHhEbjFPU0Z3Il0sInZjdCI6Imh0dHBzOi8vY3JlZGVudGlhbHMuZXhhbXBsZS5jb20vaWRlbnRpdHlfY3JlZGVudGlhbCIsIl9zZF9hbGciOiJzaGEtMjU2IiwiaXNzIjoiaHR0cHM6Ly90cmlhbC5hdXRobGV0ZS5uZXQiLCJjbmYiOnsiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJraWQiOiJRNFhHMEdnOVVDODVEcW9Zc3ZzX2RKTnFBVTJtMXliVEhQOW1GYVQzLW9RIiwieCI6IjFfVERIaHVvLXQ1WTZfaHBITFEzc3FmV0J3eGRlNDhpdHJDNllqaklVbWciLCJ5IjoiRkpxWmhsWVEzcVZmcl84NE13WWlRNmJBbDEtb1VzSkI5dHFMTXR2ZFhFMCJ9fSwiaWF0IjoxNzA1ODE5MjY4fQ.Hkgkh8ri8b-pQC0M-BJbPGcsFkC7iLT3plLcA-h-ubLEVa8gmdKfBlRYbqvoVV0DI8CWURrFFJ_Qbw8u-uMkXQ~WyIyVlo0M1dwSGVocllrLTN3QmZ5d3l3Iiwic3ViIiwiMTAwNCJd~WyJGa3VINk04amkwLXNodVRQWWRaRUVRIiwiZ2l2ZW5fbmFtZSIsIkluZ2EiXQ~WyJoRzUtdjlyU2FPVEczdC1mZ2lYVHJ3IiwiZmFtaWx5X25hbWUiLCJTaWx2ZXJzdG9uZSJd~WyJERGJhcXBpeUtmYXdSeTNONUpCUXp3IiwiYmlydGhkYXRlIiwiMTk5MS0xMS0wNiJd~',
  c_nonce: 'gTTnGBUukwkVm1GfiLFBu4W5tEGRIfO6_ytzF9iZePg',
  c_nonce_expires_in: 86399
}
```
### 5. sd-jwtのパース
 ライブラリ```@eengineer1/sd-jwt-ts-node```にあるparse関数に上記credentialを流し込めば、いい感じに分析してくれる

```js
import { SDJwt } from '@eengineer1/sd-jwt-ts-node'

async function sdJwtParse(sdPayload){
  const sdJwt = SDJwt.parse(sdPayload)
  const parse = {undisclosure:sdJwt.sdPayload.undisclosedPayload, disclosure:sdJwt.fullPayload}
  console.log(JSON.stringify(parse, null,1))
  return parse
}
```
上記関数に、引数credential値を入れて実行すると、以下のように情報開示をする前の状態(undisclosure)と、後の状態(disclosure)を表示してくれる。
```json
{
 "undisclosure": {
  "_sd": [
   "7uysFqYgSX5gZahrhttOY1yqxh6AAjCn6xMfIgNVvAI",
   "8zzBViW9r_0lV4RRXcMkaUBECedwna0OcIMtIyQWjsA",
   "BxJ_SHRgICXqr8CFwLEfT6oEUGp4bKkFPUk1yvpnzCM",
   "Gk8n41pvxZwm9cofkS_MA-wkf66mvOUEEWAVPaZzybk",
   "Q5gpcsXoHuYkxwTwaUvxaDwTZbdq99njn4c4B_eqZSQ",
   "VJukScFAaqQXZQXKGN51RkqeQ7qAP5NsRQ2JGxjF0Pg",
   "VnLXcj_4N5WWrUa5zx-dOXxN1uEHmIQq0Su2BcYwdvM",
   "kTraLj7llcxxlkyWkPuvNmPd75eMCNSN3GOzZh6xxbE",
   "mhohHg4JnjC6_n-nUr0o1duYOolHHansDcMOVbr2q6o",
   "oQ4oR1eDfsRPGARxzq6ClkFRo79vD2EbzqSXUCTL8To",
   "qGz_iFSJ-RJloz4gAP6j9nlzJBaoHwBlKB6-PRVPWwQ",
   "vxUOjRgNhxVMGoov0uErN3mKquLJ7jBGTmtxDn1OSFw"
  ],
  "vct": "https://credentials.example.com/identity_credential",
  "_sd_alg": "sha-256",
  "iss": "https://trial.authlete.net",
  "cnf": {
   "jwk": {
    "kty": "EC",
    "crv": "P-256",
    "kid": "Q4XG0Gg9UC85DqoYsvs_dJNqAU2m1ybTHP9mFaT3-oQ",
    "x": "1_TDHhuo-t5Y6_hpHLQ3sqfWBwxde48itrC6YjjIUmg",
    "y": "FJqZhlYQ3qVfr_84MwYiQ6bAl1-oUsJB9tqLMtvdXE0"
   }
  },
  "iat": 1705819268
 },
 ```
 ```json
 "disclosure": {
  "birthdate": "1991-11-06",
  "family_name": "Silverstone",
  "sub": "1004",
  "given_name": "Inga",
  "vct": "https://credentials.example.com/identity_credential",
  "iss": "https://trial.authlete.net",
  "cnf": {
   "jwk": {
    "kty": "EC",
    "crv": "P-256",
    "kid": "Q4XG0Gg9UC85DqoYsvs_dJNqAU2m1ybTHP9mFaT3-oQ",
    "x": "1_TDHhuo-t5Y6_hpHLQ3sqfWBwxde48itrC6YjjIUmg",
    "y": "FJqZhlYQ3qVfr_84MwYiQ6bAl1-oUsJB9tqLMtvdXE0"
   }
  },
  "iat": 1705819268
 }
}
```

## 体験してみる
　id walletサーバを立ち上げ、以下のようにAuthlete社のデモサイトにアクセスし、sd-jwt形式のcredentialをもらうまでを体験する
 
 ＃本当はsd-jwt対応のid walletアプリを見つけて試したかったが、2024/1時点では見つからず..。![za.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624321/367b39c7-ea5e-5047-75fc-138f29f2c2cd.png)
### 
### 1. id walletサーバを起ちあげ
　index.mjs, package.js, index.html, qr-scanner.min.js, qr-scanner-worker.min.jsを以下のフォルダ構成のように配置する.qr-scanner関係のjsは、[https://github.com/nimiq/qr-scanner](https://github.com/nimiq/qr-scanner)からdownloadする。
```
├── index.mjs
├── package.json
└── views/
   ├── index.html
   ├── qr-scanner.min.js
   ├── qr-scanner-worker.min.js

```
<details>

<summary>index.mjsのコードを見る</summary>

```js

import { CredentialOfferClient, MetadataClient,} from '@sphereon/oid4vci-client';
import { SDJwt } from '@eengineer1/sd-jwt-ts-node'
import fetch from 'node-fetch';
import crypto from 'crypto';
import * as jose from 'jose';
import  express from 'express';
import  http from 'http';
import path from 'path';
import { fileURLToPath } from 'url';
import bodyParser from 'body-parser';


//************
let hostname = 'localhost';
const port=8800;
let keys={};
let aud={};
const ALG="ES256"; // ES256, ES256K, EdDSA
if (process.argv.length > 2){ hostname = process.argv[2];}
//*************
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const app = express();
app.use('/', express.static(__dirname + '/views/'));
app.use(bodyParser.json({ extended: true, limit: '10mb' }));
app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' }));
app.use(express.json({ extended: true, limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));

const httpServer = http.createServer(app);
const server = httpServer.listen(port, () => {
	console.log('\n web page start: listening on port %s...', server.address().port);
});

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"){
      console.log("init")
      let cred =  await getCredential(req.body.qr)
      res.send(cred);
  }
})


async function getCredential(_initiationURI, _alg){
  const initiationRequestWithUrl = await CredentialOfferClient.fromURI(_initiationURI);
  const metadata = await MetadataClient.retrieveAllMetadataFromCredentialOffer(initiationRequestWithUrl);

  const clientId = '218232426';
  const tokenReq = "client_id="+clientId
            + "&grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code&pre-authorized_code="
            + initiationRequestWithUrl.preAuthorizedCode;
  const accessTokenResponse = await fetch(metadata.token_endpoint, {
    method: 'post',
    body: tokenReq,
    headers: {'Content-Type': 'application/x-www-form-urlencoded'}
  });
  const accessToken = await accessTokenResponse.json();
  if (typeof accessToken.access_token !=="undefined"){
    console.log(accessToken);
    const proofJwt = await generateKeyProof (ALG,clientId, accessToken.c_nonce, metadata.issuer)

    const credentialReq = {
      "format": "vc+sd-jwt",
      "vct": "https://credentials.example.com/identity_credential",
      "proof": {
        "proof_type": "jwt",
        "jwt":proofJwt
       }
    };
    console.log(credentialReq)
    const credentialResponse = await fetch(metadata.credential_endpoint, {
      method: 'post',
      body: JSON.stringify(credentialReq),
      headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer '+accessToken.access_token}
    });
    const credential = await credentialResponse.json();
    console.log(credential);

// verify    // todo

// parse
    const decode = await sdJwtParse(credential.credential, -1);
    return {result:true, detail:credential.credential,  decode:decode};
  }else{
   return  {result:false, detail:"", decode:""};
  }
}

//////////////////////////////////////////////////

async function generateKeyProof (_alg, _clientid,  _nonce, _aud){
  const { privateKey, publicKey } = await jose.generateKeyPair(_alg);
  let ecjwk  = await jose.exportJWK(publicKey);
  let ecjwkpri  = await jose.exportJWK(privateKey);
  let _key={"publicJwk":ecjwk,"privateJwk":ecjwkpri};
  let args = {
     "payload":{
       "iat":Math.floor(new Date().getTime() / 1000),
       "iss":_clientid,
       "nonce": _nonce,
       "aud":_aud,
     },
     "header":{
        "typ":"openid4vci-proof+jwt",
        "alg":_alg,
        "jwk":_key.publicJwk
     }
  }
  const privateKeyObj  = await jose.importJWK(ecjwkpri, _alg);
  const jwt =  await new jose.SignJWT({ ...args.payload })
    .setProtectedHeader({...args.header})
    .sign(privateKeyObj);
  console.log("-> " + jwt)
  return jwt;
}

async function sdJwtParse(_sdPayload, _index){
  let sdPayload;
  const tmp = _sdPayload.split("~");
  sdPayload=tmp[0];
  for (let i=1; i< tmp.length; i++){
    if (i != _index){
        sdPayload += "~"+tmp[i];
    }
  }
  const sdJwt = SDJwt.parse(sdPayload)
  const parse = {undisclosure:sdJwt.sdPayload.undisclosedPayload, disclosure:sdJwt.fullPayload}
  console.log(JSON.stringify(parse, null,1))
  return parse
}

```
</details>
<details>
<summary>package.jsonのコードを見る</summary>

```json
{
  "dependencies": {
    "@eengineer1/sd-jwt-ts-node": "^1.0.1",
    "@sphereon/oid4vci-client": "^0.8.2-next.34",
    "express": "^4.18.2",
    "jose": "^5.2.0",
    "uint8arrays": "^5.0.1"
  }
}
```
</details>
<details>

<summary>index.htmlのコードを見る</summary>

```html
<!DOCTYPE html>
<html lang='en'>

<head><meta charset='utf-8'></head>
<body>
  <div align=center>
    <div id="title">[ID Wallet]</div><br>
    <button id="scanQR">scan QR code</button>, <button id="stop-button">Stop</button>
    <p><div id=msg></div>
    <p><div id=qr></div>
    <div id="video-container">
      <video id="qr-video"></video>
    </div>
  </div>
<b>Detected QR code: </b>
<span id="cam-qr-result">None</span>
<div id="credresult"></div>
<br>
<hr></body>
<script type="module">
    import QrScanner from "./qr-scanner.min.js";

    let credentials =[]
    const video = document.getElementById('qr-video');
    const videoContainer = document.getElementById('video-container');
    const camQrResult = document.getElementById('cam-qr-result');
    const credResult = document.getElementById('credresult');

    function setResult(label, result) {
        console.log(result.data);
        scanner.stop();
        label.textContent = result.data;
        label.style.color = 'teal';
        _init(result.data)
    }

    const scanner = new QrScanner(video, result => setResult(camQrResult, result), {
        onDecodeError: error => {
            camQrResult.textContent = error;
            camQrResult.style.color = 'inherit';
        },
        highlightScanRegion: true,
        highlightCodeOutline: true,
    });

    document.getElementById('scanQR').addEventListener('click', () => {
        scanner.start();
    });


    function _init(_qrcode) {
      let setting={
        qr:_qrcode
      }
      sendData("./.issuer_init", JSON.stringify(setting), 'application/json', 
        function(data){
          data = JSON.parse(data)
          if (data.result){
            credentials.push({type:data.type, detail:data.detail, decode:data.decode})
            credResult.innerHTML = "<pre>"+JSON.stringify(credentials[credentials.length-1].decode.undisclosure, null,"\t")+"</pre>---><pre>"+JSON.stringify(credentials[credentials.length-1].decode.disclosure, null,"\t")+"</pre>";
          }else{
            credResult.innerHTML = "<br>--> failed<br>";
          }
        }
      );
    }

    // for debugging
    window.scanner = scanner;

    document.getElementById('stop-button').addEventListener('click', () => {
        scanner.stop();
    });


</script>
<script type="text/javascript">
  window.addEventListener('load',function() {  
  });

  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);
  }
  </script>
</html>
```
</details>

#### 2. 関連モジュールをインストールする
```
> npm install
```
#### 3. id wallet サーバに割り当てるドメイン名を取得
```
> ngrok http 8800
Forwarding       https://fe86-153-214-37-81.ngrok-free.app -> http://localhost:8800 
```
#### 4. id wallet サーバを起動する
```
> nodejs index.mjs

 web page start: listening on port 8800...
```
#### 5. スマホ内のブラウザで```https://fe86-153-214-37-81.ngrok-free.app/```にアクセス
1. スマホで表示されたwebページ内にある「scan QR code」ボタンを押す。
1. ＰＣなどで、authelete社の[デモサイト](https://trial.authlete.net/api/offer/issue)にアクセスし、QRコードを表示
1. スマホでそのQRコードをscanする