drand

模块说明

  • 定义

    Drand是一个分布式随机性信标(Distributed randomness beacon),提供公开可验证、不可预测且无偏的随机数服务。

  • 获取方式

    可以通过向Drand服务发送HTTP GET请求来获取随机数,还可以使用客户端库(GO、JS)为应用程序获取随机数。

  • 公共端点

    ProviderURL
    Protocol Labshttps://api.drand.sh/
    https://api2.drand.sh/
    https://api3.drand.sh/
    Cloudflarehttps://drand.cloudflare.com/
    StorSwifthttps://api.drand.secureweb3.com:6875/
  • 网络分类

    分类随机数产生频率网络模式生成方式安全性性能
    默认网络30s链式模式(chained mode每个随机数依赖于前一个随机数,形成随机数链较高较低
    快速网络3s非链式模式(unchained mode每个随机数独立生成,不依赖于之前的随机数较低较高

方法图解

https://drand.love/docs/http-api-reference

代码示例

以下同样是投骰子的示例,每次调用将通过传入drand随机数得到一个 1~6 的随机数 NFT。

module cookbook::drand{
    use sui::event;
    use cookbook::drand_lib::{derive_randomness, 
        verify_drand_signature, safe_selection};

    public struct Dice has key, store {
        id: UID,
        value: u8,
    }

    public struct DiceEvent has copy, drop {
        value: u8,
    }

    entry fun roll_dice(current_round: u64, drand_sig: vector<u8>): u8 {
        verify_drand_signature(drand_sig, current_round);

        let digest = derive_randomness(drand_sig);
        let random_index = safe_selection(6, &digest);

        (random_index as u8) + 1
    }

    entry fun roll_dice_nft(current_round: u64, drand_sig: vector<u8>, ctx: &mut TxContext) {
        let value = roll_dice(current_round, drand_sig);
        let dice = Dice {
            id: object::new(ctx),
            value,
        };

        event::emit(DiceEvent { value });

        transfer::transfer(dice, tx_context::sender(ctx));
    }
}

合约操作

业务合约部署

$ sui client publish
  • 记录关键信息
export PACKAGE_ID=0xf023a2832843d1d983fd91a25bdb36f43d45d0728bfc2df9c26c655e58ca09e2

命令行调用

# 获取最新轮次和随机数签名
$ curl -s https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/latest > output.txt
$ export CURRENT_ROUND=`jq '.round' output.txt`
$ export SIGNATURE=0x`jq -r '.signature' output.txt`

$ cat output.txt | jq
{
  "round": 10938210,
  "signature": "a23596a3f17b8c3553cbb590b6f11081603ab909d5603db45134c7aa9977c3bf9dfa0681144b8dfa8fed4452e9c2204d",
  "randomness": "d6e5474b7c1e27b09791bb9b483d16d9e01a7df2f1645b847ddae381ef0c6647"
}

$ sui client call --package $PACKAGE_ID --module drand --function roll_dice_nft --args $CURRENT_ROUND $SIGNATURE

可见得到的骰子点数为:4。

image-20240906182052727

代码调用

import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
import { SuiClient } from "@mysten/sui/client";
import { Transaction } from "@mysten/sui/transactions";

import axios from "axios";
import dotenv from "dotenv";
dotenv.config();
const MNEMONIC = process.env.MNEMONIC!;
const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC);

const FULLNODE_URL = "https://fullnode.testnet.sui.io:443";
const PACKAGE_ID =
  "0xe94fae15e81744ec0d64c45de6efe8feeb7e14d9894b20316d7e57b7a8274ad0";
const MODULE_NAME = "drand";
const FUNCTION_NAME = "roll_dice_nft";

const SUI_CLIENT = new SuiClient({ url: FULLNODE_URL });

function hexStringToU8Vector(hexString: string): number[] {
  const u8Vector: number[] = [];
  for (let i = 0; i < hexString.length; i += 2) {
    u8Vector.push(parseInt(hexString.slice(i, i + 2), 16));
  }

  return u8Vector;
}

(async () => {
  const url =
    "https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/latest";

  const response = await axios.get(url);
  const data = response.data;

  const round = data.round; // eg: 10934869
  const signature = data.signature; // eg: "b2916323d2a94f95648f8cc72c3462352bcca735391f5d29c141166da03526b5ab6c63b0cc5905251f9d06e5e0420e9f";

  let tx = new Transaction();

  let signatureVec = hexStringToU8Vector(signature);

  tx.moveCall({
    target: `${PACKAGE_ID}::${MODULE_NAME}::${FUNCTION_NAME}`,
    arguments: [tx.pure.u64(round), tx.pure.vector("u8", signatureVec)],
  });

  try {
    const result = await SUI_CLIENT.signAndExecuteTransaction({
      transaction: tx,
      signer: keypair,
      options: {
        showEvents: true,
      },
    });

    console.log(
      `signAndExecuteTransactionBlock result: ${JSON.stringify(
        result,
        null,
        2
      )}`
    );
  } catch (e) {
    console.error(e);
  }
})();
  • 执行输出

可见得到的骰子点数为:6。

$ ts-node client_drand.ts 
signAndExecuteTransactionBlock result: {
  "digest": "GwP6FhHwLbS1LvSP4r7YzqZ9ucPYuQKqiXdEmpkNEBQf",
  "events": [
    {
      "id": {
        "txDigest": "GwP6FhHwLbS1LvSP4r7YzqZ9ucPYuQKqiXdEmpkNEBQf",
        "eventSeq": "0"
      },
      "packageId": "0xe94fae15e81744ec0d64c45de6efe8feeb7e14d9894b20316d7e57b7a8274ad0",
      "transactionModule": "drand",
      "sender": "0xa244617bc05e4122fb825d3b9c63dbad96dd06fae8183c2f03027b1feff12028",
      "type": "0xe94fae15e81744ec0d64c45de6efe8feeb7e14d9894b20316d7e57b7a8274ad0::drand::DiceEvent",
      "parsedJson": {
        "value": 6
      },
      "bcs": "7"
    }
  ],
  "confirmedLocalExecution": false
}