Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation for mina_storePrivateCredential #1

Merged
merged 15 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions apps/docs/src/components/test-zkapp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,46 @@ import { useState, useSyncExternalStore } from "react";

const store = createStore();

const sampleCredential = {
version: "v0",
witness: {
type: "simple",
issuer: {
_type: "PublicKey",
value: "B62qqMxueXzenrchT5CKC5eCSmfcbHic9wJd9GEdHVcd9uCWrjPJjHS",
},
issuerSignature: {
_type: "Signature",
value: {
r: "27355434072539307953235904941558417174103383443074165997458891331674091021280",
s: "22156398191479529717864137276005168653180340733374387165875910835098679659803",
},
},
},
credential: {
owner: {
_type: "PublicKey",
value: "B62qqCMx9YvvjhMFVcRXBqHtAbjWWUhyA9HmgpYCehLHTGKgXsxiZpz",
},
data: {
age: {
_type: "Field",
value: "25",
},
},
},
};

export const TestZkApp = () => {
const [currentProvider, setCurrentProvider] = useLocalStorage(
"minajs:provider",
"",
);
const [message, setMessage] = useState("A message to sign");
const [fields, setFields] = useState('["1", "2", "3"]');
const [credentialInput, setCredentialInput] = useState(
JSON.stringify(sampleCredential, null, 2),
);
const [transactionBody, setTransactionBody] = useObjectState({
to: "B62qnVUL6A53E4ZaGd3qbTr6RCtEZYTu3kTijVrrquNpPo4d3MuJ3nb",
amount: "3000000000",
Expand All @@ -27,11 +60,31 @@ export const TestZkApp = () => {
mina_signFields: "",
mina_signTransaction: "",
mina_switchChain: "",
mina_storePrivateCredential: "",
});
const providers = useSyncExternalStore(store.subscribe, store.getProviders);
const provider = providers.find(
(p) => p.info.slug === currentProvider,
)?.provider;

const storePrivateCredential = async () => {
if (!provider) return;
try {
const parsedCredential = JSON.parse(credentialInput);
const { result } = await provider.request({
method: "mina_storePrivateCredential",
params: [parsedCredential],
});
setResults(() => ({
mina_storePrivateCredential: JSON.stringify(result, null, 2),
}));
} catch (error) {
setResults(() => ({
mina_storePrivateCredential: `Error: ${error.message}`,
}));
}
};

const fetchAccounts = async () => {
if (!provider) return;
const { result } = await provider.request({
Expand Down Expand Up @@ -397,6 +450,35 @@ export const TestZkApp = () => {
</div>
</div>
</section>
<section className="card bg-neutral">
<div className="card-body gap-4">
<h2 className="card-title">Store Private Credential</h2>
<p>mina_storePrivateCredential</p>
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-4">
<textarea
value={credentialInput}
onChange={(event) => setCredentialInput(event.target.value)}
className="textarea textarea-bordered h-48 font-mono text-sm"
placeholder="Enter credential JSON..."
/>
<button
type="button"
className="btn btn-primary"
onClick={storePrivateCredential}
>
Store Private Credential
</button>
</div>
<label>Result</label>
<textarea
value={results.mina_storePrivateCredential}
readOnly
className="textarea textarea-bordered h-24 resize-none font-mono"
/>
</div>
</div>
</section>
</main>
);
};
14 changes: 14 additions & 0 deletions packages/providers/src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
SignedFieldsSchema,
SignedMessageSchema,
SignedTransactionSchema,
StoredCredentialSchema,
TransactionPayloadSchema,
TransactionReceiptSchema,
TypedSendableSchema,
Expand Down Expand Up @@ -85,6 +86,11 @@ export const GetStateRequestParamsSchema = RequestWithContext.extend({
method: z.literal("mina_getState"),
params: z.array(JsonSchema),
}).strict();
export const StorePrivateCredentialRequestParamsSchema =
RequestWithContext.extend({
method: z.literal("mina_storePrivateCredential"),
params: z.array(StoredCredentialSchema),
}).strict();

// Returns
export const AccountsRequestReturnSchema = z
Expand Down Expand Up @@ -171,6 +177,12 @@ export const GetStateRequestReturnSchema = z
result: JsonSchema,
})
.strict();
export const StorePrivateCredentialReturnSchema = z
.object({
method: z.literal("mina_storePrivateCredential"),
result: z.object({ success: z.boolean() }).strict(),
})
.strict();

export const RpcReturnTypesUnion = z.discriminatedUnion("method", [
AccountsRequestReturnSchema,
Expand All @@ -187,6 +199,7 @@ export const RpcReturnTypesUnion = z.discriminatedUnion("method", [
AddChainRequestReturnSchema,
SetStateRequestReturnSchema,
GetStateRequestReturnSchema,
StorePrivateCredentialReturnSchema,
]);

export const ProviderRequestParamsUnion = z.discriminatedUnion("method", [
Expand All @@ -204,6 +217,7 @@ export const ProviderRequestParamsUnion = z.discriminatedUnion("method", [
AddChainRequestParamsSchema,
SetStateRequestParamsSchema,
GetStateRequestParamsSchema,
StorePrivateCredentialRequestParamsSchema,
]);
export type RpcReturnTypesUnionType = z.infer<typeof RpcReturnTypesUnion>;
export type ResultType<M extends string> = {
Expand Down
6 changes: 6 additions & 0 deletions packages/utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
SignedFieldsSchema,
SignedMessageSchema,
SignedTransactionSchema,
StoredCredentialSchema,
TransactionBodySchema,
TransactionPayloadSchema,
TransactionReceiptSchema,
Expand Down Expand Up @@ -48,3 +49,8 @@ export type TransactionReceipt = z.infer<typeof TransactionReceiptSchema>;
export type KlesiaRpcMethodType = z.infer<typeof KlesiaRpcMethod>;
export type KlesiaRpcRequestType = z.infer<typeof KlesiaRpcMethodSchema>;
export type KlesiaRpcResponseType = z.infer<typeof KlesiaRpcResponseSchema>;

/**
* Private Credential types
*/
export type StoredPrivateCredential = z.infer<typeof StoredCredentialSchema>;
200 changes: 200 additions & 0 deletions packages/utils/src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,203 @@ export const KlesiaRpcResponseSchema = z.union([
]),
ErrorSchema,
]);

// TODO: Should probably move these validations to a separate file

interface ProofType {
name: string;
publicInput: SerializedType;
publicOutput: SerializedType;
maxProofsVerified: number;
featureFlags: Record<string, unknown>;
}

interface SerializedType {
_type?: string;
// TODO: update based on mina-credentials
type?: "Constant";
value?: string;
size?: number;
proof?: ProofType;
innerType?: SerializedType;
[key: string]: SerializedType | string | number | ProofType | undefined;
}

// Private Credentials: Serialized Type and Value Schemas

const SerializedValueSchema = z
.object({
_type: z.string(),
value: JsonSchema,
properties: z.record(z.any()).optional(),
})
.strict();

const ProofTypeSchema: z.ZodType<ProofType> = z.lazy(() =>
z
.object({
name: z.string(),
publicInput: SerializedTypeSchema,
publicOutput: SerializedTypeSchema,
maxProofsVerified: z.number(),
featureFlags: z.record(z.any()),
})
.strict(),
);

const SerializedTypeSchema: z.ZodType<SerializedType> = z.lazy(() =>
z.union([
// Basic type
z
.object({
_type: z.string(),
})
.strict(),
// Constant type
z
.object({
type: z.literal("Constant"),
value: z.string(),
})
.strict(),
// Bytes type
z
.object({
_type: z.literal("Bytes"),
size: z.number(),
})
.strict(),
// Proof type
z
.object({
_type: z.literal("Proof"),
proof: ProofTypeSchema,
})
.strict(),
// Array type
z
.object({
_type: z.literal("Array"),
innerType: SerializedTypeSchema,
size: z.number(),
})
.strict(),
// Allow records of nested types for Struct
z.record(SerializedTypeSchema),
]),
);

const SerializedFieldSchema = z
.object({
_type: z.literal("Field"),
value: z.string(),
})
.strict();

const SerializedPublicKeySchema = z
.object({
_type: z.literal("PublicKey"),
value: z.string(),
})
.strict();

const SerializedPublicKeyTypeSchema = z
.object({
_type: z.literal("PublicKey"),
})
.strict();

const SerializedSignatureSchema = z
.object({
_type: z.literal("Signature"),
value: z.object({
r: z.string(),
s: z.string(),
}),
})
.strict();

// Private Credentials: Witness Schemas

const SimpleWitnessSchema = z
.object({
type: z.literal("simple"),
issuer: SerializedPublicKeySchema,
issuerSignature: SerializedSignatureSchema,
})
.strict();

const RecursiveWitnessSchema = z
.object({
type: z.literal("recursive"),
vk: z
.object({
data: z.string(),
hash: SerializedFieldSchema,
})
.strict(),
proof: z
.object({
_type: z.literal("Proof"),
value: z
.object({
publicInput: JsonSchema,
publicOutput: JsonSchema,
maxProofsVerified: z.number().min(0).max(2),
proof: z.string(),
})
.strict(),
})
.strict(),
})
.strict();

const UnsignedWitnessSchema = z
.object({
type: z.literal("unsigned"),
})
.strict();

const WitnessSchema = z.discriminatedUnion("type", [
SimpleWitnessSchema,
RecursiveWitnessSchema,
UnsignedWitnessSchema,
]);

// Private Credentials: Credential Schemas

const SimpleCredentialSchema = z
.object({
owner: SerializedPublicKeySchema,
data: z.record(SerializedValueSchema),
})
.strict();

const StructCredentialSchema = z
.object({
_type: z.literal("Struct"),
properties: z
.object({
owner: SerializedPublicKeyTypeSchema,
data: JsonSchema,
})
.strict(),
value: z
.object({
owner: PublicKeySchema,
data: JsonSchema,
})
.strict(),
})
.strict();

// Private Credentials: Stored Credential Schema

export const StoredCredentialSchema = z
.object({
version: z.literal("v0"),
witness: WitnessSchema,
metadata: JsonSchema.optional(),
credential: z.union([SimpleCredentialSchema, StructCredentialSchema]),
})
.strict();