# EP 5:  การรับส่งข้อมูลเข้ารหัสด้วย Walrus + Seal

สวัสดีครับ เดินทางมาถึง EP สุดท้ายแล้ว สำหรับบทความเกี่ยวกับการใช้ Walrus และ Seal ในการสร้างเนื้อหาที่มีความ Privacy ก่อนอื่นเราย้อนไปดูกันสักนิดดีกว่า เราทำอะไรมาแล้วบ้าง

* [https://onthemove.contributiondao.com/ep-1-walrus-and-seal](https://onthemove.contributiondao.com/ep-1-walrus-and-seal) เริ่มต้นด้วยตัวอย่างโค๊ด ในการส่งข้อมูลไปเก็บที่ Walrus
    
* [https://onthemove.contributiondao.com/ep-2-walrus-programmable-storage](https://onthemove.contributiondao.com/ep-2-walrus-programmable-storage) ทำความรู้จักการเขียน Programmable Storage
    
* [https://onthemove.contributiondao.com/ep-3-walrus-seal-send-secret-message](https://onthemove.contributiondao.com/ep-3-walrus-seal-send-secret-message) รู้จัก Seal และตัวอย่างโค๊ด ในการเข้ารหัสข้อมูล
    
* [https://onthemove.contributiondao.com/ep-4-seal-on-chain-access-control](https://onthemove.contributiondao.com/ep-4-seal-on-chain-access-control) ตัวอย่าง Move Smart Contract ในการจัดการ Access Control
    

ลองไปอ่านดูย้อนหลังกันก่อนน๊ะ เพราะใน EP เราจะนำทุกอย่างมาประกอบร่างกันแล้วครับ

---

จากตัวอย่างโค๊ดล่าสุดนั้น เราเขียนละติดปัญหาเกี่ยวกับฟังก์ชั่นของ Walrus พอสมควร เเละพบว่าจริงๆเเล้ว เราสามารถเรียกใช้งาน API ผ่าน Provider endpoint เพื่อ ทำการรับเเละส่งข้อมูลไปยัง Walrus network ได้ เลยขอเปลี่ยนวิธีการรับส่งข้อมูลกันหน่อยนึงนะ

มาเริ่มกันเลย นี้คือตัวอย่างโค๊ดทั้งหมดที่นำมาประกอบร่างและ มีการทำให้โค๊ดอ่านง่ายขึ้นละ

1. ทำการเเก้ไข `.env` กันก่อน
    
    ```plaintext
    SENDER_PRIVATE_KEY=suiprivatekey1xxx // Sender private key
    RECEIVER_PRIVATE_KEY=suiprivatekey1xxx //Receiver private key
    NETWORK=testnet
    PACKAGE_ID=0x0xxxx // Package Id ที่เราได้จาก EP.4
    ```
    
2. สร้างไฟล์ `config.ts`
    
    ```typescript
    import "dotenv/config";
    
    // --- Env Checks ---
    if (!process.env.SENDER_PRIVATE_KEY || !process.env.RECEIVER_PRIVATE_KEY) {
        throw new Error("Bro, you forgot the private keys in .env file.");
    }
    
    export const CONFIG = {
        // Keys
        SENDER_KEY: process.env.SENDER_PRIVATE_KEY,
        RECEIVER_KEY: process.env.RECEIVER_PRIVATE_KEY,
    
        // Network & Package
        SUI_NETWORK: process.env.SUI_NETWORK || "testnet",
        PACKAGE_ID: process.env.PACKAGE_ID
    
        // Seal Key Servers (Testnet)
        KEY_SERVERS: [
            "0x73d05d62c18d9374e3ea529e8e0ed6161da1a141a94d3f76ae3fe4e99356db75",
            "0xf5d14a81a982144ae441cd7d64b09027f116a468bd36e7eca494f750591623c8",
        ],
    
        // Dummy 32-byte hex for initial encryption setup
        PLACEHOLDER_POLICY_ID: "0x" + "0".repeat(64),
    };
    
    // --- Walrus Endpoints ---
    export const WALRUS_CONFIG = {
        EPOCHS: Number(process.env.WALRUS_EPOCHS ?? 1),
    
        PUBLISHERS: [
            "https://publisher.walrus-testnet.walrus.space",
            "https://wal-publisher-testnet.staketab.org",
            "https://walrus-testnet-publisher.redundex.com",
            "https://walrus-testnet-publisher.nodes.guru",
        ].filter(Boolean) as string[],
    
        AGGREGATORS: [
            "https://aggregator.walrus-testnet.walrus.space",
            "https://wal-aggregator-testnet.staketab.org",
            "https://walrus-testnet-aggregator.redundex.com",
            "https://walrus-testnet-aggregator.nodes.guru",
        ].filter(Boolean) as string[],
    };
    ```
    
    มีส่วนที่น่าสนใจเพิ่มขึ้นมาคือ `WALRUS_CONFIG` ซึ่งส่วนนี้จะมีทั้ง
    
    * WALRUS\_EPOCH เป็นการระบุอายุการเก็บรักษาข้อมูลนั้นเอง
        
    * PUBLISHERS คือ endpoint สำหรับใช้ในการอัพโหลดข้อมูลไปยัง Walrus network
        
    * AGGREGATORS คือ endpoint สำหรับการอ่านข้อมูลจาก Walrus netwokr
        
3. สร้างไฟล์ `walrus.ts`
    
    ```typescript
    import { WALRUS_CONFIG } from "./config";
    
    /**
     * Uploads raw bytes to Walrus.
     * It tries multiple publisher nodes until one works.
     */
    export async function uploadBlob(data: Uint8Array): Promise<string> {
        let lastError: unknown = null;
        for (const baseUrl of WALRUS_CONFIG.PUBLISHERS) {
            const url = `${baseUrl}/v1/blobs?epochs=${WALRUS_CONFIG.EPOCHS}`;
            console.log(`☁️  Trying upload to: ${baseUrl}...`);
            try {
                const res = await fetch(url, {
                    method: "PUT",
                    body: data,
                });
    
                if (!res.ok) {
                    throw new Error(`HTTP ${res.status}: ${res.statusText}`);
                }
                const info = (await res.json()) as any;
                const blobId =
                    info.newlyCreated?.blobObject?.blobId ||
                    info.alreadyCertified?.blobId;
    
                if (!blobId) throw new Error("Invalid response format from Walrus");
    
                return blobId; // Success!
            } catch (e) {
                console.error(`-> Failed at ${baseUrl}, trying next...`);
                lastError = e;
            }
        }
        throw new Error(`All Walrus publishers failed. RIP. Error: ${lastError}`);
    }
    
    /**
     * Downloads raw bytes from Walrus.
     * It tries multiple aggregator nodes until one works.
     */
    export async function downloadBlob(blobId: string): Promise<Uint8Array> {
        let lastError: unknown = null;
        for (const baseUrl of WALRUS_CONFIG.AGGREGATORS) {
            const url = `${baseUrl}/v1/blobs/${blobId}`;
            console.log(`⬇️  Trying download from: ${baseUrl}...`);
            try {
                const res = await fetch(url);
                if (!res.ok) throw new Error(`HTTP ${res.status}`);
    
                const buffer = await res.arrayBuffer();
                return new Uint8Array(buffer); // Success!
            } catch (e) {
                console.error(`-> Node ${baseUrl} failed, trying next...`);
                lastError = e;
            }
        }
        throw new Error(`All Walrus aggregators failed. Blob might be gone.`);
    }
    ```
    
    ตรงไปตรงมาเลย ก่อนหน้านี้เราต้องเรียกผ่าน Walrus library ในการอ่านเเละเขียนข้อมูลใช่ไหม ซึ่งเท่าที่ลองดูเจอปัญหางอแงบ้าง เลยตัดจบด้วยการใช้วิธีเรียกผ่าน HTTP Protocol เลยทีเดียวจบ
    
4. สร้างไฟล์ `main.ts`
    
    ```typescript
    import { getFullnodeUrl, SuiClient } from "@mysten/sui/client";
    import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
    import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";
    import { SealClient, SessionKey, EncryptedObject } from "@mysten/seal";
    import { Transaction } from "@mysten/sui/transactions";
    import { fromHex, toHex } from "@mysten/sui/utils";
    import { randomBytes } from "node:crypto";
    
    import { CONFIG } from "./config";
    import { uploadBlob, downloadBlob } from "./walrus";
    
    
    let suiClient: SuiClient;
    let sealClient: SealClient;
    
    async function main() {
        console.log("🚀 Starting the Walrus x Seal Privacy Demo...");
    
        // 1. Setup Network
        await initClients();
    
        // 2. Setup Users
        const sender = getKeypair(CONFIG.SENDER_KEY);
        const receiver = getKeypair(CONFIG.RECEIVER_KEY);
    
        console.log(`👤 Sender:   ${sender.getPublicKey().toSuiAddress()}`);
        console.log(`👤 Receiver: ${receiver.getPublicKey().toSuiAddress()}`);
    
        const secretMessage = "Yo fam, this is exclusive content! 📸 (Secured by Seal)";
    
        // 3. Sender Flow
        const policyObjectId = await runSenderFlow(secretMessage, receiver, sender);
    
        // 4. Simulate Network Delay
        console.log("\n⏳ Chilling for 5s to let Walrus propagate...");
        await sleep(5000);
    
        // 5. Receiver Flow
        await runReceiverFlow(policyObjectId, receiver);
    
        console.log("\n✨ Boom! Mission accomplished.");
    }
    
    // ============================================================================
    // WORKFLOWS
    // ============================================================================
    
    async function runSenderFlow(
        secret: string,
        recipient: Ed25519Keypair,
        sender: Ed25519Keypair
    ): Promise<string> {
        console.log(`\n--- [Step 1] Sender Turn ---`);
        console.log(`🔒 Encrypting payload...`);
    
        // Prepare a randomized ID for encryption
        // We add a nonce to ensure the ID is unique even if the policy ID is static
        const policyBytes = fromHex(CONFIG.PLACEHOLDER_POLICY_ID);
        const nonce = new Uint8Array(randomBytes(5));
        const idBytes = new Uint8Array(policyBytes.length + nonce.length);
        idBytes.set(policyBytes, 0);
        idBytes.set(nonce, policyBytes.length);
    
        // Encrypt locally
        const plainBytes = new TextEncoder().encode(secret);
        const { encryptedObject } = await sealClient.encrypt({
            threshold: 2,
            packageId: CONFIG.PACKAGE_ID,
            id: toHex(idBytes),
            data: plainBytes,
        });
    
        console.log(`-> Encrypted size: ${encryptedObject.byteLength} bytes`);
    
        // Upload to Walrus (Using our helper)
        const blobId = await uploadBlob(encryptedObject);
        console.log(`✅ Walrus Blob ID: ${blobId}`);
    
        // Mint the Policy Object on Sui
        console.log("⛓️ Minting SecretMessage object on-chain...");
        const tx = new Transaction();
        tx.moveCall({
            target: `${CONFIG.PACKAGE_ID}::message::create_secret_message`,
            arguments: [
                tx.pure.address(recipient.getPublicKey().toSuiAddress()),
                tx.pure.string(blobId)
            ],
        });
    
        const result = await suiClient.signAndExecuteTransaction({
            signer: sender,
            transaction: tx,
            options: { showObjectChanges: true },
        });
    
        // Find the new object ID
        const created = result.objectChanges?.find(
            (c: any) =>
                c.type === "created" &&
                "objectId" in c &&
                c.objectType.includes("::message::SecretMessage")
        );
    
        if (!created || !("objectId" in created)) {
            throw new Error("Tx failed. Could not find created object.");
        }
    
        const objectId = created.objectId as string;
        console.log(`📡 Policy Object created: ${objectId}`);
    
        return objectId;
    }
    
    async function runReceiverFlow(policyObjectId: string, recipient: Ed25519Keypair) {
        console.log(`\n--- [Step 2] Receiver Turn ---`);
        console.log(`📦 Fetching policy object: ${policyObjectId}`);
    
        // 1. Get Blob ID from Chain
        const obj = await suiClient.getObject({
            id: policyObjectId,
            options: { showContent: true },
        });
    
        const fields = (obj.data?.content as any)?.fields;
        if (!fields) throw new Error("Policy object is empty/invalid.");
    
        const blobId = fields.walrus_blob_id;
        console.log(`📦 Found Walrus ID: ${blobId}`);
    
        // 2. Download from Walrus (Using our helper)
        const encryptedBytes = await downloadBlob(blobId);
        console.log(`   -> Got ${encryptedBytes.byteLength} bytes`);
    
        // 3. Validation
        const info = EncryptedObject.parse(encryptedBytes);
        if (!info.id || !info.threshold) throw new Error("Data looks corrupted.");
    
        // 4. Session Key (Ephemeral security)
        console.log(`🔑 Generating temp session key...`);
        const recipientAddr = recipient.getPublicKey().toSuiAddress();
        const sessionKey = await SessionKey.create({
            address: recipientAddr,
            packageId: CONFIG.PACKAGE_ID,
            ttlMin: 10,
            signer: recipient,
            suiClient,
        });
    
        // 5. Build Proof (seal_approve)
        console.log("🛡️  Building auth proof...");
        const tx = new Transaction();
        tx.setSender(recipientAddr);
        tx.moveCall({
            target: `${CONFIG.PACKAGE_ID}::message::seal_approve`,
            arguments: [
                tx.pure.vector("u8", fromHex(info.id)),
                tx.object(policyObjectId),
            ],
        });
    
        const txBytes = await tx.build({ client: suiClient, onlyTransactionKind: true });
    
        // 6. Decrypt
        console.log("🔓 Asking Key Servers to decrypt...");
        const decryptedBytes = await sealClient.decrypt({
            data: encryptedBytes,
            sessionKey,
            txBytes,
        });
    
        const msg = new TextDecoder().decode(decryptedBytes);
        console.log(`\n🎉 SUCCESS! Message: "${msg}"`);
    }
    
    async function initClients() {
        suiClient = new SuiClient({ url: getFullnodeUrl(CONFIG.SUI_NETWORK) });
        sealClient = new SealClient({
            suiClient,
            serverConfigs: CONFIG.KEY_SERVERS.map((id) => ({
                objectId: id,
                weight: 1,
            })),
            verifyKeyServers: false, // skipping strict verification for testnet demo
        });
    }
    
    function getKeypair(secretKey: string) {
        return Ed25519Keypair.fromSecretKey(decodeSuiPrivateKey(secretKey).secretKey);
    }
    
    function sleep(ms: number) {
        return new Promise((r) => setTimeout(r, ms));
    }
    
    main().catch((e) => {
        console.error("FATAL ERROR:", e);
        process.exit(1);
    });
    ```
    
    มาถึงไฟล์สุดท้าย พระเอกของเรานั้นเอง มาไล่ทีละขั้นตอนกันนะ
    
    * เริ่มจากเป็นการ `initClients()` ก่อนส่วนนี้จะเห็นว่าไม่มีการเรียกใช้งาน Walrus แล้ว เพราะเราจะส่งข้อมูลผ่าน HTTP กัน
        
    * ส่วนของฟังก์ชั่น `runSenderFlow` นั้นจะเป็นนำข้อความของเราไปเข้ารหัสด้วย Seal หลังจากนั้นก็ทำการอัพโหลดข้อมูลไปยัง Walrus network นั้นเอง เเละเมื่อเราได้ `blobId` มาแล้วก็ทำการเรียกใช้งานฟังก์ชั่น Sui smart contract ที่เราได้เขียนไว้เเล้วเพื่อกำหนดผู้รับ
        
    * ส่วนของฟังก์ชั่น `runReceiverFlow` ทำตรงกันข้ามนั้นเองโดยนำ `blobId` ไปดาวโหลดข้อมูลจาก Walrus network ลงมาก่อนแล้วทำการถอดรหัสข้อมูลด้วย Seal หลังจากผู้รับต้องทำการเรียก Sui smart contract เพื่อยืนยันความเป็นเจ้าของนั้นเอง
        

เรียบร้อยแล้วง่ายไหม ง่ายมั๊ง ลองเอาไปรันดูนะครับ เเล้วเอาผลลัพธ์มาโชว์ด้วยนะ นี้คือตัวอย่างเล็กๆน้อยนะ ซึ่งสิ่งที่เราได้สร้างขึ้นมานั้นจะมี คุณสมบัติ

* **Client-Side Encryption** - ข้อมูลเข้ารหัสบนอุปกรณ์ผู้ใช้
    
* **Decentralized Storage** - เก็บบน Walrus แบบกระจายศูนย์
    
* **On-Chain Access Control** - Smart Contract ควบคุมสิทธิ์การเข้าถึง
    
* **Threshold Cryptography** - ไม่มี Single Point of Failure
    
* **Programmable Ownership** - สิทธิ์เป็น Object ที่โอนย้ายได้
    

ซึ่งจากคุณสมบัติเหล่านี้เราสามารถนำไปต่อยอดเป็น use case อื่นได้อีกมายเลยนะ เช่น

* Creator Platforms ทำพวก Subscription-based content ได้สบายๆ
    
* Gaming Content ทำพวก Unlockable content ตามเงื่อนไข On-Chain หรือจะทำ Secret quests และ time-limited events ก็ได้นะ
    
* AI & Data Markets สาย AI ขาย AI models และ datasets แบบ encrypted
    
* NFT Utilities ทำ NFT ที่มีคอนเทนต์จริงซ่อนอยู่ :P
    

เอาละ ลองไปต่อยอดกันดูนะ ไว้เจอกันใหม่

ปิดท้ายด้วยเเหล่งข้อมูลที่สามารถศึกษาได้เพิ่ม

* [Sui Documentation](https://docs.sui.io/)
    
* [Walrus Website](https://www.walrus.xyz/)
    
* [Seal Documentation](https://seal-docs.wal.app/UsingSeal/)
    
* [Seal GitHub](https://github.com/MystenLabs/seal)
