Course Content
Pycardano: The Ultimate Course for Python and AI Developers

Bài 6: Smart Contract DID Validator (Aiken + CKV)

Chào mừng bạn đến với chuỗi bài viết hướng dẫn xây dựng DApp kết hợp Trí tuệ nhân tạo (AI) và Blockchain! Trong chương này, chúng ta sẽ cùng nhau xây dựng một ứng dụng phân quyền hoàn chỉnh, tích hợp công nghệ Computer Vision (nhận diện khuôn mặt) với Cardano Blockchain thông qua khái niệm DID (Decentralized Identity).

Hôm nay, chúng ta sẽ bắt đầu với Bài 6, tập trung vào phần lõi on-chain: Viết Smart Contract (Hợp đồng thông minh) bằng ngôn ngữ Aiken.

Mục Tiêu Bài Học

Sau bài viết này, bạn sẽ nắm vững:

  1. Khái niệm DID trên nền tảng blockchain là gì.

  2. Cách thiết kế DIDDatum – cấu trúc dữ liệu để lưu trữ on-chain.

  3. Viết validator áp dụng mô hình CKV (Continuing Key Validation) – một dạng “cỗ máy trạng thái” (state machine) trên Cardano.

  4. Triển khai và chạy 16 unit tests để kiểm thử hợp đồng Aiken.

Lý Thuyết: DID (Decentralized Identity) Là Gì?

Trước khi bắt tay vào code, hãy cùng làm rõ định nghĩa về DID.

DID (Decentralized Identity) là khái niệm về danh tính số phi tập trung. Hãy thử tưởng tượng: Thẻ Căn cước công dân của bạn do nhà nước cấp – đó là danh tính tập trung. Tổ chức cấp phát có quyền thu hồi hoặc thay đổi nó, và bạn không thực sự “sở hữu” danh tính đó một cách tuyệt đối.

Ngược lại, với DID: Bạn là người tự tạo, tự sở hữu và tự kiểm soát danh tính của mình. Nó được lưu trữ trên blockchain với đặc tính bất biến, minh bạch và không một bên thứ ba nào có thể can thiệp.

Trong dự án này, DID của người dùng sẽ được gắn liền với Face Embedding – một dạng “dấu vân tay số” trích xuất từ khuôn mặt của bạn. Đây chính là điểm giao thoa giữa AI (Computer Vision) và Blockchain.

Luồng Hoạt Động Cốt Lõi:

Camera → AI (MediaPipe) → Face Embedding (vector 512D)
                                ↓
                          Upload lên IPFS → CID (Mã băm nội dung)
                                                   ↓
                Smart Contract lưu DIDDatum { did_id, face_ipfs_hash: CID, owner, verified }

Lưu ý: Vector đặc trưng của khuôn mặt (face embedding) chứa lượng dữ liệu quá lớn để lưu trực tiếp lên blockchain (rất tốn kém phí mạng). Do đó, giải pháp tối ưu là lưu trữ nó trên mạng phân tán IPFS, sau đó chỉ lưu mã băm CID (như một đường dẫn tham chiếu) lên on-chain thông qua Smart Contract.

Thiết Kế Dữ Liệu On-chain: DIDDatum & Action

Hãy nhìn vào cấu trúc dữ liệu on-chain được định nghĩa trong file types.ak của Aiken.

Đầu tiên là cấu trúc DIDDatum:

pub type DIDDatum {
  did_id: ByteArray,         // ID duy nhất, ví dụ "did:cardano:abc123"
  face_ipfs_hash: ByteArray, // IPFS CID chứa face embedding
  owner: ByteArray,          // Public key hash (28 bytes) của chủ sở hữu
  created_at: Int,           // Thời gian tạo (POSIX timestamp - milliseconds)
  verified: Int,             // Trạng thái: 0 = chưa xác minh, 1 = đã xác minh
}

