区块链 区块链技术 比特币公众号手机端

Mina Learning - Developers faq

liumuhui 2年前 (2025-01-28) 阅读数 385 #区块链
文章标签 MinaOpenBuild公链

如何创建自定义可证明类型?

你可以使用 o1js 的 Struct 来定义自定义可证明类型:

class MyCustomType extends Struct({
    field1: Field,
    filed2: MyCustomType
}) {
    constructor(
        field1: Field,
        filed2: MyCustomType,
    ) {
        super({
            field1: field1,
            filed2: filed2
        })
    }

    foo() {
    }

    bar(other: MyCustomType) {
    }
}

这样就可以在合约函数里作为参数传递了, 否则会报 Argument * of method *** is not a provable type 错误.

如何编写零知识证明应用(zkApp), 有哪些要求?

在 ZkNoid 中进行构建主要围绕创建你自己的运行时模块展开, 这与智能合约类似. 每个运行时模块都有其自身的状态以及用户可通过钱包调用的方法. 编写 zkApp 的关键规则是编写可证明的代码. 使用o1js编写时, 你是在生成一个电路. 不可证明的类型(如果不是常量的话)不会被包含在电路中, 这可能会导致安全问题或运行失败.

什么是可证明类型?

可证明类型在 o1js 或 Protokit 库中进行了定义(例如, Field, UInt64, CircuitString), 它们具备生成电路所需的逻辑, 但使用起来却如同常规类型一样. 对于将可证明类型转换为不可证明类型的方法要格外小心--这些方法只应在运行时模块之外使用.

我可以使用不可证明类型吗?

可以, 如果不可证明变量在每次方法调用时都是常量的话. 例如, 固定迭代次数的循环是允许的, 但基于用户输入的动态循环是不可证明的.

例如, 你可以使用具有固定循环次数的 for 循环:

for (let i = 0; i < YOUR_MAX_CYCLE_SIZE; i++) {
}

在这种情况下, 从常规角度来看 i 不是常量, 但从电路角度来看它是常量.

以下代码是不可证明的, 因为循环次数会变化, 这意味着对于不同的函数输入, 电路将会不同:

for (let i = 0; i < YOUR_MAX_CYCLE_SIZE; i++) {
    if (someCondition()) {
        break;
    }
}

我可以使用像 if, switch 或 for 这样的控制流语句吗?

可以, 但条件必须在编译时就已知. 你不能使用运行时条件, 但可以使用像 Provable.if() 这样的可证明条件语句.

例如, 你可以这样做:

for (let i = 0; i < YOUR_MAX_CYCLE_SIZE; i++) {
    if (i == YOUR_MAX_CYCLE_SIZE - 1) {
        // Do something
    }
}

但你不能进行一些真正的运行时控制流操作, 比如:

foo(a: Field) {
    if (a.graterThen(5).toBoolean()) { // this will not work

    }
}

有没有办法使用不可证明的代码?

对于无法完全证明的复杂逻辑, 你可以使用 Provable.asProver() 在代码块中执行不可证明的代码. 不过, 这种方法不安全, 只应在黑客松或开发环境中使用, 不应在生产环境中使用.

它们通过以下方式创建, 并且可以包含你想要的任何代码:

let a = Field(0)

Provable.asProver(() => {
    if (+someValue.toString() > 5) { // Some unprovable logic
        // Do some creasy stuf
    }

    a = ...
})

this.someStore.set(a); // Now we can use a, despite that it value have gotten in unprovable way

然而, 再次强调, 这是不可证明的, 所以在 asProver 代码块中发生的所有事情都很容易被篡改.

我们可以操作数组吗?

o1js 提供了可证明数组 Provable.Array(type, size). 这些数组的大小是固定的, 不能使用像 push 或 pop 这样的方法. 你需要为空元素使用虚拟值, 并处理每个元素以有条件地更新数组.

以下是一个如何更新数组元素的示例:

for (let i = 0; i < ARRAY_SIZE; i++) {
    myArray[i] = Provable.if(confition(myArray[i]), newValue, myArray[i])
}

如何获取随机数?

对于伪随机数, 使用带有种子的随机数生成器(RandomGenerator):

const generator = RandomGenerator.from(seed);
const myRandomValue = generator.getNumber(maxValue).magnitude;

对于真随机数, 你需要一个可验证随机函数(VRF), 目前 ZkNoid 中还没有提供, 但在黑客松中使用随机数生成器是可以的.

什么是零知识证明(zk proof), 如何在我的应用中使用它?

每次运行时方法调用都会创建一个零知识证明. 你也可以通过使用ZkProgram定义输入, 输出和逻辑来手动创建零知识证明.

class PublicInput extends Struct({
    // Some public input values
  }) {}

  class PublicOutput extends Struct({
    // Some public output values
  }) {}

  const Programm = ZkProgram({
    publicInput: PublicInput,
    publicOutput: PublicOutput,
    name: 'some-name',
    methods: {
      myMethodName: {
        privateInputs: [/* Some private values */],
        method: async (publicInput: PublicInput, /* Some private values*/) => {
        },
      },
    },
  });

  class Proof extends ZkProgram.Proof(Programm) {}

  ...

  {
    myMethod(proof: Proof) {
        proof.verify();

        proof.publicInput // Use publicInput somehow
        proof.publicOutput // Use publicOutput somehow
    }
  }

