EP 4: Seal กับ On-Chain Access Control
จัดการสิทธิการเข้าถึงเนื้อหาแบบกระจายศูนย์ด้วย Seal

สวัสดีครับ เดินทางมาถึงตอนที่ 4 แล้วกับซี่รีย์ Walrus + Seal ถ้าหากใครยังไม่ได้อ่าน อ่านย้อนหลังได้ที่นี้นะครับ
EP 1: https://onthemove.contributiondao.com/ep-1-walrus-and-seal
EP 2: https://onthemove.contributiondao.com/ep-2-walrus-programmable-storage
EP 3: https://onthemove.contributiondao.com/ep-3-walrus-seal-send-secret-message
สำหรับตอนนี้ เรามาเขียนในส่วนของ Move Smart Contract กันนะ เพราะจากตอนที่แล้ว เราได้เขียน Seal ไว้เรียบร้อยแล้ว แต่ขาดในส่วนของ Smart Contract ในการจัดการเรื่องสิทธิในการเข้าถึง
โดย Seal จะปกป้องข้อมูลของเราจากผู้ใช้งานทั่วไป ด้วยระบบป้องกันดังต่อไปนี้
Client-Side Encryption: หลักการสำคัญของ Seal คือข้อมูลที่ละเอียดอ่อนจะถูกเข้ารหัส บนอุปกรณ์ของเราเอง ก่อนที่จะถูกส่งออกไปที่อื่นๆ โดยไม่มีเซิร์ฟเวอร์ แม้แต่ key servers ของ Seal เอง ก็จะไม่เห็นข้อมูล plaintext ของเราเลย
Threshold Encryption: เพื่อหลีกเลี่ยงความเสี่ยงจากการจัดการกุญแจแบบรวมศูนย์ Seal ได้ใช้ Threshold Encryption แทนที่จะมีกุญแจถอดรหัสเพียงอันเดียว ซึ่งกุญแจนั้นจะถูกแบ่งออกเป็นหลาย shares และกระจายไปยังเครือข่าย Decentralized Key Servers ที่เป็นอิสระต่อกัน หากต้องการสร้างกุญแจ ต้นฉบับขึ้นมาใหม่เพื่อถอดรหัส จะต้องมี key servers จำนวนหนึ่ง (เช่น 2 ใน 3) ทำงานร่วมกัน ซึ่งขั้นตอนนี่จะช่วยขจัด single point of failure ออกไปได้เลยนั้นเอง
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 หากใครยังไม่ได้เตรียมเครื่องไม้ เครื่องมือ ให้ย้อนกลับไปดูบทความด้านล่างนี้นะ
โดย Move Smart Contract ที่เราจะเขียนกันต่อไปนี้ จะมีหัวใจหลักของ Seal อยู่ที่ฟังก์ชั่น seal_approve ซึ่ง
จุดเชื่อมต่อที่สำคัญสำหรับนักพัฒนาคือ entry function ที่มีชื่อเฉพาะใน Move module ของเรา: seal_approve ฟังก์ชันนี้ทำหน้าที่เป็น On-Chain Gatekeeper สำหรับคำขอถอดรหัสทั้งหลาย!
ซึ่งเราจะทดสอบเขียน secret_message.move contract ของเรา กันมาเริ่มกันทีละ step กันเลยครับ
สร้างโปรเจ็คขึ้นมาใหม่
sui move new secret_messageแก้ไข source code ในไฟล์
secret_message.movemodule 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); } }Deploy Move Smart Contract
เราเริ่มจากการทดสอบ Build กันก่อนนะ ว่าติดปัญหาอะไรไหม อย่าลืมสลับไปใช้งาน Testnet นะครับ
sui move buildหากทุกอย่างเรียบร้อยก็เตรียมไปสู่ขั้นตอนการ Deploy ไปยัง SUI Testnet ได้เลย
sui client publish --gas-budget 100000000เราจะได้หน้าตาประมาณนี้นะ
╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 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ของเราคือ0x04c285b7f84f9db5cbef6e3c0a48a086463fa618a45ddc14154179fbde533c34
เรียบร้อยครับ ตอนนี้เราได้ PackageID ที่พร้อมจะนำไปใช้งานแล้ว ก่อนที่เราจะไปกันต่อ ขอธิบายเพิ่มเกี่ยวกับการทำงานของ seal_approve กันสักนิดนึง โดยหลักการจะมีประมาณนี้นะ
ผู้ใช้ต้องการถอดรหัส:
Client applicationของผู้ใช้ (เช่น เว็บไซต์ Only… ของเรา) ต้องการเข้าถึงคอนเทนต์ลับจำลองธุรกรรม: แทนที่จะส่ง
transactionไปรันบน Sui Network ทันทีClient applicationจะสร้างSui transactionที่เรียกฟังก์ชันseal_approveนี้ (แต่ยังไม่executeจริง!) แล้วส่งserialized bytesของtransactionนี้ไปให้Seal Key ServersKey Serversตรวจสอบ:Key Serversหลายตัว จะทำการจำลองการรันtransactionนี้บน Sui แยกกันปล่อย Key Shares (ถ้าผ่าน): ถ้าการจำลองสำเร็จ (คือ
assert!ในฟังก์ชันseal_approveเป็นจริง เช่นctx.sender()คือrecipientที่ระบุไว้)Key Serverนั้นๆ ก็จะปล่อยkey shareของตนออกมาปฏิเสธ (ถ้าไม่ผ่าน): แต่ถ้าการจำลองล้มเหลว (เช่น
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 กันเด้อ แล้วเจอกันใหม่นะ





