dApp Docs/链上随机数VRF实现
Development reference. Not independently verified for production.

MSG Chain 链上随机数 VRF 实现指南

数据来源:MSG Chain 代码库核实

主网状态: No-Go — 当前 MSGChain 主网裁决为 No-Go,以下内容反映代码实际状态,不代表生产可用。


1 概述

1.1 为什么链上随机性至关重要

区块链是一个确定性状态机——给定相同的输入和状态,任何节点执行相同的交易都会得到相同的结果。这种确定性是共识的基础,但也使得"原生随机数"成为一个矛盾的概念。然而,大量去中心化应用场景依赖不可预测的随机性:

场景 随机性用途 安全要求
NFT 盲盒铸造 随机分配稀有度/特征 高(经济价值)
链上游戏(PVP/PVE) 伤害计算、掉落判定、地图生成 极高(公平性)
彩票/抽奖 中奖者选取 极高(资金安全)
DAO 治理 提案排序、委员会随机选取 中
验证者选举 随机分片/轮次分配 高

如果随机性机制存在漏洞,攻击者可以:

1.2 MSG Chain 的 DAR 共识

MSG Chain 采用 DAR (Dilithium-based Autonomous Randomness) 共识机制,其核心创新在于:

  1. 后量子安全:基于 CRYSTALS-Dilithium 5 数字签名方案,抵抗量子计算攻击
  2. 自主随机性:每个区块提议者在区块生成时包含一个 VRF 证明,无需外部 Oracle
  3. 无偏性:随机性来源分散在多个验证者之间,单个验证者无法偏斜输出
  4. 即时可用:随机种子在区块头中即可获取,无需等待额外的提交-揭示轮次

1.3 VRF 概览

可验证随机函数 (Verifiable Random Function, VRF) 是一种密码学原语,它接受一个输入并产生一个随机输出,同时生成一个证明,允许任何人验证该输出确实是由特定公钥对应的私钥正确生成的。

VRF 的数学定义:

(π, β) = VRF_Eval(SK, α)    // 评估:私钥 SK 对输入 α 生成证明 π 和输出 β
b = VRF_Verify(PK, α, π, β)  // 验证:公钥 PK 下,α→β 是否由 π 证明

VRF 的三个核心性质:

1.4 VRF vs 其他链上随机方案

方案 可预测攻击 验证者偏斜 延迟 成本 后量子安全
Block Hash ❌ 矿工可操纵 ❌ 低 无 ✅
Commit-Reveal ✅ ❌ 最后一个揭示者可偏斜 高(多轮) 中 ✅
Oracle (Chainlink) ✅ ✅ 中 高(LINK 费用) ❌ 部分
DAR (本链原生) ✅ ✅ Dilithium-5 VRF 低(区块级别) 无 ✅
本指南(DAR+Oracle) ✅ ✅ 低 低 ✅

1.5 本指南涵盖的内容

1.6 MSG Chain 开发环境准备

# 安装 msg-chain-devkit
cargo install msg-chain-devkit

# 创建项目
msg-chain-devkit new my-vrf-project
cd my-vrf-project

# 合约目录结构
# contracts/
#   vrf-oracle/
#     src/
#       contract.rs    # 合约入口
#       msg.rs         # 消息类型
#       state.rs       # 合约状态
#       vrf.rs         # VRF 验证逻辑
#     Cargo.toml
#   random-consumer/
#     src/
#       contract.rs
#       msg.rs
#       state.rs
#     Cargo.toml

2 DAR 共识随机性

2.1 DAR 工作原理

DAR(Dilithium-based Autonomous Randomness)是 MSG Chain 共识层的核心组件。与以太坊的 RANDAO 或 Polkadot 的 BABE 不同,DAR 使用后量子安全的 Dilithium-5 作为 VRF 基础原语。

2.1.1 随机性生成流程

区块高度 H
    |
    ├─ 输入: prev_randomness (H-1 的随机种子)
    │         round_number     (当前轮次)
    │         proposer_address (提议者地址)
    │         timestamp        (区块时间戳)
    |
    ├─ VRF_Eval(SK_proposer, input)
    │      |
    │      ├─ π = VRF_proof      (纳入区块头)
    │      └─ β = VRF_output     (H 的随机种子)
    |
    ├─ 区块头额外字段:
    │   ├─ randomness: [u8; 32]   // VRF 输出 (256 bits)
    │   ├─ vrf_proof:  [u8; 2700] // Dilithium-5 VRF 证明
    │   └─ vrf_pubkey: [u8; 1312] // 提议者 Dilithium-5 公钥
    |
    └─ 验证者接收到区块:
           ├─ VRF_Verify(pubkey, input, π, β)
           └─ 验证通过 → 更新本地随机种子

2.1.2 Dilithium-5 在 DAR 中的参数

参数 值 说明
安全级别 NIST Level 5 与 AES-256 同等安全
公钥大小 1,312 字节
私钥大小 2,560 字节
签名/证明大小 ~2,700 字节 VRF 证明在此约束内
底层困难问题 Module-LWE + Module-SIS 格密码学

2.2 在合约中访问 DAR 随机性

MSG Chain 的 CosmWasm 实现扩展了 QuerierWrapper,提供了直接访问 DAR 随机性的接口。

2.2.1 标准 DAR API

use cosmwasm_std::{
    from_binary, Binary, Deps, DepsMut, Env, MessageInfo,
    Response, StdError, StdResult, Storage,
};
use serde::{Deserialize, Serialize};

/// DAR 随机性查询响应
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct DarRandomnessResponse {
    /// 当前区块的随机种子 (32 字节)
    pub randomness: Binary,
    /// VRF 证明 (用于验证)
    pub vrf_proof: Binary,
    /// 提议者的 Dilithium-5 公钥
    pub pubkey: Binary,
    /// 区块高度
    pub height: u64,
    /// 区块时间戳
    pub timestamp: u64,
}

/// 通过环境变量获取 DAR 随机性
pub fn get_dar_randomness(env: &Env) -> StdResult<[u8; 32]> {
    // MSG Chain 扩展:env.block.randomness() 返回 Option<Binary>
    match &env.block.randomness {
        Some(r) => {
            let bytes: &[u8] = r.as_slice();
            if bytes.len() != 32 {
                return Err(StdError::generic_err(
                    format!("DAR randomness length mismatch: expected 32, got {}", bytes.len())
                ));
            }
            let mut seed = [0u8; 32];
            seed.copy_from_slice(&bytes[..32]);
            Ok(seed)
        }
        None => Err(StdError::generic_err(
            "DAR randomness not available in this block. Ensure you are on MSG Chain with DAR consensus."
        )),
    }
}

/// 通过查询获取 DAR 随机性(指定区块)
pub fn query_dar_randomness(deps: &Deps, height: Option<u64>) -> StdResult<DarRandomnessResponse> {
    let request = DarRandomnessQuery {
        height: height.unwrap_or(0),
    };
    let raw = deps.querier.query(&cosmwasm_std::QueryRequest::Custom(
        DarQueryMsg::Randomness(request).into(),
    ))?;
    from_binary(&raw)
}

2.2.2 实际合约示例:简单的随机数门限合约

use cosmwasm_std::{
    attr, ensure, Addr, Binary, DepsMut, Env, MessageInfo,
    Response, StdResult, Uint128,
};
use cw_storage_plus::Item;

const THRESHOLD: Item<Uint128> = Item::new("threshold");

pub fn execute_set_threshold(
    deps: DepsMut,
    _env: Env,
    _info: MessageInfo,
    threshold: Uint128,
) -> StdResult<Response> {
    THRESHOLD.save(deps.storage, &threshold)?;
    Ok(Response::new().add_attribute("method", "set_threshold"))
}

pub fn execute_random_draw(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
) -> StdResult<Response> {
    // Step 1: 获取 DAR 随机种子
    let seed = get_dar_randomness(&env)?;

    // Step 2: 结合调用者地址和当前时间戳派生随机值
    let entropy = (seed, info.sender.as_bytes(), env.block.time.nanos());
    let random_value = compute_random_uint128(&entropy);

    // Step 3: 与门限比较
    let threshold = THRESHOLD.load(deps.storage)?;
    let won = random_value < threshold;

    Ok(Response::new()
        .add_attribute("method", "random_draw")
        .add_attribute("sender", info.sender)
        .add_attribute("random_value", random_value)
        .add_attribute("threshold", threshold)
        .add_attribute("won", won.to_string()))
}

/// 从熵元组计算 Uint128 随机数
fn compute_random_uint128(entropy: &([u8; 32], &[u8], u64)) -> Uint128 {
    use sha2::{Digest, Sha256};

    let mut hasher = Sha256::new();
    hasher.update(entropy.0);        // DAR 种子
    hasher.update(entropy.1);        // 调用者地址
    hasher.update(entropy.2.to_le_bytes()); // 纳秒时间戳

    let hash = hasher.finalize();
    let mut bytes = [0u8; 16];
    bytes.copy_from_slice(&hash[..16]);
    Uint128::new(u128::from_le_bytes(bytes))
}

2.3 DAR 随机性验证(离链)

虽然合约内可以直接信任区块头中的 randomness 字段,但 dApp 前端或者需要额外安全保证的场景下,可以离线验证 DAR 的 VRF 证明。

2.3.1 Python 验证示例

"""
DAR VRF 证明验证
依赖: pip install msg-chain-sdk cryptography
"""

import hashlib
import struct
from typing import Tuple
from msg_chain_sdk.dilithium import Dilithium5
from msg_chain_sdk.vrf import Vrf

def verify_dar_randomness(
    block_header: dict,
    prev_randomness: bytes,
    proposer_pubkey: bytes,
) -> Tuple[bool, bytes]:
    """
    验证 DAR 区块随机性。

    Args:
        block_header: 区块头字典,包含 randomness, vrf_proof, height, timestamp
        prev_randomness: 前一个区块的随机种子 (32 字节)
        proposer_pubkey: 提议者 Dilithium-5 公钥 (1312 字节)

    Returns:
        (验证是否通过, 当前区块随机种子)
    """
    randomness = bytes.fromhex(block_header["randomness"])
    vrf_proof = bytes.fromhex(block_header["vrf_proof"])
    height = block_header["height"]
    timestamp = block_header["timestamp"]
    proposer = block_header["proposer_address"]

    # 重建 VRF 输入
    input_seed = hashlib.sha256(
        prev_randomness
        + height.to_bytes(8, "big")
        + proposer.encode("utf-8")
        + timestamp.to_bytes(8, "big")
    ).digest()

    # 验证 VRF
    vrf = Vrf(Dilithium5())
    is_valid = vrf.verify(
        public_key=proposer_pubkey,
        alpha=input_seed,
        pi=vrf_proof,
        beta=randomness,
    )

    if is_valid:
        print(f"[DAR Verify] ✅ 区块 {height} 随机性验证通过")
    else:
        print(f"[DAR Verify] ❌ 区块 {height} 随机性验证失败")

    return is_valid, randomness


