การยอมรับข้อมูลเข้าสู่ระบบดิจิทัลทางออนไลน์

คู่มือนี้อธิบายวิธีที่ฝ่ายที่เกี่ยวข้อง (RP) สามารถผสานรวม Digital Credentials API ในทางเทคนิคเพื่อขอและตรวจสอบใบขับขี่ (mDL) และบัตรประจำตัวดิจิทัล ใน Google Wallet ในแอป Android และเว็บ

กระบวนการลงทะเบียนและข้อกำหนดเบื้องต้น

ก่อนที่จะใช้งานจริง คุณต้องลงทะเบียนแอปพลิเคชัน Relying Party กับ Google อย่างเป็นทางการ

  1. ทดสอบใน Sandbox: คุณเริ่มพัฒนาได้ทันทีโดยใช้ สภาพแวดล้อม Sandbox และ การสร้างรหัสทดสอบ คุณไม่จำเป็นต้องยอมรับข้อกำหนดในการให้บริการเพื่อทำการทดสอบ
  2. ส่งแบบฟอร์มขอเข้าร่วม: กรอกข้อมูลในแบบฟอร์มการเริ่มต้นใช้งาน RP โดยปกติแล้ว การเริ่มต้นใช้งานจะใช้เวลา 3-5 วันทำการ ชื่อและโลโก้ผลิตภัณฑ์ของคุณจะแสดงในหน้าจอขอความยินยอมที่ผู้ใช้เห็นเพื่อช่วยให้ผู้ใช้ทราบว่าใครเป็นผู้ขอข้อมูล
  3. ยอมรับข้อกำหนดในการให้บริการ: คุณต้องลงนามในข้อกำหนดในการให้บริการก่อนเริ่ม ไลฟ์

รูปแบบและความสามารถที่รองรับ

Google Wallet รองรับบัตรประจำตัวดิจิทัลที่อิงตาม ISO mdoc

จัดรูปแบบคำขอ

หากต้องการขอข้อมูลเข้าสู่ระบบจากกระเป๋าเงินใดก็ตาม คุณต้องจัดรูปแบบคำขอโดยใช้ OpenID4VP คุณขอข้อมูลเข้าสู่ระบบที่เฉพาะเจาะจงหรือข้อมูลเข้าสู่ระบบหลายรายการได้ในออบเจ็กต์ dcql_query เดียว

ตัวอย่างคำขอ JSON

นี่คือตัวอย่างคำขอ mdoc requestJson เพื่อรับข้อมูลเข้าสู่ระบบสำหรับระบุตัวตน จากกระเป๋าเงินใดก็ได้ในอุปกรณ์ Android หรือเว็บ

{
      "requests" : [
        {
          "protocol": "openid4vp-v1-signed",
          "data": {<signed_credential_request>} // This is an object, shouldn't be a string.
        }
      ]
}

ขอการเข้ารหัส

client_metadata มีคีย์สาธารณะสำหรับการเข้ารหัสสำหรับคำขอแต่ละรายการ คุณจะต้องจัดเก็บคีย์ส่วนตัวสำหรับคำขอแต่ละรายการและใช้คีย์ดังกล่าวเพื่อตรวจสอบสิทธิ์ และให้สิทธิ์โทเค็นที่คุณได้รับจากแอปกระเป๋าเงิน

พารามิเตอร์ credential_request ใน requestJson มีฟิลด์ต่อไปนี้

ข้อมูลเข้าสู่ระบบที่เฉพาะเจาะจง

{
  "response_type": "vp_token",
  "response_mode": "dc_api.jwt", // change this to dc_api if you want to demo with a non encrypted response.
  "nonce": "1234",
  "dcql_query": {
    "credentials": [
      {
        "id": "cred1",
        "format": "mso_mdoc",
        "meta": {
          "doctype_value": "org.iso.18013.5.1.mDL"  // this is for mDL. Use com.google.wallet.idcard.1 for ID pass
        },
        "claims": [
          {
            "path": [
              "org.iso.18013.5.1",
              "family_name"
            ],
            "intent_to_retain": false // set this to true if you are saving the value of the field
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "given_name"
            ],
            "intent_to_retain": false
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "age_over_18"
            ],
            "intent_to_retain": false
          }
        ]
      }
    ]
  },
  "client_metadata": {
    "jwks": {
      "keys": [ // sample request encryption key
        {
          "kty": "EC",
          "crv": "P-256",
          "x": "pDe667JupOe9pXc8xQyf_H03jsQu24r5qXI25x_n1Zs",
          "y": "w-g0OrRBN7WFLX3zsngfCWD3zfor5-NLHxJPmzsSvqQ",
          "use": "enc",
          "kid" : "1",  // This is required
          "alg" : "ECDH-ES",  // This is required
        }
      ]
    },
    "vp_formats_supported": {
      "mso_mdoc": {
        "deviceauth_alg_values": [
          -7
        ],
        "isserauth_alg_values": [
          -7
        ]
      }
    }
  }
}

