@midnight-ntwrk/credential-compact 0.1.0-rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/LICENSE +201 -0
- package/README.md +59 -0
- package/dist/compact-build.json +29 -0
- package/dist/contract.d.ts +2 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +2 -0
- package/dist/contract.js.map +1 -0
- package/dist/credentials/bindings.compact +5 -0
- package/dist/credentials/composable.compact +14 -0
- package/dist/credentials/holder-bindings.compact +320 -0
- package/dist/credentials/issue.compact +91 -0
- package/dist/credentials/present.compact +84 -0
- package/dist/credentials/proofs.compact +122 -0
- package/dist/credentials/protocol-support.compact +7 -0
- package/dist/credentials/protocols.compact +125 -0
- package/dist/credentials/relations.compact +36 -0
- package/dist/credentials/status-bindings.compact +60 -0
- package/dist/credentials/types.compact +141 -0
- package/dist/credentials/vc-support.compact +8 -0
- package/dist/credentials/vc.compact +59 -0
- package/dist/credentials/vp.compact +25 -0
- package/dist/credentials.compact +20 -0
- package/dist/holder-binding/same-holder/composable.compact +179 -0
- package/dist/holder-binding/same-holder.compact +180 -0
- package/dist/holder-binding/same-holder.d.ts +2 -0
- package/dist/holder-binding/same-holder.d.ts.map +1 -0
- package/dist/holder-binding/same-holder.js +2 -0
- package/dist/holder-binding/same-holder.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/jubjub.d.ts +3 -0
- package/dist/jubjub.d.ts.map +1 -0
- package/dist/jubjub.js +6 -0
- package/dist/jubjub.js.map +1 -0
- package/dist/managed/credentials/contract/index.d.ts +330 -0
- package/dist/managed/credentials/contract/index.js +2213 -0
- package/dist/managed/credentials/contract/index.js.map +8 -0
- package/dist/managed/same-holder/contract/index.d.ts +400 -0
- package/dist/managed/same-holder/contract/index.js +2688 -0
- package/dist/managed/same-holder/contract/index.js.map +8 -0
- package/package.json +113 -0
- package/src/credentials/bindings.compact +5 -0
- package/src/credentials/composable.compact +14 -0
- package/src/credentials/holder-bindings.compact +320 -0
- package/src/credentials/issue.compact +91 -0
- package/src/credentials/present.compact +84 -0
- package/src/credentials/proofs.compact +122 -0
- package/src/credentials/protocol-support.compact +7 -0
- package/src/credentials/protocols.compact +125 -0
- package/src/credentials/relations.compact +36 -0
- package/src/credentials/status-bindings.compact +60 -0
- package/src/credentials/types.compact +141 -0
- package/src/credentials/vc-support.compact +8 -0
- package/src/credentials/vc.compact +59 -0
- package/src/credentials/vp.compact +25 -0
- package/src/credentials.compact +20 -0
- package/src/holder-binding/same-holder/composable.compact +179 -0
- package/src/holder-binding/same-holder.compact +180 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Context-tagged proof primitives.
|
|
2
|
+
// This file owns signature verification plus the shared proof payload/challenge
|
|
3
|
+
// derivation rules for generic issuance and presentation flows.
|
|
4
|
+
export pure circuit verifySignature(
|
|
5
|
+
pk: JubjubPoint,
|
|
6
|
+
signature: Signature,
|
|
7
|
+
challenge: Field
|
|
8
|
+
): Boolean {
|
|
9
|
+
const leftSide: JubjubPoint = ecMulGenerator(signature.s);
|
|
10
|
+
const cPk: JubjubPoint = ecMul(pk, challenge);
|
|
11
|
+
const rightSide: JubjubPoint = ecAdd(signature.r, cPk);
|
|
12
|
+
const xMatches: Boolean = jubjubPointX(leftSide) == jubjubPointX(rightSide);
|
|
13
|
+
const yMatches: Boolean = jubjubPointY(leftSide) == jubjubPointY(rightSide);
|
|
14
|
+
assert(xMatches && yMatches, "Signature verification failed");
|
|
15
|
+
return xMatches && yMatches;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// NOTE: The proof object does not carry a purpose field. Instead, the verifier
|
|
19
|
+
// chooses the context-specific challenge derivation helper, which gives us
|
|
20
|
+
// explicit domain separation without asking callers to populate a mutable enum.
|
|
21
|
+
// =====================
|
|
22
|
+
// Context tags
|
|
23
|
+
// =====================
|
|
24
|
+
export pure circuit issuanceContextTag(): Bytes<32> {
|
|
25
|
+
return pad(32, "midnight:vc:issuance");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export pure circuit presentationContextTag(): Bytes<32> {
|
|
29
|
+
return pad(32, "midnight:vc:presentation");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// =====================
|
|
33
|
+
// Internal payload and challenge derivation
|
|
34
|
+
// =====================
|
|
35
|
+
pure circuit proofPayloadRootForContext(
|
|
36
|
+
bodyRoot: Bytes<32>,
|
|
37
|
+
contextTag: Bytes<32>,
|
|
38
|
+
proof: Proof
|
|
39
|
+
): Bytes<32> {
|
|
40
|
+
return persistentHash<Vector<5, Bytes<32>>>([
|
|
41
|
+
bodyRoot,
|
|
42
|
+
contextTag,
|
|
43
|
+
persistentHash<VerificationMethodRef>(proof.signerVerificationMethodRef),
|
|
44
|
+
upgradeFromTransient(transientHash<Uint<64>>(proof.createdAt)),
|
|
45
|
+
proof.challengeHash
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pure circuit proofChallengeForContext(
|
|
50
|
+
bodyRoot: Bytes<32>,
|
|
51
|
+
contextTag: Bytes<32>,
|
|
52
|
+
proof: Proof
|
|
53
|
+
): Field {
|
|
54
|
+
return degradeToTransient(persistentHash<Vector<3, Bytes<32>>>([
|
|
55
|
+
proofPayloadRootForContext(bodyRoot, contextTag, proof),
|
|
56
|
+
upgradeFromTransient(transientHash<JubjubPoint>(proof.publicKey)),
|
|
57
|
+
upgradeFromTransient(transientHash<JubjubPoint>(proof.signature.r))
|
|
58
|
+
]));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
pure circuit assertValidProofForContext(
|
|
62
|
+
bodyRoot: Bytes<32>,
|
|
63
|
+
contextTag: Bytes<32>,
|
|
64
|
+
proof: Proof
|
|
65
|
+
): [] {
|
|
66
|
+
assert(
|
|
67
|
+
verifySignature(
|
|
68
|
+
proof.publicKey,
|
|
69
|
+
proof.signature,
|
|
70
|
+
proofChallengeForContext(bodyRoot, contextTag, proof)
|
|
71
|
+
),
|
|
72
|
+
"Proof verification failed"
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export pure circuit issuanceProofPayloadRoot(
|
|
77
|
+
bodyRoot: Bytes<32>,
|
|
78
|
+
proof: Proof
|
|
79
|
+
): Bytes<32> {
|
|
80
|
+
return proofPayloadRootForContext(bodyRoot, issuanceContextTag(), proof);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export pure circuit presentationProofPayloadRoot(
|
|
84
|
+
bodyRoot: Bytes<32>,
|
|
85
|
+
proof: Proof
|
|
86
|
+
): Bytes<32> {
|
|
87
|
+
return proofPayloadRootForContext(bodyRoot, presentationContextTag(), proof);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export pure circuit issuanceProofChallenge(
|
|
91
|
+
bodyRoot: Bytes<32>,
|
|
92
|
+
proof: Proof
|
|
93
|
+
): Field {
|
|
94
|
+
return proofChallengeForContext(bodyRoot, issuanceContextTag(), proof);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export pure circuit presentationProofChallenge(
|
|
98
|
+
bodyRoot: Bytes<32>,
|
|
99
|
+
proof: Proof
|
|
100
|
+
): Field {
|
|
101
|
+
return proofChallengeForContext(bodyRoot, presentationContextTag(), proof);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// =====================
|
|
105
|
+
// Public context-specific proof entrypoints
|
|
106
|
+
// =====================
|
|
107
|
+
// NOTE: The proof container is generic, but the verification context is
|
|
108
|
+
// explicit. Callers choose the context helper rather than mutating the proof
|
|
109
|
+
// payload itself.
|
|
110
|
+
export pure circuit assertValidIssuanceContextProof(
|
|
111
|
+
bodyRoot: Bytes<32>,
|
|
112
|
+
proof: Proof
|
|
113
|
+
): [] {
|
|
114
|
+
assertValidProofForContext(bodyRoot, issuanceContextTag(), proof);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export pure circuit assertValidPresentationContextProof(
|
|
118
|
+
bodyRoot: Bytes<32>,
|
|
119
|
+
proof: Proof
|
|
120
|
+
): [] {
|
|
121
|
+
assertValidProofForContext(bodyRoot, presentationContextTag(), proof);
|
|
122
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Shared protocol-envelope primitives.
|
|
2
|
+
// These helpers are intentionally generic over issuance and presentation
|
|
3
|
+
// choreography and remain reusable across family- and transport-specific flows.
|
|
4
|
+
export enum HolderBindingProfile {
|
|
5
|
+
explicitDid,
|
|
6
|
+
secretHolder,
|
|
7
|
+
blindedSecretHolder
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Compatibility protocol hint. Authoritative family capabilities belong on the
|
|
11
|
+
// schema descriptor; protocol messages may carry this shape only as a transport
|
|
12
|
+
// hint while older adapters migrate.
|
|
13
|
+
export struct CredentialProtocolFeatures {
|
|
14
|
+
supportsSelectiveDisclosure: Boolean,
|
|
15
|
+
supportsPredicateProofs: Boolean,
|
|
16
|
+
supportsVerifierScopedPseudonym: Boolean,
|
|
17
|
+
supportsSameHolderProof: Boolean,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export pure circuit protocolFeaturesAsSchemaCapabilities(
|
|
21
|
+
features: CredentialProtocolFeatures
|
|
22
|
+
): SchemaCapabilities {
|
|
23
|
+
return SchemaCapabilities {
|
|
24
|
+
supportsSelectiveDisclosure: features.supportsSelectiveDisclosure,
|
|
25
|
+
supportsPredicateProofs: features.supportsPredicateProofs,
|
|
26
|
+
supportsVerifierScopedPseudonym: features.supportsVerifierScopedPseudonym,
|
|
27
|
+
supportsSameHolderProof: features.supportsSameHolderProof,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export pure circuit assertProtocolFeaturesMatchSchemaCapabilities(
|
|
32
|
+
features: CredentialProtocolFeatures,
|
|
33
|
+
capabilities: SchemaCapabilities
|
|
34
|
+
): [] {
|
|
35
|
+
assertMatchingSchemaCapabilities(
|
|
36
|
+
protocolFeaturesAsSchemaCapabilities(features),
|
|
37
|
+
capabilities
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export struct ProtocolMessageEnvelope {
|
|
42
|
+
version: Uint<16>,
|
|
43
|
+
messageId: Bytes<32>,
|
|
44
|
+
threadId: Bytes<32>,
|
|
45
|
+
initialMessage: Boolean,
|
|
46
|
+
respondsToMessageId: Bytes<32>,
|
|
47
|
+
createdAt: Uint<64>,
|
|
48
|
+
hasExpiresAt: Boolean,
|
|
49
|
+
expiresAt: Uint<64>,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export pure circuit noProtocolResponseReference(): Bytes<32> {
|
|
53
|
+
return pad(32, "midnight:vc:protocol:none");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export pure circuit assertValidVerificationMethodRef(
|
|
57
|
+
verificationMethodRef: VerificationMethodRef
|
|
58
|
+
): [] {
|
|
59
|
+
assert(
|
|
60
|
+
verificationMethodRef.methodId != pad(32, ""),
|
|
61
|
+
"Verification method reference must be set"
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export pure circuit assertMatchingSchemaRefs(
|
|
66
|
+
expected: SchemaRef,
|
|
67
|
+
actual: SchemaRef
|
|
68
|
+
): [] {
|
|
69
|
+
assertValidSchemaRef(expected);
|
|
70
|
+
assertValidSchemaRef(actual);
|
|
71
|
+
assert(
|
|
72
|
+
expected.packageId == actual.packageId
|
|
73
|
+
&& expected.schemaId == actual.schemaId
|
|
74
|
+
&& expected.majorVersion == actual.majorVersion
|
|
75
|
+
&& expected.minorVersion == actual.minorVersion,
|
|
76
|
+
"Schema reference mismatch"
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export pure circuit assertValidProtocolMessageEnvelope(
|
|
81
|
+
envelope: ProtocolMessageEnvelope
|
|
82
|
+
): [] {
|
|
83
|
+
const noResponse = noProtocolResponseReference();
|
|
84
|
+
assert(envelope.version == 1, "Protocol message version mismatch");
|
|
85
|
+
assert(envelope.messageId != noResponse, "Protocol message id must be set");
|
|
86
|
+
assert(envelope.threadId != noResponse, "Protocol thread id must be set");
|
|
87
|
+
if (envelope.initialMessage) {
|
|
88
|
+
assert(
|
|
89
|
+
envelope.respondsToMessageId == noResponse,
|
|
90
|
+
"Initial protocol message must not reference a previous message"
|
|
91
|
+
);
|
|
92
|
+
} else {
|
|
93
|
+
assert(
|
|
94
|
+
envelope.respondsToMessageId != noResponse,
|
|
95
|
+
"Protocol response message must reference a previous message"
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
if (envelope.hasExpiresAt) {
|
|
99
|
+
assert(
|
|
100
|
+
envelope.expiresAt >= envelope.createdAt,
|
|
101
|
+
"Protocol message expiration must not precede creation"
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export pure circuit assertProtocolResponseEnvelope(
|
|
107
|
+
requestEnvelope: ProtocolMessageEnvelope,
|
|
108
|
+
responseEnvelope: ProtocolMessageEnvelope
|
|
109
|
+
): [] {
|
|
110
|
+
assertValidProtocolMessageEnvelope(requestEnvelope);
|
|
111
|
+
assertValidProtocolMessageEnvelope(responseEnvelope);
|
|
112
|
+
assert(!responseEnvelope.initialMessage, "Protocol response must not be initial");
|
|
113
|
+
assert(
|
|
114
|
+
responseEnvelope.threadId == requestEnvelope.threadId,
|
|
115
|
+
"Protocol response thread id does not match the request thread id"
|
|
116
|
+
);
|
|
117
|
+
assert(
|
|
118
|
+
responseEnvelope.respondsToMessageId == requestEnvelope.messageId,
|
|
119
|
+
"Protocol response does not reference the request message id"
|
|
120
|
+
);
|
|
121
|
+
assert(
|
|
122
|
+
responseEnvelope.createdAt >= requestEnvelope.createdAt,
|
|
123
|
+
"Protocol response creation time must not precede the request"
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Generic VC↔VP linkage helpers.
|
|
2
|
+
// This module intentionally validates only shared envelope relations; family
|
|
3
|
+
// semantics and holder-binding witness checks stay in higher layers.
|
|
4
|
+
module CredentialPresentationRelations<
|
|
5
|
+
TPublicClaims,
|
|
6
|
+
TClaimCommitments,
|
|
7
|
+
TDisclosures,
|
|
8
|
+
THolderBinding,
|
|
9
|
+
TStatusBinding
|
|
10
|
+
> {
|
|
11
|
+
// The generic parameters are intentionally threaded through the imported
|
|
12
|
+
// `VC<>` and `VP<>` instantiations so the relation helper stays family-typed
|
|
13
|
+
// even though the linkage checks only read shared envelope fields.
|
|
14
|
+
import VC<TPublicClaims, TClaimCommitments, THolderBinding, TStatusBinding> prefix VC_;
|
|
15
|
+
import VP<TDisclosures, THolderBinding> prefix VP_;
|
|
16
|
+
|
|
17
|
+
export pure circuit assertMatchingCredentialPresentation(
|
|
18
|
+
credential: VC_Credential,
|
|
19
|
+
presentation: VP_Presentation
|
|
20
|
+
): [] {
|
|
21
|
+
// Holder-binding equality is intentionally left to the family layer because
|
|
22
|
+
// different holder-binding profiles require different witness semantics.
|
|
23
|
+
assert(
|
|
24
|
+
presentation.credentialClaimRoot == credential.claimRoot,
|
|
25
|
+
"Presentation must reference the credential claim root"
|
|
26
|
+
);
|
|
27
|
+
assert(
|
|
28
|
+
presentation.issuerVerificationMethodRef.didContractAddress == credential.issuerVerificationMethodRef.didContractAddress,
|
|
29
|
+
"Presentation issuer contract does not match credential issuer"
|
|
30
|
+
);
|
|
31
|
+
assert(
|
|
32
|
+
presentation.issuerVerificationMethodRef.methodId == credential.issuerVerificationMethodRef.methodId,
|
|
33
|
+
"Presentation issuer method reference does not match credential issuer"
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// VC-side status-binding primitives.
|
|
2
|
+
// Owns only the credential-embedded binding layer; proof protocols and live
|
|
3
|
+
// registry evaluation stay in the status-registry package.
|
|
4
|
+
export struct StatusRegistryRef {
|
|
5
|
+
registryId: Bytes<32>,
|
|
6
|
+
authorityVerificationMethodRef: VerificationMethodRef,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export struct NoStatusBinding {}
|
|
10
|
+
|
|
11
|
+
export enum StatusType {
|
|
12
|
+
revocationRegistry
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export struct RegistryBoundStatusBinding {
|
|
16
|
+
statusType: StatusType,
|
|
17
|
+
registryRef: StatusRegistryRef,
|
|
18
|
+
statusHandleCommitment: Bytes<32>,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// NOTE: `StatusType` is explicit even though the current repo only supports the
|
|
22
|
+
// revocation-registry binding mode. Keeping it in the binding preserves an
|
|
23
|
+
// extension seam without overloading registry ids or handle commitment shapes.
|
|
24
|
+
export pure circuit assertValidStatusRegistryRef(
|
|
25
|
+
registryRef: StatusRegistryRef
|
|
26
|
+
): [] {
|
|
27
|
+
assert(
|
|
28
|
+
registryRef.registryId != pad(32, ""),
|
|
29
|
+
"Status registry id must be set"
|
|
30
|
+
);
|
|
31
|
+
assertValidVerificationMethodRef(registryRef.authorityVerificationMethodRef);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export pure circuit assertValidNoStatusBinding(
|
|
35
|
+
binding: NoStatusBinding
|
|
36
|
+
): [] {
|
|
37
|
+
// NOTE: NoStatusBinding is intentionally empty; this validator stays as a
|
|
38
|
+
// symmetry point with the real binding variants.
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export pure circuit assertValidRegistryBoundStatusBinding(
|
|
42
|
+
binding: RegistryBoundStatusBinding
|
|
43
|
+
): [] {
|
|
44
|
+
assert(
|
|
45
|
+
binding.statusType == StatusType.revocationRegistry,
|
|
46
|
+
"Registry-bound status type must be revocationRegistry"
|
|
47
|
+
);
|
|
48
|
+
assertValidStatusRegistryRef(binding.registryRef);
|
|
49
|
+
assert(
|
|
50
|
+
binding.statusHandleCommitment != pad(32, ""),
|
|
51
|
+
"Status handle commitment must be set"
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export pure circuit registryBoundStatusBindingRoot(
|
|
56
|
+
binding: RegistryBoundStatusBinding
|
|
57
|
+
): Bytes<32> {
|
|
58
|
+
assertValidRegistryBoundStatusBinding(binding);
|
|
59
|
+
return persistentHash<RegistryBoundStatusBinding>(binding);
|
|
60
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export struct SchemaRef {
|
|
2
|
+
packageId: Bytes<32>,
|
|
3
|
+
schemaId: Bytes<32>,
|
|
4
|
+
majorVersion: Uint<16>,
|
|
5
|
+
minorVersion: Uint<16>,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export struct SchemaCapabilities {
|
|
9
|
+
supportsSelectiveDisclosure: Boolean,
|
|
10
|
+
supportsPredicateProofs: Boolean,
|
|
11
|
+
supportsVerifierScopedPseudonym: Boolean,
|
|
12
|
+
supportsSameHolderProof: Boolean,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export struct SchemaFamilyResolutionHint {
|
|
16
|
+
hasResolverHint: Boolean,
|
|
17
|
+
resolverHint: Bytes<32>,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export struct SchemaDescriptor {
|
|
21
|
+
schema: SchemaRef,
|
|
22
|
+
capabilities: SchemaCapabilities,
|
|
23
|
+
familyResolutionHint: SchemaFamilyResolutionHint,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export pure circuit noSchemaFamilyResolverHint(): Bytes<32> {
|
|
27
|
+
return pad(32, "midnight:vc:schema:no-hint");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export pure circuit assertValidSchemaRef(schema: SchemaRef): [] {
|
|
31
|
+
assert(schema.packageId != pad(32, ""), "Schema package id must be set");
|
|
32
|
+
assert(schema.schemaId != pad(32, ""), "Schema id must be set");
|
|
33
|
+
assert(schema.majorVersion > 0, "Schema major version must be positive");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export pure circuit assertValidSchemaCapabilities(
|
|
37
|
+
capabilities: SchemaCapabilities
|
|
38
|
+
): [] {
|
|
39
|
+
// Boolean-only capabilities have no invalid values today. Keep this helper so
|
|
40
|
+
// future non-boolean capability fields get one validation entry point.
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export pure circuit assertValidSchemaFamilyResolutionHint(
|
|
44
|
+
hint: SchemaFamilyResolutionHint
|
|
45
|
+
): [] {
|
|
46
|
+
const noHint = noSchemaFamilyResolverHint();
|
|
47
|
+
if (hint.hasResolverHint) {
|
|
48
|
+
assert(hint.resolverHint != noHint, "Schema resolver hint must be set");
|
|
49
|
+
assert(
|
|
50
|
+
hint.resolverHint != pad(32, ""),
|
|
51
|
+
"Schema resolver hint must not be empty"
|
|
52
|
+
);
|
|
53
|
+
} else {
|
|
54
|
+
assert(
|
|
55
|
+
hint.resolverHint == noHint,
|
|
56
|
+
"Absent schema resolver hint must use the no-hint sentinel"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export pure circuit assertValidSchemaDescriptor(
|
|
62
|
+
descriptor: SchemaDescriptor
|
|
63
|
+
): [] {
|
|
64
|
+
assertValidSchemaRef(descriptor.schema);
|
|
65
|
+
assertValidSchemaCapabilities(descriptor.capabilities);
|
|
66
|
+
assertValidSchemaFamilyResolutionHint(descriptor.familyResolutionHint);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export pure circuit assertMatchingSchemaCapabilities(
|
|
70
|
+
expected: SchemaCapabilities,
|
|
71
|
+
actual: SchemaCapabilities
|
|
72
|
+
): [] {
|
|
73
|
+
assert(
|
|
74
|
+
expected.supportsSelectiveDisclosure == actual.supportsSelectiveDisclosure
|
|
75
|
+
&& expected.supportsPredicateProofs == actual.supportsPredicateProofs
|
|
76
|
+
&& expected.supportsVerifierScopedPseudonym == actual.supportsVerifierScopedPseudonym
|
|
77
|
+
&& expected.supportsSameHolderProof == actual.supportsSameHolderProof,
|
|
78
|
+
"Schema capabilities mismatch"
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export struct VerificationMethodRef {
|
|
83
|
+
didContractAddress: ContractAddress,
|
|
84
|
+
methodId: Bytes<32>,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export struct NoPublicClaims {
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export struct NoClaimCommitments {
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export struct Signature {
|
|
94
|
+
r: JubjubPoint,
|
|
95
|
+
s: Field,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export struct ExplicitHolderBinding {
|
|
99
|
+
holderVerificationMethodRef: VerificationMethodRef,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export struct JubjubHolderBinding {
|
|
103
|
+
// Legacy compatibility/minimal holder binding.
|
|
104
|
+
// This binds the VC/VP to a specific Jubjub public key, but it is not the
|
|
105
|
+
// recommended public profile for new DID-shaped flows.
|
|
106
|
+
// Proof-of-possession still depends on validating the presentation Proof
|
|
107
|
+
// alongside this binding.
|
|
108
|
+
holderPublicKey: JubjubPoint,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export struct OffchainMidnightHolderBinding {
|
|
112
|
+
// Preferred lightweight DID-shaped binding for offchain/prototype flows.
|
|
113
|
+
// This adds DID-shaped metadata for prototype offchain Midnight DID flows.
|
|
114
|
+
// It does not by itself prove that holderPublicKey is the key published in
|
|
115
|
+
// the offchain DID state; callers still need the surrounding DID semantics.
|
|
116
|
+
holderDidStateHash: Bytes<32>,
|
|
117
|
+
holderMethodId: Bytes<32>,
|
|
118
|
+
holderPublicKey: JubjubPoint,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export struct SecretHolderBinding {
|
|
122
|
+
holderSecretCommitment: Bytes<32>,
|
|
123
|
+
requestChallengeResponse: Bytes<32>,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export struct BlindedSecretHolderBinding {
|
|
127
|
+
blindedHolderSecretCommitment: Bytes<32>,
|
|
128
|
+
issuerNonce: Bytes<32>,
|
|
129
|
+
requestChallengeResponse: Bytes<32>,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Proof is kept separate from the VC/VP body on purpose.
|
|
133
|
+
// The body is the semantic payload; the proof is the cryptographic artifact
|
|
134
|
+
// over that payload plus interaction context.
|
|
135
|
+
export struct Proof {
|
|
136
|
+
signerVerificationMethodRef: VerificationMethodRef,
|
|
137
|
+
createdAt: Uint<64>,
|
|
138
|
+
challengeHash: Bytes<32>,
|
|
139
|
+
publicKey: JubjubPoint,
|
|
140
|
+
signature: Signature,
|
|
141
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Thin VC envelope surface.
|
|
2
|
+
// Owns credential-local structure, body-root derivation, and issuer-proof
|
|
3
|
+
// verification. Family-specific claim validation stays outside this module.
|
|
4
|
+
module VC<TPublicClaims, TClaimCommitments, THolderBinding, TStatusBinding> {
|
|
5
|
+
export struct Credential {
|
|
6
|
+
version: Uint<16>,
|
|
7
|
+
schema: SchemaRef,
|
|
8
|
+
issuerVerificationMethodRef: VerificationMethodRef,
|
|
9
|
+
holderBinding: THolderBinding,
|
|
10
|
+
statusBinding: TStatusBinding,
|
|
11
|
+
issuedAt: Uint<64>,
|
|
12
|
+
hasExpiration: Boolean,
|
|
13
|
+
expiresAt: Uint<64>,
|
|
14
|
+
// Direct values intentionally carried in the signed credential body.
|
|
15
|
+
claims: TPublicClaims,
|
|
16
|
+
// Commitment digests for private and predicate-only claim values.
|
|
17
|
+
claimCommitments: TClaimCommitments,
|
|
18
|
+
claimRoot: Bytes<32>,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export pure circuit credentialBodyRoot(credential: Credential): Bytes<32> {
|
|
22
|
+
return persistentHash<Credential>(credential);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export pure circuit assertValidCredentialEnvelope(
|
|
26
|
+
credential: Credential,
|
|
27
|
+
expectedClaimRoot: Bytes<32>
|
|
28
|
+
): [] {
|
|
29
|
+
assert(credential.version == 1, "Credential version mismatch");
|
|
30
|
+
assert(credential.claimRoot == expectedClaimRoot, "Credential claim root mismatch");
|
|
31
|
+
if (credential.hasExpiration) {
|
|
32
|
+
assert(credential.expiresAt >= credential.issuedAt, "Expiration must not precede issuance");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export pure circuit assertValidCredentialProof(
|
|
37
|
+
credential: Credential,
|
|
38
|
+
proof: Proof
|
|
39
|
+
): [] {
|
|
40
|
+
const bodyRoot: Bytes<32> = credentialBodyRoot(credential);
|
|
41
|
+
assertValidCredentialProofForBodyRoot(credential, proof, bodyRoot);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export pure circuit assertValidCredentialProofForBodyRoot(
|
|
45
|
+
credential: Credential,
|
|
46
|
+
proof: Proof,
|
|
47
|
+
bodyRoot: Bytes<32>
|
|
48
|
+
): [] {
|
|
49
|
+
assert(
|
|
50
|
+
credential.issuerVerificationMethodRef.didContractAddress == proof.signerVerificationMethodRef.didContractAddress,
|
|
51
|
+
"Issuer proof contract address does not match issuer verification method"
|
|
52
|
+
);
|
|
53
|
+
assert(
|
|
54
|
+
credential.issuerVerificationMethodRef.methodId == proof.signerVerificationMethodRef.methodId,
|
|
55
|
+
"Issuer proof method reference does not match issuer verification method"
|
|
56
|
+
);
|
|
57
|
+
assertValidIssuanceContextProof(bodyRoot, proof);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Thin VP envelope surface.
|
|
2
|
+
// Owns presentation-local structure and body-root derivation. VC↔VP linkage
|
|
3
|
+
// and holder-binding equality stay outside this module.
|
|
4
|
+
module VP<TDisclosures, THolderBinding> {
|
|
5
|
+
export struct Presentation {
|
|
6
|
+
version: Uint<16>,
|
|
7
|
+
schema: SchemaRef,
|
|
8
|
+
credentialClaimRoot: Bytes<32>,
|
|
9
|
+
issuerVerificationMethodRef: VerificationMethodRef,
|
|
10
|
+
holderBinding: THolderBinding,
|
|
11
|
+
disclosed: TDisclosures,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export pure circuit presentationBodyRoot(presentation: Presentation): Bytes<32> {
|
|
15
|
+
return persistentHash<Presentation>(presentation);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export pure circuit assertValidPresentationEnvelope(
|
|
19
|
+
presentation: Presentation
|
|
20
|
+
): [] {
|
|
21
|
+
// VC/VP linkage now lives in `relations.compact`; the thin VP surface only
|
|
22
|
+
// owns presentation-local envelope invariants.
|
|
23
|
+
assert(presentation.version == 1, "Presentation version mismatch");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// This file is part of midnightntwrk/midnight-did.
|
|
2
|
+
// Copyright (C) 2026 Midnight Foundation
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
|
|
16
|
+
pragma language_version >= 0.20;
|
|
17
|
+
|
|
18
|
+
import CompactStandardLibrary;
|
|
19
|
+
|
|
20
|
+
include "./credentials/composable";
|