def verify_dar_chain(
    blocks: list[dict],
    genesis_randomness: bytes,
) -> bool:
    """
    验证从创世区块开始的整个 DAR 随机性链。

    Args:
        blocks: 按高度排序的区块头列表
        genesis_randomness: 创世区块随机种子

    Returns:
        整条链的随机性是否全部有效
    """
    prev = genesis_randomness

    for block in blocks:
        ok, curr = verify_dar_randomness(block, prev, ...)
        if not ok:
            return False
        prev = curr

    return True

2.3.2 TypeScript 验证示例

import { Dilithium5, Vrf } from '@msg-chain/crypto';
import { BlockHeader } from '@msg-chain/client';
import { sha256 } from '@noble/hashes/sha256';
import { concatBytes, bytesToHex } from '@noble/hashes/utils';

interface DarBlockHeader extends BlockHeader {
  randomness: string;
  vrfProof: string;
  vrfPubkey: string;
  proposerAddress: string;
}

async function verifyDarRandomness(
  header: DarBlockHeader,
  prevRandomness: Uint8Array,
): Promise<{ valid: boolean; seed: Uint8Array }> {
  const heightBytes = new Uint8Array(8);
  new DataView(heightBytes.buffer).setBigUint64(0, BigInt(header.height), false);

  const timestampBytes = new Uint8Array(8);
  new DataView(timestampBytes.buffer).setBigUint64(
    0, BigInt(header.timestamp), false,
  );

  const inputSeed = sha256(
    concatBytes(
      prevRandomness,
      heightBytes,
      new TextEncoder().encode(header.proposerAddress),
      timestampBytes,
    ),
  );

  const vrf = new Vrf(new Dilithium5());
  const valid = vrf.verify(
    Uint8Array.from(Buffer.from(header.vrfPubkey, 'hex')),
    inputSeed,
    Uint8Array.from(Buffer.from(header.vrfProof, 'hex')),
    Uint8Array.from(Buffer.from(header.randomness, 'hex')),
  );

  return {
    valid,
    seed: Uint8Array.from(Buffer.from(header.randomness, 'hex')),
  };
}

2.4 DAR 随机性的安全保证

2.4.1 无偏性 (Bias Resistance)

DAR 的 VRF 输出无法被任何单一实体偏斜。

2.4.2 不可预测性 (Unpredictability)

攻击者模型 能否预测 β 说明
外部用户 ❌ 不能 没有 VRF 私钥,无法预计算输出
区块提议者 ❌ 不能提前 知道 β 但无法改变已提交的区块
下一个提议者 ❌ 不能提前 上一个随机种子作为输入的一部分
全网(事后) ✅ 可验证 所有人在区块确认后可验证

2.5 何时应使用 DAR 原生随机性

适合 DAR 的场景

/// NFT 盲盒铸造 - 使用 DAR 是理想选择
pub fn execute_mint_nft(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult<Response> {
    let seed = get_dar_randomness(&env)?;
    let traits = derive_traits(seed, info.sender.as_ref(), env.block.time.nanos());
    Ok(Response::new()
        .add_attribute("action", "mint_nft")
        .add_attribute("traits", traits.to_string()))
}

/// DAO 投票随机排序 - DAR 足够安全
pub fn execute_shuffle_proposals(deps: DepsMut, env: Env) -> StdResult<Response> {
    let seed = get_dar_randomness(&env)?;
    let shuffled = shuffle(proposals, seed);
    // ...
}

不适合 DAR 的场景(需要 Oracle 回退)

/// 高价值彩票 - 攻击者可能通过影响提议者选择来偏斜
/// 建议:DAR + VRF Oracle 双重验证
pub fn execute_lottery_draw(deps: DepsMut, env: Env) -> StdResult<Response> {
    let dar_seed = get_dar_randomness(&env)?;
    let oracle_seed = VRF_ORACLE.load(deps.storage)?.last_random;
    let combined = combine_seeds(dar_seed, oracle_seed.to_be_bytes());
    // ...
}

3 VRF Oracle 合约实现

当应用对随机性的安全要求极高(例如大型彩票、高价值 NFT),或者需要可审计的随机性来源时,可以使用基于 Oracle 的 VRF 方案作为 DAR 的补充或回退。

3.1 架构设计

+----------+      +-------------------+      +--------------+
| Consumer |      |   VRF Oracle      |      |  Oracle Node |
|  (dApp)  |----->|   (Contract)      |<---->|  (Off-chain) |
|          |      |                   |      |              |
| 1. 请求  |----->| 2. 存储请求       |      | 3. 监听事件  |
|          |      | 4. VRF Request    |<-----| 5. 计算 VRF  |
| 7. 消费  |<-----| 6. 存储随机数     |----->|    (Dilithium)|
|          |      | 8. 验证 VRF       |      |              |
+----------+      +-------------------+      +--------------+

3.2 完整的 VRF Oracle 合约

3.2.1 合约状态 (state.rs)

use cosmwasm_std::{Addr, Binary, Timestamp, Uint128};
use cw_storage_plus::{Item, Map};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

/// VRF Oracle 合约配置
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Config {
    /// 管理员的 Dilithium-5 公钥(用于 VRF 验证)
    pub pubkey: Binary,
    /// Oracle 节点地址(允许提交 VRF 结果)
    pub oracle_address: Addr,
    /// 请求超时(以秒为单位)
    pub request_timeout: u64,
    /// 最大待处理请求数
    pub max_pending_requests: u32,
    /// 每次请求费用
    pub fee: Uint128,
}

/// VRF 请求
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct VrfRequest {
    /// 请求 ID(自增)
    pub id: u64,
    /// 请求者地址
    pub requester: Addr,
    /// 请求者指定的种子(可选,用于防重放)
    pub seed: Binary,
    /// 请求时区块高度
    pub request_height: u64,
    /// 请求时间戳
    pub request_time: Timestamp,
    /// 是否已完成(已生成随机数)
    pub fulfilled: bool,
    /// 回调合约地址(谁消费这个随机数)
    pub callback_contract: Option<Addr>,
    /// 回调消息(Base64 编码的 JSON)
    pub callback_msg: Option<Binary>,
}

/// VRF 响应
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct VrfResponse {
    /// 对应的请求 ID
    pub request_id: u64,
    /// VRF 输出(随机数)
    pub randomness: Binary,
    /// VRF 证明
    pub proof: Binary,
    /// 提交此结果的 Oracle 地址
    pub submitter: Addr,
    /// 提交时的区块高度
    pub submit_height: u64,
}

/// 合约全局状态
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct State {
    /// 当前请求计数器
    pub request_count: u64,
    /// 已完成的请求数
    pub fulfilled_count: u64,
}

pub const CONFIG: Item<Config> = Item::new("config");
pub const STATE: Item<State> = Item::new("state");
pub const REQUESTS: Map<u64, VrfRequest> = Map::new("requests");
pub const RESPONSES: Map<u64, VrfResponse> = Map::new("responses");
pub const PENDING_BY_REQUESTER: Map<&Addr, Vec<u64>> = Map::new("pending");

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct RandomnessResult {
    pub request_id: u64,
    pub randomness: Binary,
    pub proof: Binary,
    pub pubkey: Binary,
}

3.2.2 消息类型 (msg.rs)

use cosmwasm_std::{Addr, Binary, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

// ========== 实例化 ==========

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
    pub pubkey: Binary,
    pub oracle_address: String,
    pub request_timeout: u64,
    pub fee: Option<Uint128>,
    pub max_pending_requests: Option<u32>,
}

// ========== 执行消息 ==========

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
    RequestRandomness {
        seed: Option<Binary>,
        callback_contract: Option<String>,
        callback_msg: Option<Binary>,
    },
    FulfillRandomness {
        request_id: u64,
        randomness: Binary,
        proof: Binary,
    },
    UpdateConfig {
        pubkey: Option<Binary>,
        oracle_address: Option<String>,
        request_timeout: Option<u64>,
        fee: Option<Uint128>,
    },
    CancelExpiredRequest {
        request_id: u64,
    },
}

// ========== 查询消息 ==========

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
    GetConfig {},
    GetRequest { request_id: u64 },
    GetResponse { request_id: u64 },
    GetPendingRequests { requester: String },
    GetLatestRandomness {},
    VerifyRandomness {
        request_id: u64,
        randomness: Binary,
        proof: Binary,
    },
}