ข้อมูลประจำตัวที่มีสิทธิ์

ตัวอย่างคำขอสำหรับทั้ง mDL และบัตรประจำตัวมีดังนี้ ผู้ใช้สามารถดำเนินการต่อ ด้วยวิธีใดวิธีหนึ่ง

{
  "response_type": "vp_token",
  "response_mode": "dc_api.jwt", // change this to dc_api if you want to demo with a non encrypted response.
  "nonce": "1234",
  "dcql_query": {
    "credentials": [
      {
        "id": "mdl-request",
        "format": "mso_mdoc",
        "meta": {
          "doctype_value": "org.iso.18013.5.1.mDL"
        },
        "claims": [
          {
            "path": [
              "org.iso.18013.5.1",
              "family_name"
            ],
            "intent_to_retain": false // set this to true if you are saving the value of the field
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "given_name"
            ],
            "intent_to_retain": false
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "age_over_18"
            ],
            "intent_to_retain": false
          }
        ]
      },
      {  // Credential type 2
        "id": "id_pass-request",
        "format": "mso_mdoc",
        "meta": {
          "doctype_value": "com.google.wallet.idcard.1"
        },
        "claims": [
          {
            "path": [
              "org.iso.18013.5.1",
              "family_name"
            ],
            "intent_to_retain": false // set this to true if you are saving the value of the field
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "given_name"
            ],
            "intent_to_retain": false
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "age_over_18"
            ],
            "intent_to_retain": false
          }
        ]
      }
    ]
    credential_sets : [
      {
        "options": [
          [ "mdl-request" ],
          [ "id_pass-request" ]
        ]
      }
    ]
  },
  "client_metadata": {
    "jwks": {
      "keys": [ // sample request encryption key
        {
          "kty": "EC",
          "crv": "P-256",
          "x": "pDe667JupOe9pXc8xQyf_H03jsQu24r5qXI25x_n1Zs",
          "y": "w-g0OrRBN7WFLX3zsngfCWD3zfor5-NLHxJPmzsSvqQ",
          "use": "enc",
          "kid" : "1",  // This is required
          "alg" : "ECDH-ES",  // This is required
        }
      ]
    },
    "vp_formats_supported": {
      "mso_mdoc": {
        "deviceauth_alg_values": [
          -7
        ],
        "isserauth_alg_values": [
          -7
        ]
      }
    }
  }
}

คุณขอแอตทริบิวต์ที่รองรับ จำนวนเท่าใดก็ได้จากข้อมูลเข้าสู่ระบบที่จัดเก็บไว้ใน Google Wallet

คำขอที่ลงชื่อ

คำขอที่ลงนาม (คำขอการให้สิทธิ์ที่ปลอดภัยด้วย JWT) จะห่อหุ้ม คำขอการนำเสนอที่ตรวจสอบได้ภายในโทเค็นเว็บ JSON (JWT) ที่ลงนามแบบเข้ารหัสโดยใช้โครงสร้างพื้นฐาน PKI เพื่อให้มั่นใจในความสมบูรณ์ของคำขอและพิสูจน์ ตัวตนของคุณต่อ Google Wallet

ข้อกำหนดเบื้องต้น

ก่อนที่จะใช้การเปลี่ยนแปลงโค้ดสำหรับคำขอที่ลงนาม โปรดตรวจสอบว่าคุณมีสิ่งต่อไปนี้

  • คีย์ส่วนตัว: คุณต้องมีคีย์ส่วนตัว (เช่น Elliptic Curve ES256) เพื่อลงนามในคำขอที่จัดการในเซิร์ฟเวอร์
  • ใบรับรอง: คุณต้องมีใบรับรอง X.509 มาตรฐานที่ได้จากคู่คีย์
  • การลงทะเบียน: ตรวจสอบว่าได้ลงทะเบียนใบรับรองสาธารณะกับ Google Wallet แล้ว โปรดติดต่อทีมสนับสนุนที่ wallet-identity-rp-support@google.com