Kinh nghiệm thực chiến: Hãy chú ý trường verified. Tại sao chúng ta dùng kiểu Int (0 hoặc 1) mà không phải Bool (True/False)? Nguyên nhân là khi bạn tương tác off-chain bằng Python ở các bước sau, kiểu Bool trong Aiken khi mã hóa sang chuẩn CBOR sẽ không tương thích trực tiếp với kiểu bool của Python. Việc sử dụng Int giúp quá trình mã hóa/giải mã (serialize/deserialize) giữa on-chain và off-chain luôn nhất quán và không phát sinh lỗi ẩn.

Tiếp theo là Action – kiểu Enum quy định các hành động người dùng có thể gọi (Redeemer):

pub type Action {
  Register    // CONSTR_ID = 0
  Update      // CONSTR_ID = 1
  Verify      // CONSTR_ID = 2
  Revoke      // CONSTR_ID = 3
}

Thứ tự khai báo ở đây rất quan trọng vì nó quyết định mã CONSTR_ID trong CBOR. Mã off-chain sau này phải khớp chính xác với thứ tự này.

CKV Pattern — “State Machine” trên Cardano

Cardano sử dụng mô hình UTxO, nghĩa là mỗi UTxO chỉ được chi tiêu (spend) một lần duy nhất. Không có khái niệm “trạng thái” (state) được lưu trữ và cập nhật liên tục trên một địa chỉ như Ethereum. Vậy làm sao để tạo ra một cỗ máy trạng thái (state machine)?

Câu trả lời là: Continuing Output (Đầu ra tiếp nối), hay còn gọi là pattern CKV (Continuing Key Validation).

Khi bạn chi tiêu 1 UTxO tại địa chỉ của Smart Contract, validator sẽ bắt buộc bạn phải tạo ra ít nhất 1 UTxO mới trả về đúng địa chỉ Smart Contract đó, mang theo một Datum mới thể hiện sự thay đổi trạng thái.

Ví dụ:

  • Register/Update: Chi tiêu UTxO cũ, tạo UTxO mới tại script với Datum được cập nhật.

  • Verify: Chi tiêu UTxO (verified=0), tạo UTxO mới tại script (verified=1).

  • Revoke: Đây là trường hợp đặc biệt, không có continuing output. DID bị “đốt” (burn) vĩnh viễn và ADA được hoàn trả về ví chủ sở hữu.

Cấu Trúc Validator Chính (did_manager.ak)