// ========== 查询响应 ==========

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ConfigResponse {
    pub pubkey: Binary,
    pub oracle_address: Addr,
    pub request_timeout: u64,
    pub max_pending_requests: u32,
    pub fee: Uint128,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct PendingRequestsResponse {
    pub requests: Vec<u64>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct LatestRandomnessResponse {
    pub request_id: u64,
    pub randomness: Binary,
    pub height: u64,
    pub timestamp: u64,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct VerifyResponse {
    pub valid: bool,
}

3.2.3 合约实现 (contract.rs)

use cosmwasm_std::{
    attr, to_binary, Binary, Deps, DepsMut, Env,
    MessageInfo, Reply, Response, StdError, StdResult,
    SubMsg, Timestamp, Uint128, WasmMsg, CosmosMsg,
};

use crate::msg::{
    ConfigResponse, ExecuteMsg, InstantiateMsg, LatestRandomnessResponse,
    PendingRequestsResponse, QueryMsg, VerifyResponse,
};
use crate::state::{
    Config, RandomnessResult, State, VrfRequest, VrfResponse,
    CONFIG, PENDING_BY_REQUESTER, REQUESTS, RESPONSES, STATE,
};
use crate::vrf::verify_vrf;

const DEFAULT_MAX_PENDING: u32 = 100;

pub fn instantiate(
    deps: DepsMut,
    _env: Env,
    _info: MessageInfo,
    msg: InstantiateMsg,
) -> StdResult<Response> {
    if msg.pubkey.len() != 1312 {
        return Err(StdError::generic_err(format!(
            "Invalid Dilithium-5 public key length: expected 1312, got {}",
            msg.pubkey.len()
        )));
    }

    let oracle_address = deps.api.addr_validate(&msg.oracle_address)?;

    let config = Config {
        pubkey: msg.pubkey,
        oracle_address,
        request_timeout: msg.request_timeout,
        max_pending_requests: msg.max_pending_requests.unwrap_or(DEFAULT_MAX_PENDING),
        fee: msg.fee.unwrap_or(Uint128::zero()),
    };

    let state = State {
        request_count: 0,
        fulfilled_count: 0,
    };

    CONFIG.save(deps.storage, &config)?;
    STATE.save(deps.storage, &state)?;

    Ok(Response::new()
        .add_attribute("method", "instantiate")
        .add_attribute("oracle", msg.oracle_address))
}

pub fn execute(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> StdResult<Response> {
    match msg {
        ExecuteMsg::RequestRandomness { seed, callback_contract, callback_msg } => {
            execute_request_randomness(deps, env, info, seed, callback_contract, callback_msg)
        }
        ExecuteMsg::FulfillRandomness { request_id, randomness, proof } => {
            execute_fulfill_randomness(deps, env, info, request_id, randomness, proof)
        }
        ExecuteMsg::UpdateConfig { pubkey, oracle_address, request_timeout, fee } => {
            execute_update_config(deps, env, info, pubkey, oracle_address, request_timeout, fee)
        }
        ExecuteMsg::CancelExpiredRequest { request_id } => {
            execute_cancel_request(deps, env, info, request_id)
        }
    }
}

pub fn execute_request_randomness(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    seed: Option<Binary>,
    callback_contract: Option<String>,
    callback_msg: Option<Binary>,
) -> StdResult<Response> {
    let config = CONFIG.load(deps.storage)?;
    let mut state = STATE.load(deps.storage)?;

    if !config.fee.is_zero() && info.funds.iter().all(|c| c.denom != "umsg" || c.amount < config.fee) {
        return Err(StdError::generic_err(format!(
            "Insufficient fee: required {}", config.fee
        )));
    }

    let pending = PENDING_BY_REQUESTER
        .may_load(deps.storage, &info.sender)?
        .unwrap_or_default();
    if pending.len() as u32 >= config.max_pending_requests {
        return Err(StdError::generic_err(
            "Maximum pending requests reached",
        ));
    }

    state.request_count += 1;
    let request_id = state.request_count;

    let callback_addr = match &callback_contract {
        Some(addr) => Some(deps.api.addr_validate(addr)?),
        None => None,
    };

    let mut entropy = Vec::new();
    if let Some(r) = &env.block.randomness {
        entropy.extend_from_slice(r.as_slice());
    }
    entropy.extend_from_slice(info.sender.as_bytes());
    if let Some(ref s) = seed {
        entropy.extend_from_slice(s.as_slice());
    }
    entropy.extend_from_slice(&request_id.to_be_bytes());
    let derived_seed = Binary::from(sha2::Sha256::digest(&entropy).as_slice());

    let request = VrfRequest {
        id: request_id,
        requester: info.sender.clone(),
        seed: derived_seed,
        request_height: env.block.height,
        request_time: env.block.time,
        fulfilled: false,
        callback_contract: callback_addr,
        callback_msg,
    };

    REQUESTS.save(deps.storage, request_id, &request)?;

    let mut pending = PENDING_BY_REQUESTER
        .may_load(deps.storage, &info.sender)?
        .unwrap_or_default();
    pending.push(request_id);
    PENDING_BY_REQUESTER.save(deps.storage, &info.sender, &pending)?;

    STATE.save(deps.storage, &state)?;

    Ok(Response::new()
        .add_attribute("method", "request_randomness")
        .add_attribute("request_id", request_id.to_string())
        .add_attribute("requester", info.sender)
        .add_attribute("seed", derived_seed.to_hex())
        .add_event(
            cosmwasm_std::Event::new("vrf-request")
                .add_attribute("request_id", request_id.to_string())
                .add_attribute("requester", info.sender)
                .add_attribute("seed", derived_seed.to_hex()),
        ))
}

pub fn execute_fulfill_randomness(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    request_id: u64,
    randomness: Binary,
    proof: Binary,
) -> StdResult<Response> {
    let config = CONFIG.load(deps.storage)?;

    if info.sender != config.oracle_address {
        return Err(StdError::generic_err(
            "Only the configured oracle address can fulfill requests",
        ));
    }

    let mut request = REQUESTS
        .load(deps.storage, request_id)
        .map_err(|_| StdError::generic_err(format!("Request {} not found", request_id)))?;

    if request.fulfilled {
        return Err(StdError::generic_err(format!(
            "Request {} already fulfilled", request_id
        )));
    }

    let elapsed = env.block.height - request.request_height;
    if elapsed > config.request_timeout {
        return Err(StdError::generic_err(format!(
            "Request {} has expired after {} blocks", request_id, elapsed
        )));
    }

    // ---- 验证 VRF 证明 ----
    let mut alpha_bytes = Vec::new();
    alpha_bytes.extend_from_slice(request.seed.as_slice());
    alpha_bytes.extend_from_slice(&request_id.to_be_bytes());
    alpha_bytes.extend_from_slice(request.requester.as_bytes());
    let alpha = sha2::Sha256::digest(&alpha_bytes);

    let valid = verify_vrf(
        config.pubkey.as_slice(),
        alpha.as_slice(),
        proof.as_slice(),
        randomness.as_slice(),
    );

    if !valid {
        return Err(StdError::generic_err(
            "VRF proof verification failed",
        ));
    }
    // ---- 验证通过 ----

    request.fulfilled = true;
    REQUESTS.save(deps.storage, request_id, &request)?;

    let response = VrfResponse {
        request_id,
        randomness: randomness.clone(),
        proof: proof.clone(),
        submitter: info.sender.clone(),
        submit_height: env.block.height,
    };
    RESPONSES.save(deps.storage, request_id, &response)?;

    let mut pending = PENDING_BY_REQUESTER
        .load(deps.storage, &request.requester)?;
    pending.retain(|&id| id != request_id);
    if pending.is_empty() {
        PENDING_BY_REQUESTER.remove(deps.storage, &request.requester);
    } else {
        PENDING_BY_REQUESTER.save(deps.storage, &request.requester, &pending)?;
    }

    let mut state = STATE.load(deps.storage)?;
    state.fulfilled_count += 1;
    STATE.save(deps.storage, &state)?;

    // ---- 可选回调 ----
    let mut msgs: Vec<SubMsg> = Vec::new();

    if let (Some(ref callback_addr), Some(_)) =
        (&request.callback_contract, &request.callback_msg)
    {
        let exec_msg = WasmMsg::Execute {
            contract_addr: callback_addr.to_string(),
            msg: Binary::from(
                serde_json::to_vec(&serde_json::json!({
                    "randomness_callback": {
                        "request_id": request_id,
                        "randomness": randomness.to_hex(),
                        "proof": proof.to_hex(),
                        "pubkey": config.pubkey.to_hex(),
                    }
                }))
                .map_err(|e| StdError::generic_err(format!("JSON encoding: {}", e)))?,
            ),
            funds: vec![],
        };

        msgs.push(SubMsg::new(exec_msg));
    }

    Ok(Response::new()
        .add_submessages(msgs)
        .add_attribute("method", "fulfill_randomness")
        .add_attribute("request_id", request_id.to_string())
        .add_attribute("randomness", randomness.to_hex())
        .add_attribute("submitter", info.sender)
        .add_event(
            cosmwasm_std::Event::new("vrf-fulfilled")
                .add_attribute("request_id", request_id.to_string())
                .add_attribute("randomness", randomness.to_hex()),
        ))
}

pub fn execute_update_config(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    pubkey: Option<Binary>,
    oracle_address: Option<String>,
    request_timeout: Option<u64>,
    fee: Option<Uint128>,
) -> StdResult<Response> {
    let mut config = CONFIG.load(deps.storage)?;

    if info.sender != config.oracle_address {
        return Err(StdError::generic_err(
            "Only the oracle address can update configuration",
        ));
    }

    if let Some(pk) = pubkey {
        if pk.len() != 1312 {
            return Err(StdError::generic_err("Invalid Dilithium-5 public key length"));
        }
        config.pubkey = pk;
    }
    if let Some(addr) = oracle_address {
        config.oracle_address = deps.api.addr_validate(&addr)?;
    }
    if let Some(t) = request_timeout {
        config.request_timeout = t;
    }
    if let Some(f) = fee {
        config.fee = f;
    }

    CONFIG.save(deps.storage, &config)?;
    Ok(Response::new().add_attribute("method", "update_config"))
}

pub fn execute_cancel_request(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    request_id: u64,
) -> StdResult<Response> {
    let config = CONFIG.load(deps.storage)?;
    let request = REQUESTS.load(deps.storage, request_id)?;

    if info.sender != request.requester && info.sender != config.oracle_address {
        return Err(StdError::generic_err(
            "Only the requester or oracle can cancel this request",
        ));
    }

    if request.fulfilled {
        return Err(StdError::generic_err("Cannot cancel an already fulfilled request"));
    }

    let elapsed = env.block.height - request.request_height;
    if elapsed < config.request_timeout && info.sender == request.requester {
        return Err(StdError::generic_err(format!(
            "Request {} has not expired yet", request_id
        )));
    }

    let mut pending = PENDING_BY_REQUESTER
        .load(deps.storage, &request.requester)?;
    pending.retain(|&id| id != request_id);
    if pending.is_empty() {
        PENDING_BY_REQUESTER.remove(deps.storage, &request.requester);
    } else {
        PENDING_BY_REQUESTER.save(deps.storage, &request.requester, &pending)?;
    }

    let cancelled = VrfResponse {
        request_id,
        randomness: Binary::default(),
        proof: Binary::default(),
        submitter: info.sender.clone(),
        submit_height: env.block.height,
    };
    RESPONSES.save(deps.storage, request_id, &cancelled)?;

    Ok(Response::new()
        .add_attribute("method", "cancel_request")
        .add_attribute("request_id", request_id.to_string()))
}

// ========== 查询入口 ==========

pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
    match msg {
        QueryMsg::GetConfig {} => to_binary(&query_config(deps)?),
        QueryMsg::GetRequest { request_id } => to_binary(&query_request(deps, request_id)?),
        QueryMsg::GetResponse { request_id } => to_binary(&query_response(deps, request_id)?),
        QueryMsg::GetPendingRequests { requester } => {
            to_binary(&query_pending_requests(deps, requester)?)
        }
        QueryMsg::GetLatestRandomness {} => to_binary(&query_latest_randomness(deps)?),
        QueryMsg::VerifyRandomness { request_id, randomness, proof } => {
            to_binary(&query_verify_randomness(deps, request_id, randomness, proof)?)
        }
    }
}

pub fn query_config(deps: Deps) -> StdResult<ConfigResponse> {
    let config = CONFIG.load(deps.storage)?;
    Ok(ConfigResponse {
        pubkey: config.pubkey,
        oracle_address: config.oracle_address,
        request_timeout: config.request_timeout,
        max_pending_requests: config.max_pending_requests,
        fee: config.fee,
    })
}

pub fn query_request(deps: Deps, request_id: u64) -> StdResult<VrfRequest> {
    REQUESTS.load(deps.storage, request_id)
}

pub fn query_response(deps: Deps, request_id: u64) -> StdResult<VrfResponse> {
    RESPONSES.load(deps.storage, request_id)
}

pub fn query_pending_requests(deps: Deps, requester: String) -> StdResult<PendingRequestsResponse> {
    let addr = deps.api.addr_validate(&requester)?;
    let ids = PENDING_BY_REQUESTER
        .may_load(deps.storage, &addr)?
        .unwrap_or_default();
    Ok(PendingRequestsResponse { requests: ids })
}

pub fn query_latest_randomness(deps: Deps) -> StdResult<LatestRandomnessResponse> {
    let state = STATE.load(deps.storage)?;
    if state.fulfilled_count == 0 {
        return Err(StdError::generic_err("No fulfilled requests yet"));
    }
    let mut latest_id = state.request_count;
    loop {
        if let Ok(resp) = RESPONSES.may_load(deps.storage, latest_id)? {
            if !resp.randomness.is_empty() {
                return Ok(LatestRandomnessResponse {
                    request_id: resp.request_id,
                    randomness: resp.randomness,
                    height: resp.submit_height,
                    timestamp: 0,
                });
            }
        }
        if latest_id == 0 {
            break;
        }
        latest_id -= 1;
    }
    Err(StdError::generic_err("No fulfilled requests found"))
}

pub fn query_verify_randomness(
    deps: Deps,
    _request_id: u64,
    randomness: Binary,
    proof: Binary,
) -> StdResult<VerifyResponse> {
    let config = CONFIG.load(deps.storage)?;
    let valid = verify_vrf(
        config.pubkey.as_slice(),
        randomness.as_slice(),
        proof.as_slice(),
        randomness.as_slice(),
    );
    Ok(VerifyResponse { valid })
}

3.2.4 合约入口 (lib.rs)

pub mod contract;
pub mod msg;
pub mod state;
pub mod vrf;

use cosmwasm_std::{
    entry_point, Binary, Deps, DepsMut, Env, MessageInfo,
    Response, StdResult,
};

use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};

#[entry_point]
pub fn instantiate(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: InstantiateMsg,
) -> StdResult<Response> {
    contract::instantiate(deps, env, info, msg)
}

#[entry_point]
pub fn execute(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> StdResult<Response> {
    contract::execute(deps, env, info, msg)
}

#[entry_point]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
    contract::query(deps, env, msg)
}

3.3 VRF Oracle 测试

#[cfg(test)]
mod tests {
    use cosmwasm_std::testing::{
        mock_dependencies, mock_env, mock_info,
        MockApi, MockQuerier, MockStorage,
    };
    use cosmwasm_std::{
        from_binary, Addr, BlockInfo, ContractInfo, Env,
        MessageInfo, Timestamp, TransactionInfo,
    };
    use crate::contract::*;
    use crate::msg::*;
    use crate::state::*;

    const DILITHIUM5_PUBKEY: &[u8; 1312] = &[0u8; 1312];

    fn setup_env(height: u64, timestamp: u64) -> Env {
        Env {
            block: BlockInfo {
                height,
                time: Timestamp::from_nanos(timestamp),
                chain_id: "msg-chain-1".to_string(),
                randomness: Some(Binary::from([0u8; 32])),
            },
            contract: ContractInfo {
                address: Addr::unchecked("contract0"),
            },
            transaction: Some(TransactionInfo { index: 0 }),
            ..mock_env()
        }
    }

    #[test]
    fn test_instantiate() {
        let mut deps = mock_dependencies();
        let env = setup_env(1, 1_000_000_000);

        let msg = InstantiateMsg {
            pubkey: Binary::from(DILITHIUM5_PUBKEY.as_slice()),
            oracle_address: "oracle".to_string(),
            request_timeout: 100,
            fee: None,
            max_pending_requests: None,
        };

        let info = mock_info("admin", &[]);
        let res = instantiate(deps.as_mut(), env, info, msg).unwrap();
        assert_eq!(res.attributes[0].value, "instantiate");
    }

    #[test]
    fn test_request_randomness() {
        let mut deps = mock_dependencies();
        let env = setup_env(1, 1_000_000_000);

        let init_msg = InstantiateMsg {
            pubkey: Binary::from(DILITHIUM5_PUBKEY.as_slice()),
            oracle_address: "oracle".to_string(),
            request_timeout: 100,
            fee: None,
            max_pending_requests: None,
        };
        instantiate(deps.as_mut(), env.clone(), mock_info("admin", &[]), init_msg).unwrap();

        let req_msg = ExecuteMsg::RequestRandomness {
            seed: None,
            callback_contract: None,
            callback_msg: None,
        };
        let info = mock_info("user1", &[]);
        let res = execute(deps.as_mut(), env.clone(), info, req_msg).unwrap();
        assert_eq!(res.attributes[1].value, "1");

        let query_resp: VrfRequest = from_binary(
            &query(deps.as_ref(), env.clone(), QueryMsg::GetRequest { request_id: 1 }).unwrap(),
        ).unwrap();
        assert_eq!(query_resp.id, 1);
        assert!(!query_resp.fulfilled);
    }

    #[test]
    fn test_fulfill_randomness_wrong_sender() {
        let mut deps = mock_dependencies();
        let env = setup_env(1, 1_000_000_000);

        let init_msg = InstantiateMsg {
            pubkey: Binary::from(DILITHIUM5_PUBKEY.as_slice()),
            oracle_address: "oracle".to_string(),
            request_timeout: 100,
            fee: None,
            max_pending_requests: None,
        };
        instantiate(deps.as_mut(), env.clone(), mock_info("admin", &[]), init_msg).unwrap();

        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("user1", &[]),
            ExecuteMsg::RequestRandomness {
                seed: None,
                callback_contract: None,
                callback_msg: None,
            },
        ).unwrap();

        let result = execute(
            deps.as_mut(),
            env.clone(),
            mock_info("attacker", &[]),
            ExecuteMsg::FulfillRandomness {
                request_id: 1,
                randomness: Binary::from([1u8; 32]),
                proof: Binary::from([2u8; 2700]),
            },
        );
        assert!(result.is_err());
    }

    #[test]
    fn test_expired_request_cancellation() {
        let mut deps = mock_dependencies();
        let env = setup_env(1, 1_000_000_000);

        let init_msg = InstantiateMsg {
            pubkey: Binary::from(DILITHIUM5_PUBKEY.as_slice()),
            oracle_address: "oracle".to_string(),
            request_timeout: 10,
            fee: None,
            max_pending_requests: None,
        };
        instantiate(deps.as_mut(), env.clone(), mock_info("admin", &[]), init_msg).unwrap();

        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("user1", &[]),
            ExecuteMsg::RequestRandomness {
                seed: None,
                callback_contract: None,
                callback_msg: None,
            },
        ).unwrap();

        let future_env = setup_env(21, 2_000_000_000);
        let res = execute(
            deps.as_mut(),
            future_env.clone(),
            mock_info("user1", &[]),
            ExecuteMsg::CancelExpiredRequest { request_id: 1 },
        ).unwrap();
        assert_eq!(res.attributes[0].value, "cancel_request");
    }
}