ตรรกะการสร้างคำขอ

หากต้องการสร้างคำขอ คุณต้องใช้คีย์ส่วนตัวและรวมเพย์โหลด ไว้ใน JWS

def construct_openid4vp_request(
    doctypes: list[str],
    requested_fields: list[dict],
    nonce_base64: str,
    jwe_encryption_public_jwk: jwk.JWK,
    is_zkp_request: bool,
    is_signed_request: bool,
    state: dict,
    origin: str
) -> dict:

    # ... [Existing logic to build 'presentation_definition' and basic 'request_payload'] ...

    # ------------------------------------------------------------------
    # SIGNED REQUEST IMPLEMENTATION (JAR)
    # ------------------------------------------------------------------
    if is_signed_request:
        try:
            # 1. Load the Verifier's Certificate
            # We must load the PEM string into a cryptography x509 object
            verifier_cert_obj = x509.load_pem_x509_certificate(
                CERTIFICATE.encode('utf-8'),
                backend=default_backend()
            )

            # 2. Calculate Client ID (x509_hash)
            # We calculate the SHA-256 hash of the DER-encoded certificate.
            cert_der = verifier_cert_obj.public_bytes(serialization.Encoding.DER)
            verifier_fingerprint_bytes = hashlib.sha256(cert_der).digest()

            # Create a URL-safe Base64 hash (removing padding '=')
            verifier_fingerprint_b64 = base64.urlsafe_b64encode(verifier_fingerprint_bytes).decode('utf-8').rstrip("=")

            # Format the client_id as required by the spec
            client_id = f'x509_hash:{verifier_fingerprint_b64}'

            # 3. Update Request Payload with JAR specific fields
            request_payload["client_id"] = client_id

            # Explicitly set expected origins to prevent relay attacks
            # Format for android origin: origin = android:apk-key-hash:<base64SHA256_ofAppSigningCert>
            # Format for web origin: origin = <origin_url>
            if origin:
                request_payload["expected_origins"] = [origin]

            # 4. Create Signed JWT (JWS)
            # Load the signing private key
            signing_key = jwk.JWK.from_pem(PRIVATE_KEY.encode('utf-8'))

            # Initialize JWS with the JSON payload
            jws_token = jws.JWS(json.dumps(request_payload).encode('utf-8'))

            # Construct the JOSE Header
            # 'x5c' (X.509 Certificate Chain) is critical: it allows the wallet
            # to validate your key against the one registered in the console.
            x5c_value = base64.b64encode(cert_der).decode('utf-8')

            protected_header = {
                "alg": "ES256",                 # Algorithm (e.g., ES256 or RS256)
                "typ": "oauth-authz-req+jwt",   # Standard type for JAR
                "kid": "1",                     # Key ID
                "x5c": [x5c_value]              # Embed the certificate
            }

            # Sign the token
            jws_token.add_signature(
                key=signing_key,
                alg=None,
                protected=json_encode(protected_header)
            )

            # 5. Return the Request Object
            # Instead of returning the raw JSON, we return the signed JWT string
            # under the 'request' key.
            return {"request": jws_token.serialize(compact=True)}

        except Exception as e:
            print(f"Error signing OpenID4VP request: {e}")
            return None

    # ... [Fallback for unsigned requests] ...
    return request_payload

ทริกเกอร์ API

คำขอ API ทั้งหมดควรสร้างขึ้นฝั่งเซิร์ฟเวอร์ คุณจะส่ง JSON ที่สร้างขึ้นไปยัง API ดั้งเดิมได้โดยขึ้นอยู่กับแพลตฟอร์ม

ในแอป (Android)

หากต้องการขอข้อมูลเข้าสู่ระบบเพื่อยืนยันตัวตนจากแอป Android ให้ทำตามขั้นตอนต่อไปนี้

อัปเดตทรัพยากร Dependency

