diff --git a/apps/docs/src/components/test-zkapp.tsx b/apps/docs/src/components/test-zkapp.tsx index fb24a28..d8bedaf 100644 --- a/apps/docs/src/components/test-zkapp.tsx +++ b/apps/docs/src/components/test-zkapp.tsx @@ -5,6 +5,36 @@ 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", @@ -12,6 +42,9 @@ export const TestZkApp = () => { ); 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", @@ -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({ @@ -397,6 +450,35 @@ export const TestZkApp = () => { + + + Store Private Credential + mina_storePrivateCredential + + + setCredentialInput(event.target.value)} + className="textarea textarea-bordered h-48 font-mono text-sm" + placeholder="Enter credential JSON..." + /> + + Store Private Credential + + + Result + + + + ); }; diff --git a/packages/providers/src/validation.ts b/packages/providers/src/validation.ts index 2423a89..f7ce318 100644 --- a/packages/providers/src/validation.ts +++ b/packages/providers/src/validation.ts @@ -7,6 +7,7 @@ import { SignedFieldsSchema, SignedMessageSchema, SignedTransactionSchema, + StoredCredentialSchema, TransactionPayloadSchema, TransactionReceiptSchema, TypedSendableSchema, @@ -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 @@ -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, @@ -187,6 +199,7 @@ export const RpcReturnTypesUnion = z.discriminatedUnion("method", [ AddChainRequestReturnSchema, SetStateRequestReturnSchema, GetStateRequestReturnSchema, + StorePrivateCredentialReturnSchema, ]); export const ProviderRequestParamsUnion = z.discriminatedUnion("method", [ @@ -204,6 +217,7 @@ export const ProviderRequestParamsUnion = z.discriminatedUnion("method", [ AddChainRequestParamsSchema, SetStateRequestParamsSchema, GetStateRequestParamsSchema, + StorePrivateCredentialRequestParamsSchema, ]); export type RpcReturnTypesUnionType = z.infer; export type ResultType = { diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index b523f96..ff76188 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -12,6 +12,7 @@ import type { SignedFieldsSchema, SignedMessageSchema, SignedTransactionSchema, + StoredCredentialSchema, TransactionBodySchema, TransactionPayloadSchema, TransactionReceiptSchema, @@ -48,3 +49,8 @@ export type TransactionReceipt = z.infer; export type KlesiaRpcMethodType = z.infer; export type KlesiaRpcRequestType = z.infer; export type KlesiaRpcResponseType = z.infer; + +/** + * Private Credential types + */ +export type StoredPrivateCredential = z.infer; diff --git a/packages/utils/src/validation.ts b/packages/utils/src/validation.ts index b173013..b3cf68a 100644 --- a/packages/utils/src/validation.ts +++ b/packages/utils/src/validation.ts @@ -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; +} + +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 = z.lazy(() => + z + .object({ + name: z.string(), + publicInput: SerializedTypeSchema, + publicOutput: SerializedTypeSchema, + maxProofsVerified: z.number(), + featureFlags: z.record(z.any()), + }) + .strict(), +); + +const SerializedTypeSchema: z.ZodType = 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();
mina_storePrivateCredential