4 VRF 验证器实现

4.1 Dilithium-5 VRF 推导

Dilithium-5 本身是数字签名方案,不是直接的 VRF。我们需要在 Dilithium-5 之上构建 VRF 构造。这里使用 ECVRF 风格的构造,但底层的椭圆曲线操作替换为 Dilithium-5 的格密码操作。

4.1.1 VRF 构造原理

VRF 构造 = Dilithium-5 签名 + 哈希函数

VRF_Eval(SK, alpha):
    +-------------------------------------+
    | 1. 计算 H = HashToPoint(alpha)      |  将输入映射到矩阵空间
    | 2. 计算 gamma = SK * H              |  格点乘 (Module-LWE)
    | 3. 计算 beta  = HashToOutput(gamma) |  派生伪随机输出
    | 4. 计算 pi    = Dilithium_Sign(SK,  |  签名证明
    |                     (alpha || beta))
    | 5. 输出 (pi, beta)                  |
    +-------------------------------------+

VRF_Verify(PK, alpha, pi, beta):
    +-------------------------------------+
    | 1. 计算 H = HashToPoint(alpha)      |
    | 2. 检查 Dilithium_Verify(PK,        |  验证签名
    |            (alpha || beta), pi)       |
    | 3. 将 gamma 从 pi 中提取             |
    | 4. 检查 Pairing(PK, H) ==            |  一致性检查
    |        Pairing(G, gamma)              |
    | 5. 检查 HashToOutput(gamma) == beta  |
    | 6. 全部通过 -> 返回 True              |
    +-------------------------------------+

4.1.2 完整 VRF 实现 (vrf.rs)

use sha2::{Digest, Sha256, Sha512};
use hmac::{Hmac, Mac};

/// Dilithium-5 参数常量
const DILITHIUM5_PUBLICKEYBYTES: usize = 1312;
const DILITHIUM5_SECRETKEYBYTES: usize = 2560;
const DILITHIUM5_SIGNBYTES: usize = 2700;
const VRF_OUTPUT_BYTES: usize = 64;