ใน build.gradle ของโปรเจ็กต์ ให้อัปเดตทรัพยากร Dependency เพื่อ ใช้ Credential Manager (เบต้า) ดังนี้

dependencies {
    implementation("androidx.credentials:credentials:1.5.0-beta01")
    implementation("androidx.credentials:credentials-play-services-auth:1.5.0-beta01")
}

กำหนดค่าเครื่องมือจัดการข้อมูลเข้าสู่ระบบ

หากต้องการกำหนดค่าและเริ่มต้นออบเจ็กต์ CredentialManager ให้เพิ่มตรรกะที่คล้ายกับ รายการต่อไปนี้

// Use your app or activity context to instantiate a client instance of CredentialManager.
val credentialManager = CredentialManager.create(context)

แอตทริบิวต์อัตลักษณ์ของคำขอ

แอปจะระบุพารามิเตอร์ทั้งหมดพร้อมกันเป็นสตริง JSON ภายใน CredentialOption แทนที่จะระบุพารามิเตอร์แต่ละรายการสำหรับคำขอระบุตัวตน เครื่องมือจัดการข้อมูลเข้าสู่ระบบจะส่งสตริง JSON นี้ไปยังกระเป๋าเงินดิจิทัลที่พร้อมใช้งานโดยไม่ต้องตรวจสอบเนื้อหา จากนั้นกระเป๋าเงินแต่ละใบจะมีหน้าที่ดังนี้ - แยกวิเคราะห์สตริง JSON เพื่อทำความเข้าใจคำขอระบุตัวตน - การพิจารณาว่าข้อมูลเข้าสู่ระบบที่จัดเก็บไว้รายการใด (หากมี) ตรงตามคำขอ

เราขอแนะนำให้พาร์ทเนอร์สร้างคำขอในเซิร์ฟเวอร์แม้ว่าจะเป็นการผสานรวมแอป Android ก็ตาม

คุณจะใช้ requestJson จากรูปแบบคำขอ เป็น request ในการเรียกใช้ฟังก์ชัน GetDigitalCredentialOption()

// The request in the JSON format to conform with
// the JSON-ified Digital Credentials API request definition.
val requestJson = generateRequestFromServer()
val digitalCredentialOption =
    GetDigitalCredentialOption(requestJson = requestJson)

// Use the option from the previous step to build the `GetCredentialRequest`.
val getCredRequest = GetCredentialRequest(
    listOf(digitalCredentialOption)
)

coroutineScope.launch {
    try {
        val result = credentialManager.getCredential(
            context = activityContext,
            request = getCredRequest
        )
        verifyResult(result)
    } catch (e : GetCredentialException) {
        handleFailure(e)
    }
}

จัดการการตอบกลับของข้อมูลเข้าสู่ระบบ

เมื่อได้รับคำตอบจากกระเป๋าเงินแล้ว คุณจะยืนยันว่าคำตอบสำเร็จและมีคำตอบ credentialJson หรือไม่

// Handle the successfully returned credential.
fun verifyResult(result: GetCredentialResponse) {
    val credential = result.credential
    when (credential) {
        is DigitalCredential -> {
            val responseJson = credential.credentialJson
            validateResponseOnServer(responseJson) // make a server call to validate the response
        }
        else -> {
            // Catch any unrecognized credential type here.
            Log.e(TAG, "Unexpected type of credential ${credential.type}")
        }
    }
}

// Handle failure.
fun handleFailure(e: GetCredentialException) {
  when (e) {
        is GetCredentialCancellationException -> {
            // The user intentionally canceled the operation and chose not
            // to share the credential.
        }
        is GetCredentialInterruptedException -> {
            // Retry-able error. Consider retrying the call.
        }
        is NoCredentialException -> {
            // No credential was available.
        }
        else -> Log.w(TAG, "Unexpected exception type ${e::class.java}")
    }
}

credentialJsonการตอบกลับมี identityToken (JWT) ที่เข้ารหัส ซึ่งกำหนดโดย W3C แอป Wallet มีหน้าที่สร้างคำตอบนี้

ตัวอย่าง

{
  "protocol" : "openid4vp-v1-signed",
  "data" : {
    <encrpted_response>
  }
}

คุณจะส่งการตอบกลับนี้กลับไปยังเซิร์ฟเวอร์เพื่อตรวจสอบความถูกต้อง คุณดูขั้นตอนในการตรวจสอบการตอบกลับของข้อมูลเข้าสู่ระบบได้

