@humanagencyp/hap-core 0.12.1 → 0.13.0
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/README.md +3 -3
- package/dist/index.d.mts +567 -123
- package/dist/index.d.ts +567 -123
- package/dist/index.js +869 -23
- package/dist/index.mjs +829 -22
- package/package.json +2 -1
- package/src/canonicalize.ts +9 -0
- package/src/commitment-modes.ts +21 -9
- package/src/errors.ts +24 -8
- package/src/gatekeeper.ts +52 -8
- package/src/index.ts +5 -1
- package/src/issuance.ts +250 -0
- package/src/mandate.ts +52 -14
- package/src/owner-signature.ts +86 -39
- package/src/profile.ts +45 -0
- package/src/proposal-content.ts +219 -0
- package/src/ticket-projection.ts +167 -0
- package/src/ticket.ts +254 -20
- package/src/types.ts +33 -5
- package/src/verifier-policy.ts +178 -0
- package/src/versions.ts +50 -19
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/canonicalize.ts
|
|
2
|
+
var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
2
3
|
function canonicalize(value) {
|
|
3
4
|
if (value === void 0) {
|
|
4
5
|
throw new Error("canonicalize: undefined is not serializable");
|
|
@@ -6,6 +7,9 @@ function canonicalize(value) {
|
|
|
6
7
|
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
7
8
|
throw new Error(`canonicalize: ${value} is not a valid JSON number`);
|
|
8
9
|
}
|
|
10
|
+
if (typeof value === "string" && LONE_SURROGATE.test(value)) {
|
|
11
|
+
throw new Error("canonicalize: string contains a lone surrogate and cannot be signed");
|
|
12
|
+
}
|
|
9
13
|
if (value === null || typeof value !== "object") {
|
|
10
14
|
return JSON.stringify(value);
|
|
11
15
|
}
|
|
@@ -55,6 +59,10 @@ var MANDATE_ERROR_CODES = [
|
|
|
55
59
|
"BOUNDS_INVALID_VALUE",
|
|
56
60
|
"SCOPE_INVALID_VALUE",
|
|
57
61
|
"ABOVE_CAP_CONFIG_INVALID",
|
|
62
|
+
"COMMITMENT_MODE_NOT_ALLOWED",
|
|
63
|
+
// v0.8
|
|
64
|
+
"GROUP_CAP_EXCEEDED",
|
|
65
|
+
// v0.8
|
|
58
66
|
"OWNER_SIGNATURE_REQUIRED",
|
|
59
67
|
"OWNER_SIGNATURE_INVALID"
|
|
60
68
|
];
|
|
@@ -68,6 +76,10 @@ var TICKET_ERROR_CODES = [
|
|
|
68
76
|
"PROFILE_NOT_FOUND",
|
|
69
77
|
"INVALID_ACTION_TYPE",
|
|
70
78
|
"APPROVAL_REQUIRED",
|
|
79
|
+
"APPROVAL_IMPOSSIBLE",
|
|
80
|
+
// v0.8
|
|
81
|
+
"PROPOSAL_CONTENT_MISMATCH",
|
|
82
|
+
// v0.8
|
|
71
83
|
"PROPOSAL_REQUIRED",
|
|
72
84
|
"PROPOSAL_NOT_FOUND",
|
|
73
85
|
"PROPOSAL_NOT_APPROVED",
|
|
@@ -76,11 +88,25 @@ var TICKET_ERROR_CODES = [
|
|
|
76
88
|
"PROPOSAL_MISMATCH",
|
|
77
89
|
"PROPOSAL_MANDATE_MISMATCH",
|
|
78
90
|
"PROPOSAL_ALREADY_EXECUTED",
|
|
91
|
+
"PROPOSAL_WITHDRAWN",
|
|
92
|
+
// v0.8
|
|
79
93
|
"MALFORMED_TICKET_REQUEST",
|
|
94
|
+
"CONTENT_BINDING_INCOMPLETE",
|
|
95
|
+
// v0.8, Gatekeeper · local
|
|
80
96
|
"IDEMPOTENCY_KEY_REQUIRED",
|
|
81
97
|
"IDEMPOTENCY_MISMATCH",
|
|
82
98
|
"APPROVAL_SIGNATURE_REQUIRED",
|
|
83
|
-
"APPROVAL_SIGNATURE_INVALID"
|
|
99
|
+
"APPROVAL_SIGNATURE_INVALID",
|
|
100
|
+
"TICKET_REQUEST_MISMATCH",
|
|
101
|
+
// v0.8, Gatekeeper · local
|
|
102
|
+
"POLICY_UNMET"
|
|
103
|
+
// v0.8, Verifier
|
|
104
|
+
];
|
|
105
|
+
var PROPOSAL_ERROR_CODES = [
|
|
106
|
+
"PROPOSAL_NOT_ENCRYPTED",
|
|
107
|
+
"DECIDER_INVALID",
|
|
108
|
+
"PROPOSAL_ALREADY_DECIDED",
|
|
109
|
+
"PROPOSAL_APPROVERS_INVALID"
|
|
84
110
|
];
|
|
85
111
|
var HapError = class extends Error {
|
|
86
112
|
constructor(code, message) {
|
|
@@ -91,9 +117,13 @@ var HapError = class extends Error {
|
|
|
91
117
|
};
|
|
92
118
|
|
|
93
119
|
// src/versions.ts
|
|
94
|
-
var PROTOCOL_VERSION = "0.
|
|
95
|
-
var SUPPORTED_VERSIONS = ["0.7"];
|
|
96
|
-
|
|
120
|
+
var PROTOCOL_VERSION = "0.8";
|
|
121
|
+
var SUPPORTED_VERSIONS = ["0.8", "0.7"];
|
|
122
|
+
var ISSUABLE_VERSIONS = [PROTOCOL_VERSION];
|
|
123
|
+
function isSupportedVersion(v) {
|
|
124
|
+
return typeof v === "string" && SUPPORTED_VERSIONS.includes(v);
|
|
125
|
+
}
|
|
126
|
+
function negotiateVersion(requested, supported = ISSUABLE_VERSIONS) {
|
|
97
127
|
const requestedVersions = requested ?? ["0.6"];
|
|
98
128
|
const common = supported.filter((v) => requestedVersions.includes(v)).sort().reverse();
|
|
99
129
|
if (common.length === 0) {
|
|
@@ -104,6 +134,14 @@ function negotiateVersion(requested, supported = SUPPORTED_VERSIONS) {
|
|
|
104
134
|
}
|
|
105
135
|
return common[0];
|
|
106
136
|
}
|
|
137
|
+
function assertSupportedVersion(version, what) {
|
|
138
|
+
if (!isSupportedVersion(version)) {
|
|
139
|
+
throw new HapError(
|
|
140
|
+
"VERSION_UNSUPPORTED",
|
|
141
|
+
`${what} version ${JSON.stringify(version)} is not verifiable by this implementation (verifies [${SUPPORTED_VERSIONS.join(", ")}])`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
107
145
|
|
|
108
146
|
// src/content-binding.ts
|
|
109
147
|
import { createHash } from "crypto";
|
|
@@ -410,6 +448,7 @@ function computeScopeHash(params, profile) {
|
|
|
410
448
|
|
|
411
449
|
// src/commitment-modes.ts
|
|
412
450
|
var SIGNABLE_COMMITMENT_MODES = ["review", "automatic"];
|
|
451
|
+
var COMMITMENT_MODES = ["automatic", "review", "review_above_cap"];
|
|
413
452
|
var isSignable = (v) => typeof v === "string" && SIGNABLE_COMMITMENT_MODES.includes(v);
|
|
414
453
|
function allowedCommitmentModes(profile) {
|
|
415
454
|
const declared = profile.commitment_modes;
|
|
@@ -418,7 +457,9 @@ function allowedCommitmentModes(profile) {
|
|
|
418
457
|
return SIGNABLE_COMMITMENT_MODES.filter((m) => declared.includes(m));
|
|
419
458
|
}
|
|
420
459
|
function isCommitmentModeAllowed(profile, mode) {
|
|
421
|
-
|
|
460
|
+
const allowed = allowedCommitmentModes(profile);
|
|
461
|
+
if (mode === "review_above_cap") return allowed.includes("automatic");
|
|
462
|
+
return isSignable(mode) && allowed.includes(mode);
|
|
422
463
|
}
|
|
423
464
|
|
|
424
465
|
// src/profile.ts
|
|
@@ -533,6 +574,7 @@ function validateProfile(profile) {
|
|
|
533
574
|
}
|
|
534
575
|
const seen = /* @__PURE__ */ new Set();
|
|
535
576
|
for (const m of declaredModes) {
|
|
577
|
+
if (m === "review_above_cap") continue;
|
|
536
578
|
if (!SIGNABLE_COMMITMENT_MODES.includes(m)) {
|
|
537
579
|
errors.push({
|
|
538
580
|
code: "PROFILE_INVALID",
|
|
@@ -554,6 +596,46 @@ function validateProfile(profile) {
|
|
|
554
596
|
message: "commitment_modes declares no mode this profile can actually use."
|
|
555
597
|
});
|
|
556
598
|
}
|
|
599
|
+
if (boundsSchema) {
|
|
600
|
+
const readFields = /* @__PURE__ */ new Set();
|
|
601
|
+
for (const [fieldName, fieldDef] of Object.entries(boundsSchema.fields)) {
|
|
602
|
+
const bt = fieldDef.boundType;
|
|
603
|
+
if (bt && (bt.kind === "per_transaction" || bt.kind === "cumulative_sum")) readFields.add(bt.of);
|
|
604
|
+
const max = fieldDef.maximum;
|
|
605
|
+
if (max !== void 0 && (typeof max !== "number" || !Number.isFinite(max))) {
|
|
606
|
+
errors.push({
|
|
607
|
+
code: "PROFILE_INVALID",
|
|
608
|
+
field: fieldName,
|
|
609
|
+
message: `Bounds field "${fieldName}": maximum MUST be a number (Bounds Schema rule 9).`
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
const enforcedBy = fieldDef.enforcedBy;
|
|
613
|
+
if (enforcedBy !== void 0 && enforcedBy !== "gatekeeper") {
|
|
614
|
+
errors.push({
|
|
615
|
+
code: "PROFILE_INVALID",
|
|
616
|
+
field: fieldName,
|
|
617
|
+
message: `Bounds field "${fieldName}": enforcedBy MUST be "gatekeeper" when present (Bounds Schema rule 11).`
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
for (const f of profile.disclose_fields ?? []) {
|
|
622
|
+
if (!readFields.has(f)) {
|
|
623
|
+
errors.push({
|
|
624
|
+
code: "PROFILE_INVALID",
|
|
625
|
+
field: "disclose_fields",
|
|
626
|
+
message: `disclose_fields names "${f}", which no bound reads \u2014 only what a ticket carries can be disclosed.`
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
const binding = profile.content_binding;
|
|
632
|
+
if (binding && String(binding.version) === "2" && binding.kind === "text") {
|
|
633
|
+
errors.push({
|
|
634
|
+
code: "PROFILE_INVALID",
|
|
635
|
+
field: "content_binding",
|
|
636
|
+
message: 'A version-2 content_binding is defined for kind "jcs" only; kind "text" is invalid.'
|
|
637
|
+
});
|
|
638
|
+
}
|
|
557
639
|
return errors;
|
|
558
640
|
}
|
|
559
641
|
function checkDiscloseSubset(mandateDiscloseFields, profileDiscloseFields) {
|
|
@@ -720,7 +802,7 @@ function validateMandateOwners(payload) {
|
|
|
720
802
|
if (!Array.isArray(owners) || owners.length !== 1) {
|
|
721
803
|
throw new HapError(
|
|
722
804
|
"MALFORMED_MANDATE",
|
|
723
|
-
`mandate_owners MUST contain exactly one entry
|
|
805
|
+
`mandate_owners MUST contain exactly one entry (Mandate rule 7), got ${Array.isArray(owners) ? owners.length : typeof owners}`
|
|
724
806
|
);
|
|
725
807
|
}
|
|
726
808
|
const entry = owners[0];
|
|
@@ -739,17 +821,45 @@ function validateMandateOwners(payload) {
|
|
|
739
821
|
throw new HapError("OWNER_SIGNATURE_INVALID", `co-signing DID ${entry.did} is not key-bearing`);
|
|
740
822
|
}
|
|
741
823
|
}
|
|
824
|
+
function validateAboveCapFields(payload) {
|
|
825
|
+
const hasCaps = payload.above_cap_caps !== void 0;
|
|
826
|
+
const hasApprovers = payload.above_cap_approvers !== void 0;
|
|
827
|
+
const hasCount = payload.above_cap_approvals !== void 0;
|
|
828
|
+
if (hasCaps !== hasApprovers) {
|
|
829
|
+
throw new HapError("MALFORMED_MANDATE", "above_cap_caps and above_cap_approvers are present together or absent together");
|
|
830
|
+
}
|
|
831
|
+
if (payload.commitment_mode === "review_above_cap" && !hasApprovers) {
|
|
832
|
+
throw new HapError("MALFORMED_MANDATE", "a review_above_cap mandate MUST carry above_cap_caps and above_cap_approvers");
|
|
833
|
+
}
|
|
834
|
+
if (payload.version === "0.7") {
|
|
835
|
+
if (hasCount) {
|
|
836
|
+
throw new HapError("MALFORMED_MANDATE", "a v0.7 mandate carries no above_cap_approvals");
|
|
837
|
+
}
|
|
838
|
+
if (hasApprovers && payload.commitment_mode !== "review_above_cap") {
|
|
839
|
+
throw new HapError("MALFORMED_MANDATE", "a v0.7 mandate carries above-cap fields only under review_above_cap");
|
|
840
|
+
}
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
if (hasApprovers !== hasCount) {
|
|
844
|
+
throw new HapError("MALFORMED_MANDATE", "above_cap_approvals is present exactly when above_cap_approvers is (Mandate rule 10)");
|
|
845
|
+
}
|
|
846
|
+
if (hasCount) {
|
|
847
|
+
const n = payload.above_cap_approvals;
|
|
848
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1) {
|
|
849
|
+
throw new HapError("MALFORMED_MANDATE", `above_cap_approvals MUST be an integer >= 1, got ${JSON.stringify(n)}`);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
if (hasApprovers && payload.commitment_mode === "automatic") {
|
|
853
|
+
throw new HapError("MALFORMED_MANDATE", "an automatic mandate carries no above-cap fields");
|
|
854
|
+
}
|
|
855
|
+
}
|
|
742
856
|
async function verifyMandate(blob, opts) {
|
|
743
857
|
const mandate = decodeMandateBlob(blob);
|
|
744
|
-
|
|
745
|
-
throw new HapError(
|
|
746
|
-
"VERSION_UNSUPPORTED",
|
|
747
|
-
`mandate version ${JSON.stringify(mandate.payload.version)} is not verifiable by this implementation (verifies ${PROTOCOL_VERSION} only \u2014 re-issue the mandate)`
|
|
748
|
-
);
|
|
749
|
-
}
|
|
858
|
+
assertSupportedVersion(mandate.payload.version, "mandate");
|
|
750
859
|
await verifyMandateSignature(mandate, opts);
|
|
751
860
|
checkMandateExpiry(mandate.payload, opts?.now);
|
|
752
861
|
validateMandateOwners(mandate.payload);
|
|
862
|
+
validateAboveCapFields(mandate.payload);
|
|
753
863
|
return mandate.payload;
|
|
754
864
|
}
|
|
755
865
|
|
|
@@ -786,9 +896,10 @@ function buildMandateProjection(payload, entry) {
|
|
|
786
896
|
"mandate projection requires profile_hash, bounds_hash, scope_hash and commitment_mode"
|
|
787
897
|
);
|
|
788
898
|
}
|
|
899
|
+
const version = payload.version === "0.7" ? "0.7" : "0.8";
|
|
789
900
|
const projection = {
|
|
790
901
|
typ: "HAP-mandate-projection",
|
|
791
|
-
version
|
|
902
|
+
version,
|
|
792
903
|
profile_id,
|
|
793
904
|
profile_hash,
|
|
794
905
|
owner_did: entry.did,
|
|
@@ -803,13 +914,32 @@ function buildMandateProjection(payload, entry) {
|
|
|
803
914
|
if (payload.intent_disclosure_hash !== void 0) {
|
|
804
915
|
projection.intent_disclosure_hash = payload.intent_disclosure_hash;
|
|
805
916
|
}
|
|
806
|
-
if (
|
|
807
|
-
|
|
808
|
-
|
|
917
|
+
if (version === "0.7") {
|
|
918
|
+
if (payload.commitment_mode === "review_above_cap") {
|
|
919
|
+
projection.above_cap_caps = payload.above_cap_caps;
|
|
920
|
+
projection.above_cap_approvers = payload.above_cap_approvers;
|
|
921
|
+
}
|
|
922
|
+
} else {
|
|
923
|
+
if (payload.above_cap_approvers !== void 0) {
|
|
924
|
+
projection.above_cap_caps = payload.above_cap_caps;
|
|
925
|
+
projection.above_cap_approvers = payload.above_cap_approvers;
|
|
926
|
+
projection.above_cap_approvals = payload.above_cap_approvals;
|
|
927
|
+
}
|
|
809
928
|
}
|
|
810
929
|
if (payload.disclose_fields !== void 0) {
|
|
811
930
|
projection.disclose_fields = payload.disclose_fields;
|
|
812
931
|
}
|
|
932
|
+
if (version === "0.8") {
|
|
933
|
+
if (entry.binding === void 0 || entry.signing_surface === void 0) {
|
|
934
|
+
throw new OwnerSignatureError(
|
|
935
|
+
"OWNER_SIGNATURE_INVALID",
|
|
936
|
+
"a v0.8 projection requires the entry's binding and signing_surface"
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
projection.binding = entry.binding;
|
|
940
|
+
projection.signing_surface = entry.signing_surface;
|
|
941
|
+
projection.subjects_disclosed = (payload.subjects ?? []).some((s) => s.did === entry.did);
|
|
942
|
+
}
|
|
813
943
|
return projection;
|
|
814
944
|
}
|
|
815
945
|
function projectionSigningBytes(projection) {
|
|
@@ -855,11 +985,14 @@ async function verifyOwnerSignatures(mandate) {
|
|
|
855
985
|
return entries;
|
|
856
986
|
}
|
|
857
987
|
async function verifyApproval(approval, signature, signerDid) {
|
|
988
|
+
if (approval.version === "0.8" && approval.approver_did !== signerDid) {
|
|
989
|
+
throw new OwnerSignatureError("APPROVAL_SIGNATURE_INVALID", `approval names approver ${approval.approver_did}, verified under ${signerDid}`);
|
|
990
|
+
}
|
|
858
991
|
let publicKey;
|
|
859
992
|
try {
|
|
860
993
|
publicKey = decodeDidKey(signerDid);
|
|
861
994
|
} catch (err) {
|
|
862
|
-
throw new OwnerSignatureError("
|
|
995
|
+
throw new OwnerSignatureError("APPROVAL_SIGNATURE_INVALID", err.message);
|
|
863
996
|
}
|
|
864
997
|
const ok = await ed2.verifyAsync(decodeOrThrow(signature, "APPROVAL_SIGNATURE_INVALID"), approvalSigningBytes(approval), publicKey).catch(() => false);
|
|
865
998
|
if (!ok) {
|
|
@@ -867,6 +1000,9 @@ async function verifyApproval(approval, signature, signerDid) {
|
|
|
867
1000
|
}
|
|
868
1001
|
}
|
|
869
1002
|
var BINDING_RANK = { raw: 0, webauthn: 1, eudi: 2 };
|
|
1003
|
+
function bindingRank(binding) {
|
|
1004
|
+
return BINDING_RANK[binding] ?? 0;
|
|
1005
|
+
}
|
|
870
1006
|
function checkOwnerSignatureRequirement(payload, floor) {
|
|
871
1007
|
if (!floor?.required) return;
|
|
872
1008
|
const entry = payload.mandate_owners[0];
|
|
@@ -878,7 +1014,7 @@ function checkOwnerSignatureRequirement(payload, floor) {
|
|
|
878
1014
|
);
|
|
879
1015
|
}
|
|
880
1016
|
const minRank = floor.minBinding ? BINDING_RANK[floor.minBinding] : 0;
|
|
881
|
-
const haveRank =
|
|
1017
|
+
const haveRank = bindingRank(entry.binding);
|
|
882
1018
|
if (haveRank < minRank) {
|
|
883
1019
|
throw new OwnerSignatureError(
|
|
884
1020
|
"OWNER_SIGNATURE_REQUIRED",
|
|
@@ -889,11 +1025,27 @@ function checkOwnerSignatureRequirement(payload, floor) {
|
|
|
889
1025
|
|
|
890
1026
|
// src/ticket.ts
|
|
891
1027
|
import * as ed3 from "@noble/ed25519";
|
|
1028
|
+
var TICKET_FRESHNESS_SECONDS = 300;
|
|
892
1029
|
async function signTicket(payload, privateKey) {
|
|
893
1030
|
const signature = toBase64Url(await ed3.signAsync(new TextEncoder().encode(canonicalize(payload)), privateKey));
|
|
894
1031
|
return { ...payload, signature };
|
|
895
1032
|
}
|
|
1033
|
+
function assertTicketShape(ticket) {
|
|
1034
|
+
if ("typ" in ticket) {
|
|
1035
|
+
throw new HapError("INVALID_SIGNATURE", "a ticket carries no typ \u2014 an object with one is a projection, not a ticket");
|
|
1036
|
+
}
|
|
1037
|
+
if ("mandate_id" in ticket || "issued_at" in ticket) {
|
|
1038
|
+
throw new HapError("INVALID_SIGNATURE", "a ticket carries no mandate_id or issued_at \u2014 this object is a mandate payload");
|
|
1039
|
+
}
|
|
1040
|
+
for (const required of ["id", "mandateId", "actionType", "timestamp", "issuer"]) {
|
|
1041
|
+
if (ticket[required] === void 0) {
|
|
1042
|
+
throw new HapError("INVALID_SIGNATURE", `ticket is missing required field ${required}`);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
896
1046
|
async function verifyTicketSignature(ticket, opts) {
|
|
1047
|
+
assertTicketShape(ticket);
|
|
1048
|
+
assertSupportedVersion(ticket.version, "ticket");
|
|
897
1049
|
const { signature, ...unsigned } = ticket;
|
|
898
1050
|
if (!signature) {
|
|
899
1051
|
throw new HapError("INVALID_SIGNATURE", "ticket carries no signature");
|
|
@@ -919,6 +1071,120 @@ async function verifyTicketSignature(ticket, opts) {
|
|
|
919
1071
|
throw new HapError("INVALID_SIGNATURE", "ticket signature verification failed");
|
|
920
1072
|
}
|
|
921
1073
|
}
|
|
1074
|
+
var ECHOED_FIELDS = [
|
|
1075
|
+
"profileId",
|
|
1076
|
+
"actionType",
|
|
1077
|
+
"boundsHash",
|
|
1078
|
+
"executionContext",
|
|
1079
|
+
"contentHash",
|
|
1080
|
+
"contentBinding",
|
|
1081
|
+
"proposalId",
|
|
1082
|
+
"proposalContentHash"
|
|
1083
|
+
];
|
|
1084
|
+
function verifyTicketAgainstRequest(ticket, request, opts = {}) {
|
|
1085
|
+
for (const field of ECHOED_FIELDS) {
|
|
1086
|
+
const sent = request[field];
|
|
1087
|
+
const got = ticket[field];
|
|
1088
|
+
if (sent === void 0 && got === void 0) continue;
|
|
1089
|
+
if (sent === void 0 || got === void 0 || canonicalize(sent) !== canonicalize(got)) {
|
|
1090
|
+
throw new HapError("TICKET_REQUEST_MISMATCH", `ticket ${field} differs from the request's`);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
if (request.idempotencyKey !== void 0 && ticket.idempotencyKey !== request.idempotencyKey) {
|
|
1094
|
+
throw new HapError(
|
|
1095
|
+
"TICKET_REQUEST_MISMATCH",
|
|
1096
|
+
ticket.idempotencyKey === void 0 ? "ticket carries no idempotencyKey although the request sent one" : "ticket idempotencyKey differs from the one the request sent"
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
const expectedMandateId = opts.mandate?.payload.mandate_id ?? request.mandateId;
|
|
1100
|
+
if (expectedMandateId !== void 0 && ticket.mandateId !== expectedMandateId) {
|
|
1101
|
+
throw new HapError("TICKET_REQUEST_MISMATCH", `ticket mandateId ${ticket.mandateId} is not the mandate this request was made under`);
|
|
1102
|
+
}
|
|
1103
|
+
if (opts.mandate && ticket.version !== opts.mandate.payload.version) {
|
|
1104
|
+
throw new HapError("TICKET_REQUEST_MISMATCH", `ticket version ${ticket.version} differs from its mandate's ${opts.mandate.payload.version}`);
|
|
1105
|
+
}
|
|
1106
|
+
if (!opts.replay) {
|
|
1107
|
+
const now = opts.now ?? Math.floor(Date.now() / 1e3);
|
|
1108
|
+
if (Math.abs(now - ticket.timestamp) > TICKET_FRESHNESS_SECONDS) {
|
|
1109
|
+
throw new HapError("TICKET_REQUEST_MISMATCH", `ticket timestamp ${ticket.timestamp} is outside the freshness window at ${now}`);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
function reconstructApproval(ticket, entry, approverDid) {
|
|
1114
|
+
if (!ticket.proposalId) {
|
|
1115
|
+
throw new HapError("APPROVAL_SIGNATURE_INVALID", "ticket carries an approval signature but no proposalId");
|
|
1116
|
+
}
|
|
1117
|
+
if (ticket.version === "0.7") {
|
|
1118
|
+
if (!ticket.contentHash) {
|
|
1119
|
+
throw new HapError("APPROVAL_SIGNATURE_INVALID", "a v0.7 approval without a content binding is not reconstructible from the ticket alone");
|
|
1120
|
+
}
|
|
1121
|
+
return {
|
|
1122
|
+
typ: "HAP-approval",
|
|
1123
|
+
version: "0.7",
|
|
1124
|
+
proposal_id: ticket.proposalId,
|
|
1125
|
+
mandate_id: ticket.mandateId,
|
|
1126
|
+
decision: "commit",
|
|
1127
|
+
content_hash: ticket.contentHash,
|
|
1128
|
+
decided_at: entry.decided_at,
|
|
1129
|
+
nonce: entry.nonce
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
if (!ticket.proposalContentHash) {
|
|
1133
|
+
throw new HapError("APPROVAL_SIGNATURE_INVALID", "a v0.8 review-path ticket carries proposalContentHash");
|
|
1134
|
+
}
|
|
1135
|
+
return {
|
|
1136
|
+
typ: "HAP-approval",
|
|
1137
|
+
version: "0.8",
|
|
1138
|
+
approver_did: approverDid,
|
|
1139
|
+
proposal_id: ticket.proposalId,
|
|
1140
|
+
mandate_id: ticket.mandateId,
|
|
1141
|
+
action: ticket.action,
|
|
1142
|
+
action_type: ticket.actionType,
|
|
1143
|
+
decision: "commit",
|
|
1144
|
+
content_hash: ticket.contentHash ?? ticket.proposalContentHash,
|
|
1145
|
+
proposal_content_hash: ticket.proposalContentHash,
|
|
1146
|
+
decided_at: entry.decided_at,
|
|
1147
|
+
nonce: entry.nonce
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
async function verifyTicketApprovals(ticket, mandate) {
|
|
1151
|
+
if (ticket.mandateId !== mandate.payload.mandate_id) {
|
|
1152
|
+
throw new HapError("APPROVAL_SIGNATURE_INVALID", "the mandate supplied is not the one the ticket names");
|
|
1153
|
+
}
|
|
1154
|
+
const ownerDid = mandate.payload.mandate_owners[0]?.did;
|
|
1155
|
+
const result = { approvers: [] };
|
|
1156
|
+
try {
|
|
1157
|
+
if (ticket.approvalSignature) {
|
|
1158
|
+
const approval = reconstructApproval(ticket, ticket.approvalSignature, ownerDid);
|
|
1159
|
+
await verifyApproval(approval, ticket.approvalSignature.signature, ownerDid);
|
|
1160
|
+
result.owner = ownerDid;
|
|
1161
|
+
}
|
|
1162
|
+
const signed = new Set(mandate.payload.above_cap_approvers ?? []);
|
|
1163
|
+
for (const entry of ticket.approvalSignatures ?? []) {
|
|
1164
|
+
if (!signed.has(entry.approver_did)) {
|
|
1165
|
+
throw new HapError("APPROVAL_SIGNATURE_INVALID", `approver ${entry.approver_did} is not in the mandate's signed above_cap_approvers`);
|
|
1166
|
+
}
|
|
1167
|
+
if (entry.approver_did === ownerDid) {
|
|
1168
|
+
throw new HapError("APPROVAL_SIGNATURE_INVALID", "the proposer never counts as an above-cap approver");
|
|
1169
|
+
}
|
|
1170
|
+
const approval = reconstructApproval(ticket, entry, entry.approver_did);
|
|
1171
|
+
await verifyApproval(approval, entry.signature, entry.approver_did);
|
|
1172
|
+
if (!result.approvers.includes(entry.approver_did)) result.approvers.push(entry.approver_did);
|
|
1173
|
+
}
|
|
1174
|
+
} catch (err) {
|
|
1175
|
+
if (err instanceof OwnerSignatureError) throw new HapError(err.code, err.message);
|
|
1176
|
+
throw err;
|
|
1177
|
+
}
|
|
1178
|
+
return result;
|
|
1179
|
+
}
|
|
1180
|
+
function approvalsSufficient(mandate, verified, aboveCap) {
|
|
1181
|
+
const p = mandate.payload;
|
|
1182
|
+
const ownerNeeded = p.commitment_mode === "review";
|
|
1183
|
+
if (ownerNeeded && !verified.owner) return false;
|
|
1184
|
+
if (!aboveCap) return true;
|
|
1185
|
+
const required = p.version === "0.7" ? (p.above_cap_approvers ?? []).length : p.above_cap_approvals ?? 1;
|
|
1186
|
+
return verified.approvers.length >= required;
|
|
1187
|
+
}
|
|
922
1188
|
function publicTicketView(ticket, governingDiscloseFields = []) {
|
|
923
1189
|
const { userId: _userId, cumulativeState: _cumulativeState, limits: _limits, executionContext, ...rest } = ticket;
|
|
924
1190
|
const allowed = new Set(governingDiscloseFields);
|
|
@@ -929,6 +1195,475 @@ function publicTicketView(ticket, governingDiscloseFields = []) {
|
|
|
929
1195
|
return { ...rest, executionContext: disclosedExecutionContext };
|
|
930
1196
|
}
|
|
931
1197
|
|
|
1198
|
+
// src/ticket-projection.ts
|
|
1199
|
+
import * as ed4 from "@noble/ed25519";
|
|
1200
|
+
var PROJECTION_MAX_VALIDITY_SECONDS = 86400;
|
|
1201
|
+
function buildTicketProjection(ticket, mandate, opts) {
|
|
1202
|
+
if (ticket.mandateId !== mandate.payload.mandate_id) {
|
|
1203
|
+
throw new HapError("MALFORMED_TICKET_REQUEST", "the mandate supplied is not the one the ticket names");
|
|
1204
|
+
}
|
|
1205
|
+
if (opts.validUntil > ticket.timestamp + PROJECTION_MAX_VALIDITY_SECONDS) {
|
|
1206
|
+
throw new HapError("MALFORMED_TICKET_REQUEST", `valid_until MUST NOT end later than ${PROJECTION_MAX_VALIDITY_SECONDS}s after the ticket's timestamp`);
|
|
1207
|
+
}
|
|
1208
|
+
const projection = {
|
|
1209
|
+
typ: "HAP-ticket-projection",
|
|
1210
|
+
version: ticket.version,
|
|
1211
|
+
id: ticket.id,
|
|
1212
|
+
mandateId: ticket.mandateId,
|
|
1213
|
+
issuer: ticket.issuer,
|
|
1214
|
+
timestamp: ticket.timestamp,
|
|
1215
|
+
valid_until: opts.validUntil,
|
|
1216
|
+
profileId: ticket.profileId,
|
|
1217
|
+
actionType: ticket.actionType,
|
|
1218
|
+
boundsHash: ticket.boundsHash
|
|
1219
|
+
};
|
|
1220
|
+
if (ticket.contentHash !== void 0) projection.contentHash = ticket.contentHash;
|
|
1221
|
+
if (ticket.contentBinding !== void 0) projection.contentBinding = ticket.contentBinding;
|
|
1222
|
+
if (ticket.subjects !== void 0) projection.subjects = ticket.subjects;
|
|
1223
|
+
const disclosed = {};
|
|
1224
|
+
for (const field of opts.discloseFields ?? []) {
|
|
1225
|
+
if (ticket.executionContext[field] !== void 0) disclosed[field] = ticket.executionContext[field];
|
|
1226
|
+
}
|
|
1227
|
+
if (Object.keys(disclosed).length > 0) projection.executionContext = disclosed;
|
|
1228
|
+
const owner = mandate.payload.mandate_owners[0];
|
|
1229
|
+
if (owner?.signature !== void 0) projection.owner = owner;
|
|
1230
|
+
return projection;
|
|
1231
|
+
}
|
|
1232
|
+
async function signTicketProjection(projection, privateKey) {
|
|
1233
|
+
const signature = toBase64Url(await ed4.signAsync(new TextEncoder().encode(canonicalize(projection)), privateKey));
|
|
1234
|
+
return { ...projection, signature };
|
|
1235
|
+
}
|
|
1236
|
+
async function verifyTicketProjection(projection, opts = {}) {
|
|
1237
|
+
if (projection.typ !== "HAP-ticket-projection") {
|
|
1238
|
+
throw new HapError("INVALID_SIGNATURE", 'a projection verifier requires typ "HAP-ticket-projection"');
|
|
1239
|
+
}
|
|
1240
|
+
for (const k of ["userId", "cumulativeState", "limits", "action"]) {
|
|
1241
|
+
if (k in projection) throw new HapError("INVALID_SIGNATURE", `a projection MUST NOT carry ${k}`);
|
|
1242
|
+
}
|
|
1243
|
+
if (opts.trustedIssuers && !opts.trustedIssuers.includes(projection.issuer)) {
|
|
1244
|
+
throw new HapError("INVALID_SIGNATURE", `issuer ${JSON.stringify(projection.issuer)} is not a trusted Authority Server`);
|
|
1245
|
+
}
|
|
1246
|
+
const { signature, ...unsigned } = projection;
|
|
1247
|
+
if (!signature) throw new HapError("INVALID_SIGNATURE", "projection carries no signature");
|
|
1248
|
+
let publicKey;
|
|
1249
|
+
try {
|
|
1250
|
+
publicKey = decodeDidKey(projection.issuer);
|
|
1251
|
+
} catch (err) {
|
|
1252
|
+
throw new HapError("INVALID_SIGNATURE", `cannot resolve a key from issuer ${JSON.stringify(projection.issuer)}: ${err.message}`);
|
|
1253
|
+
}
|
|
1254
|
+
let sigBytes;
|
|
1255
|
+
try {
|
|
1256
|
+
sigBytes = fromBase64Url(signature);
|
|
1257
|
+
} catch (err) {
|
|
1258
|
+
throw new HapError("INVALID_SIGNATURE", err.message);
|
|
1259
|
+
}
|
|
1260
|
+
const ok = await ed4.verifyAsync(sigBytes, new TextEncoder().encode(canonicalize(unsigned)), publicKey).catch(() => false);
|
|
1261
|
+
if (!ok) throw new HapError("INVALID_SIGNATURE", "projection signature verification failed");
|
|
1262
|
+
if (opts.asAuthorization) {
|
|
1263
|
+
const now = opts.now ?? Math.floor(Date.now() / 1e3);
|
|
1264
|
+
if (now > projection.valid_until) {
|
|
1265
|
+
throw new HapError("POLICY_UNMET", `projection presented after valid_until ${projection.valid_until}`);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
async function verifyProjectionOwner(projection, mandate) {
|
|
1270
|
+
if (!projection.owner) {
|
|
1271
|
+
throw new HapError("OWNER_SIGNATURE_INVALID", "projection carries no owner entry");
|
|
1272
|
+
}
|
|
1273
|
+
if (projection.mandateId !== mandate.payload.mandate_id) {
|
|
1274
|
+
throw new HapError("OWNER_SIGNATURE_INVALID", "the mandate supplied is not the one the projection names");
|
|
1275
|
+
}
|
|
1276
|
+
const own = mandate.payload.mandate_owners[0];
|
|
1277
|
+
if (!own || canonicalize(own) !== canonicalize(projection.owner)) {
|
|
1278
|
+
throw new HapError("OWNER_SIGNATURE_INVALID", "projection owner entry differs from the mandate's mandate_owners entry");
|
|
1279
|
+
}
|
|
1280
|
+
try {
|
|
1281
|
+
await verifyOwnerSignature(mandate, projection.owner);
|
|
1282
|
+
} catch (err) {
|
|
1283
|
+
if (err instanceof OwnerSignatureError) throw new HapError(err.code, err.message);
|
|
1284
|
+
throw err;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// src/verifier-policy.ts
|
|
1289
|
+
var PolicyError = class extends HapError {
|
|
1290
|
+
constructor(line, code, message) {
|
|
1291
|
+
super(code, `${line}: ${message}`);
|
|
1292
|
+
this.line = line;
|
|
1293
|
+
this.name = "PolicyError";
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
async function evaluateVerifierPolicy(policy, input) {
|
|
1297
|
+
if (policy.typ !== "HAP-verifier-policy") {
|
|
1298
|
+
throw new HapError("POLICY_UNMET", 'policy document must carry typ "HAP-verifier-policy"');
|
|
1299
|
+
}
|
|
1300
|
+
const artifact = input.ticket ?? input.projection;
|
|
1301
|
+
if (!artifact) throw new HapError("POLICY_UNMET", "no ticket or projection to evaluate");
|
|
1302
|
+
const now = input.now ?? Math.floor(Date.now() / 1e3);
|
|
1303
|
+
try {
|
|
1304
|
+
if (input.ticket) {
|
|
1305
|
+
await verifyTicketSignature(input.ticket, { trustedIssuers: policy.trusted_issuers });
|
|
1306
|
+
} else if (input.projection) {
|
|
1307
|
+
await verifyTicketProjection(input.projection, { trustedIssuers: policy.trusted_issuers, now, asAuthorization: input.asAuthorization });
|
|
1308
|
+
}
|
|
1309
|
+
if (input.mandate) {
|
|
1310
|
+
await verifyMandateSignature(input.mandate, { trustedIssuers: policy.trusted_issuers });
|
|
1311
|
+
}
|
|
1312
|
+
} catch (err) {
|
|
1313
|
+
const code = err instanceof HapError ? err.code : "INVALID_SIGNATURE";
|
|
1314
|
+
throw new PolicyError(code === "POLICY_UNMET" ? "freshness_seconds" : "trusted_issuers", code, err.message);
|
|
1315
|
+
}
|
|
1316
|
+
let verifiedOwnerDids;
|
|
1317
|
+
const needOwners = policy.min_binding !== void 0 || policy.required_owners !== void 0;
|
|
1318
|
+
if (needOwners) {
|
|
1319
|
+
verifiedOwnerDids = /* @__PURE__ */ new Map();
|
|
1320
|
+
if (input.mandate) {
|
|
1321
|
+
try {
|
|
1322
|
+
const entries = await verifyOwnerSignatures(input.mandate);
|
|
1323
|
+
for (const e of entries) verifiedOwnerDids.set(e.did, e.binding ?? "raw");
|
|
1324
|
+
if (input.projection?.owner) await verifyProjectionOwner(input.projection, input.mandate);
|
|
1325
|
+
} catch (err) {
|
|
1326
|
+
throw new PolicyError("min_binding", "OWNER_SIGNATURE_REQUIRED", `no verifiable owner signature: ${err.message}`);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
if (policy.min_binding !== void 0) {
|
|
1331
|
+
const minRank = BINDING_RANK[policy.min_binding];
|
|
1332
|
+
if (minRank === void 0) {
|
|
1333
|
+
throw new PolicyError("min_binding", "OWNER_SIGNATURE_REQUIRED", `unknown binding "${policy.min_binding}" \u2014 satisfies no minimum`);
|
|
1334
|
+
}
|
|
1335
|
+
const best = Math.max(-1, ...[...verifiedOwnerDids.values()].map(bindingRank));
|
|
1336
|
+
if (best < minRank) {
|
|
1337
|
+
throw new PolicyError("min_binding", "OWNER_SIGNATURE_REQUIRED", `no verified owner signature at binding >= "${policy.min_binding}"`);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
if (policy.required_owners !== void 0) {
|
|
1341
|
+
for (const did of policy.required_owners) {
|
|
1342
|
+
if (!verifiedOwnerDids.has(did)) {
|
|
1343
|
+
throw new PolicyError("required_owners", "OWNER_SIGNATURE_REQUIRED", `no verified owner signature by ${did}`);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
if (policy.profile_hashes !== void 0) {
|
|
1348
|
+
const mandate = input.mandate;
|
|
1349
|
+
if (!mandate) throw new PolicyError("profile_hashes", "PROFILE_HASH_MISMATCH", "the mandate is needed to check profile_hashes");
|
|
1350
|
+
const expected = policy.profile_hashes[mandate.payload.profile_id];
|
|
1351
|
+
if (expected === void 0) {
|
|
1352
|
+
throw new PolicyError("profile_hashes", "PROFILE_HASH_MISMATCH", `profile ${mandate.payload.profile_id} is not listed`);
|
|
1353
|
+
}
|
|
1354
|
+
if (expected !== mandate.payload.profile_hash) {
|
|
1355
|
+
throw new PolicyError("profile_hashes", "PROFILE_HASH_MISMATCH", `profile_hash differs from the listed one for ${mandate.payload.profile_id}`);
|
|
1356
|
+
}
|
|
1357
|
+
if (mandate.payload.mandate_id !== artifact.mandateId) {
|
|
1358
|
+
throw new PolicyError("profile_hashes", "PROFILE_HASH_MISMATCH", "the mandate supplied is not the one the artifact names");
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
if (policy.action_types !== void 0 && !policy.action_types.includes(artifact.actionType)) {
|
|
1362
|
+
throw new PolicyError("action_types", "POLICY_UNMET", `actionType ${artifact.actionType} is not in the listed set`);
|
|
1363
|
+
}
|
|
1364
|
+
if (policy.freshness_seconds !== void 0) {
|
|
1365
|
+
if (now - artifact.timestamp > policy.freshness_seconds) {
|
|
1366
|
+
throw new PolicyError("freshness_seconds", "POLICY_UNMET", `ticket is ${now - artifact.timestamp}s old, policy allows ${policy.freshness_seconds}s`);
|
|
1367
|
+
}
|
|
1368
|
+
if (input.projection && input.asAuthorization && now > input.projection.valid_until) {
|
|
1369
|
+
throw new PolicyError("freshness_seconds", "POLICY_UNMET", "projection presented after valid_until");
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
if (policy.required_fields !== void 0) {
|
|
1373
|
+
const ctx = input.ticket?.executionContext ?? input.projection?.executionContext ?? {};
|
|
1374
|
+
for (const f of policy.required_fields) {
|
|
1375
|
+
if (ctx[f] === void 0) {
|
|
1376
|
+
throw new PolicyError("required_fields", "POLICY_UNMET", `execution-context field ${f} is not ${input.ticket ? "present" : "disclosed"}`);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
if (policy.require_approval_signature) {
|
|
1381
|
+
const ticket = input.ticket;
|
|
1382
|
+
const mandate = input.mandate;
|
|
1383
|
+
if (!ticket || !mandate) {
|
|
1384
|
+
throw new PolicyError("require_approval_signature", "APPROVAL_SIGNATURE_REQUIRED", "the complete ticket and its mandate are needed to verify approvals");
|
|
1385
|
+
}
|
|
1386
|
+
if (ticket.proposalId !== void 0) {
|
|
1387
|
+
let verified;
|
|
1388
|
+
try {
|
|
1389
|
+
verified = await verifyTicketApprovals(ticket, mandate);
|
|
1390
|
+
} catch (err) {
|
|
1391
|
+
throw new PolicyError("require_approval_signature", "APPROVAL_SIGNATURE_REQUIRED", err.message);
|
|
1392
|
+
}
|
|
1393
|
+
const aboveCap = input.aboveCap ?? (ticket.approvalSignatures !== void 0 && ticket.approvalSignatures.length > 0);
|
|
1394
|
+
if (!approvalsSufficient(mandate, verified, aboveCap)) {
|
|
1395
|
+
throw new PolicyError("require_approval_signature", "APPROVAL_SIGNATURE_REQUIRED", "the ticket lacks a verifiable signature for each decision it rests on");
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// src/proposal-content.ts
|
|
1402
|
+
import { createHash as createHash6 } from "crypto";
|
|
1403
|
+
import { CipherSuite, DhkemX25519HkdfSha256, HkdfSha256, Aes256Gcm } from "@hpke/core";
|
|
1404
|
+
function suite() {
|
|
1405
|
+
return new CipherSuite({ kem: new DhkemX25519HkdfSha256(), kdf: new HkdfSha256(), aead: new Aes256Gcm() });
|
|
1406
|
+
}
|
|
1407
|
+
var webCrypto = () => {
|
|
1408
|
+
const c = globalThis.crypto;
|
|
1409
|
+
if (!c?.subtle) throw new Error("WebCrypto is not available in this runtime");
|
|
1410
|
+
return c;
|
|
1411
|
+
};
|
|
1412
|
+
function proposalContentJcs(args) {
|
|
1413
|
+
if (args === null || typeof args !== "object" || Array.isArray(args)) {
|
|
1414
|
+
throw new HapError("PROPOSAL_NOT_ENCRYPTED", "proposal content is one object \u2014 the action's argument object");
|
|
1415
|
+
}
|
|
1416
|
+
return canonicalize(args);
|
|
1417
|
+
}
|
|
1418
|
+
function computeProposalContentHash(args) {
|
|
1419
|
+
return `sha256:${createHash6("sha256").update(proposalContentJcs(args), "utf8").digest("hex")}`;
|
|
1420
|
+
}
|
|
1421
|
+
async function generateReaderKeyPair() {
|
|
1422
|
+
const kp = await suite().kem.generateKeyPair();
|
|
1423
|
+
return exportKeyPair(kp);
|
|
1424
|
+
}
|
|
1425
|
+
async function deriveReaderKeyPair(ikm) {
|
|
1426
|
+
const kp = await suite().kem.deriveKeyPair(ikm);
|
|
1427
|
+
return exportKeyPair(kp);
|
|
1428
|
+
}
|
|
1429
|
+
async function exportKeyPair(kp) {
|
|
1430
|
+
const s = suite();
|
|
1431
|
+
return {
|
|
1432
|
+
privateKey: new Uint8Array(await s.kem.serializePrivateKey(kp.privateKey)),
|
|
1433
|
+
publicKey: new Uint8Array(await s.kem.serializePublicKey(kp.publicKey))
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
async function encryptProposalContent(args, readers, opts = {}) {
|
|
1437
|
+
if (readers.length === 0) {
|
|
1438
|
+
throw new HapError("PROPOSAL_NOT_ENCRYPTED", "a proposal has at least one reader (the Mandate Owner)");
|
|
1439
|
+
}
|
|
1440
|
+
const jcs = proposalContentJcs(args);
|
|
1441
|
+
const proposalContentHash = computeProposalContentHash(args);
|
|
1442
|
+
const crypto = webCrypto();
|
|
1443
|
+
const s = suite();
|
|
1444
|
+
const cek = opts.deterministic?.contentKey ?? crypto.getRandomValues(new Uint8Array(32));
|
|
1445
|
+
const iv = opts.deterministic?.iv ?? crypto.getRandomValues(new Uint8Array(12));
|
|
1446
|
+
if (cek.length !== 32 || iv.length !== 12) {
|
|
1447
|
+
throw new HapError("PROPOSAL_NOT_ENCRYPTED", "content key is 32 bytes and IV is 12 bytes");
|
|
1448
|
+
}
|
|
1449
|
+
const aesKey = await crypto.subtle.importKey("raw", cek, { name: "AES-GCM", length: 256 }, false, ["encrypt"]);
|
|
1450
|
+
const sealed = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, new TextEncoder().encode(jcs)));
|
|
1451
|
+
const ciphertext = new Uint8Array(iv.length + sealed.length);
|
|
1452
|
+
ciphertext.set(iv, 0);
|
|
1453
|
+
ciphertext.set(sealed, iv.length);
|
|
1454
|
+
const encrypted_keys = {};
|
|
1455
|
+
for (const { did, publicKey } of readers) {
|
|
1456
|
+
const recipientPublicKey = await s.kem.deserializePublicKey(publicKey);
|
|
1457
|
+
const ekmIkm = opts.deterministic?.ephemeralIkm[did];
|
|
1458
|
+
const sender = await s.createSenderContext({
|
|
1459
|
+
recipientPublicKey,
|
|
1460
|
+
...ekmIkm ? { ekm: await s.kem.deriveKeyPair(ekmIkm) } : {}
|
|
1461
|
+
});
|
|
1462
|
+
const ct = new Uint8Array(await sender.seal(cek));
|
|
1463
|
+
encrypted_keys[did] = { ct: toBase64Url(ct), enc: toBase64Url(new Uint8Array(sender.enc)) };
|
|
1464
|
+
}
|
|
1465
|
+
return {
|
|
1466
|
+
content: {
|
|
1467
|
+
ciphertext: toBase64Url(ciphertext),
|
|
1468
|
+
encrypted_keys,
|
|
1469
|
+
readers: readers.map((r) => r.did),
|
|
1470
|
+
unreadable: [...opts.unreadable ?? []]
|
|
1471
|
+
},
|
|
1472
|
+
proposalContentHash
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
async function decryptProposalContent(content, readerDid, readerPrivateKey) {
|
|
1476
|
+
const wrap = content.encrypted_keys[readerDid];
|
|
1477
|
+
if (!wrap) throw new HapError("PROPOSAL_NOT_ENCRYPTED", `no key wrapped for reader ${readerDid}`);
|
|
1478
|
+
const s = suite();
|
|
1479
|
+
const crypto = webCrypto();
|
|
1480
|
+
let cek;
|
|
1481
|
+
try {
|
|
1482
|
+
const recipient = await s.createRecipientContext({
|
|
1483
|
+
recipientKey: await s.kem.deserializePrivateKey(readerPrivateKey),
|
|
1484
|
+
enc: fromBase64Url(wrap.enc)
|
|
1485
|
+
});
|
|
1486
|
+
cek = new Uint8Array(await recipient.open(fromBase64Url(wrap.ct)));
|
|
1487
|
+
} catch (err) {
|
|
1488
|
+
throw new HapError("PROPOSAL_NOT_ENCRYPTED", `proposal content is unreadable for ${readerDid}: ${err.message}`);
|
|
1489
|
+
}
|
|
1490
|
+
const bytes = fromBase64Url(content.ciphertext);
|
|
1491
|
+
if (bytes.length < 12 + 16) throw new HapError("PROPOSAL_NOT_ENCRYPTED", "ciphertext is shorter than IV + tag");
|
|
1492
|
+
let plaintext;
|
|
1493
|
+
try {
|
|
1494
|
+
const aesKey = await crypto.subtle.importKey("raw", cek, { name: "AES-GCM", length: 256 }, false, ["decrypt"]);
|
|
1495
|
+
plaintext = new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv: bytes.subarray(0, 12) }, aesKey, bytes.subarray(12)));
|
|
1496
|
+
} catch (err) {
|
|
1497
|
+
throw new HapError("PROPOSAL_NOT_ENCRYPTED", `proposal content is unreadable for ${readerDid}: ${err.message}`);
|
|
1498
|
+
}
|
|
1499
|
+
const contentJcs = new TextDecoder().decode(plaintext);
|
|
1500
|
+
let args;
|
|
1501
|
+
try {
|
|
1502
|
+
args = JSON.parse(contentJcs);
|
|
1503
|
+
} catch {
|
|
1504
|
+
throw new HapError("PROPOSAL_NOT_ENCRYPTED", "decrypted content is not JSON");
|
|
1505
|
+
}
|
|
1506
|
+
return { arguments: args, contentJcs, proposalContentHash: computeProposalContentHash(args) };
|
|
1507
|
+
}
|
|
1508
|
+
function checkProposalReaders(content, expected) {
|
|
1509
|
+
const want = /* @__PURE__ */ new Set([expected.ownerDid, ...expected.deciders]);
|
|
1510
|
+
const have = /* @__PURE__ */ new Set([...content.readers, ...content.unreadable]);
|
|
1511
|
+
if (want.size !== have.size || [...want].some((d) => !have.has(d))) {
|
|
1512
|
+
throw new HapError("PROPOSAL_APPROVERS_INVALID", "readers \u222A unreadable differs from the owner plus the signed deciders");
|
|
1513
|
+
}
|
|
1514
|
+
const keyed = new Set(Object.keys(content.encrypted_keys));
|
|
1515
|
+
const readers = new Set(content.readers);
|
|
1516
|
+
if (keyed.size !== readers.size || [...readers].some((d) => !keyed.has(d))) {
|
|
1517
|
+
throw new HapError("PROPOSAL_NOT_ENCRYPTED", "readers differs from the key set of encrypted_keys");
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// src/issuance.ts
|
|
1522
|
+
import { createHash as createHash7 } from "crypto";
|
|
1523
|
+
function computeExecutionContextHash(profile) {
|
|
1524
|
+
const schema = profile.executionContextSchema ?? {};
|
|
1525
|
+
return `sha256:${createHash7("sha256").update(canonicalize(schema), "utf8").digest("hex")}`;
|
|
1526
|
+
}
|
|
1527
|
+
function boundReadFields(profile) {
|
|
1528
|
+
const fields = /* @__PURE__ */ new Set();
|
|
1529
|
+
for (const def of Object.values(profile.boundsSchema?.fields ?? {})) {
|
|
1530
|
+
const bt = def.boundType;
|
|
1531
|
+
if (bt && (bt.kind === "per_transaction" || bt.kind === "cumulative_sum")) fields.add(bt.of);
|
|
1532
|
+
}
|
|
1533
|
+
return fields;
|
|
1534
|
+
}
|
|
1535
|
+
function wireExecutionContext(execution, profile) {
|
|
1536
|
+
const allowed = boundReadFields(profile);
|
|
1537
|
+
const out = {};
|
|
1538
|
+
for (const [k, v] of Object.entries(execution)) {
|
|
1539
|
+
if (!allowed.has(k) || k.startsWith("_") || v === void 0 || v === null) continue;
|
|
1540
|
+
out[k] = v;
|
|
1541
|
+
}
|
|
1542
|
+
return out;
|
|
1543
|
+
}
|
|
1544
|
+
function checkExecutionValues(execution, profile) {
|
|
1545
|
+
for (const k of Object.keys(execution)) {
|
|
1546
|
+
if (k.startsWith("_")) {
|
|
1547
|
+
throw new HapError("MALFORMED_TICKET_REQUEST", `executionContext key "${k}" is reserved and MUST NOT travel`);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
for (const field of boundReadFields(profile)) {
|
|
1551
|
+
const v = execution[field];
|
|
1552
|
+
if (v === void 0 || v === null) continue;
|
|
1553
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v < 0) {
|
|
1554
|
+
throw new HapError(
|
|
1555
|
+
"MALFORMED_TICKET_REQUEST",
|
|
1556
|
+
`executionContext.${field} MUST be a finite, non-negative number, got ${JSON.stringify(v)}`
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
function boundGoverns(def, actionType) {
|
|
1562
|
+
return !def.appliesTo || def.appliesTo.includes(actionType);
|
|
1563
|
+
}
|
|
1564
|
+
function missingSumFields(execution, profile, actionType) {
|
|
1565
|
+
const missing = [];
|
|
1566
|
+
for (const [name, def] of Object.entries(profile.boundsSchema?.fields ?? {})) {
|
|
1567
|
+
const bt = def.boundType;
|
|
1568
|
+
if (!bt || bt.kind !== "cumulative_sum" || !boundGoverns(def, actionType)) continue;
|
|
1569
|
+
const v = execution[bt.of];
|
|
1570
|
+
if (v === void 0 || v === null) missing.push({ bound: name, field: bt.of });
|
|
1571
|
+
}
|
|
1572
|
+
return missing;
|
|
1573
|
+
}
|
|
1574
|
+
function checkBoundsValues(bounds, profile) {
|
|
1575
|
+
for (const [name, def] of Object.entries(profile.boundsSchema?.fields ?? {})) {
|
|
1576
|
+
if (name === "profile") continue;
|
|
1577
|
+
const v = bounds[name];
|
|
1578
|
+
if (v === void 0 || v === null) continue;
|
|
1579
|
+
if (def.type === "number" || def.maximum !== void 0) {
|
|
1580
|
+
if (typeof v !== "number" || !Number.isFinite(v)) {
|
|
1581
|
+
throw new HapError("BOUNDS_INVALID_VALUE", `bound "${name}" MUST be a number, got ${JSON.stringify(v)}`);
|
|
1582
|
+
}
|
|
1583
|
+
if (def.maximum !== void 0 && v > def.maximum) {
|
|
1584
|
+
throw new HapError("BOUNDS_INVALID_VALUE", `bound "${name}" = ${v} exceeds the profile's maximum ${def.maximum}`);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
const bt = def.boundType;
|
|
1588
|
+
if (bt?.kind === "enum" && !bt.values.includes(String(v))) {
|
|
1589
|
+
throw new HapError("BOUNDS_INVALID_VALUE", `bound "${name}" = ${JSON.stringify(v)} is not in [${bt.values.join(", ")}]`);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
function exceedsCap(value, cap, def) {
|
|
1594
|
+
const bt = def.boundType;
|
|
1595
|
+
if (bt?.kind === "enum") {
|
|
1596
|
+
const order = bt.values;
|
|
1597
|
+
const vi = order.indexOf(String(value));
|
|
1598
|
+
const ci = order.indexOf(String(cap));
|
|
1599
|
+
if (vi < 0 || ci < 0) return true;
|
|
1600
|
+
return vi < ci;
|
|
1601
|
+
}
|
|
1602
|
+
return typeof value !== "number" || typeof cap !== "number" || value > cap;
|
|
1603
|
+
}
|
|
1604
|
+
function checkGroupCeilings(bounds, profile, caps) {
|
|
1605
|
+
const fields = profile.boundsSchema?.fields ?? {};
|
|
1606
|
+
const hard = { ...caps.hard ?? {} };
|
|
1607
|
+
for (const [name, cap] of Object.entries(caps.approval ?? {})) {
|
|
1608
|
+
const def = fields[name];
|
|
1609
|
+
const bt = def?.boundType;
|
|
1610
|
+
if (!def || !bt) continue;
|
|
1611
|
+
if (bt.kind !== "per_transaction" || def.enforcedBy === "gatekeeper") hard[name] = cap;
|
|
1612
|
+
}
|
|
1613
|
+
for (const [name, def] of Object.entries(fields)) {
|
|
1614
|
+
if (name === "profile") continue;
|
|
1615
|
+
const cap = hard[name];
|
|
1616
|
+
if (cap === void 0) continue;
|
|
1617
|
+
const v = bounds[name];
|
|
1618
|
+
if (v === void 0 || v === null) {
|
|
1619
|
+
if (!def.required) {
|
|
1620
|
+
throw new HapError("GROUP_CAP_EXCEEDED", `bound "${name}" is omitted under a group ceiling of ${JSON.stringify(cap)} \u2014 an omitted bound is unlimited`);
|
|
1621
|
+
}
|
|
1622
|
+
continue;
|
|
1623
|
+
}
|
|
1624
|
+
if (exceedsCap(v, cap, def)) {
|
|
1625
|
+
throw new HapError("GROUP_CAP_EXCEEDED", `bound "${name}" = ${JSON.stringify(v)} exceeds the group's hard cap ${JSON.stringify(cap)}`);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
function validateAboveCapConfig(config, profile, opts) {
|
|
1630
|
+
const fields = profile.boundsSchema?.fields ?? {};
|
|
1631
|
+
for (const name of Object.keys(config.caps ?? {})) {
|
|
1632
|
+
if (name === "profile") throw new HapError("ABOVE_CAP_CONFIG_INVALID", "above_cap_caps MUST NOT name profile");
|
|
1633
|
+
const def = fields[name];
|
|
1634
|
+
if (!def || def.boundType?.kind !== "per_transaction" || def.type !== "number") {
|
|
1635
|
+
throw new HapError("ABOVE_CAP_CONFIG_INVALID", `above_cap_caps names "${name}", which is not a numeric per_transaction bound of ${profile.id}`);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
for (const did of config.approvers ?? []) {
|
|
1639
|
+
if (!isKeyBearingDid(did)) {
|
|
1640
|
+
throw new HapError("ABOVE_CAP_CONFIG_INVALID", `above-cap approver ${did} is not a key-bearing DID`);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
if (config.approvers !== void 0 && !opts.inGroup) {
|
|
1644
|
+
const others = config.approvers.filter((d) => d !== opts.ownerDid).length;
|
|
1645
|
+
const n = config.approvals;
|
|
1646
|
+
if (n === void 0 || !Number.isInteger(n) || n < 1 || n > others) {
|
|
1647
|
+
throw new HapError(
|
|
1648
|
+
"ABOVE_CAP_CONFIG_INVALID",
|
|
1649
|
+
`above_cap_approvals MUST be an integer between 1 and the ${others} approver(s) other than the owner, got ${JSON.stringify(n)}`
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
function capsExceeded(execution, mandate, profile) {
|
|
1655
|
+
const out = [];
|
|
1656
|
+
for (const [name, cap] of Object.entries(mandate.above_cap_caps ?? {})) {
|
|
1657
|
+
const bt = profile.boundsSchema?.fields[name]?.boundType;
|
|
1658
|
+
if (!bt || bt.kind !== "per_transaction") continue;
|
|
1659
|
+
const v = execution[bt.of];
|
|
1660
|
+
if (v === void 0 || v === null || typeof v !== "number" || v > cap) {
|
|
1661
|
+
out.push({ bound: name, field: bt.of, cap, value: v });
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
return out;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
932
1667
|
// src/profiles/index.ts
|
|
933
1668
|
var PROFILES = {};
|
|
934
1669
|
function registerProfile(profileId, profile) {
|
|
@@ -987,8 +1722,9 @@ async function verify(request, opts = {}) {
|
|
|
987
1722
|
}
|
|
988
1723
|
const expectedProfileHash = computeProfileHash(profile);
|
|
989
1724
|
const errors = [];
|
|
1725
|
+
const verifiedPayloads = [];
|
|
990
1726
|
for (const blob of request.mandates) {
|
|
991
|
-
const mandateErrors = await verifyOneMandate(blob, {
|
|
1727
|
+
const { errors: mandateErrors, payload } = await verifyOneMandate(blob, {
|
|
992
1728
|
expectedBoundsHash,
|
|
993
1729
|
expectedScopeHash,
|
|
994
1730
|
expectedProfileHash,
|
|
@@ -996,6 +1732,7 @@ async function verify(request, opts = {}) {
|
|
|
996
1732
|
now
|
|
997
1733
|
});
|
|
998
1734
|
errors.push(...mandateErrors);
|
|
1735
|
+
if (payload) verifiedPayloads.push(payload);
|
|
999
1736
|
}
|
|
1000
1737
|
if (errors.length > 0) {
|
|
1001
1738
|
return { approved: false, errors };
|
|
@@ -1014,6 +1751,31 @@ async function verify(request, opts = {}) {
|
|
|
1014
1751
|
};
|
|
1015
1752
|
}
|
|
1016
1753
|
}
|
|
1754
|
+
try {
|
|
1755
|
+
checkExecutionValues(request.execution, profile);
|
|
1756
|
+
} catch (err) {
|
|
1757
|
+
return { approved: false, errors: [{ code: codeFromThrown(err), message: err.message }] };
|
|
1758
|
+
}
|
|
1759
|
+
if (actionType !== void 0) {
|
|
1760
|
+
const capped = /* @__PURE__ */ new Set();
|
|
1761
|
+
for (const p of verifiedPayloads) {
|
|
1762
|
+
for (const name of Object.keys(p.above_cap_caps ?? {})) {
|
|
1763
|
+
const bt = profile.boundsSchema?.fields[name]?.boundType;
|
|
1764
|
+
if (bt && bt.kind === "per_transaction") capped.add(bt.of);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
const missing = missingSumFields(request.execution, profile, actionType).filter((m) => !capped.has(m.field));
|
|
1768
|
+
if (missing.length > 0) {
|
|
1769
|
+
return {
|
|
1770
|
+
approved: false,
|
|
1771
|
+
errors: missing.map(({ bound, field }) => ({
|
|
1772
|
+
code: "BOUND_EXCEEDED",
|
|
1773
|
+
field,
|
|
1774
|
+
message: `Bound "${bound}" governs "${actionType}" and reads "${field}", but this call exposes no ${field}. Refusing: an action without an amount would consume nothing of the limit (v0.8: a missing amount fails safe).`
|
|
1775
|
+
}))
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1017
1779
|
const boundsErrors = checkBounds(request, profile, actionType);
|
|
1018
1780
|
if (boundsErrors.length > 0) {
|
|
1019
1781
|
return { approved: false, errors: boundsErrors };
|
|
@@ -1047,16 +1809,21 @@ function codeFromThrown(err) {
|
|
|
1047
1809
|
return "MALFORMED_MANDATE";
|
|
1048
1810
|
}
|
|
1049
1811
|
async function verifyOneMandate(blob, opts) {
|
|
1812
|
+
const errors = await verifyOneMandateErrors(blob, opts);
|
|
1813
|
+
if (errors.length > 0) return { errors };
|
|
1814
|
+
return { errors, payload: decodeMandateBlob(blob).payload };
|
|
1815
|
+
}
|
|
1816
|
+
async function verifyOneMandateErrors(blob, opts) {
|
|
1050
1817
|
let mandate;
|
|
1051
1818
|
try {
|
|
1052
1819
|
mandate = decodeMandateBlob(blob);
|
|
1053
1820
|
} catch (err) {
|
|
1054
1821
|
return [{ code: "MALFORMED_MANDATE", message: err.message }];
|
|
1055
1822
|
}
|
|
1056
|
-
if (mandate.payload.version
|
|
1823
|
+
if (!isSupportedVersion(mandate.payload.version)) {
|
|
1057
1824
|
return [{
|
|
1058
1825
|
code: "VERSION_UNSUPPORTED",
|
|
1059
|
-
message: `mandate version ${JSON.stringify(mandate.payload.version)} is not verifiable by this Gatekeeper (verifies ${
|
|
1826
|
+
message: `mandate version ${JSON.stringify(mandate.payload.version)} is not verifiable by this Gatekeeper (verifies [${SUPPORTED_VERSIONS.join(", ")}])`
|
|
1060
1827
|
}];
|
|
1061
1828
|
}
|
|
1062
1829
|
try {
|
|
@@ -1071,6 +1838,7 @@ async function verifyOneMandate(blob, opts) {
|
|
|
1071
1838
|
}
|
|
1072
1839
|
try {
|
|
1073
1840
|
validateMandateOwners(mandate.payload);
|
|
1841
|
+
validateAboveCapFields(mandate.payload);
|
|
1074
1842
|
} catch (err) {
|
|
1075
1843
|
return [{ code: codeFromThrown(err), message: err.message }];
|
|
1076
1844
|
}
|
|
@@ -1236,43 +2004,70 @@ function checkScopeConstraints(scope, execution, profile) {
|
|
|
1236
2004
|
return errors;
|
|
1237
2005
|
}
|
|
1238
2006
|
export {
|
|
2007
|
+
BINDING_RANK,
|
|
2008
|
+
COMMITMENT_MODES,
|
|
1239
2009
|
CanonicalValueError,
|
|
1240
2010
|
ContentBindingError,
|
|
1241
2011
|
HapError,
|
|
2012
|
+
ISSUABLE_VERSIONS,
|
|
1242
2013
|
MANDATE_ERROR_CODES,
|
|
1243
2014
|
OwnerSignatureError,
|
|
2015
|
+
PROJECTION_MAX_VALIDITY_SECONDS,
|
|
2016
|
+
PROPOSAL_ERROR_CODES,
|
|
1244
2017
|
PROTOCOL_VERSION,
|
|
2018
|
+
PolicyError,
|
|
1245
2019
|
SIGNABLE_COMMITMENT_MODES,
|
|
1246
2020
|
SUPPORTED_VERSIONS,
|
|
1247
2021
|
TICKET_ERROR_CODES,
|
|
2022
|
+
TICKET_FRESHNESS_SECONDS,
|
|
1248
2023
|
allowedCommitmentModes,
|
|
1249
2024
|
approvalSigningBytes,
|
|
2025
|
+
approvalsSufficient,
|
|
2026
|
+
assertSupportedVersion,
|
|
2027
|
+
assertTicketShape,
|
|
1250
2028
|
bindingAppliesTo,
|
|
2029
|
+
bindingRank,
|
|
1251
2030
|
boundActionTypes,
|
|
2031
|
+
boundGoverns,
|
|
2032
|
+
boundReadFields,
|
|
1252
2033
|
buildMandateProjection,
|
|
2034
|
+
buildTicketProjection,
|
|
1253
2035
|
canonicalBounds,
|
|
1254
2036
|
canonicalScope,
|
|
1255
2037
|
canonicalize,
|
|
1256
2038
|
canonicalizeText,
|
|
2039
|
+
capsExceeded,
|
|
2040
|
+
checkBoundsValues,
|
|
1257
2041
|
checkDiscloseSubset,
|
|
2042
|
+
checkExecutionValues,
|
|
2043
|
+
checkGroupCeilings,
|
|
1258
2044
|
checkMandateExpiry,
|
|
1259
2045
|
checkOwnerSignatureRequirement,
|
|
2046
|
+
checkProposalReaders,
|
|
1260
2047
|
clearProfiles,
|
|
1261
2048
|
computeBoundsHash,
|
|
1262
2049
|
computeContentHash,
|
|
2050
|
+
computeExecutionContextHash,
|
|
1263
2051
|
computeFieldsContentHash,
|
|
1264
2052
|
computeIntentDisclosureHash,
|
|
1265
2053
|
computeIntentHash,
|
|
1266
2054
|
computeProfileHash,
|
|
2055
|
+
computeProposalContentHash,
|
|
1267
2056
|
computeScopeHash,
|
|
1268
2057
|
contentCanonicalBytes,
|
|
1269
2058
|
decodeDidKey,
|
|
1270
2059
|
decodeMandateBlob,
|
|
2060
|
+
decryptProposalContent,
|
|
1271
2061
|
deriveIdentityLine,
|
|
2062
|
+
deriveReaderKeyPair,
|
|
1272
2063
|
encodeDidKey,
|
|
1273
2064
|
encodeMandateBlob,
|
|
2065
|
+
encryptProposalContent,
|
|
2066
|
+
evaluateVerifierPolicy,
|
|
2067
|
+
exceedsCap,
|
|
1274
2068
|
filesFields,
|
|
1275
2069
|
fromBase64Url,
|
|
2070
|
+
generateReaderKeyPair,
|
|
1276
2071
|
getAllProfiles,
|
|
1277
2072
|
getProfile,
|
|
1278
2073
|
intentDisclosureCanonicalBytes,
|
|
@@ -1280,18 +2075,25 @@ export {
|
|
|
1280
2075
|
isCommitmentModeAllowed,
|
|
1281
2076
|
isFieldBinding,
|
|
1282
2077
|
isKeyBearingDid,
|
|
2078
|
+
isSupportedVersion,
|
|
1283
2079
|
listProfiles,
|
|
1284
2080
|
mandateBlobId,
|
|
2081
|
+
missingSumFields,
|
|
1285
2082
|
negotiateVersion,
|
|
1286
2083
|
projectionSigningBytes,
|
|
2084
|
+
proposalContentJcs,
|
|
1287
2085
|
publicTicketView,
|
|
2086
|
+
reconstructApproval,
|
|
1288
2087
|
registerProfile,
|
|
1289
2088
|
selectBoundFields,
|
|
1290
2089
|
signApproval,
|
|
1291
2090
|
signMandate,
|
|
1292
2091
|
signMandateProjection,
|
|
1293
2092
|
signTicket,
|
|
2093
|
+
signTicketProjection,
|
|
1294
2094
|
toBase64Url,
|
|
2095
|
+
validateAboveCapConfig,
|
|
2096
|
+
validateAboveCapFields,
|
|
1295
2097
|
validateBoundsParams,
|
|
1296
2098
|
validateMandateOwners,
|
|
1297
2099
|
validateProfile,
|
|
@@ -1303,5 +2105,10 @@ export {
|
|
|
1303
2105
|
verifyMandateSignature,
|
|
1304
2106
|
verifyOwnerSignature,
|
|
1305
2107
|
verifyOwnerSignatures,
|
|
1306
|
-
|
|
2108
|
+
verifyProjectionOwner,
|
|
2109
|
+
verifyTicketAgainstRequest,
|
|
2110
|
+
verifyTicketApprovals,
|
|
2111
|
+
verifyTicketProjection,
|
|
2112
|
+
verifyTicketSignature,
|
|
2113
|
+
wireExecutionContext
|
|
1307
2114
|
};
|