Skip to content

Commit cbaba55

Browse files
author
adamczykm
committed
Reorganize data types #1
1 parent e03d8d7 commit cbaba55

File tree

8 files changed

+517
-118
lines changed

8 files changed

+517
-118
lines changed

minauth-plugins/minauth-verified-zkdocument-plugin/src/claim.ts

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,8 @@ import { CircuitString, Field, Poseidon } from 'o1js';
22
import { z } from 'zod';
33
import { ClaimStruct, IClaimStruct } from './data/claims.js';
44
import { FieldsSchema } from './data/simple.js';
5+
import { ClaimSchema_, ClaimStandard } from './data/transit-cred.js';
56

6-
export const ClaimStandardSchema = z.object({
7-
standardId: z.string().min(2),
8-
description: z.string(),
9-
referenceToExternalStandard: z.string().optional(),
10-
fieldsConversion: z.object({
11-
length: z.number().int().min(1),
12-
description: z.string(),
13-
codeExample: z.string()
14-
})
15-
});
16-
17-
export type ClaimStandard = z.infer<typeof ClaimStandardSchema>;
18-
19-
export function ClaimSchema<V>(valueSchema: z.ZodType<V>) {
20-
return z.object({
21-
name: z.string().min(1),
22-
value: valueSchema,
23-
fieldsValue: FieldsSchema,
24-
standard: ClaimStandardSchema
25-
});
26-
}
277

