Mina Learning - Developers faq

  • longerd
  • 更新于 2024-12-11 18:39
  • 阅读 198

Mina 开发者常见问题解答, 例如(如何创建自定义可证明类型?)

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

你可以使用 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, switchfor 这样的控制流语句吗?

可以, 但条件必须在编译时就已知. 你不能使用运行时条件, 但可以使用像 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). 这些数组的大小是固定的, 不能使用像 pushpop 这样的方法. 你需要为空元素使用虚拟值, 并处理每个元素以有条件地更新数组.

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

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

点赞 3
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
longerd
longerd
code longer