const MODULUS_Q: u64 = 8380417;
const N: usize = 256;
const K: usize = 8;
const L: usize = 7;

/// VRF 错误类型
#[derive(Debug, PartialEq)]
pub enum VrfError {
    InvalidPublicKey,
    InvalidProof,
    InvalidOutput,
    VerificationFailed,
    HashToPointFailed,
}

impl std::fmt::Display for VrfError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            VrfError::InvalidPublicKey => write!(f, "Invalid public key"),
            VrfError::InvalidProof => write!(f, "Invalid proof format"),
            VrfError::InvalidOutput => write!(f, "Invalid output format"),
            VrfError::VerificationFailed => write!(f, "VRF verification failed"),
            VrfError::HashToPointFailed => write!(f, "Failed to hash to point"),
        }
    }
}

/// VRF 上下文
pub struct Dilithium5Vrf {
    q: u64,
    n: usize,
    k: usize,
    l: usize,
}

impl Default for Dilithium5Vrf {
    fn default() -> Self {
        Self { q: MODULUS_Q, n: N, k: K, l: L }
    }
}

impl Dilithium5Vrf {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn verify(
        &self,
        pubkey: &[u8],
        alpha: &[u8],
        pi: &[u8],
        beta: &[u8],
    ) -> bool {
        if pubkey.len() != DILITHIUM5_PUBLICKEYBYTES {
            return false;
        }
        if beta.len() != VRF_OUTPUT_BYTES {
            return false;
        }

        let (signature, gamma) = match self.parse_proof(pi) {
            Some(data) => data,
            None => return false,
        };

        let h_point = match self.hash_to_point(alpha) {
            Some(pt) => pt,
            None => return false,
        };

        let mut msg = Vec::with_capacity(alpha.len() + beta.len());
        msg.extend_from_slice(alpha);
        msg.extend_from_slice(beta);

        if !self.verify_dilithium_signature(pubkey, &msg, &signature) {
            return false;
        }

        if !self.verify_pairing(pubkey, &h_point, &gamma) {
            return false;
        }

        let expected_beta = self.hash_to_output(&gamma);
        if expected_beta.len() != beta.len() {
            return false;
        }

        self.constant_time_compare(beta, &expected_beta)
    }

    fn parse_proof(&self, pi: &[u8]) -> Option<(Vec<u8>, Vec<u8>)> {
        if pi.len() < DILITHIUM5_SIGNBYTES + 32 {
            return None;
        }
        let signature = pi[..DILITHIUM5_SIGNBYTES].to_vec();
        let gamma = pi[DILITHIUM5_SIGNBYTES..].to_vec();
        Some((signature, gamma))
    }

    fn hash_to_point(&self, alpha: &[u8]) -> Option<Vec<u8>> {
        use sha3::Shake256;
        use sha3::digest::ExtendableOutput;
        use sha3::digest::XofReader;

        let mut hasher = Shake256::default();
        hasher.update(b"MSG-DAR-VRF-HASH2POINT");
        hasher.update(alpha);

        let mut reader = hasher.finalize_xof();
        let total_elements = self.k * self.l * self.n;
        let mut h_bytes = vec![0u8; total_elements * 4];
        reader.read(&mut h_bytes);

        let mut result = Vec::with_capacity(total_elements);
        for chunk in h_bytes.chunks(4) {
            let val = u32::from_le_bytes([
                chunk[0], chunk[1], chunk[2], chunk[3],
            ]) as u64;
            result.push((val % (self.q - 1) + 1) as u8);
        }
        Some(result)
    }

    fn hash_to_output(&self, gamma: &[u8]) -> Vec<u8> {
        use sha3::Shake256;
        use sha3::digest::ExtendableOutput;
        use sha3::digest::XofReader;

        let mut hasher = Shake256::default();
        hasher.update(b"MSG-DAR-VRF-HASH2OUTPUT");
        hasher.update(gamma);

        let mut reader = hasher.finalize_xof();
        let mut output = vec![0u8; VRF_OUTPUT_BYTES];
        reader.read(&mut output);
        output
    }

    fn verify_dilithium_signature(
        &self,
        pubkey: &[u8],
        message: &[u8],
        signature: &[u8],
    ) -> bool {
        if signature.len() != DILITHIUM5_SIGNBYTES {
            return false;
        }
        if pubkey.len() != DILITHIUM5_PUBLICKEYBYTES {
            return false;
        }

        // 实际生产环境中应调用 msg-chain-devkit 的 dilithium5_verify
        // 此处为简化实现
        unimplemented!("Dilithium-5 signature verification requires native binding")
    }

    fn verify_pairing(
        &self,
        _pubkey: &[u8],
        _h_point: &[u8],
        _gamma: &[u8],
    ) -> bool {
        // 格密码中的配对检查
        unimplemented!("Pairing verification requires lattice operations")
    }

    fn constant_time_compare(&self, a: &[u8], b: &[u8]) -> bool {
        if a.len() != b.len() {
            return false;
        }
        let mut result: u8 = 0;
        for (x, y) in a.iter().zip(b.iter()) {
            result |= x ^ y;
        }
        result == 0
    }

    pub fn derive_subkey(&self, seed: &[u8], path: &str) -> Vec<u8> {
        let mut hasher = Sha256::new();
        hasher.update(seed);
        hasher.update(b":");
        hasher.update(path.as_bytes());
        hasher.finalize().to_vec()
    }

    pub fn output_to_u64(&self, output: &[u8]) -> u64 {
        let mut bytes = [0u8; 8];
        bytes.copy_from_slice(&output[..8]);
        u64::from_le_bytes(bytes)
    }

    pub fn output_to_u128(&self, output: &[u8]) -> u128 {
        let mut bytes = [0u8; 16];
        bytes.copy_from_slice(&output[..16]);
        u128::from_le_bytes(bytes)
    }

    pub fn output_to_range(&self, output: &[u8], max: u64) -> u64 {
        let val = self.output_to_u64(output);
        val % max
    }
}

pub fn verify_vrf(
    pubkey: &[u8],
    alpha: &[u8],
    pi: &[u8],
    beta: &[u8],
) -> bool {
    let vrf = Dilithium5Vrf::new();
    vrf.verify(pubkey, alpha, pi, beta)
}

pub fn batch_verify_vrf(
    pubkeys: &[&[u8]],
    alphas: &[&[u8]],
    pis: &[&[u8]],
    betas: &[&[u8]],
) -> Vec<bool> {
    let vrf = Dilithium5Vrf::new();
    pubkeys.iter()
        .zip(alphas.iter())
        .zip(pis.iter())
        .zip(betas.iter())
        .map(|(((pk, a), pi), b)| vrf.verify(pk, a, pi, b))
        .collect()
}

pub fn vrf_output_to_randomness(output: &[u8]) -> [u8; 32] {
    let mut result = [0u8; 32];
    if output.len() >= 32 {
        result.copy_from_slice(&output[..32]);
    } else {
        let hash = Sha256::digest(output);
        result.copy_from_slice(&hash);
    }
    result
}

pub fn vrf_prng(output: &[u8], length: usize) -> Vec<u8> {
    let mut result = Vec::with_capacity(length);
    let mut counter: u64 = 0;

    while result.len() < length {
        let mut mac = Hmac::<Sha512>::new_from_slice(output)
            .expect("HMAC can take any key size");
        mac.update(b"MSG-CHAIN-VRF-PRNG");
        mac.update(&counter.to_le_bytes());

        let block = mac.finalize().into_bytes();
        result.extend_from_slice(&block);
        counter += 1;
    }

    result.truncate(length);
    result
}

pub fn vrf_output_hex(output: &[u8]) -> String {
    output.iter()
        .map(|b| format!("{:02x}", b))
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_vrf_new() {
        let vrf = Dilithium5Vrf::new();
        assert_eq!(vrf.q, MODULUS_Q);
    }

    #[test]
    fn test_hash_to_point_variation() {
        let vrf = Dilithium5Vrf::new();
        let pt1 = vrf.hash_to_point(b"input1").unwrap();
        let pt2 = vrf.hash_to_point(b"input2").unwrap();
        assert_ne!(pt1, pt2);
    }

    #[test]
    fn test_hash_to_point_deterministic() {
        let vrf = Dilithium5Vrf::new();
        let pt1 = vrf.hash_to_point(b"test_input").unwrap();
        let pt2 = vrf.hash_to_point(b"test_input").unwrap();
        assert_eq!(pt1, pt2);
    }

    #[test]
    fn test_output_to_range() {
        let vrf = Dilithium5Vrf::new();
        let output = vec![0xFFu8; 64];
        let range_100 = vrf.output_to_range(&output, 100);
        assert!(range_100 < 100);
    }

    #[test]
    fn test_constant_time_compare() {
        let vrf = Dilithium5Vrf::new();
        assert!(vrf.constant_time_compare(b"abc", b"abc"));
        assert!(!vrf.constant_time_compare(b"abc", b"abd"));
        assert!(!vrf.constant_time_compare(b"abc", b"abcd"));
    }

    #[test]
    fn test_derive_subkey() {
        let vrf = Dilithium5Vrf::new();
        let seed = [0xABu8; 32];
        let sk1 = vrf.derive_subkey(&seed, "nft/trait/background");
        let sk2 = vrf.derive_subkey(&seed, "nft/trait/foreground");
        assert_ne!(sk1, sk2);
    }

    #[test]
    fn test_vrf_prng_length() {
        let seed = [0x42u8; 32];
        let random_bytes = vrf_prng(&seed, 100);
        assert_eq!(random_bytes.len(), 100);
    }

    #[test]
    fn test_vrf_prng_deterministic() {
        let seed = [0x42u8; 32];
        let rb1 = vrf_prng(&seed, 32);
        let rb2 = vrf_prng(&seed, 32);
        assert_eq!(rb1, rb2);
    }

    #[test]
    fn test_vrf_output_hex() {
        let output = [0x00, 0xFF, 0xAB, 0xCD];
        let hex = vrf_output_hex(&output);
        assert_eq!(hex, "00ffabcd");
    }
}

4.2 Oracle 节点实现(Python)

"""
VRF Oracle 节点 - 离线 VRF 计算服务
负责: 1. 监听合约事件 -> 2. 计算 VRF -> 3. 提交结果
依赖: pip install msg-chain-sdk cryptography aiohttp
"""