什么是递归零知识证明?

递归零知识证明允许一个证明将另一个证明作为输入. 这使你能够将大量逻辑压缩到单个证明中, 并创建更复杂, 可定制的逻辑.

export const RecursiveProgramm = ZkProgram({
  name: 'some-recursive-program',
  publicInput: PublicInput,
  publicOutput: PublicOutput,
  methods: {
    init: {
      privateInputs: [],
      async method(
        publicInput: PublicInput
      ): Promise<TicketReduceProofPublicOutput> {
        // return PublicOutputs with some inital
      },
    },
    recursiveMethod: {
      privateInputs: [SelfProof],
      async method(
        input: PublicInput,
        prevProof: SelfProof<
          PublicInput,
          PublicOutput
        >
      ) {
        prevProof.verify()

        // do some logic with it and return PublicOutput
      },
    },
  },
});

export class MyRecursiveProof extends ZkProgram.Proof(RecursiveProgramm) {}

如何从运行时模块中查询数据?

要从运行时模块中查询数据, 使用查询API:

await client.query.runtime.YourRuntimeModule.yourField.get();

如何调用运行时模块中的方法并进行应用链交易?

要做到这一点, 你需要:

  1. 从 ZkNoid 上下文中获取应用链客户端.
  2. 对客户端进行类型转换以推断出正确的运行时模块类型.
  3. 实现进行链上交易的函数, 找到正确的模块, 签署并发送交易.

在数字猜谜游戏中可以找到一个易于理解的示例.

  const { client } = useContext(ZkNoidGameContext);

  if (!client) {
    throw Error('Context app chain client is not set');
  }

  const client_ = client as ClientAppChain<
    typeof numberGuessingConfig.runtimeModules,
    any,
    any,
    any
  >;

    // Query is needed to query data from chain
  const query = networkStore.protokitClientStarted
    ? client_.query.runtime.GuessGame
    : undefined;

    // Function that makes appchain transaction
  const hideNumber = async (number: number) => {
      // Resolving the correct appchain module
    const guessLogic = client_.runtime.resolve('GuessGame');

        // Making a transaction
    const tx = await client.transaction(
      PublicKey.fromBase58(networkStore.address!),
      async () => {
        await guessLogic.hideNumber(Field.from(number));
      }
    );

    await tx.sign();
    await tx.send();
  };

如何从我的运行时模块中获取日志?

console.log 是不可证明的, 所以将其放在 asProver 代码块内:

Provable.asProver(() => {
  console.log('Your awesome log');
});

在运行我的运行时模块时出现断言错误

Protokit 使用它自己的断言, 所以o1js的断言将不起作用. 请改用 Protokit 的断言:

import { assert } from '@proto-kit/protocol';
assert(UInt64.from(5).greaterThan(UInt64.from(1)), "Your assert message");

另外, 使用来自 Protokit 的 UInt64 以避免兼容性问题:

import { UInt64 } from '@proto-kit/library';

Provable.if 与我的自定义类型无法协同工作

在使用自定义类型时, Provable.if 需要显式定义类型:

Provable.if<MyCustomType>(condition, value1, value2);

如何使用 GamePage.tsx?

你只需将你的游戏包装到 <GamePage> 组件中, 并将游戏配置作为属性传递, 这样你就能获得游戏的默认布局, 该布局在"游戏模板"中有描述.

GamePage 有一些属性:

  • useLayout(bool)--禁用此属性可允许为你的游戏构建完全自定义的用户界面.
  • useTabs(bool)--此属性允许使用游戏的默认/自定义选项卡.
  • useTitle(bool)--此属性允许移除标题图片(如果你不需要它的话).
  • gameTitleImage--此属性允许你为游戏设置一张图片.
  • customGameTitle(ReactNode)--此属性允许你在原位置创建一个完全自定义的游戏标题.
  • tabs(array)--此属性允许你在需要时创建除默认游戏选项卡(大厅和竞赛选项卡是默认选项卡)之外的自定义游戏选项卡.
export default function GameTemplate() {
  return (
    <GamePage gameConfig={gameTemplateConfig}>
      <section className={"w-full h-screen text-center"}>Game Template</section>
    </GamePage>
  );
}

你们有自定义游戏选项卡吗?

在ZkNoid中编写自定义选项卡很容易, 以下是一个除默认选项卡之外添加自定义信息选项卡的示例.

你可以通过使用 useSearchParams 并依据参数 "tab" 来查看当前选项卡.

export default function GameTemplate() {
    const searchParams = useSearchParams();
    const currentTab = searchParams.get("tab");

  return (
    <GamePage 
        gameConfig={gameTemplateConfig}
        tabs={[{
            name: 'Information',
            tab: 'information',
        }]}
    >
        {currentTab === "information" ? (
            <section className={"w-full h-screen text-center"}>
                Information tab!
            </section>
        ): (
            <section className={"w-full h-screen text-center"}>
                Game Page!
            </section>
        )}
    </GamePage>
  );
}

Ref

  • https://docs.zknoid.io/docs/zknoid_for_developers/faq
  • https://github.com/ZkNoid/docs/blob/main/pages/docs/zknoid_for_developers/faq.mdx
版权声明

本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门