# EP 4: Seal กับ On-Chain Access Control

สวัสดีครับ เดินทางมาถึงตอนที่ 4 แล้วกับซี่รีย์ Walrus + Seal ถ้าหากใครยังไม่ได้อ่าน อ่านย้อนหลังได้ที่นี้นะครับ

* EP 1: [https://onthemove.contributiondao.com/ep-1-walrus-and-seal](https://onthemove.contributiondao.com/ep-1-walrus-and-seal)
    
* EP 2: [https://onthemove.contributiondao.com/ep-2-walrus-programmable-storage](https://onthemove.contributiondao.com/ep-2-walrus-programmable-storage)
    
* EP 3: [https://onthemove.contributiondao.com/ep-3-walrus-seal-send-secret-message](https://onthemove.contributiondao.com/ep-3-walrus-seal-send-secret-message)
    

สำหรับตอนนี้ เรามาเขียนในส่วนของ Move Smart Contract กันนะ เพราะจากตอนที่แล้ว เราได้เขียน Seal ไว้เรียบร้อยแล้ว แต่ขาดในส่วนของ Smart Contract ในการจัดการเรื่องสิทธิในการเข้าถึง

โดย Seal จะปกป้องข้อมูลของเราจากผู้ใช้งานทั่วไป ด้วยระบบป้องกันดังต่อไปนี้

1. Client-Side Encryption: หลักการสำคัญของ Seal คือข้อมูลที่ละเอียดอ่อนจะถูกเข้ารหัส บนอุปกรณ์ของเราเอง ก่อนที่จะถูกส่งออกไปที่อื่นๆ โดยไม่มีเซิร์ฟเวอร์ แม้แต่ key servers ของ Seal เอง ก็จะไม่เห็นข้อมูล plaintext ของเราเลย
    
2. Threshold Encryption: เพื่อหลีกเลี่ยงความเสี่ยงจากการจัดการกุญแจแบบรวมศูนย์ Seal ได้ใช้ Threshold Encryption แทนที่จะมีกุญแจถอดรหัสเพียงอันเดียว ซึ่งกุญแจนั้นจะถูกแบ่งออกเป็นหลาย shares และกระจายไปยังเครือข่าย Decentralized Key Servers ที่เป็นอิสระต่อกัน หากต้องการสร้างกุญแจ ต้นฉบับขึ้นมาใหม่เพื่อถอดรหัส จะต้องมี key servers จำนวนหนึ่ง (เช่น 2 ใน 3) ทำงานร่วมกัน ซึ่งขั้นตอนนี่จะช่วยขจัด single point of failure ออกไปได้เลยนั้นเอง
    
3. On-Chain Access Control: นี่คือส่วนที่ล้ำสมัยที่สุดของสถาปัตยกรรม Seal! โดยตรรกะที่กำหนดว่าใครได้รับอนุญาตให้ถอดรหัสข้อมูลนั้นไม่ได้ถูก hard-coded อยู่ใน off-chain server แต่ถูกกำหนดไว้ใน Move Smart Contract นั้นเอง ซึ่งข้อมูลนี้จะถูกเปิดเผยต่อสาธารณะและตรวจสอบได้บน Sui Network! สิ่งนี้ทำให้เราสามารถสร้าง Access Policies ที่ละเอียดและสามารถ Programmable ได้ เช่น เช่น token-gating (อนุญาตเฉพาะผู้ถือ NFT บางประเภท), time-locks หรือ role-based permissions ที่ซับซ้อน
    

มาเริ่มกันเลย ต่อไปนี้เราจะลงในส่วนของ Technical แล้วนะ ในคือการเขียน Move Smart Contract หากใครยังไม่ได้เตรียมเครื่องไม้ เครื่องมือ ให้ย้อนกลับไปดูบทความด้านล่างนี้นะ

* [https://onthemove.contributiondao.com/walrus-simple-usage](https://onthemove.contributiondao.com/walrus-simple-usage)
    

โดย Move Smart Contract ที่เราจะเขียนกันต่อไปนี้ จะมีหัวใจหลักของ Seal อยู่ที่ฟังก์ชั่น `seal_approve` ซึ่ง

จุดเชื่อมต่อที่สำคัญสำหรับนักพัฒนาคือ `entry function` ที่มีชื่อเฉพาะใน Move module ของเรา: `seal_approve` ฟังก์ชันนี้ทำหน้าที่เป็น **On-Chain Gatekeeper** สำหรับคำขอถอดรหัสทั้งหลาย!

ซึ่งเราจะทดสอบเขียน `secret_message.move` contract ของเรา กันมาเริ่มกันทีละ step กันเลยครับ

1. สร้างโปรเจ็คขึ้นมาใหม่
    
    ```bash
    sui move new secret_message
    ```
    
2. แก้ไข source code ในไฟล์ `secret_message.move`
    
    ```rust
    module secret_message::message {
        // Import เฉพาะตัวที่ต้องใช้จริง (module ส่วนใหญ่ Sui prelude มีให้แล้ว)
        use sui::object::UID;
        use sui::tx_context::TxContext;
        use std::string::String;
    
        /// Error ที่จะเกิดขึ้นเมื่อผู้ใช้ที่ไม่ได้รับอนุญาตพยายามเข้าถึงข้อความลับ
        const ENoAccess: u64 = 0;
    
        /// โครงสร้างของ SecretMessage ซึ่งเป็น Policy Object
        ///
        /// - Policy นี้ไม่เก็บข้อความจริงบน-chain
        /// - ตัวข้อความจะถูกเข้ารหัสและเก็บบน Walrus (off-chain)
        /// - On-chain จะเก็บเฉพาะ metadata และสิทธิ์การเข้าถึง
        ///
        /// ฟิลด์:
        /// - `sender`: address ของผู้สร้าง (ผู้ส่งข้อความลับ)
        /// - `recipient`: address ของผู้รับที่ได้รับอนุญาต
        /// - `walrus_blob_id`: ID ของ blob ที่เก็บ encrypted content บน Walrus
        public struct SecretMessage has key, store {
            id: UID,
            sender: address,
            recipient: address,
            walrus_blob_id: String,
        }
    
        /// ฟังก์ชันสำหรับสร้าง SecretMessage ใหม่
        ///
        /// ขั้นตอน:
        /// 1. ผู้ส่งเรียกฟังก์ชันนี้พร้อมระบุผู้รับ และ Walrus blob ID
        /// 2. ระบบสร้าง Policy Object ใหม่ (SecretMessage)
        /// 3. ส่ง Policy Object ไปยัง wallet ของผู้รับ
        ///
        /// หมายเหตุ:
        /// - ผู้รับเป็นคนเดียวที่ถือ object นี้ → สอดคล้องกับ access control
        public fun create_secret_message(
            recipient: address,
            walrus_blob_id: String,
            ctx: &mut TxContext
        ) {
            let message_policy = SecretMessage {
                id: object::new(ctx),
                sender: ctx.sender(),
                recipient,
                walrus_blob_id,
            };
    
            // โอน Policy Object ไปที่ผู้รับ → เพื่อให้เป็นเจ้าของสิทธิ์การถอดรหัส
            transfer::public_transfer(message_policy, recipient);
        }
    
        /// ฟังก์ชันหลักสำหรับทำงานร่วมกับ Seal (Decentralized Access Control Service)
        ///
        /// Concept:
        /// - Seal Key Servers จะจำลองการเรียกฟังก์ชันนี้ในขั้นตอน Verify Access
        /// - ถ้าฟังก์ชันนี้ "ผ่านเงื่อนไข" → Key Servers จะอนุญาตให้ user รับ key share
        /// - ถ้าไม่ผ่าน → จะไม่ปลดล็อก key share
        ///
        /// มันคือ "On-chain Access Control Policy"
        /// ใช้เงื่อนไขบน-chain ตรวจสอบว่าผู้ที่ขอถอดรหัสเป็น "recipient" ตัวจริงหรือไม่
        ///
        /// พารามิเตอร์:
        /// - `_`: ค่า vector<u8> ที่ Seal ต้องส่งมา (แต่ในที่นี้ไม่ใช้งาน)
        /// - `policy`: reference ไปยัง SecretMessage ของข้อความนั้น
        /// - `ctx`: ใช้ตรวจสอบว่า user ที่เรียกคือใคร
        public fun seal_approve(
            _: vector<u8>,
            policy: &SecretMessage,
            ctx: &TxContext
        ) {
            // อนุญาตเฉพาะผู้รับที่กำหนดไว้ใน policy เท่านั้น
            // ถ้า ctx.sender() ไม่ใช่ recipient → throw error
            assert!(ctx.sender() == policy.recipient, ENoAccess);
        }
    }
    ```
    
3. Deploy Move Smart Contract
    
    เราเริ่มจากการทดสอบ Build กันก่อนนะ ว่าติดปัญหาอะไรไหม อย่าลืมสลับไปใช้งาน Testnet นะครับ
    
    ```rust
    sui move build
    ```
    
    หากทุกอย่างเรียบร้อยก็เตรียมไปสู่ขั้นตอนการ Deploy ไปยัง SUI Testnet ได้เลย
    
    ```rust
    sui client publish --gas-budget 100000000
    ```
    
    เราจะได้หน้าตาประมาณนี้นะ
    
    ```bash
    ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
    │ Object Changes                                                                                   │
    ├──────────────────────────────────────────────────────────────────────────────────────────────────┤
    │ Created Objects:                                                                                 │
    │  ┌──                                                                                             │
    │  │ ObjectID: 0x3e841ab9ffb9dd26ef128631030e2fed4ce19d40e2be1521e760fd4c2dbfe18b                  │
    │  │ Sender: 0xc1a83e6ed8a891cc891208e4288290013ed3f1ebf1576eede3f2c7bc4c56e75c                    │
    │  │ Owner: Account Address ( 0xc1a83e6ed8a891cc891208e4288290013ed3f1ebf1576eede3f2c7bc4c56e75c ) │
    │  │ ObjectType: 0x2::package::UpgradeCap                                                          │
    │  │ Version: 349180711                                                                            │
    │  │ Digest: 4zJPCm9Xab7VkY2bfsH347FwFcPQo74rYN75Qz9dddzk                                          │
    │  └──                                                                                             │
    │ Mutated Objects:                                                                                 │
    │  ┌──                                                                                             │
    │  │ ObjectID: 0x0e77d7f999871c0a82f098699b0128715ade05416531deee0902f8a52bee4f9e                  │
    │  │ Sender: 0xc1a83e6ed8a891cc891208e4288290013ed3f1ebf1576eede3f2c7bc4c56e75c                    │
    │  │ Owner: Account Address ( 0xc1a83e6ed8a891cc891208e4288290013ed3f1ebf1576eede3f2c7bc4c56e75c ) │
    │  │ ObjectType: 0x2::coin::Coin<0x2::sui::SUI>                                                    │
    │  │ Version: 349180711                                                                            │
    │  │ Digest: G8GFetS11dJDiDPYJvnvVaJ3kZnvy3ec31CPJfVSZTnu                                          │
    │  └──                                                                                             │
    │ Published Objects:                                                                               │
    │  ┌──                                                                                             │
    │  │ PackageID: 0x04c285b7f84f9db5cbef6e3c0a48a086463fa618a45ddc14154179fbde533c34                 │
    │  │ Version: 1                                                                                    │
    │  │ Digest: AFZExHfnygn3MqDCkfK1Hh9Ht5aaSxuGHu3bovjvbMhG                                          │
    │  │ Modules: message                                                                              │
    │  └──                                                                                             │
    ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
    ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮
    │ Balance Changes                                                                                   │
    ├───────────────────────────────────────────────────────────────────────────────────────────────────┤
    │  ┌──                                                                                              │
    │  │ Owner: Account Address ( 0xc1a83e6ed8a891cc891208e4288290013ed3f1ebf1576eede3f2c7bc4c56e75c )  │
    │  │ CoinType: 0x2::sui::SUI                                                                        │
    │  │ Amount: -8488280                                                                               │
    │  └──                                                                                              │
    ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯
    ```
    
    ซึ่งสิ่งที่สำคัญคือ หลังจากที่เราได้ทำการ Deploy สำเร็จเเล้ว เราจะเห็น `PackageID` เป็น String ยาวๆ `0x...` ให้เก็บ `PackageID` นั้นเอาไว้ เพราะเราจะต้องนำไปใช้ใน EP 3 ซึ่งยังจำกันได้ใช่ไหมว่า ค่าของ `PackageID` เรายังเป็นค่าที่ยังไม่ได้กำหนด ซึ่งทำให้ตัวอย่างของเราทำงานไม่ได้
    
    ซึ่งนี้ที่ `PackageID` ของเราคือ
    
    ```bash
    0x04c285b7f84f9db5cbef6e3c0a48a086463fa618a45ddc14154179fbde533c34
    ```
    

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

1. **ผู้ใช้ต้องการถอดรหัส**: `Client application` ของผู้ใช้ (เช่น เว็บไซต์ Only… ของเรา) ต้องการเข้าถึงคอนเทนต์ลับ
    
2. **จำลองธุรกรรม**: แทนที่จะส่ง `transaction` ไปรันบน Sui Network ทันที `Client application` จะสร้าง `Sui transaction` ที่เรียกฟังก์ชัน `seal_approve` นี้ (แต่ยังไม่ `execute` จริง!) แล้วส่ง `serialized bytes` ของ `transaction` นี้ไปให้ `Seal Key Servers`
    
3. `Key Servers` ตรวจสอบ: `Key Servers` หลายตัว จะทำการจำลองการรัน `transaction` นี้บน Sui แยกกัน
    
4. **ปล่อย Key Shares (ถ้าผ่าน)**: ถ้าการจำลองสำเร็จ (คือ `assert!` ในฟังก์ชัน `seal_approve` เป็นจริง เช่น `ctx.sender()` คือ `recipient` ที่ระบุไว้) `Key Server` นั้นๆ ก็จะปล่อย `key share` ของตนออกมา
    
5. **ปฏิเสธ (ถ้าไม่ผ่าน)**: แต่ถ้าการจำลองล้มเหลว (เช่น `assert!` เป็นเท็จ เพราะผู้ที่พยายามถอดรหัสไม่ใช่ `recipient` ที่ถูกต้อง) `Key Server` ก็จะปฏิเสธที่จะให้ `key share`
    

Sui ถูกใช้เป็น decentralized และ verifiable access-control oracle ได้อย่างชาญฉลาด! ข้อมูลที่เข้ารหัสสามารถเก็บไว้ที่ไหนก็ได้ — ไม่ว่าจะเป็น Walrus หรือเครือข่ายอื่น—เพราะฟังก์ชัน seal\_approve ทำหน้าที่เป็น public, immutable, composable API สำหรับตรวจสอบสิทธิ์การเข้าถึงบน chain โดยตรง

พูดง่ายๆ คือ: ตัว blockchain กลายเป็น access-control engine ที่ทุกระบบเชื่อถือร่วมกันได้ เป็นดีไซน์ที่ทั้งเรียบง่ายและทรงพลังมากๆ เลยใช่ไหมล่ะ?

เราเตรียมทุกอย่างพร้อมแล้ว สัปดาห์เราจะมาประกอบร่างสุดท้ายกันแล้ว อย่าลืมไปทดสอบ Deploy กันเด้อ แล้วเจอกันใหม่นะ