เว็บ

หากต้องการขอข้อมูลเข้าสู่ระบบเพื่อยืนยันตัวตนโดยใช้ Digital Credentials API ใน Chrome หรือ เบราว์เซอร์อื่นๆ ที่รองรับ ให้ส่งคำขอต่อไปนี้

const credentialResponse = await navigator.credentials.get({
          digital : {
          requests : [
            {
              protocol: "openid4vp-v1-signed",
              data: {<credential_request>} // This is an object, shouldn't be a string.
            }
          ]
        }
      })

ส่งการตอบกลับจาก API นี้กลับไปยังเซิร์ฟเวอร์เพื่อตรวจสอบความถูกต้องของ การตอบกลับของข้อมูลเข้าสู่ระบบ

ตรวจสอบคำตอบ

เมื่อวอลเล็ตส่ง identityToken (JWT) ที่เข้ารหัสแล้ว คุณต้องทำการตรวจสอบฝั่งเซิร์ฟเวอร์อย่างเข้มงวดก่อนที่จะเชื่อถือข้อมูล

ถอดรหัสคำตอบ

ใช้คีย์ส่วนตัวที่สอดคล้องกับคีย์สาธารณะที่ส่งในclient_metadataของคำขอเพื่อถอดรหัส JWE ซึ่งจะทำให้ได้ vp_token

ตัวอย่าง Python

  from jwcrypto import jwe, jwk

  # Retrieve the Private Key from Datastore
  reader_private_jwk = jwk.JWK.from_json(jwe_private_key_json_str)
  # Save public key thumbprint for session transcript
  encryption_public_jwk_thumbprint = reader_private_jwk.thumbprint()


  # Decrypt the JWE encrypted response from Google Wallet
  jwe_object = jwe.JWE()
  jwe_object.deserialize(encrypted_jwe_response_from_wallet)
  jwe_object.decrypt(reader_private_jwk)
  decrypted_payload_bytes = jwe_object.payload
  decrypted_data = json.loads(decrypted_payload_bytes)

decrypted_data จะส่งผลให้ได้ vp_token JSON ที่มี ข้อมูลเข้าสู่ระบบ

  {
    "vp_token":
    {
      "cred1": ["<base64UrlNoPadding_encoded_credential>"] // This applies to OpenID4VP 1.0 spec.
    }
  }
  1. สร้างข้อความถอดเสียงของเซสชัน

    ขั้นตอนถัดไปคือการสร้าง SessionTranscript จาก ISO/IEC 18013-5:2021 ด้วยโครงสร้างการส่งต่อที่เฉพาะเจาะจงสำหรับ Android หรือเว็บ

    SessionTranscript = [
      null,                // DeviceEngagementBytes not available
      null,                // EReaderKeyBytes not available
      [
        "OpenID4VPDCAPIHandover",
        AndroidHandoverDataBytes   // BrowserHandoverDataBytes for Web
      ]
    ]
    

    สำหรับการส่งต่อทั้งใน Android และเว็บ คุณจะต้องใช้ Nonce เดียวกันกับที่ใช้สร้าง credential_request

    การส่งต่อใน Android

        AndroidHandoverData = [
          origin,             // "android:apk-key-hash:<base64SHA256_ofAppSigningCert>",
          nonce,           // nonce that was used to generate credential request,
          encryption_public_jwk_thumbprint,  // Encryption public key (JWK) Thumbprint
        ]
    
        AndroidHandoverDataBytes = hashlib.sha256(cbor2.dumps(AndroidHandoverData)).digest()
        

    การส่งต่อเบราว์เซอร์

        BrowserHandoverData =[
          origin,               // Origin URL
          nonce,               //  nonce that was used to generate credential request
          encryption_public_jwk_thumbprint,  // Encryption public key (JWK) Thumbprint
        ]
    
        BrowserHandoverDataBytes = hashlib.sha256(cbor2.dumps(BrowserHandoverData)).digest()
        

    เมื่อใช้ SessionTranscript จะต้องตรวจสอบการตอบกลับของอุปกรณ์ตาม ข้อ 9 ของ ISO/IEC 18013-5:2021 ซึ่งรวมถึงขั้นตอนต่างๆ เช่น

  2. ตรวจสอบใบรับรองผู้ออกบัตรประจำรัฐ โปรดดูใบรับรอง IACA ของผู้ออกบัตรที่รองรับ

  3. ยืนยันลายเซ็น MSO (18013-5 ส่วนที่ 9.1.2)

  4. คำนวณและตรวจสอบ ValueDigests สำหรับองค์ประกอบข้อมูล (18013-5 ส่วน 9.1.2)

  5. ยืนยันลายเซ็น deviceSignature (18013-5 ส่วนที่ 9.1.3)

