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 错误.
在 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 中还没有提供, 但在黑客松中使用随机数生成器是可以的.
每次运行时方法调用都会创建一个零知识证明. 你也可以通过使用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();
要做到这一点, 你需要:
在数字猜谜游戏中可以找到一个易于理解的示例.
  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>
  );
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!