288
export interface IClaim<V> {
299
get name(): string;
@@ -50,7 +30,7 @@ export const claimStandardHash = (standard: ClaimStandard): Field => {
5030
// ==========================================
5131
// inline tests
5232

53-
const TypeCheckerTestSchema = ClaimSchema(z.number());
33+
const TypeCheckerTestSchema = ClaimSchema_(z.number());
5434
type TypeCheckerTestType = z.infer<typeof TypeCheckerTestSchema>;
5535
const typeCheckerTestValue = undefined as unknown as TypeCheckerTestType;
5636
typeCheckerTestValue satisfies IClaim<number>;
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { Struct, Provable, Field } from 'o1js';
2+
import { ZodTypeAny, z } from 'zod';
3+
import { arraysAreEqual, ftn } from '../../helpers/utils.js';
4+
import { FieldsSchema } from './common.js';
5+
import { Claim } from '../transit/cred.js';
6+
7+
const CLAIMS_MAX_SIZE = 128;
8+
9+
export interface IClaimStruct {
10+
get length(): Field;
11+
get claimValue(): Field[];
12+
toFields(): Field[];
13+
}
14+
15+
export function mkClaimStruct(claim: Claim): IClaimStruct;
16+
export function mkClaimStruct(flds: Field[]): IClaimStruct;
17+
18+
export function mkClaimStruct(claim: Claim | Field[]): IClaimStruct {
19+
if (Array.isArray(claim)) {
20+
return ClaimStruct(claim.length).fromFields(claim);
21+
} else {
22+
return ClaimStruct(claim.fieldsValue.length).fromFields(claim.fieldsValue.map(s => new Field(s)));
23+
}
24+
}
25+
26+
export interface IClaimStructClass {
27+
new (claimValue: Field[]): IClaimStruct;
28+
fromFields(fields: Field[]): IClaimStruct;
29+
schema: z.ZodType<any>;
30+
}
31+
32+
export interface IFieldClaims {
33+
count: Field;
34+
packed: Field[];
35+
toFields(): Field[];
36+
getClaim(ix: Field, assert: Field[]): Field[];
37+
}
38+
39+
export const ClaimStruct = (length: number): IClaimStructClass => {
40+
class Claim_
41+
extends Struct({
42+
length: Field,
43+
claimValue: Provable.Array(Field, length)
44+
})
45+
implements IClaimStruct
46+
{
47+
constructor(claimValue: Field[]) {
48+
if (claimValue.length !== length) {
49+
throw new Error(`Invalid claim array size: ${claimValue.length}`);
50+
}
51+
super({ length: new Field(length), claimValue });
52+
}
53+
54+
public toFields() {
55+
return [this.length, ...this.claimValue];
56+
}
57+
58+
static fromFields(fields: Field[]) {
59+
const length = Number(fields[0].toBigInt());
60+
if (fields.length !== length + 1) {
61+
throw new Error(`Invalid claim array size: ${fields.length}`);
62+
}
63+
return new Claim_(fields.slice(1, length + 1));
64+
}
65+
66+
static schema = z
67+
.object({
68+
claimValue: FieldsSchema.length(length)
69+
})
70+
.transform((o) => new Claim_(o.claimValue));
71+
}
72+
73+
return Claim_ as IClaimStructClass;
74+
};
75+
76+
77+
export const computeClaimSizes = (claims: IClaimStruct[]) => {
78+
return claims.map(c => ftn(c.length));
79+
};
80+
81+
export const mkClaims = (claims: IClaimStruct[]): IFieldClaims => {
82+
const size = computeClaimSizes(claims);
83+
return Claims(size).fromClaims(claims);
84+
};
85+
86+
export function Claims(claimSizes: number[]) {
87+
if (claimSizes.length == 0) {
88+
throw new Error('claimSizes must not be empty.');
89+
}
90+
const claimCount = claimSizes.length;
91+
// 0 (start index) + indices + claims
92+
const totalSize = 1 + claimCount + claimSizes.reduce((a, v) => a + v, 0);
93+
94+
if (totalSize > CLAIMS_MAX_SIZE) {
95+
throw new Error(
96+
`Total Claims size ${totalSize} exceeds the maximum size ${CLAIMS_MAX_SIZE}`
97+
);
98+
}
99+
100+
class Claims_
101+
extends Struct({
102+
count: Field,
103+
packed: Provable.Array(Field, totalSize)
104+
})
105+
implements IFieldClaims
106+
{
107+
static MAX_SIZE = CLAIMS_MAX_SIZE;
108+
109+
public toFields() {
110+
return [this.count, ...this.packed];
111+
}
112+
113+
static fromClaims(claims: IClaimStruct[]) {
114+
const receivedSizes = claims.map((c) => ftn(c.length));
115+
116+
if (!arraysAreEqual(receivedSizes, claimSizes)) {
117+
throw new Error("'claims' array sizes are invalid");
118+
}
119+
120+
let packed: Field[] = [];
121+
// first push all locations
122+
packed.push(new Field(0));
123+
for (let i = 0; i < claims.length; i++) {
124+
const claimEndIx = packed[i].add(claims[i].length);
125+
packed.push(claimEndIx);
126+
}
127+
// then push all values
128+
for (const claim of claims) {
129+
packed.push(...claim.claimValue);
130+
}
131+
132+
return new Claims_({ count: new Field(claims.length), packed });
133+
}
134+
135+
static get schema() {
136+
const claimSchemas = claimSizes.map(
137+
(size) => ClaimStruct(size).schema
138+
) as [ZodTypeAny, ...ZodTypeAny[]];
139+
return z
140+
.tuple(claimSchemas)
141+
.transform((claims) => Claims_.fromClaims(claims));
142+
}
143+
144+
public getClaim(ix: Field, assert: Field[]): Field[] {
145+
const i = Number(ix.toBigInt());
146+
const length = Number(this.packed[i + 1].sub(this.packed[i]).toBigInt());
147+
const c = Number(this.count.toBigInt());
148+
149+
const op = Provable.witness(Provable.Array(Field, length), () => {
150+
const start = Number(this.packed[i].toBigInt());
151+
const next = Number(this.packed[i + 1].toBigInt());
152+
return this.packed.slice(c + 1 + start, c + 1 + next);
153+
});
154+
155+
for (let j = 0; j < length; j++) {
156+
op[j].assertEquals(assert[j]);
157+
}
158+
159+
return op;
160+
}
161+
}
162+
163+
return Claims_;
164+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { z } from "zod";
2+
import { Signature, Field, PublicKey, Struct } from "o1js"
3+
import * as T from "../transit/common.js";
4+
import { DateTime } from "luxon";
5+
6+
export const FieldSchema = T.FieldSchema.transform(Field);
7+
8+
export const FieldsSchema = z.array(FieldSchema);
9+
10+
export const PublicKeySchema = T.PublicKeyB58Schema.transform(PublicKey.fromBase58);
11+
12+
export const SignatureSchema = T.SignatureSchema.transform(Signature.fromBase58);
13+
14+
export const UnixTimestampFieldSchema = T.DateTimeSchema.transform((dt) => {
15+
const dt2 = dt || "";
16+
const ts = DateTime.fromISO(dt2)
17+
if (!ts.isValid) {
18+
throw new Error(`Invalid timestamp: ${dt}`)
19+
}
20+
const seconds = ts.toUTC().toUnixInteger();
21+
22+
return new Field(seconds);
23+
})
24+
25+
export class UnixTimestamp extends Struct({
26+
unixTimestamp: Field
27+
}) {
28+
static fromTransit(t: unknown) {
29+
const unixTimestamp = UnixTimestampFieldSchema.parse(t);
30+
return new UnixTimestamp({ unixTimestamp });
31+
}
32+
public toFields() {
33+
return [this.unixTimestamp]
34+
}
35+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import * as z from 'zod';
2+
import * as S from './common.js';
3+
import { Field, PublicKey, Struct } from 'o1js';
4+
import * as T from '../transit/cred.js';
5+
6+
// =============== IDs ===============
7+
8+
export const PubKeyIdSchema = z.object({
9+
pubkey: S.PublicKeySchema
10+
});
11+
12+
export type PubKeyId = z.infer<typeof PubKeyIdSchema>;
13+
14+
export const IssuerIdSchema = PubKeyIdSchema;
15+
16+
export class IssuerId extends Struct({
17+
pubkey: PublicKey
18+
}) {
19+
static fromTransit(t: T.IssuerId) {
20+
const pubkey = S.PublicKeySchema.parse(t);
21+
return new IssuerId({pubkey});
22+
}
23+
24+
public toFields() {
25+
return this.pubkey.toFields();
26+
}
27+
}
28+
29+
export const CredSubjectIdSchema = PubKeyIdSchema;
30+
31+
export class CredSubjectId extends Struct({
32+
pubkey: PublicKey
33+
}) {
34+
static fromTransit(t: T.CredSubjectId) {
35+
const pubkey = S.PublicKeySchema.parse(t);
36+
return new CredSubjectId({pubkey});
37+
}
38+
39+
public toFields() {
40+
return this.pubkey.toFields();
41+
}
42+
}
43+
44+
export class VCredId extends Struct({
45+
id: Field
46+
}) {
47+
static fromTransit(t: T.VCredId) {
48+
const id = S.FieldSchema.parse(t);
49+
return new VCredId({id});
50+
}
51+
52+
public toFields() {
53+
return this.id.toFields();
54+
}
55+
}
56+
57+
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { Struct, Field, Signature } from 'o1js';
2+
import {
3+
SignatureSchema,
4+
FieldSchema,
5+
UnixTimestamp
6+
} from './common.js';
7+
import { Claims, mkClaimStruct, mkClaims } from './claim.js';
8+
import { CredSubjectId, IssuerId, VCredId } from './ids.js';
9+
import { Credential, CredentialData } from '../transit/cred.js';
10+
11+
export function VCredStructUnsigned(claimSizes: number[]) {
12+
const ClaimsType = Claims(claimSizes);
13+
14+
const Data = {
15+
id: VCredId,
16+
issuer: IssuerId,
17+
issuanceDate: UnixTimestamp,
18+
expirationDate: UnixTimestamp,
19+
subject: CredSubjectId,
20+
claims: ClaimsType,
21+
credentialStandardHash: Field
22+
};
23+
24+
class BaseVCredStruct_ extends Struct(Data) {
25+
static Fields = Data;
26+
27+
public toFields(): Field[] {
28+
return [
29+
...this.id.toFields(),
30+
...this.issuer.toFields(),
31+
...this.issuanceDate.toFields(),
32+
...this.expirationDate.toFields(),
33+
...this.subject.toFields(),
34+
...this.claims.toFields(),
35+
...this.credentialStandardHash.toFields()
36+
];
37+
}
38+
39+
static fromTransit(t: CredentialData): BaseVCredStruct_ {
40+
const id = VCredId.fromTransit(t.id);
41+
const issuer = IssuerId.fromTransit(t.issuer);
42+
const issuanceDate = UnixTimestamp.fromTransit(t.issuanceDate);
43+
const expirationDate = UnixTimestamp.fromTransit(t.expirationDate);
44+
const subject = CredSubjectId.fromTransit(t.subject);
45+
const claims = mkClaims(Object.values(t.claims).map(mkClaimStruct));
46+
const credentialStandardHash = FieldSchema.parse(t.credentialStandardHash);
47+
48+
return new BaseVCredStruct_({
49+
id,
50+
issuer,
51+
issuanceDate,
52+
expirationDate,
53+
subject,
54+
claims,
55+
credentialStandardHash
56+
});
57+
}
58+
}
59+
60+
return BaseVCredStruct_;
61+
}
62+
63+
export function VCredStruct(claimSizes: number[]) {
64+
const BaseVCredStructType = VCredStructUnsigned(claimSizes);
65+
66+
const Fields = { ...BaseVCredStructType.Fields, signature: Signature };
67+
68+
class VCredStruct_ extends Struct(Fields) {
69+
static Fields = Fields;
70+
71+
public contentToFields() {
72+
return new BaseVCredStructType(this).toFields();
73+
}
74+
75+
public toFields() {
76+
return [...this.contentToFields(), ...this.signature.toFields()];
77+
}
78+
79+
static fromTransit(t: Credential): VCredStruct_ {
80+
const base = BaseVCredStructType.fromTransit(t);
81+
const signature = SignatureSchema.parse(t.signature);
82+
83+
return new VCredStruct_({ ...base, signature });
84+
}
85+
86+
}
87+
88+
return VCredStruct_;
89+
}

0 commit comments

Comments
 (0)