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>
);
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!