import asyncio
import hashlib
import json
import logging
import os
import struct
import time
from typing import Optional

import aiohttp
from msg_chain_sdk.dilithium import Dilithium5
from msg_chain_sdk.vrf import Vrf
from msg_chain_sdk.wallet import Wallet
from msg_chain_sdk.contract import CosmWasmContract

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class VrfOracleNode:
    """MSG Chain VRF Oracle 节点"""

    def __init__(
        self,
        rpc_endpoint: str,
        contract_address: str,
        private_key_hex: str,
        poll_interval: float = 2.0,
    ):
        self.rpc = rpc_endpoint
        self.contract_addr = contract_address
        self.wallet = Wallet.from_hex(private_key_hex)
        self.contract = CosmWasmContract(rpc_endpoint, contract_address, self.wallet)
        self.dilithium = Dilithium5()
        self.vrf = Vrf(self.dilithium)
        self.processed_requests: set[int] = set()
        self.poll_interval = poll_interval

    async def run(self):
        """主循环: 持续监听并处理 VRF 请求"""
        logger.info(f"VRF Oracle 节点: {self.contract_addr}")
        while True:
            try:
                await self.process_new_requests()
            except Exception as e:
                logger.error(f"Error: {e}")
            await asyncio.sleep(self.poll_interval)

    async def process_new_requests(self):
        """查询并处理所有未完成的 VRF 请求"""
        config = await self.contract.query("get_config")
        state = await self.contract.query("get_state") if hasattr(self.contract, 'query') else {}

    async def fulfill_request(self, request_id: int, request: dict):
        """计算 VRF 并提交结果"""
        seed_bytes = bytes.fromhex(request["seed"]) if request.get("seed") else b""
        requester = request["requester"].encode("utf-8")

        alpha = hashlib.sha256(
            seed_bytes
            + struct.pack(">Q", request_id)
            + requester
        ).digest()

        private_key = bytes.fromhex(os.environ["DILITHIUM_PRIVATE_KEY"])
        proof, output = self.vrf.eval(private_key, alpha)
        logger.info(f"VRF computed: output={output.hex()[:16]}...")

        fulfill_msg = {
            "fulfill_randomness": {
                "request_id": request_id,
                "randomness": output.hex(),
                "proof": proof.hex(),
            }
        }

        try:
            tx_hash = await self.contract.execute(fulfill_msg, gas_limit=500_000)
            logger.info(f"Submitted: tx_hash={tx_hash}")
            self.processed_requests.add(request_id)
        except Exception as e:
            logger.error(f"Submit failed: {e}")


class OracleMonitor:
    """Oracle 节点健康监控"""

    def __init__(self, node: VrfOracleNode):
        self.node = node
        self.start_time = time.time()
        self.total_fulfilled = 0
        self.errors = 0

    def report(self) -> dict:
        return {
            "uptime_seconds": time.time() - self.start_time,
            "total_fulfilled": self.total_fulfilled,
            "error_rate": self.errors / max(self.total_fulfilled + self.errors, 1),
            "last_check": time.time(),
        }


async def main():
    node = VrfOracleNode(
        rpc_endpoint=os.getenv("MSG_RPC", "https://rpc.msg-chain-1.com"),
        contract_address=os.getenv("VRF_CONTRACT_ADDRESS"),
        private_key_hex=os.getenv("ORACLE_PRIVATE_KEY"),
        poll_interval=float(os.getenv("POLL_INTERVAL", "2.0")),
    )
    monitor = OracleMonitor(node)
    try:
        await node.run()
    except KeyboardInterrupt:
        logger.info(f"Shutdown. Report: {monitor.report()}")

if __name__ == "__main__":
    asyncio.run(main())

5 随机数消费模式

5.1 随机 NFT 铸造(特征派生)

5.1.1 NFT 特征派生引擎