{
  "version": "1.0",
  "documents": [
    {
      "docType": "org.iso.18013.5.1.mDL",
      "issuerSigned": {
        "nameSpaces": {...}, // contains data elements
        "issuerAuth": [...]  // COSE_Sign1 w/ issuer PK, mso + sig
      },
      "deviceSigned": {
        "nameSpaces": 24(<< {} >>), // empty
        "deviceAuth": {
          "deviceSignature": [...] // COSE_Sign1 w/ device signature
        }
      }
    }
  ],
  "status": 0
}

การยืนยันอายุที่รักษาความเป็นส่วนตัว (ZKP)

หากต้องการรองรับการพิสูจน์แบบไม่เปิดเผยความรู้ (เช่น การยืนยันว่าผู้ใช้มีอายุมากกว่า 18 ปีโดยไม่ต้อง ดูวันเกิดที่แน่นอน) ให้เปลี่ยนรูปแบบคำขอเป็น mso_mdoc_zk และ ระบุการกำหนดค่า zk_system_type ที่จำเป็น

  ...
  "dcql_query": {
    "credentials": [{
      "id": "cred1",
      "format": "mso_mdoc_zk",
      "meta": {
        "doctype_value": "org.iso.18013.5.1.mDL"
        "zk_system_type": [
        {
          "system": "longfellow-libzk-v1",
          "circuit_hash": "f88a39e561ec0be02bb3dfe38fb609ad154e98decbbe632887d850fc612fea6f", // This will differ if you need more than 1 attribute.
          "num_attributes": 1, // number of attributes (in claims) this has can support
          "version": 5,
          "block_enc_hash": 4096,
          "block_enc_sig": 2945,
        }
        {
          "system": "longfellow-libzk-v1",
          "circuit_hash": "137e5a75ce72735a37c8a72da1a8a0a5df8d13365c2ae3d2c2bd6a0e7197c7c6", // This will differ if you need more than 1 attribute.
          "num_attributes": 1, // number of attributes (in claims) this has can support
          "version": 6,
          "block_enc_hash": 4096,
          "block_enc_sig": 2945,
        }
       ],
       "verifier_message": "challenge"
      },
     "claims": [{
         ...
      "client_metadata": {
        "jwks": {
          "keys": [ // sample request encryption key
            {
              ...

คุณจะได้รับหลักฐานแบบปกปิดข้อมูลที่เข้ารหัสจากกระเป๋าเงิน คุณสามารถ ตรวจสอบความถูกต้องของหลักฐานนี้กับใบรับรอง IACA ของผู้ออกโดยใช้ ไลบรารี longfellow-zk ของ Google

verifier-service มีเซิร์ฟเวอร์ที่ใช้ Docker ซึ่งพร้อมสำหรับการติดตั้งใช้งานและช่วยให้คุณตรวจสอบการตอบกลับกับใบรับรอง IACA ของผู้ออกใบรับรองบางรายได้

คุณสามารถแก้ไข certs.pem เพื่อจัดการใบรับรองผู้ออก IACA ที่คุณต้องการเชื่อถือ

แหล่งข้อมูลและการสนับสนุน

  • การใช้งานอ้างอิง: ดูการใช้งานอ้างอิงของเครื่องมือยืนยันตัวตนใน GitHub
  • เว็บไซต์ทดสอบ: ลองใช้โฟลว์แบบครบวงจรที่ verifier.multipaz.org
  • ข้อกำหนด OpenID4VP: ดูข้อกำหนดทางเทคนิคสำหรับ openID4VP
  • การสนับสนุน: หากต้องการความช่วยเหลือในการแก้ไขข้อบกพร่องหรือมีคำถามระหว่างการผสานรวม โปรดติดต่อ wallet-identity-rp-support@google.com