@majikah/majik-signature 0.2.6 → 0.2.7
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/dist/core/embed/majik-embed.d.ts +13 -0
- package/dist/core/embed/majik-embed.js +36 -0
- package/dist/core/order.d.ts +100 -0
- package/dist/core/order.js +209 -0
- package/dist/majik-signature.d.ts +27 -0
- package/dist/majik-signature.js +27 -0
- package/package.json +1 -1
|
@@ -26,6 +26,7 @@ import { MajikSignatureEnvelope } from "../../core/envelope";
|
|
|
26
26
|
import { FormatHandlerRegistry } from "./registry";
|
|
27
27
|
import { MajikChainAnchor } from "../../anchor/types";
|
|
28
28
|
import { MajikSignatureMap } from "../mjksmap";
|
|
29
|
+
import { SignatureOrderResult, VerifySignatureOrderOptions } from "../order";
|
|
29
30
|
export interface MajikSignatureAdapter {
|
|
30
31
|
toJSON(): MajikSignatureJSON;
|
|
31
32
|
/**
|
|
@@ -178,6 +179,18 @@ export declare class MajikSignatureEmbed {
|
|
|
178
179
|
* without the caller re-deriving it from the array each time.
|
|
179
180
|
*/
|
|
180
181
|
static summarizeBatchVerification(results: FileVerifyResult[]): BatchVerifySummary;
|
|
182
|
+
/**
|
|
183
|
+
* Verify the chronological signing order of a file's embedded envelope
|
|
184
|
+
* against an expected sequence of signers.
|
|
185
|
+
* expectedOrder accepts MajikKey instances and/or ExpectedSigner objects,
|
|
186
|
+
* mixed freely — normalized internally.
|
|
187
|
+
*/
|
|
188
|
+
static verifyFileOrder(file: Blob, expectedOrder: readonly (MajikKey | ExpectedSigner)[], MajikSig: MajikSignatureStaticAdapter, options?: ExtractOptions & VerifySignatureOrderOptions): Promise<SignatureOrderResult>;
|
|
189
|
+
/**
|
|
190
|
+
* Verify the chronological signing order against a detached envelope
|
|
191
|
+
* (instance, JSON, MJKSIG bytes, or Blob).
|
|
192
|
+
*/
|
|
193
|
+
static verifyDetachedOrder(file: Blob, envelopeInput: MajikSignatureEnvelope | MajikSignatureEnvelopeJSON | Uint8Array | Blob, expectedOrder: readonly (MajikKey | ExpectedSigner)[], MajikSig: MajikSignatureStaticAdapter, options?: ExtractOptions & VerifySignatureOrderOptions): Promise<SignatureOrderResult>;
|
|
181
194
|
/**
|
|
182
195
|
* Seal a multi-sig envelope, preventing any further signatures.
|
|
183
196
|
* Issuer-only / already-sealed checks are enforced by envelope.withSeal().
|
|
@@ -37,6 +37,7 @@ import { FallbackHandler } from "./fallback";
|
|
|
37
37
|
import { bytesToBase64, hashContent } from "../hash";
|
|
38
38
|
import { MajikSignatureError, MajikSignatureValidationError } from "../errors";
|
|
39
39
|
import { MajikSignatureMap } from "../mjksmap";
|
|
40
|
+
import { verifySignatureOrder } from "../order";
|
|
40
41
|
// ─── Registry ─────────────────────────────────────────────────────────────────
|
|
41
42
|
const DEFAULT_REGISTRY = new FormatHandlerRegistry()
|
|
42
43
|
.register(new PdfHandler())
|
|
@@ -473,6 +474,41 @@ export class MajikSignatureEmbed {
|
|
|
473
474
|
summary.allValid = summary.verified === summary.total && summary.total > 0;
|
|
474
475
|
return summary;
|
|
475
476
|
}
|
|
477
|
+
/**
|
|
478
|
+
* Verify the chronological signing order of a file's embedded envelope
|
|
479
|
+
* against an expected sequence of signers.
|
|
480
|
+
* expectedOrder accepts MajikKey instances and/or ExpectedSigner objects,
|
|
481
|
+
* mixed freely — normalized internally.
|
|
482
|
+
*/
|
|
483
|
+
static async verifyFileOrder(file, expectedOrder, MajikSig, options) {
|
|
484
|
+
const { bytes, handler } = await MajikSignatureEmbed._prepare(file, options);
|
|
485
|
+
const raw = await handler.extract(bytes);
|
|
486
|
+
const envelope = raw
|
|
487
|
+
? MajikSignatureEnvelope.fromJSON(raw)
|
|
488
|
+
: MajikSignatureEnvelope.empty();
|
|
489
|
+
if (raw) {
|
|
490
|
+
const integrity = envelope.verifyAllowlistIntegrity();
|
|
491
|
+
if (!integrity.valid) {
|
|
492
|
+
throw new MajikSignatureError(integrity.reason ?? "Allowlist integrity check failed");
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
const originalBytes = raw ? await handler.strip(bytes) : bytes;
|
|
496
|
+
return verifySignatureOrder(envelope, originalBytes, expectedOrder, (content, sig, pk) => MajikSig.verify(content, sig, pk), { strict: options?.strict });
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Verify the chronological signing order against a detached envelope
|
|
500
|
+
* (instance, JSON, MJKSIG bytes, or Blob).
|
|
501
|
+
*/
|
|
502
|
+
static async verifyDetachedOrder(file, envelopeInput, expectedOrder, MajikSig, options) {
|
|
503
|
+
const { bytes, handler } = await MajikSignatureEmbed._prepare(file, options);
|
|
504
|
+
const envelope = await MajikSignatureEnvelope.from(envelopeInput);
|
|
505
|
+
const integrity = envelope.verifyAllowlistIntegrity();
|
|
506
|
+
if (!integrity.valid) {
|
|
507
|
+
throw new MajikSignatureError(integrity.reason ?? "Allowlist integrity check failed");
|
|
508
|
+
}
|
|
509
|
+
const originalBytes = await handler.strip(bytes);
|
|
510
|
+
return verifySignatureOrder(envelope, originalBytes, expectedOrder, (content, sig, pk) => MajikSig.verify(content, sig, pk), { strict: options?.strict });
|
|
511
|
+
}
|
|
476
512
|
// ── seal ───────────────────────────────────────────────────────────────────
|
|
477
513
|
/**
|
|
478
514
|
* Seal a multi-sig envelope, preventing any further signatures.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/order.ts
|
|
3
|
+
*
|
|
4
|
+
* Chronological signing-order verification.
|
|
5
|
+
*
|
|
6
|
+
* Pure /structural, like envelope.ts — no MajikSig-specific imports beyond a
|
|
7
|
+
* caller-supplied verify callback, so this has zero circular-dependency risk
|
|
8
|
+
* with majik-signature.ts or majik-embed.ts.
|
|
9
|
+
*
|
|
10
|
+
* Trust model:
|
|
11
|
+
* - tsa.payload.timestamp is server-attested (a TSA stamped a nonce) —
|
|
12
|
+
* preferred whenever present.
|
|
13
|
+
* - The bare `timestamp` field is self-reported by the signer's local
|
|
14
|
+
* clock. It's tamper-evident (covered by the signature) but NOT
|
|
15
|
+
* independently attested — a signer could set their clock to anything.
|
|
16
|
+
* - Every result surfaces `usesUnattestedTimestamp` so callers can show
|
|
17
|
+
* a caveat when order was decided using a self-reported clock.
|
|
18
|
+
*/
|
|
19
|
+
import type { MajikKey } from "@majikah/majik-key";
|
|
20
|
+
import type { MajikSignatureEnvelope } from "./envelope";
|
|
21
|
+
import type { ExpectedSigner, MajikSignatureJSON, MajikSignerPublicKeys, VerificationResult } from "./types";
|
|
22
|
+
export type TimestampSource = "tsa" | "self-reported";
|
|
23
|
+
export interface SignerOrderStatus {
|
|
24
|
+
signerId: string;
|
|
25
|
+
expectedPosition: number;
|
|
26
|
+
hasSigned: boolean;
|
|
27
|
+
/** Present only when hasSigned is true */
|
|
28
|
+
valid?: boolean;
|
|
29
|
+
/** Present only when hasSigned && !valid */
|
|
30
|
+
reason?: string;
|
|
31
|
+
effectiveTimestamp?: string;
|
|
32
|
+
timestampSource?: TimestampSource;
|
|
33
|
+
}
|
|
34
|
+
export interface OrderViolation {
|
|
35
|
+
/** signerId expected to have signed earlier */
|
|
36
|
+
earlier: string;
|
|
37
|
+
/** signerId expected to have signed later */
|
|
38
|
+
later: string;
|
|
39
|
+
earlierTimestamp: string;
|
|
40
|
+
laterTimestamp: string;
|
|
41
|
+
}
|
|
42
|
+
export interface SoftTieWarning {
|
|
43
|
+
a: string;
|
|
44
|
+
b: string;
|
|
45
|
+
timestamp: string;
|
|
46
|
+
}
|
|
47
|
+
export interface SignatureOrderResult {
|
|
48
|
+
/** True only when everyone expected signed, every signature is valid,
|
|
49
|
+
* the order was respected, AND (in strict mode) no extra signers exist. */
|
|
50
|
+
valid: boolean;
|
|
51
|
+
allExpectedSigned: boolean;
|
|
52
|
+
allValid: boolean;
|
|
53
|
+
orderRespected: boolean;
|
|
54
|
+
strict: boolean;
|
|
55
|
+
/** Only populated when strict === true */
|
|
56
|
+
unexpectedSigners: string[];
|
|
57
|
+
pendingSigners: string[];
|
|
58
|
+
invalidSigners: string[];
|
|
59
|
+
violations: OrderViolation[];
|
|
60
|
+
/** True if any timestamp used in a comparison was self-reported rather
|
|
61
|
+
* than TSA-attested — order in that case is a claim, not a proof. */
|
|
62
|
+
usesUnattestedTimestamp: boolean;
|
|
63
|
+
/** Two signers landed on the identical instant — order between them is
|
|
64
|
+
* indistinguishable. Doesn't fail `valid`, just a note. */
|
|
65
|
+
softTieWarnings: SoftTieWarning[];
|
|
66
|
+
signers: SignerOrderStatus[];
|
|
67
|
+
reason?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface VerifySignatureOrderOptions {
|
|
70
|
+
/** When true, any signer present in the envelope but absent from
|
|
71
|
+
* expectedOrder is reported and fails the overall result. */
|
|
72
|
+
strict?: boolean;
|
|
73
|
+
}
|
|
74
|
+
/** Minimal shape the order verifier needs to check one signature's crypto. */
|
|
75
|
+
export type OrderVerifyFn = (content: Uint8Array, signature: MajikSignatureJSON, publicKeys: MajikSignerPublicKeys) => VerificationResult;
|
|
76
|
+
/**
|
|
77
|
+
* Normalize a mixed array of MajikKey instances and/or ExpectedSigner
|
|
78
|
+
* objects into a plain ExpectedSigner[]. Order-preserving — position in
|
|
79
|
+
* the input array IS the expected signing position.
|
|
80
|
+
*/
|
|
81
|
+
export declare function normalizeExpectedOrder(input: readonly (MajikKey | ExpectedSigner)[]): ExpectedSigner[];
|
|
82
|
+
export declare function resolveEffectiveTimestamp(sig: MajikSignatureJSON): {
|
|
83
|
+
timestamp: string;
|
|
84
|
+
source: TimestampSource;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Verify that an envelope's signatures were produced in the sequence given
|
|
88
|
+
* by expectedOrder (array position == expected chronological position).
|
|
89
|
+
*
|
|
90
|
+
* Each expected signer is verified against THEIR OWN publicly-known keys
|
|
91
|
+
* (as supplied in expectedOrder), not the self-asserted keys embedded in
|
|
92
|
+
* their own envelope entry — this ties identity to a key you actually
|
|
93
|
+
* trust, rather than trusting whatever key a signature claims to be signed
|
|
94
|
+
* with.
|
|
95
|
+
*
|
|
96
|
+
* Only signers who (a) signed and (b) verified valid are compared for
|
|
97
|
+
* order — an invalid signature's timestamp isn't trustworthy, so it's
|
|
98
|
+
* excluded from ordering but still reported via invalidSigners.
|
|
99
|
+
*/
|
|
100
|
+
export declare function verifySignatureOrder(envelope: MajikSignatureEnvelope, originalBytes: Uint8Array, expectedOrder: readonly (MajikKey | ExpectedSigner)[], verifyFn: OrderVerifyFn, options?: VerifySignatureOrderOptions): SignatureOrderResult;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/order.ts
|
|
3
|
+
*
|
|
4
|
+
* Chronological signing-order verification.
|
|
5
|
+
*
|
|
6
|
+
* Pure /structural, like envelope.ts — no MajikSig-specific imports beyond a
|
|
7
|
+
* caller-supplied verify callback, so this has zero circular-dependency risk
|
|
8
|
+
* with majik-signature.ts or majik-embed.ts.
|
|
9
|
+
*
|
|
10
|
+
* Trust model:
|
|
11
|
+
* - tsa.payload.timestamp is server-attested (a TSA stamped a nonce) —
|
|
12
|
+
* preferred whenever present.
|
|
13
|
+
* - The bare `timestamp` field is self-reported by the signer's local
|
|
14
|
+
* clock. It's tamper-evident (covered by the signature) but NOT
|
|
15
|
+
* independently attested — a signer could set their clock to anything.
|
|
16
|
+
* - Every result surfaces `usesUnattestedTimestamp` so callers can show
|
|
17
|
+
* a caveat when order was decided using a self-reported clock.
|
|
18
|
+
*/
|
|
19
|
+
import { MajikSignatureKeyError, MajikSignatureValidationError, } from "./errors";
|
|
20
|
+
import { base64ToBytes, bytesToBase64 } from "./hash";
|
|
21
|
+
// ─── Normalization ─────────────────────────────────────────────────────────
|
|
22
|
+
function isExpectedSignerShape(v) {
|
|
23
|
+
return typeof v.edPublicKey === "string";
|
|
24
|
+
}
|
|
25
|
+
function toExpectedSigner(input) {
|
|
26
|
+
if (isExpectedSignerShape(input))
|
|
27
|
+
return input;
|
|
28
|
+
const key = input;
|
|
29
|
+
if (!key.hasSigningKeys) {
|
|
30
|
+
throw new MajikSignatureKeyError("MajikKey has no signing public keys — cannot build expected order entry.");
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
signerId: key.fingerprint,
|
|
34
|
+
edPublicKey: bytesToBase64(key.edPublicKey),
|
|
35
|
+
mlDsaPublicKey: bytesToBase64(key.mlDsaPublicKey),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Normalize a mixed array of MajikKey instances and/or ExpectedSigner
|
|
40
|
+
* objects into a plain ExpectedSigner[]. Order-preserving — position in
|
|
41
|
+
* the input array IS the expected signing position.
|
|
42
|
+
*/
|
|
43
|
+
export function normalizeExpectedOrder(input) {
|
|
44
|
+
return input.map(toExpectedSigner);
|
|
45
|
+
}
|
|
46
|
+
// ─── Timestamp resolution ───────────────────────────────────────────────────
|
|
47
|
+
export function resolveEffectiveTimestamp(sig) {
|
|
48
|
+
if (sig.tsa?.payload?.timestamp) {
|
|
49
|
+
return { timestamp: sig.tsa.payload.timestamp, source: "tsa" };
|
|
50
|
+
}
|
|
51
|
+
return { timestamp: sig.timestamp, source: "self-reported" };
|
|
52
|
+
}
|
|
53
|
+
// ─── Core verification ───────────────────────────────────────────────────────
|
|
54
|
+
/**
|
|
55
|
+
* Verify that an envelope's signatures were produced in the sequence given
|
|
56
|
+
* by expectedOrder (array position == expected chronological position).
|
|
57
|
+
*
|
|
58
|
+
* Each expected signer is verified against THEIR OWN publicly-known keys
|
|
59
|
+
* (as supplied in expectedOrder), not the self-asserted keys embedded in
|
|
60
|
+
* their own envelope entry — this ties identity to a key you actually
|
|
61
|
+
* trust, rather than trusting whatever key a signature claims to be signed
|
|
62
|
+
* with.
|
|
63
|
+
*
|
|
64
|
+
* Only signers who (a) signed and (b) verified valid are compared for
|
|
65
|
+
* order — an invalid signature's timestamp isn't trustworthy, so it's
|
|
66
|
+
* excluded from ordering but still reported via invalidSigners.
|
|
67
|
+
*/
|
|
68
|
+
export function verifySignatureOrder(envelope, originalBytes, expectedOrder, verifyFn, options) {
|
|
69
|
+
const strict = options?.strict ?? false;
|
|
70
|
+
const normalizedExpected = normalizeExpectedOrder(expectedOrder);
|
|
71
|
+
if (normalizedExpected.length === 0) {
|
|
72
|
+
throw new MajikSignatureValidationError("expectedOrder must contain at least one signer.", "expectedOrder");
|
|
73
|
+
}
|
|
74
|
+
const seenIds = new Set();
|
|
75
|
+
for (const e of normalizedExpected) {
|
|
76
|
+
if (seenIds.has(e.signerId)) {
|
|
77
|
+
throw new MajikSignatureValidationError(`Duplicate signerId in expectedOrder: "${e.signerId}"`, "expectedOrder");
|
|
78
|
+
}
|
|
79
|
+
seenIds.add(e.signerId);
|
|
80
|
+
}
|
|
81
|
+
const sigsById = new Map(envelope.signatures.map((s) => [s.signerId, s]));
|
|
82
|
+
const signers = [];
|
|
83
|
+
const pendingSigners = [];
|
|
84
|
+
const invalidSigners = [];
|
|
85
|
+
let usesUnattestedTimestamp = false;
|
|
86
|
+
// Only expected signers who signed AND verified valid are eligible for
|
|
87
|
+
// order comparison — their timestamps are the only trustworthy ones.
|
|
88
|
+
const considered = [];
|
|
89
|
+
for (let i = 0; i < normalizedExpected.length; i++) {
|
|
90
|
+
const expected = normalizedExpected[i];
|
|
91
|
+
const sig = sigsById.get(expected.signerId);
|
|
92
|
+
if (!sig) {
|
|
93
|
+
pendingSigners.push(expected.signerId);
|
|
94
|
+
signers.push({
|
|
95
|
+
signerId: expected.signerId,
|
|
96
|
+
expectedPosition: i,
|
|
97
|
+
hasSigned: false,
|
|
98
|
+
});
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const publicKeys = {
|
|
102
|
+
signerId: expected.signerId,
|
|
103
|
+
edPublicKey: base64ToBytes(expected.edPublicKey),
|
|
104
|
+
mlDsaPublicKey: base64ToBytes(expected.mlDsaPublicKey),
|
|
105
|
+
};
|
|
106
|
+
const result = verifyFn(originalBytes, sig, publicKeys);
|
|
107
|
+
const { timestamp, source } = resolveEffectiveTimestamp(sig);
|
|
108
|
+
if (!result.valid) {
|
|
109
|
+
invalidSigners.push(expected.signerId);
|
|
110
|
+
signers.push({
|
|
111
|
+
signerId: expected.signerId,
|
|
112
|
+
expectedPosition: i,
|
|
113
|
+
hasSigned: true,
|
|
114
|
+
valid: false,
|
|
115
|
+
reason: result.reason,
|
|
116
|
+
effectiveTimestamp: timestamp,
|
|
117
|
+
timestampSource: source,
|
|
118
|
+
});
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (source === "self-reported")
|
|
122
|
+
usesUnattestedTimestamp = true;
|
|
123
|
+
signers.push({
|
|
124
|
+
signerId: expected.signerId,
|
|
125
|
+
expectedPosition: i,
|
|
126
|
+
hasSigned: true,
|
|
127
|
+
valid: true,
|
|
128
|
+
effectiveTimestamp: timestamp,
|
|
129
|
+
timestampSource: source,
|
|
130
|
+
});
|
|
131
|
+
considered.push({
|
|
132
|
+
signerId: expected.signerId,
|
|
133
|
+
timestamp,
|
|
134
|
+
timeMs: Date.parse(timestamp),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
// ── Order check — every pair among considered signers, not just neighbors ──
|
|
138
|
+
const violations = [];
|
|
139
|
+
const softTieWarnings = [];
|
|
140
|
+
for (let i = 0; i < considered.length; i++) {
|
|
141
|
+
for (let j = i + 1; j < considered.length; j++) {
|
|
142
|
+
const a = considered[i]; // expected earlier
|
|
143
|
+
const b = considered[j]; // expected later
|
|
144
|
+
if (a.timeMs > b.timeMs) {
|
|
145
|
+
violations.push({
|
|
146
|
+
earlier: a.signerId,
|
|
147
|
+
later: b.signerId,
|
|
148
|
+
earlierTimestamp: a.timestamp,
|
|
149
|
+
laterTimestamp: b.timestamp,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
else if (a.timeMs === b.timeMs) {
|
|
153
|
+
softTieWarnings.push({
|
|
154
|
+
a: a.signerId,
|
|
155
|
+
b: b.signerId,
|
|
156
|
+
timestamp: a.timestamp,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// ── Strict mode: any actual signer absent from expectedOrder ────────────
|
|
162
|
+
const unexpectedSigners = [];
|
|
163
|
+
if (strict) {
|
|
164
|
+
for (const sig of envelope.signatures) {
|
|
165
|
+
if (!seenIds.has(sig.signerId))
|
|
166
|
+
unexpectedSigners.push(sig.signerId);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const allExpectedSigned = pendingSigners.length === 0;
|
|
170
|
+
const allValid = invalidSigners.length === 0;
|
|
171
|
+
const orderRespected = violations.length === 0;
|
|
172
|
+
const strictSatisfied = !strict || unexpectedSigners.length === 0;
|
|
173
|
+
const valid = allExpectedSigned && allValid && orderRespected && strictSatisfied;
|
|
174
|
+
let reason;
|
|
175
|
+
if (!allExpectedSigned) {
|
|
176
|
+
reason = `Missing signature(s) from: ${pendingSigners.join(", ")}`;
|
|
177
|
+
}
|
|
178
|
+
else if (!allValid) {
|
|
179
|
+
reason = `Invalid signature(s) from: ${invalidSigners.join(", ")}`;
|
|
180
|
+
}
|
|
181
|
+
else if (!strictSatisfied) {
|
|
182
|
+
reason = `Unexpected signer(s) present (strict mode): ${unexpectedSigners.join(", ")}`;
|
|
183
|
+
}
|
|
184
|
+
else if (!orderRespected) {
|
|
185
|
+
reason = `Signing order violated: ${violations
|
|
186
|
+
.map((v) => `"${v.earlier}" expected before "${v.later}" but signed after`)
|
|
187
|
+
.join("; ")}`;
|
|
188
|
+
}
|
|
189
|
+
else if (softTieWarnings.length > 0) {
|
|
190
|
+
reason = `All signatures valid and in order, but ${softTieWarnings.length} pair(s) share an identical timestamp — order cannot be distinguished for: ${softTieWarnings
|
|
191
|
+
.map((w) => `"${w.a}"/"${w.b}"`)
|
|
192
|
+
.join(", ")}`;
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
valid,
|
|
196
|
+
allExpectedSigned,
|
|
197
|
+
allValid,
|
|
198
|
+
orderRespected,
|
|
199
|
+
strict,
|
|
200
|
+
unexpectedSigners,
|
|
201
|
+
pendingSigners,
|
|
202
|
+
invalidSigners,
|
|
203
|
+
violations,
|
|
204
|
+
usesUnattestedTimestamp,
|
|
205
|
+
softTieWarnings,
|
|
206
|
+
signers,
|
|
207
|
+
reason,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
@@ -10,6 +10,7 @@ import type { ImageVerificationResult, ImageSignOptions, ImageSignatureStub } fr
|
|
|
10
10
|
import { MajikChainAnchor, MajikChainAnchorMemo } from "./anchor/types";
|
|
11
11
|
import { MajikSignatureEnvelope } from "./core/envelope";
|
|
12
12
|
import { MajikSignatureMap } from "./core/mjksmap";
|
|
13
|
+
import { SignatureOrderResult } from "./core/order";
|
|
13
14
|
/**
|
|
14
15
|
* Majik Signature
|
|
15
16
|
* ---
|
|
@@ -217,6 +218,32 @@ export declare class MajikSignature {
|
|
|
217
218
|
static verifyFilesFromMjksMap(map: MajikSignatureMap, files: BatchVerifyInput[], publicKeys: MajikSignerPublicKeys, options?: BatchVerifyOptions, debug?: boolean): Promise<FileVerifyResult[]>;
|
|
218
219
|
static verifyFilesFromMjksMapWithKey(map: MajikSignatureMap, files: BatchVerifyInput[], key: MajikKey, options?: BatchVerifyOptions, debug?: boolean): Promise<FileVerifyResult[]>;
|
|
219
220
|
static summarizeBatchVerification(results: FileVerifyResult[]): ReturnType<typeof MajikSignatureEmbed.summarizeBatchVerification>;
|
|
221
|
+
/**
|
|
222
|
+
* Verify that a file's embedded signatures were produced in the given
|
|
223
|
+
* order. expectedOrder accepts MajikKey instances and/or ExpectedSigner
|
|
224
|
+
* objects, mixed freely.
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* const result = await MajikSignature.verifyFileOrder(file, [bobKey, daveKey]);
|
|
228
|
+
* if (!result.valid) console.warn(result.reason);
|
|
229
|
+
*/
|
|
230
|
+
static verifyFileOrder(file: Blob, expectedOrder: readonly (MajikKey | ExpectedSigner)[], options?: {
|
|
231
|
+
mimeType?: string;
|
|
232
|
+
strict?: boolean;
|
|
233
|
+
}): Promise<SignatureOrderResult>;
|
|
234
|
+
/**
|
|
235
|
+
* Verify signing order against a detached envelope.
|
|
236
|
+
*/
|
|
237
|
+
static verifyFileDetachedOrder(file: Blob, envelope: MajikSignatureEnvelope | MajikSignatureEnvelopeJSON | Uint8Array | Blob, expectedOrder: readonly (MajikKey | ExpectedSigner)[], options?: {
|
|
238
|
+
mimeType?: string;
|
|
239
|
+
strict?: boolean;
|
|
240
|
+
}): Promise<SignatureOrderResult>;
|
|
241
|
+
/**
|
|
242
|
+
* Normalize a mixed array of MajikKey instances / ExpectedSigner objects
|
|
243
|
+
* into a plain ExpectedSigner[]. Exposed standalone in case you want to
|
|
244
|
+
* cache/store the normalized order without immediately verifying.
|
|
245
|
+
*/
|
|
246
|
+
static normalizeExpectedOrder(expectedOrder: readonly (MajikKey | ExpectedSigner)[]): ExpectedSigner[];
|
|
220
247
|
/**
|
|
221
248
|
* Embed this MajikSignature instance into a file.
|
|
222
249
|
* The signature must cover the original file bytes BEFORE embedding.
|
package/dist/majik-signature.js
CHANGED
|
@@ -13,6 +13,7 @@ import { hashContent, bytesToBase64, base64ToBytes } from "./core/hash";
|
|
|
13
13
|
import { MajikSignatureEmbed } from "./core/embed/majik-embed";
|
|
14
14
|
// ── Stamp (image signing) imports ─────────────────────────────────────────────
|
|
15
15
|
import { MajikImageSignature } from "./core/stamp/image-signature";
|
|
16
|
+
import { normalizeExpectedOrder as normalizeExpectedOrderUtil, } from "./core/order";
|
|
16
17
|
const secureFill = Uint8Array.prototype.fill;
|
|
17
18
|
/**
|
|
18
19
|
* Majik Signature
|
|
@@ -494,6 +495,32 @@ export class MajikSignature {
|
|
|
494
495
|
static summarizeBatchVerification(results) {
|
|
495
496
|
return MajikSignatureEmbed.summarizeBatchVerification(results);
|
|
496
497
|
}
|
|
498
|
+
/**
|
|
499
|
+
* Verify that a file's embedded signatures were produced in the given
|
|
500
|
+
* order. expectedOrder accepts MajikKey instances and/or ExpectedSigner
|
|
501
|
+
* objects, mixed freely.
|
|
502
|
+
*
|
|
503
|
+
* @example
|
|
504
|
+
* const result = await MajikSignature.verifyFileOrder(file, [bobKey, daveKey]);
|
|
505
|
+
* if (!result.valid) console.warn(result.reason);
|
|
506
|
+
*/
|
|
507
|
+
static async verifyFileOrder(file, expectedOrder, options) {
|
|
508
|
+
return MajikSignatureEmbed.verifyFileOrder(file, expectedOrder, MajikSignature, options);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Verify signing order against a detached envelope.
|
|
512
|
+
*/
|
|
513
|
+
static async verifyFileDetachedOrder(file, envelope, expectedOrder, options) {
|
|
514
|
+
return MajikSignatureEmbed.verifyDetachedOrder(file, envelope, expectedOrder, MajikSignature, options);
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Normalize a mixed array of MajikKey instances / ExpectedSigner objects
|
|
518
|
+
* into a plain ExpectedSigner[]. Exposed standalone in case you want to
|
|
519
|
+
* cache/store the normalized order without immediately verifying.
|
|
520
|
+
*/
|
|
521
|
+
static normalizeExpectedOrder(expectedOrder) {
|
|
522
|
+
return normalizeExpectedOrderUtil(expectedOrder);
|
|
523
|
+
}
|
|
497
524
|
/**
|
|
498
525
|
* Embed this MajikSignature instance into a file.
|
|
499
526
|
* The signature must cover the original file bytes BEFORE embedding.
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@majikah/majik-signature",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"description": "Majik Signature is a hybrid post-quantum content signing and verification library for the Majikah ecosystem. Built on top of Majik Key, it provides tamper-proof, forgery-resistant digital signatures for any content format — using a dual-algorithm architecture that combines classical Ed25519 with post-quantum ML-DSA-87 (FIPS-204).",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.7",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "Zelijah",
|
|
8
8
|
"main": "./dist/index.js",
|