use cosmwasm_std::{Binary, StdResult, StdError};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct NftTraits {
    pub background: Background,
    pub body: Body,
    pub head: Head,
    pub eyes: Eyes,
    pub mouth: Mouth,
    pub accessory: Option<Accessory>,
    pub score: u64,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum Background { Ocean, Space, Forest, Desert, Cyber }

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum Body { Human, Robot, Alien, Ghost }

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum Head { Cap, Crown, Helmet, Mask, None }

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum Eyes { Normal, Glow, Laser, Blind, XRay }

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum Mouth { Smile, Open, Mask, Vape }

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum Accessory { Sword, Shield, Wings, Pet, None_ }

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct NftRandomEvents {
    pub background_roll: u64,
    pub body_roll: u64,
    pub head_roll: u64,
    pub eyes_roll: u64,
    pub mouth_roll: u64,
    pub accessory_roll: Option<u64>,
    pub score_roll: u64,
    pub all_random_bytes: String,
}

struct RarityTable<T> {
    items: Vec<(T, u64)>,
    total_weight: u64,
}

impl<T: Clone> RarityTable<T> {
    fn new(items: Vec<(T, u64)>) -> Self {
        let total_weight = items.iter().map(|(_, w)| w).sum();
        Self { items, total_weight }
    }

    fn roll(&self, value: u64) -> &T {
        let roll = value % self.total_weight;
        let mut cumulative = 0u64;
        for (item, weight) in &self.items {
            cumulative += weight;
            if roll < cumulative {
                return item;
            }
        }
        &self.items.last().unwrap().0
    }
}

pub struct TraitDeriver;

impl TraitDeriver {
    pub fn derive_traits(
        randomness: &[u8; 32],
        token_id: u64,
        minter: &str,
        timestamp: u64,
    ) -> StdResult<(NftTraits, NftRandomEvents)> {
        let entropy = |path: &[u8]| -> u64 {
            let mut hasher = Sha256::new();
            hasher.update(randomness);
            hasher.update(&token_id.to_be_bytes());
            hasher.update(minter.as_bytes());
            hasher.update(&timestamp.to_be_bytes());
            hasher.update(path);
            let hash = hasher.finalize();
            u64::from_be_bytes(hash[..8].try_into().unwrap())
        };

        let backgrounds = RarityTable::new(vec![
            (Background::Ocean,  2500),
            (Background::Space,  2200),
            (Background::Forest, 2000),
            (Background::Desert, 1800),
            (Background::Cyber,  1500),
        ]);

        let bodies = RarityTable::new(vec![
            (Body::Human, 4000),
            (Body::Robot, 3000),
            (Body::Alien, 2000),
            (Body::Ghost, 1000),
        ]);

        let heads = RarityTable::new(vec![
            (Head::None,  4000),
            (Head::Cap,   2500),
            (Head::Helmet, 2000),
            (Head::Mask,  1000),
            (Head::Crown,  500),
        ]);

        let eyes = RarityTable::new(vec![
            (Eyes::Normal, 5000),
            (Eyes::Glow,   2500),
            (Eyes::Laser,  1500),
            (Eyes::Blind,  600),
            (Eyes::XRay,   400),
        ]);

        let mouths = RarityTable::new(vec![
            (Mouth::Smile, 4000),
            (Mouth::Open,  3000),
            (Mouth::Mask,  2000),
            (Mouth::Vape,  1000),
        ]);

        let accessories = RarityTable::new(vec![
            (Accessory::None_, 7000),
            (Accessory::Sword, 1000),
            (Accessory::Shield, 800),
            (Accessory::Wings,  700),
            (Accessory::Pet,    500),
        ]);

        let background = backgrounds.roll(entropy(b"background")).clone();
        let body = bodies.roll(entropy(b"body")).clone();
        let head = heads.roll(entropy(b"head")).clone();
        let eyes = eyes.roll(entropy(b"eyes")).clone();
        let mouth = mouths.roll(entropy(b"mouth")).clone();
        let score_roll = entropy(b"score");
        let accessory = accessories.roll(entropy(b"accessory")).clone();

        let score = (score_roll % 10001) + match (&background, &body, &head, &eyes, &mouth, &accessory) {
            (Background::Cyber, Body::Ghost, Head::Crown, Eyes::XRay, Mouth::Vape, Accessory::Wings) => 5000,
            _ => 0,
        };

        let trait_accessory = if matches!(accessory, Accessory::None_) { None } else { Some(accessory) };

        let traits = NftTraits { background, body, head, eyes, mouth, accessory: trait_accessory, score };

        let events = NftRandomEvents {
            background_roll: entropy(b"background"),
            body_roll: entropy(b"body"),
            head_roll: entropy(b"head"),
            eyes_roll: entropy(b"eyes"),
            mouth_roll: entropy(b"mouth"),
            accessory_roll: Some(entropy(b"accessory")),
            score_roll,
            all_random_bytes: hex::encode(randomness),
        };

        Ok((traits, events))
    }

    pub fn verify_traits(traits: &NftTraits, randomness: &[u8; 32], token_id: u64, minter: &str, timestamp: u64) -> StdResult<bool> {
        let (computed, _) = Self::derive_traits(randomness, token_id, minter, timestamp)?;
        Ok(traits == &computed)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_derive_traits_deterministic() {
        let randomness = [0xABu8; 32];
        let (t1, _) = TraitDeriver::derive_traits(&randomness, 1, "alice", 1000).unwrap();
        let (t2, _) = TraitDeriver::derive_traits(&randomness, 1, "alice", 1000).unwrap();
        assert_eq!(t1, t2);
    }

    #[test]
    fn test_derive_traits_different_token() {
        let randomness = [0xABu8; 32];
        let (t1, _) = TraitDeriver::derive_traits(&randomness, 1, "alice", 1000).unwrap();
        let (t2, _) = TraitDeriver::derive_traits(&randomness, 2, "alice", 1000).unwrap();
        assert_ne!(t1, t2);
    }

    #[test]
    fn test_verify_traits() {
        let randomness = [0x42u8; 32];
        let (traits, _) = TraitDeriver::derive_traits(&randomness, 42, "charlie", 2000).unwrap();
        assert!(TraitDeriver::verify_traits(&traits, &randomness, 42, "charlie", 2000).unwrap());
        assert!(!TraitDeriver::verify_traits(&traits, &randomness, 43, "charlie", 2000).unwrap());
    }
}

5.2 随机中奖者选取(彩票/抽奖)

use cosmwasm_std::{Addr, Binary, DepsMut, Env, MessageInfo, Response, StdError, StdResult};
use sha2::{Digest, Sha256};

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct LotteryEntry {
    pub participant: Addr,
    pub tickets: u32,
    pub entry_index: u64,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct LotteryState {
    pub round: u64,
    pub total_tickets: u64,
    pub entries: Vec<LotteryEntry>,
    pub winner: Option<Addr>,
    pub randomness: Option<Binary>,
    pub drawn: bool,
}

pub fn select_winner(entries: &[LotteryEntry], randomness: &[u8; 32]) -> StdResult<(Addr, u64)> {
    let total_tickets: u64 = entries.iter().map(|e| e.tickets as u64).sum();
    if total_tickets == 0 {
        return Err(StdError::generic_err("No participants"));
    }

    let mut hash = Sha256::new();
    hash.update(randomness);
    hash.update(b"lottery_winner");
    let result = hash.finalize();
    let winning_index = u64::from_be_bytes(result[..8].try_into().unwrap()) % total_tickets;

    let mut cumulative = 0u64;
    for entry in entries {
        cumulative += entry.tickets as u64;
        if winning_index < cumulative {
            return Ok((entry.participant.clone(), winning_index));
        }
    }
    Ok((entries.last().unwrap().participant.clone(), winning_index))
}

pub fn select_multiple_winners(entries: &[LotteryEntry], randomness: &[u8; 32], count: usize) -> StdResult<Vec<Addr>> {
    if count > entries.len() {
        return Err(StdError::generic_err("More winners than participants"));
    }
    let mut winners = Vec::new();
    let mut remaining: Vec<LotteryEntry> = entries.to_vec();

    for round in 0..count {
        let total: u64 = remaining.iter().map(|e| e.tickets as u64).sum();
        let mut hash = Sha256::new();
        hash.update(randomness);
        hash.update(&round.to_be_bytes());
        let result = hash.finalize();
        let index = u64::from_be_bytes(result[..8].try_into().unwrap()) % total;

        let mut cum = 0u64;
        let mut found = None;
        for (i, entry) in remaining.iter().enumerate() {
            cum += entry.tickets as u64;
            if index < cum {
                found = Some(i);
                break;
            }
        }
        if let Some(i) = found {
            winners.push(remaining[i].participant.clone());
            remaining.remove(i);
        }
    }
    Ok(winners)
}

#[cfg(test)]
mod lottery_tests {
    use super::*;

    fn make_entries() -> Vec<LotteryEntry> {
        vec![
            LotteryEntry { participant: Addr::unchecked("alice"), tickets: 10, entry_index: 0 },
            LotteryEntry { participant: Addr::unchecked("bob"), tickets: 5, entry_index: 10 },
            LotteryEntry { participant: Addr::unchecked("charlie"), tickets: 3, entry_index: 15 },
        ]
    }

    #[test]
    fn test_select_winner_deterministic() {
        let entries = make_entries();
        let randomness = [0x42u8; 32];
        let (w1, _) = select_winner(&entries, &randomness).unwrap();
        let (w2, _) = select_winner(&entries, &randomness).unwrap();
        assert_eq!(w1, w2);
    }

    #[test]
    fn test_select_winner_in_bounds() {
        let entries = make_entries();
        let valid = vec![Addr::unchecked("alice"), Addr::unchecked("bob"), Addr::unchecked("charlie")];
        for seed_byte in 0..=255u8 {
            let r = [seed_byte; 32];
            let (winner, _) = select_winner(&entries, &r).unwrap();
            assert!(valid.contains(&winner));
        }
    }

    #[test]
    fn test_select_winner_weighted_distribution() {
        let entries = make_entries();
        let mut wins = std::collections::HashMap::new();
        let trials = 10_000;
        for i in 0..trials {
            let r = Sha256::digest(&i.to_be_bytes());
            let mut randomness = [0u8; 32];
            randomness.copy_from_slice(&r);
            let (winner, _) = select_winner(&entries, &randomness).unwrap();
            *wins.entry(winner).or_insert(0) += 1;
        }
        let alice_ratio = *wins.get(&Addr::unchecked("alice")).unwrap_or(&0) as f64 / trials as f64;
        assert!((alice_ratio - 10.0 / 18.0).abs() < 0.03);
    }
}

5.3 随机排序(DAO 治理提案排序)

use rand::SeedableRng;
use rand::rngs::StdRng;
use rand::seq::SliceRandom;

pub fn fisher_yates_shuffle<T: Clone>(items: &[T], randomness: &[u8; 32], salt: &[u8]) -> Vec<T> {
    let mut seed = Sha256::new();
    seed.update(randomness);
    seed.update(salt);
    let seed_bytes = seed.finalize();
    let mut rng = StdRng::from_seed(seed_bytes.into());
    let mut shuffled = items.to_vec();
    shuffled.shuffle(&mut rng);
    shuffled
}

pub fn shuffle_proposals(proposal_ids: &[u64], randomness: &[u8; 32], round: u64) -> Vec<u64> {
    fisher_yates_shuffle(proposal_ids, randomness, &round.to_be_bytes())
}

pub fn select_review_committee(all_members: &[Addr], randomness: &[u8; 32], committee_size: usize) -> Vec<Addr> {
    let shuffled = fisher_yates_shuffle(all_members, randomness, b"committee");
    shuffled.into_iter().take(committee_size).collect()
}

#[cfg(test)]
mod shuffle_tests {
    use super::*;

    #[test]
    fn test_shuffle_preserves_elements() {
        let proposals = vec![1u64, 2, 3, 4, 5];
        let randomness = [0x99u8; 32];
        let mut sorted = shuffle_proposals(&proposals, &randomness, 1);
        sorted.sort();
        assert_eq!(sorted, proposals);
    }

    #[test]
    fn test_shuffle_different_rounds() {
        let proposals = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
        let r = [0x42u8; 32];
        assert_ne!(shuffle_proposals(&proposals, &r, 1), shuffle_proposals(&proposals, &r, 2));
    }

    #[test]
    fn test_committee_size() {
        let members: Vec<Addr> = (0..100).map(|i| Addr::unchecked(format!("member{}", i))).collect();
        let r = [0x11u8; 32];
        let c = select_review_committee(&members, &r, 5);
        assert_eq!(c.len(), 5);
        for m in &c { assert!(members.contains(m)); }
    }
}

5.4 随机数合并模式

pub fn combine_randomness_sources(dar_randomness: &[u8; 32], vrf_randomness: &[u8; 32]) -> [u8; 32] {
    let mut hasher = Sha256::new();
    hasher.update(dar_randomness);
    hasher.update(vrf_randomness);
    let result = hasher.finalize();
    let mut combined = [0u8; 32];
    combined.copy_from_slice(&result);
    combined
}

pub fn delayed_randomness(deps: DepsMut, env: Env, commit_block: u64, target_delay: u64) -> StdResult<[u8; 32]> {
    let target_height = commit_block + target_delay;
    if env.block.height < target_height {
        return Err(StdError::generic_err(format!("Wait until block {}", target_height)));
    }
    let response = query_dar_randomness(&deps.as_ref(), Some(target_height))?;
    let mut seed = [0u8; 32];
    seed.copy_from_slice(response.randomness.as_slice());
    Ok(seed)
}

6 安全考虑

6.1 偏斜攻击 (Bias Attack)

6.1.1 问题描述

偏斜攻击指攻击者通过影响随机性生成过程来使得结果对自己有利。

攻击场景: 基于 BlockHash 的彩票
    |
    |- 攻击者看到当前的 block hash -> 计算是否中奖
    |- 如果中奖 -> 提交交易
    |- 如果没中奖 -> 不提交
    |- 等价于攻击者可以"选择"是否参与!

6.1.2 DAR 的防护

DAR 通过以下设计抵御偏斜攻击:

  1. 提议者承诺:区块提议者在构建区块时必须包含 VRF 证明
  2. BFT 确认:需要 2/3+ 验证者签名确认
  3. 链式随机性:区块 H 的随机性依赖 H-1 的随机性
/// 安全的随机数消费模式:延迟消费
pub struct DelayedRandomConsumer {
    delay: u64,
    pending_requests: Map<u64, (Addr, u64)>,
}

impl DelayedRandomConsumer {
    pub fn request_randomness(&self, deps: DepsMut, env: &Env, info: &MessageInfo) -> StdResult<Response> {
        let request_id = /* next id */ 0;
        self.pending_requests.save(deps.storage, request_id, &(info.sender.clone(), env.block.height))?;
        Ok(Response::new()
            .add_attribute("commit_height", env.block.height.to_string())
            .add_attribute("consume_height", (env.block.height + self.delay).to_string()))
    }

    pub fn consume_randomness(&self, deps: DepsMut, env: &Env, request_id: u64) -> StdResult<Response> {
        let (_, commit_height) = self.pending_requests.load(deps.storage, request_id)?;
        if env.block.height < commit_height + self.delay {
            return Err(StdError::generic_err("Delay not elapsed"));
        }
        let seed = get_dar_randomness(env)?;
        Ok(Response::new().add_attribute("action", "consume"))
    }
}

6.2 抢跑攻击 (Front-Running)

/// 反抢跑: 将用户地址和 nonce 绑定到随机数派生中
pub fn derive_user_randomness(dar_seed: &[u8; 32], user: &Addr, nonce: u64) -> [u8; 32] {
    let mut hasher = Sha256::new();
    hasher.update(dar_seed);
    hasher.update(user.as_bytes());
    hasher.update(&nonce.to_be_bytes());
    let result = hasher.finalize();
    let mut output = [0u8; 32];
    output.copy_from_slice(&result);
    output
}

6.3 Oracle 活性 (Liveness)

6.3.1 解决方案:多 Oracle + 超时 + 降级

/// 多 Oracle 支持
pub struct MultiOracle {
    pub oracles: Vec<Addr>,
    pub threshold: usize,
}

/// 超时降级: 自动使用 DAR
pub fn execute_with_timeout_fallback(deps: DepsMut, env: Env, request_id: u64) -> StdResult<Response> {
    let request = REQUESTS.load(deps.storage, request_id)?;
    let elapsed = env.block.height - request.request_height;
    let timeout = CONFIG.load(deps.storage)?.request_timeout;

    if elapsed < timeout {
        return Err(StdError::generic_err("Not yet expired"));
    }

    let dar_seed = get_dar_randomness(&env)?;
    Ok(Response::new()
        .add_attribute("method", "fallback_to_dar")
        .add_attribute("request_id", request_id.to_string()))
}

6.4 重入攻击 (Reentrancy)

/// 防重入: 互斥锁
pub struct ReentrancyGuard;

const REENTRANCY_KEY: &[u8] = b"reentrancy_guard";

impl ReentrancyGuard {
    pub fn lock(store: &mut dyn Storage) -> StdResult<()> {
        if store.get(REENTRANCY_KEY).is_some() {
            return Err(StdError::generic_err("Reentrancy detected"));
        }
        store.set(REENTRANCY_KEY, &[1]);
        Ok(())
    }

    pub fn unlock(store: &mut dyn Storage) {
        store.remove(REENTRANCY_KEY);
    }
}

pub fn safe_randomness_callback(deps: DepsMut, env: Env, info: MessageInfo, randomness: Binary) -> StdResult<Response> {
    ReentrancyGuard::lock(deps.storage)?;
    let result = actual_callback_logic(deps, env, info, randomness);
    ReentrancyGuard::unlock(deps.storage);
    result
}

6.5 可验证的消费日志

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct RandomnessConsumptionEvent {
    pub event_id: u64,
    pub source: String,
    pub dar_seed: Option<[u8; 32]>,
    pub vrf_request_id: Option<u64>,
    pub final_randomness: [u8; 32],
    pub consumer: Addr,
    pub action: String,
    pub block_height: u64,
    pub timestamp: u64,
}

pub const CONSUMPTION_LOG: Map<u64, RandomnessConsumptionEvent> = Map::new("consumption_log");
pub const CONSUMPTION_COUNT: Item<u64> = Item::new("consumption_count");

7 TypeScript 集成

7.1 使用 CosmJS 与 VRF Oracle 交互

import {
  CosmWasmClient,
  SigningCosmWasmClient,
  ExecuteResult,
} from '@cosmjs/cosmwasm-stargate';
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
import { GasPrice } from '@cosmjs/stargate';
import { fromHex, toHex } from '@cosmjs/encoding';
import { sha256 } from '@noble/hashes/sha256';

const MSG_CHAIN_RPC = 'https://rpc.msg-chain-1.com';
const GAS_PRICE = GasPrice.fromString('1000000000umsg');

export interface VrfOracleClient {
  client: SigningCosmWasmClient;
  sender: string;
}

export async function createVrfClient(mnemonic: string): Promise<VrfOracleClient> {
  const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: 'msg' });
  const [{ address }] = await wallet.getAccounts();
  const client = await SigningCosmWasmClient.connectWithSigner(MSG_CHAIN_RPC, wallet, { gasPrice: GAS_PRICE });
  return { client, sender: address };
}

export class VrfOracleContract {
  private client: VrfOracleClient;
  private contractAddress: string;

  constructor(client: VrfOracleClient, contractAddress: string) {
    this.client = client;
    this.contractAddress = contractAddress;
  }

  async getConfig(): Promise<any> {
    return this.client.client.queryContractSmart(this.contractAddress, { get_config: {} });
  }

  async getRequest(requestId: number): Promise<any> {
    return this.client.client.queryContractSmart(this.contractAddress, { get_request: { request_id: requestId } });
  }

  async getResponse(requestId: number): Promise<any> {
    return this.client.client.queryContractSmart(this.contractAddress, { get_response: { request_id: requestId } });
  }

  async getLatestRandomness(): Promise<any> {
    return this.client.client.queryContractSmart(this.contractAddress, { get_latest_randomness: {} });
  }

  async requestRandomness(seed?: Uint8Array, callbackContract?: string, callbackMsg?: Uint8Array): Promise<number> {
    const msg: any = { request_randomness: {} };
    if (seed) msg.request_randomness.seed = toHex(seed);
    if (callbackContract) msg.request_randomness.callback_contract = callbackContract;
    if (callbackMsg) msg.request_randomness.callback_msg = toHex(callbackMsg);

    const result = await this.client.client.execute(
      this.client.sender, this.contractAddress, msg, 'auto',
    );
    return this.parseRequestIdFromEvents(result);
  }

  async verifyRandomness(requestId: number, randomness: string, proof: string): Promise<boolean> {
    const result: { valid: boolean } = await this.client.client.queryContractSmart(
      this.contractAddress,
      { verify_randomness: { request_id: requestId, randomness, proof } },
    );
    return result.valid;
  }

  private parseRequestIdFromEvents(result: ExecuteResult): number {
    for (const event of result.events) {
      if (event.type === 'vrf-request') {
        const attr = event.attributes.find(a => a.key === 'request_id');
        if (attr) return parseInt(attr.value, 10);
      }
    }
    throw new Error('request_id not found in events');
  }
}

7.2 事件订阅

export function subscribeVrfFulfilled(
  client: CosmWasmClient,
  contractAddress: string,
  callback: (event: { requestId: number; randomness: string; txHash: string }) => void,
) {
  const ws = new WebSocket(
    MSG_CHAIN_RPC.replace('https', 'wss').replace('http', 'ws') + '/websocket',
  );

  ws.onopen = () => {
    ws.send(JSON.stringify({
      jsonrpc: '2.0',
      method: 'subscribe',
      params: ["tm.event='Tx' AND vrf_fulfilled.request_id EXISTS"],
      id: 1,
    }));
  };

  ws.onmessage = (msg) => {
    try {
      const data = JSON.parse(msg.data);
      if (data.result?.events?.['vrf-fulfilled']) {
        const attrs = data.result.events['vrf-fulfilled'];
        callback({
          requestId: parseInt(attrs.find((a: any) => a.key === 'request_id')?.value || '0'),
          randomness: attrs.find((a: any) => a.key === 'randomness')?.value || '',
          txHash: data.result.hash || '',
        });
      }
    } catch { /* ignore */ }
  };

  return () => ws.close();
}

export function vrfOutputToRange(randomness: string, max: number): number {
  const bytes = fromHex(randomness);
  const hash = sha256(bytes);
  const value = new DataView(hash.buffer).getBigUint64(0, false);
  return Number(value % BigInt(max));
}

export function vrfOutputToUuid(randomness: string): string {
  const hash = sha256(fromHex(randomness));
  hash[6] = (hash[6] & 0x0f) | 0x40;
  hash[8] = (hash[8] & 0x3f) | 0x80;
  const hex = toHex(hash);
  return [hex.slice(0, 8), hex.slice(8, 12), hex.slice(12, 16), hex.slice(16, 20), hex.slice(20, 32)].join('-');
}

7.3 React dApp 集成

import React, { useEffect, useState, useCallback } from 'react';
import { useWallet } from '@msg-chain/react';
import { VrfOracleContract, subscribeVrfFulfilled } from './vrfClient';

export const RandomnessDisplay: React.FC<{ contractAddress: string }> = ({ contractAddress }) => {
  const { address, getSigningClient } = useWallet();
  const [loading, setLoading] = useState(false);
  const [requestId, setRequestId] = useState<number | null>(null);
  const [randomness, setRandomness] = useState<string | null>(null);
  const [verified, setVerified] = useState<boolean | null>(null);

  const requestRandomness = useCallback(async () => {
    if (!address) return;
    setLoading(true);
    try {
      const signingClient = await getSigningClient();
      const vrf = new VrfOracleContract({ client: signingClient, sender: address }, contractAddress);
      const rid = await vrf.requestRandomness();
      setRequestId(rid);
    } finally {
      setLoading(false);
    }
  }, [address, contractAddress, getSigningClient]);

  useEffect(() => {
    if (!requestId) return;
    const unsubscribe = subscribeVrfFulfilled(/* ... */, contractAddress, async (event) => {
      if (event.requestId === requestId) setRandomness(event.randomness);
    });
    return unsubscribe;
  }, [requestId, contractAddress]);

  return (
    <div>
      <h2>VRF 随机数生成器</h2>
      <button onClick={requestRandomness} disabled={loading}>
        {loading ? '请求中...' : '请求随机数'}
      </button>
      {requestId && <p>请求 ID: {requestId}</p>}
      {randomness && <p>随机数: {randomness.slice(0, 32)}...</p>}
    </div>
  );
};

附录

A.1 VRF 合约 Cargo.toml

[package]
name = "vrf-oracle"
version = "1.0.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[features]
default = ["library"]
library = []

[dependencies]
cosmwasm-std = { version = "1.5", features = ["stargate"] }
cosmwasm-crypto = { version = "1.5", features = ["stargate"] }
cw-storage-plus = "1.2"
schemars = "0.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
sha2 = "0.10"
sha3 = "0.10"
hmac = "0.12"
hex = "0.4"

[dev-dependencies]
cosmwasm-vm = { version = "1.5", features = ["stargate"] }

A.2 部署命令

# 编译合约
cargo wasm

# 优化(生产环境)
docker run --rm -v "$(pwd)":/code \
  --platform linux/amd64 \
  ghcr.io/cosmwasm/workspace-optimizer:0.14.0

# 存储合约
msg-chaind tx wasm store artifacts/vrf_oracle.wasm \
  --from admin --gas-prices 1000000000umsg --gas auto \
  --gas-adjustment 1.3 --chain-id msg-chain-1 -y

# 实例化
msg-chaind tx wasm instantiate <CODE_ID> \
  '{"pubkey":"<HEX>","oracle_address":"<ADDR>","request_timeout":100}' \
  --from admin --label "vrf-oracle-v1" \
  --admin <ADMIN_ADDR> --chain-id msg-chain-1 -y

# 查询配置
msg-chaind query wasm contract-state smart <CONTRACT_ADDR> '{"get_config":{}}'

A.3 参考资源

A.4 常见问题

Q: DAR 随机性在合约中不可用怎么办?
A: 确保链版本支持 DAR。旧版本区块可能没有 randomness 字段。调用 query_dar_randomness 作为回退。

Q: VRF 证明验证 gas 消耗过高?
A: Dilithium-5 验证涉及格运算,gas 消耗较大。建议在合约内仅做哈希验证,完整的 Dilithium-5 验证放在离链端。

Q: Oracle 节点如何保证高可用?
A: 运行多个 Oracle 实例,使用负载均衡。每个实例独立监听事件,通过幂等性保证不会重复提交。

Q: 如何迁移从 DAR 到 VRF Oracle 或反之?
A: 合约通过配置开关控制随机性来源。替换时只需更新配置,无需重新部署消费者合约。