Skip to content

Commit df00082

Browse files
authored
Merge pull request #1 from zksecurity/feature/private-credentials
Validation for mina_storePrivateCredential
2 parents 758fc6f + 0b7f905 commit df00082

File tree

4 files changed

+302
-0
lines changed

4 files changed

+302
-0
lines changed

apps/docs/src/components/test-zkapp.tsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,46 @@ import { useState, useSyncExternalStore } from "react";
55

66
const store = createStore();
77

8+
const sampleCredential = {
9+
version: "v0",
10+
witness: {
11+
type: "simple",
12+
issuer: {
13+
_type: "PublicKey",
14+
value: "B62qqMxueXzenrchT5CKC5eCSmfcbHic9wJd9GEdHVcd9uCWrjPJjHS",
15+
},
16+
issuerSignature: {
17+
_type: "Signature",
18+
value: {
19+
r: "27355434072539307953235904941558417174103383443074165997458891331674091021280",
20+
s: "22156398191479529717864137276005168653180340733374387165875910835098679659803",
21+
},
22+
},
23+
},
24+
credential: {
25+
owner: {
26+
_type: "PublicKey",
27+
value: "B62qqCMx9YvvjhMFVcRXBqHtAbjWWUhyA9HmgpYCehLHTGKgXsxiZpz",
28+
},
29+
data: {
30+
age: {
31+
_type: "Field",
32+
value: "25",
33+
},
34+
},
35+
},
36+
};
37+
838
export const TestZkApp = () => {
939
const [currentProvider, setCurrentProvider] = useLocalStorage(
1040
"minajs:provider",
1141
"",
1242
);
1343
const [message, setMessage] = useState("A message to sign");
1444
const [fields, setFields] = useState('["1", "2", "3"]');
45+
const [credentialInput, setCredentialInput] = useState(
46+
JSON.stringify(sampleCredential, null, 2),
47+
);
1548
const [transactionBody, setTransactionBody] = useObjectState({
1649
to: "B62qnVUL6A53E4ZaGd3qbTr6RCtEZYTu3kTijVrrquNpPo4d3MuJ3nb",
1750
amount: "3000000000",
@@ -27,11 +60,31 @@ export const TestZkApp = () => {
2760
mina_signFields: "",
2861
mina_signTransaction: "",
2962
mina_switchChain: "",
63+
mina_storePrivateCredential: "",
3064
});
3165
const providers = useSyncExternalStore(store.subscribe, store.getProviders);
3266
const provider = providers.find(
3367
(p) => p.info.slug === currentProvider,
3468
)?.provider;
69+
70+
const storePrivateCredential = async () => {
71+
if (!provider) return;
72+
try {
73+
const parsedCredential = JSON.parse(credentialInput);
74+
const { result } = await provider.request({
75+
method: "mina_storePrivateCredential",
76+
params: [parsedCredential],
77+
});
78+
setResults(() => ({
79+
mina_storePrivateCredential: JSON.stringify(result, null, 2),
80+
}));
81+
} catch (error) {
82+
setResults(() => ({
83+
mina_storePrivateCredential: `Error: ${error.message}`,
84+
}));
85+
}
86+
};
87+
3588
const fetchAccounts = async () => {
3689
if (!provider) return;
3790
const { result } = await provider.request({
@@ -397,6 +450,35 @@ export const TestZkApp = () => {
397450
</div>
398451
</div>
399452
</section>
453+
<section className="card bg-neutral">
454+
<div className="card-body gap-4">
455+
<h2 className="card-title">Store Private Credential</h2>
456+
<p>mina_storePrivateCredential</p>
457+
<div className="flex flex-col gap-2">
458+
<div className="flex flex-col gap-4">
459+
<textarea
460+
value={credentialInput}
461+
onChange={(event) => setCredentialInput(event.target.value)}
462+
className="textarea textarea-bordered h-48 font-mono text-sm"
463+
placeholder="Enter credential JSON..."
464+
/>
465+
<button
466+
type="button"
467+
className="btn btn-primary"
468+
onClick={storePrivateCredential}
469+
>
470+
Store Private Credential
471+
</button>
472+
</div>
473+
<label>Result</label>
474+
<textarea
475+
value={results.mina_storePrivateCredential}
476+
readOnly
477+
className="textarea textarea-bordered h-24 resize-none font-mono"
478+
/>
479+
</div>
480+
</div>
481+
</section>
400482
</main>
401483
);
402484
};

packages/providers/src/validation.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
SignedFieldsSchema,
88
SignedMessageSchema,
99
SignedTransactionSchema,
10+
StoredCredentialSchema,
1011
TransactionPayloadSchema,
1112
TransactionReceiptSchema,
1213
TypedSendableSchema,
@@ -85,6 +86,11 @@ export const GetStateRequestParamsSchema = RequestWithContext.extend({
8586
method: z.literal("mina_getState"),
8687
params: z.array(JsonSchema),
8788
}).strict();
89+
export const StorePrivateCredentialRequestParamsSchema =
90+
RequestWithContext.extend({
91+
method: z.literal("mina_storePrivateCredential"),
92+
params: z.array(StoredCredentialSchema),
93+
}).strict();
8894

8995
// Returns
9096
export const AccountsRequestReturnSchema = z
@@ -171,6 +177,12 @@ export const GetStateRequestReturnSchema = z
171177
result: JsonSchema,
172178
})
173179
.strict();
180+
export const StorePrivateCredentialReturnSchema = z
181+
.object({
182+
method: z.literal("mina_storePrivateCredential"),
183+
result: z.object({ success: z.boolean() }).strict(),
184+
})
185+
.strict();
174186

175187
export const RpcReturnTypesUnion = z.discriminatedUnion("method", [
176188
AccountsRequestReturnSchema,
@@ -187,6 +199,7 @@ export const RpcReturnTypesUnion = z.discriminatedUnion("method", [
187199
AddChainRequestReturnSchema,
188200
SetStateRequestReturnSchema,
189201
GetStateRequestReturnSchema,
202+
StorePrivateCredentialReturnSchema,
190203
]);
191204

192205
export const ProviderRequestParamsUnion = z.discriminatedUnion("method", [
@@ -204,6 +217,7 @@ export const ProviderRequestParamsUnion = z.discriminatedUnion("method", [
204217
AddChainRequestParamsSchema,
205218
SetStateRequestParamsSchema,
206219
GetStateRequestParamsSchema,
220+
StorePrivateCredentialRequestParamsSchema,
207221
]);
208222
export type RpcReturnTypesUnionType = z.infer<typeof RpcReturnTypesUnion>;
209223
export type ResultType<M extends string> = {

packages/utils/src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
SignedFieldsSchema,
1313
SignedMessageSchema,
1414
SignedTransactionSchema,
15+
StoredCredentialSchema,
1516
TransactionBodySchema,
1617
TransactionPayloadSchema,
1718
TransactionReceiptSchema,
@@ -48,3 +49,8 @@ export type TransactionReceipt = z.infer<typeof TransactionReceiptSchema>;
4849
export type KlesiaRpcMethodType = z.infer<typeof KlesiaRpcMethod>;
4950
export type KlesiaRpcRequestType = z.infer<typeof KlesiaRpcMethodSchema>;
5051
export type KlesiaRpcResponseType = z.infer<typeof KlesiaRpcResponseSchema>;
52+
53+
/**
54+
* Private Credential types
55+
*/
56+
export type StoredPrivateCredential = z.infer<typeof StoredCredentialSchema>;

packages/utils/src/validation.ts

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,203 @@ export const KlesiaRpcResponseSchema = z.union([
240240
]),
241241
ErrorSchema,
242242
]);
243+
244+
// TODO: Should probably move these validations to a separate file
245+
246+
interface ProofType {
247+
name: string;
248+
publicInput: SerializedType;
249+
publicOutput: SerializedType;
250+
maxProofsVerified: number;
251+
featureFlags: Record<string, unknown>;
252+
}
253+
254+
interface SerializedType {
255+
_type?: string;
256+
// TODO: update based on mina-credentials
257+
type?: "Constant";
258+
value?: string;
259+
size?: number;
260+
proof?: ProofType;
261+
innerType?: SerializedType;
262+
[key: string]: SerializedType | string | number | ProofType | undefined;
263+
}
264+
265+
// Private Credentials: Serialized Type and Value Schemas
266+
267+
const SerializedValueSchema = z
268+
.object({
269+
_type: z.string(),
270+
value: JsonSchema,
271+
properties: z.record(z.any()).optional(),
272+
})
273+
.strict();
274+
275+
const ProofTypeSchema: z.ZodType<ProofType> = z.lazy(() =>
276+
z
277+
.object({
278+
name: z.string(),
279+
publicInput: SerializedTypeSchema,
280+
publicOutput: SerializedTypeSchema,
281+
maxProofsVerified: z.number(),
282+
featureFlags: z.record(z.any()),
283+
})
284+
.strict(),
285+
);
286+
287+
const SerializedTypeSchema: z.ZodType<SerializedType> = z.lazy(() =>
288+
z.union([
289+
// Basic type
290+
z
291+
.object({
292+
_type: z.string(),
293+
})
294+
.strict(),
295+
// Constant type
296+
z
297+
.object({
298+
type: z.literal("Constant"),
299+
value: z.string(),
300+
})
301+
.strict(),
302+
// Bytes type
303+
z
304+
.object({
305+
_type: z.literal("Bytes"),
306+
size: z.number(),
307+
})
308+
.strict(),
309+
// Proof type
310+
z
311+
.object({
312+
_type: z.literal("Proof"),
313+
proof: ProofTypeSchema,
314+
})
315+
.strict(),
316+
// Array type
317+
z
318+
.object({
319+
_type: z.literal("Array"),
320+
innerType: SerializedTypeSchema,
321+
size: z.number(),
322+
})
323+
.strict(),
324+
// Allow records of nested types for Struct
325+
z.record(SerializedTypeSchema),
326+
]),
327+
);
328+
329+
const SerializedFieldSchema = z
330+
.object({
331+
_type: z.literal("Field"),
332+
value: z.string(),
333+
})
334+
.strict();
335+
336+
const SerializedPublicKeySchema = z
337+
.object({
338+
_type: z.literal("PublicKey"),
339+
value: z.string(),
340+
})
341+
.strict();
342+
343+
const SerializedPublicKeyTypeSchema = z
344+
.object({
345+
_type: z.literal("PublicKey"),
346+
})
347+
.strict();
348+
349+
const SerializedSignatureSchema = z
350+
.object({
351+
_type: z.literal("Signature"),
352+
value: z.object({
353+
r: z.string(),
354+
s: z.string(),
355+
}),
356+
})
357+
.strict();
358+
359+
// Private Credentials: Witness Schemas
360+
361+
const SimpleWitnessSchema = z
362+
.object({
363+
type: z.literal("simple"),
364+
issuer: SerializedPublicKeySchema,
365+
issuerSignature: SerializedSignatureSchema,
366+
})
367+
.strict();
368+
369+
const RecursiveWitnessSchema = z
370+
.object({
371+
type: z.literal("recursive"),
372+
vk: z
373+
.object({
374+
data: z.string(),
375+
hash: SerializedFieldSchema,
376+
})
377+
.strict(),
378+
proof: z
379+
.object({
380+
_type: z.literal("Proof"),
381+
value: z
382+
.object({
383+
publicInput: JsonSchema,
384+
publicOutput: JsonSchema,
385+
maxProofsVerified: z.number().min(0).max(2),
386+
proof: z.string(),
387+
})
388+
.strict(),
389+
})
390+
.strict(),
391+
})
392+
.strict();
393+
394+
const UnsignedWitnessSchema = z
395+
.object({
396+
type: z.literal("unsigned"),
397+
})
398+
.strict();
399+
400+
const WitnessSchema = z.discriminatedUnion("type", [
401+
SimpleWitnessSchema,
402+
RecursiveWitnessSchema,
403+
UnsignedWitnessSchema,
404+
]);
405+
406+
// Private Credentials: Credential Schemas
407+
408+
const SimpleCredentialSchema = z
409+
.object({
410+
owner: SerializedPublicKeySchema,
411+
data: z.record(SerializedValueSchema),
412+
})
413+
.strict();
414+
415+
const StructCredentialSchema = z
416+
.object({
417+
_type: z.literal("Struct"),
418+
properties: z
419+
.object({
420+
owner: SerializedPublicKeyTypeSchema,
421+
data: JsonSchema,
422+
})
423+
.strict(),
424+
value: z
425+
.object({
426+
owner: PublicKeySchema,
427+
data: JsonSchema,
428+
})
429+
.strict(),
430+
})
431+
.strict();
432+
433+
// Private Credentials: Stored Credential Schema
434+
435+
export const StoredCredentialSchema = z
436+
.object({
437+
version: z.literal("v0"),
438+
witness: WitnessSchema,
439+
metadata: JsonSchema.optional(),
440+
credential: z.union([SimpleCredentialSchema, StructCredentialSchema]),
441+
})
442+
.strict();

0 commit comments

Comments
 (0)