1- import { CircuitString , Field , Poseidon } from 'o1js' ;
1+ import { CircuitString , Field , Poseidon , PrivateKey } from 'o1js' ;
2+ import * as o1js from 'o1js' ;
23import { z } from 'zod' ;
34import {
45 ClaimSchema ,
5- ClaimStandard ,
66 ClaimStandardSchema ,
77 IClaim ,
88 claimStandardHash
99} from './claim.js' ;
1010import {
11+ CredSubjectId ,
1112 CredSubjectIdSchema ,
1213 IssuerIdSchema ,
1314 VCredIdSchema
1415} from './data/ids.js' ;
1516import {
1617 FieldSchema ,
18+ SignatureB58 ,
1719 SignatureSchema ,
1820 UnixTimestampSchema
1921} from './data/simple.js' ;
2022import { arraysAreEqual } from './helpers/utils.js' ;
23+ import { Issuer } from './issuer.js' ;
24+ import { VCredStruct , VCredStructUnsigned } from './data/vcred.js' ;
25+ import { Logger } from 'tslog' ;
26+
27+ const log = new Logger ( { name : 'credential.ts' } ) ;
28+
29+ // =============== Credential Trasit Types ==================
30+
31+ // ---------------- Credential Standard ----------------
2132
2233export const CredentialStandardSchema = z . object ( {
2334 standardId : z . string ( ) . min ( 2 ) ,
2435 description : z . string ( ) . min ( 2 ) ,
25- schema : z . record ( z . string ( ) . min ( 2 ) , ClaimStandardSchema ) . refine (
26- ( schema ) => Object . keys ( schema ) . length > 0 ,
27- { message : 'Must define at least one claim.' }
28- )
36+ schema : z
37+ . record ( z . string ( ) . min ( 2 ) , ClaimStandardSchema )
38+ . refine ( ( schema ) => Object . keys ( schema ) . length > 0 , {
39+ message : 'Must define at least one claim.'
40+ } )
2941} ) ;
3042
3143export type CredentialStandard = z . infer < typeof CredentialStandardSchema > ;
3244
33- export const credentialStandardHash = ( standard : CredentialStandard ) : Field => {
34- const idHash = Poseidon . hash (
35- CircuitString . fromString ( standard . standardId ) . toFields ( )
36- ) ;
37- const schemaHash = Poseidon . hash (
38- Object . values ( standard . schema ) . map ( claimStandardHash )
39- ) ;
40- return Poseidon . hash ( [ idHash , schemaHash ] ) ;
41- } ;
45+ // ---------------- Credential Data ----------------
4246
4347export const CredentialDataSchema = z . object ( {
44- claims : z . record ( z . string ( ) . min ( 1 ) , ClaimSchema ( z . unknown ( ) ) ) . refine (
45- ( claims ) => Object . keys ( claims ) . length > 0 ,
46- { message : 'Must define at least one claim.' }
47- ) ,
48+ claims : z
49+ . record ( z . string ( ) . min ( 1 ) , ClaimSchema ( z . unknown ( ) ) )
50+ . refine ( ( claims ) => Object . keys ( claims ) . length > 0 , {
51+ message : 'Must define at least one claim.'
52+ } ) ,
4853 id : VCredIdSchema ,
4954 issuer : IssuerIdSchema ,
5055 issuanceDate : UnixTimestampSchema ,
5156 expirationDate : UnixTimestampSchema ,
5257 subject : CredSubjectIdSchema ,
53- credentialSchemaHash : FieldSchema ,
58+ credentialStandardHash : FieldSchema
5459} ) ;
5560
61+ export type CredentialData = z . infer < typeof CredentialDataSchema > ;
62+
63+ // ---------------- Credential ----------------
64+
5665export const CredentialSchema = CredentialDataSchema . extend ( {
5766 signature : SignatureSchema
5867} ) ;
5968
60- export type CredentialData = z . infer < typeof CredentialDataSchema > ;
61-
6269export type Credential = z . infer < typeof CredentialSchema > ;
6370
71+ // =============== To fields-encoded types ===============
72+
73+ export const mkStructCredentialUnsigned = ( credential : CredentialData ) => {
74+ log . debug ( 'Entering mkStructCredentialUnsigned...' , credential ) ;
75+ return VCredStructUnsigned (
76+ Object . values ( credential . claims ) . map ( ( claim ) => claim . fieldsValue . length )
77+ ) . schema . parse ( {
78+ ...credential ,
79+ claims : Object . values ( credential . claims ) . map ( ( claim ) => ( {
80+ claimValue : claim . fieldsValue
81+ } ) )
82+ } ) ;
83+ } ;
84+
85+ // signed version
86+ export const mkStructCredential = ( credential : Credential ) => {
87+ return VCredStruct (
88+ Object . values ( credential . claims ) . map ( ( claim ) => claim . fieldsValue . length )
89+ ) . schema . parse ( {
90+ ...credential ,
91+ claims : Object . values ( credential . claims ) . map ( ( claim ) => ( {
92+ claimValue : claim . fieldsValue
93+ } ) )
94+ } ) ;
95+ } ;
6496
6597export const checkCredentialStandardConformance = (
6698 credential : Credential ,
@@ -85,20 +117,68 @@ export const checkCredentialStandardConformance = (
85117 return true ;
86118} ;
87119
120+ export const credentialStandardHash = ( standard : CredentialStandard ) : Field => {
121+ const idHash = Poseidon . hash (
122+ CircuitString . fromString ( standard . standardId ) . toFields ( )
123+ ) ;
124+ const schemaHash = Poseidon . hash (
125+ Object . values ( standard . schema ) . map ( claimStandardHash )
126+ ) ;
127+ return Poseidon . hash ( [ idHash , schemaHash ] ) ;
128+ } ;
88129
130+ export const verifyAndIssueCredential =
131+ (
132+ standard : CredentialStandard ,
133+ credentialData : CredentialData ,
134+ subject : CredSubjectId ,
135+ issuer : Issuer
136+ ) =>
137+ ( privateKey : PrivateKey ) => {
138+ // get fields for the signature
139+ log . debug ( 'Entering verifyAndIssueCredential...' ) ;
140+
141+ log . debug ( 'Converting the credential data to o1js fields.' ) ;
142+ const flds = mkStructCredentialUnsigned ( credentialData ) . toFields ( ) ;
143+
144+ log . debug ( 'Creating signature for the fields data' ) ;
145+ const signature : SignatureB58 = SignatureSchema . parse (
146+ o1js . Signature . create ( privateKey , flds ) . toBase58 ( )
147+ ) ;
148+
149+ // create the credential
150+
151+ log . debug ( 'Parsing the credential with the signature' ) ;
152+ const credential = CredentialSchema . parse ( {
153+ ...credentialData ,
154+ signature
155+ } ) ;
156+
157+ // check if the credential conforms to the standard
158+ log . debug ( 'Checking credential standard conformance...' ) ;
159+ const check = checkCredentialStandardConformance ( credential , standard ) ;
160+ if ( ! check ) {
161+ throw new Error ( 'Credential does not conform to standard' ) ;
162+ }
89163
90- // ==========================================
91- // inline tests
164+ // additional assertions
165+ // issuer pubkey matches the private key
166+ if ( ! issuer . pubkey . equals ( privateKey . toPublicKey ( ) ) ) {
167+ throw new Error ( 'Issuer pubkey does not match the private key' ) ;
168+ }
92169
93- let asd = undefined as unknown as ClaimStandard ;
170+ // credential subject public key matches the given subject public key
171+ if ( ! credential . subject . pubkey . equals ( subject . pubkey ) ) {
172+ throw new Error ( 'Subject does not match the credential subject' ) ;
173+ }
94174
95- let cred : CredentialStandard = {
96- standardId : 'personhood1' ,
97- description : 'Proves that subject is a person' ,
98- schema : {
99- name : asd
100- }
101- } ;
175+ return {
176+ credential
177+ } ;
178+ } ;
179+
180+ // ==========================================
181+ // inline tests
102182
103183const TypeCheckerTestSchema = ClaimSchema ( z . number ( ) ) ;
104184type TypeCheckerTestType = z . infer < typeof TypeCheckerTestSchema > ;
0 commit comments