validator did_manager {
  spend(datum_opt, action, own_ref, self) {
    expect Some(datum) = datum_opt

    // Lấy địa chỉ của Smart Contract hiện tại
    let script_address = own_input.output.address

    // Lọc ra các continuing outputs (các output quay về đúng địa chỉ script)
    let continuing_outputs =
      list.filter(self.outputs, fn(output) { output.address == script_address })
      
    // ... logic kiểm tra tùy theo Action

Đi Sâu Vào Validation Logic

Để code dễ đọc và dễ test, toàn bộ logic kiểm tra được tách ra file validation.ak.

1. Hành động Register (Đăng ký mới):

pub fn validate_register(datum: DIDDatum) -> Bool {
  validate_datum_fields(datum) && datum.verified == 0
}

Khi đăng ký, Datum phải chứa thông tin hợp lệ (ID và hash không được rỗng) và quan trọng nhất: trạng thái verified phải bằng 0. Trong validator, giao dịch này phải chứa đúng 1 continuing output có Datum giống hệt input và phải có chữ ký của chủ sở hữu.

2. Hành động Update (Cập nhật):

pub fn validate_update(input: DIDDatum, output: DIDDatum) -> Bool {
  input.did_id == output.did_id &&          // Giữ nguyên ID
  input.owner == output.owner &&            // Giữ nguyên chủ sở hữu
  input.face_ipfs_hash != output.face_ipfs_hash &&  // Hash khuôn mặt phải thay đổi
  validate_datum_fields(output)             
}

Update cho phép người dùng thay đổi dữ liệu khuôn mặt (chụp lại ảnh mới), nhưng bắt buộc không được đổi DID hay chuyển nhượng chủ sở hữu.

3. Hành động Verify (Xác minh):

pub fn validate_verify(input: DIDDatum, output: DIDDatum) -> Bool {
  input.verified == 0 && output.verified == 1 &&  // Chuyển từ 0 -> 1
  input.did_id == output.did_id &&
  input.face_ipfs_hash == output.face_ipfs_hash &&
  input.owner == output.owner
}

Hành động này chuyển trạng thái verified từ 0 sang 1, tất cả các dữ liệu khác phải giữ nguyên. Điểm đặc biệt: Verify không cần chữ ký của chủ sở hữu. Bất kỳ ai cũng có thể gửi giao dịch Verify nếu họ cung cấp được bằng chứng (khuôn mặt) hợp lệ ở môi trường off-chain (chúng ta sẽ xử lý logic này bằng Python ở Bài 9).

4. Hành động Revoke (Thu hồi/Hủy bỏ):

pub fn validate_revoke(datum: DIDDatum) -> Bool {
  datum.did_id != #""
}

Hành động thu hồi yêu cầu phải có chữ ký của chủ sở hữu và bắt buộc tập hợp continuing_outputs phải rỗng (nghĩa là không có UTxO nào quay lại Smart Contract).

Kiểm Thử (Unit Tests) Với Aiken

Aiken cung cấp một hệ thống test nội trang cực kỳ mạnh mẽ. Đối với Smart Contract này, chúng ta cần viết 16 bài test bao phủ toàn bộ các trường hợp hợp lệ (positive) và không hợp lệ (negative).

Các bài test negative (kiểm thử các trường hợp sai) là cực kỳ quan trọng để đảm bảo tính bảo mật. Ví dụ:

  • test_register_no_continuing_output: Cố gắng gọi Register nhưng không tạo output quay lại script -> Bắt buộc phải Fail.

  • test_revoke_has_continuing_output: Gọi Revoke nhưng lại tạo output giữ lại DID -> Bắt buộc phải Fail.

Sau khi tất cả các test đã PASS, chạy lệnh aiken build sẽ tạo ra tệp plutus.json. Đây chính là bản thiết kế (blueprint) chứa mã CBOR hex đã biên dịch của validator, đóng vai trò là cầu nối để mã Python (Off-chain) có thể tương tác với Cardano.

Tổng Kết Bài 6

Tuyệt vời! Qua bài học này, bạn đã tự tay thiết kế được phần lõi của ứng dụng:

  • Hiểu cách lưu trữ danh tính on-chain với DIDDatum và liên kết với dữ liệu lớn qua IPFS CID.

  • Quản lý vòng đời của DID thông qua mô hình CKV với 4 hành động: Register, Update, Verify, Revoke.

  • Nắm được bí quyết xử lý kiểu dữ liệu (Int thay vì Bool) để tránh lỗi tương thích off-chain sau này.

Tiếp theo là gì? Trong Bài 7, chúng ta sẽ rời khỏi thế giới Blockchain một chút để bước vào thế giới Trí tuệ nhân tạo (AI). Chúng ta sẽ thiết lập mô hình AI (MediaPipe) để nhận diện khuôn mặt và đẩy dữ liệu lên IPFS.

Hẹn gặp lại các bạn trong bài viết tiếp theo! Chúc các bạn code vui vẻ!
Chi tiết về source code toàn bộ bài học các bạn có thể tham khảo tại!!!
Pycardano integration with AI Implementation Example

SLOT88
totoslot777
https://nextlevelacademygr.es/
SLOT88
CARVALHO-MANZON Digital Arts
https://kaizen7.pe/
slot maxwin
BANDAR SLOT
SLOT THAILAND
https://www.ui-academy.co.uk/faq/
https://harton.be/
SLOT THAILAND
ANGKATOTO
SLOT THAILAND